From 447f6fb4c7c914f6344e83919ab8ceb37154810d Mon Sep 17 00:00:00 2001 From: Amie Date: Thu, 7 Mar 2024 13:31:14 -0800 Subject: [PATCH] Implement `Retire` on Removal.sol for direct retirement (#733) * tests, snapshot, docgen * fix build * fix vesting task * restore Migrate event * changes to migrate-certificates.ts * snapshot and docgen --------- Co-authored-by: amiecorso --- .gas-snapshot | 160 +++++++++++++++++----------------- contracts/Removal.sol | 31 +++++-- docs/Removal.md | 33 +++++-- tasks/migrate-certificates.ts | 6 +- test/Removal.t.sol | 20 ++--- test/tasks/vesting.test.ts | 14 ++- types/events/Removal.ts | 5 ++ 7 files changed, 156 insertions(+), 113 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 5b70304f4..d7e8c0c2f 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -20,36 +20,36 @@ 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: 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_buyingFromOneRemoval:test() (gas: 653182) +Checkout_buyingFromOneRemoval_byApproval:test() (gas: 589240) +Checkout_buyingFromTenRemovals:test() (gas: 1978293) +Checkout_buyingFromTenRemovals_singleSupplier:test() (gas: 1713863) +Checkout_buyingFromTenRemovals_singleSupplier_withoutFee:test() (gas: 1709063) +Checkout_buyingFromTenRemovals_withoutFee:test() (gas: 1721769) +Checkout_buyingFromTenSuppliers:test() (gas: 2856579) +Checkout_buyingWithAlternateERC20:test() (gas: 547028) +Checkout_buyingWithAlternateERC20_floatingPointPriceMultiple:test() (gas: 513082) 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:test() (gas: 569319) +Checkout_swapWithoutFeeSpecialOrder_specificSupplier:test() (gas: 564018) Checkout_swapWithoutFeeSpecialOrder_specificSupplier:test_revertsWhenSupplierDoesNotExistInMarket() (gas: 57731) -Checkout_swapWithoutFeeSpecialOrder_specificVintages:test_basicFulfillment() (gas: 965410) +Checkout_swapWithoutFeeSpecialOrder_specificVintages:test_basicFulfillment() (gas: 965796) Checkout_swapWithoutFeeSpecialOrder_specificVintages:test_revertsWhenNoRemovalsFromSpecifiedVintages() (gas: 91000) -Checkout_swapWithoutFeeSpecialOrder_specificVintagesSpecificSupplier:test_basicFulfillment() (gas: 701767) +Checkout_swapWithoutFeeSpecialOrder_specificVintagesSpecificSupplier:test_basicFulfillment() (gas: 702054) 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: 4269) -MarketInvariantTest:invariant_sumOfPurchaseAmounts() (runs: 400, calls: 6000, reverts: 4292) -MarketSupplierSelectionNotUsingUpSuppliersLastRemoval:test() (gas: 923444) +MarketInvariantTest:invariant_callSummary() (runs: 400, calls: 6000, reverts: 4267) +MarketInvariantTest:invariant_sumOfPurchaseAmounts() (runs: 400, calls: 6000, reverts: 4261) +MarketSupplierSelectionNotUsingUpSuppliersLastRemoval:test() (gas: 923731) 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_USDC_swap_respects_decimal_mismatch:test() (gas: 799239) +Market_USDC_swap_withholds_restricted_nori:test() (gas: 974402) Market__addActiveRemoval:test() (gas: 183344) Market__addActiveRemoval:test__lis2VintagesFor1SupplierFor2SubIdentifiers() (gas: 242901) Market__addActiveRemoval:test__list1VintageFor1Supplier() (gas: 188309) @@ -73,48 +73,48 @@ Market__validateSupply:test_reverts_OutOfSupply() (gas: 3194) Market_calculates_prices_using_decimal:test() (gas: 66492) Market_convertPurchasingTokenDecimalsToRemovalDecimals:test() (gas: 26029) Market_convertRemovalDecimalsToPurchasingTokenDecimals:test() (gas: 29773) -Market_getActiveSuppliers:test_1_supplier() (gas: 610132) -Market_getActiveSuppliers:test_3_suppliers() (gas: 1262512) +Market_getActiveSuppliers:test_1_supplier() (gas: 610143) +Market_getActiveSuppliers:test_3_suppliers() (gas: 1262545) 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_1_removal() (gas: 610568) +Market_getRemovalIdsForSupplier:test_3_removals() (gas: 994023) +Market_getRemovalIdsForSupplier:test_3_removals_different_vintages() (gas: 1040132) Market_getRemovalIdsForSupplier:test_no_removals() (gas: 26044) Market_onERC1155BatchReceived:test() (gas: 208168) -Market_onERC1155BatchReceived_reverts_SenderNotRemovalContract:test() (gas: 355367) +Market_onERC1155BatchReceived_reverts_SenderNotRemovalContract:test() (gas: 355477) Market_onERC1155Received:test() (gas: 206036) Market_onERC1155Received_reverts_SenderNotRemovalContract:test() (gas: 159000) Market_purchasingTokenAddress:test() (gas: 17191) -Market_replace:test() (gas: 345000) +Market_replace:test() (gas: 345188) 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_setPriorityRestrictedThreshold:test() (gas: 157359) +Market_setPriorityRestrictedThreshold:test_zeroAvailable() (gas: 152334) 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_supplierSelectionUsingUpSuppliersLastRemoval:test() (gas: 920440) +Market_swapWithoutFeeSpecialOrder_emits_and_skips_transfer_when_transferring_wrong_erc20_to_rNori:test() (gas: 421170) +Market_swap_emits_and_skips_transfer_when_transferring_wrong_erc20_to_rNori:test() (gas: 521772) +Market_swap_emits_event_and_skips_mint_when_minting_rNori_to_nonERC1155Receiver:test() (gas: 599145) 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) +Market_withdraw_1x3_center:test() (gas: 340825) +Market_withdraw_2x1_back:test() (gas: 345485) +Market_withdraw_2x1_front:test() (gas: 333842) +Market_withdraw_2x1_front_relist:test() (gas: 381777) +Market_withdraw_as_DEFAULT_ADMIN_ROLE:test() (gas: 276535) +Market_withdraw_as_operator:test() (gas: 285702) +Market_withdraw_as_supplier:test() (gas: 274676) +Market_withdraw_reverts:test() (gas: 138709) 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_getTotalBalanceFromRemovalQueue:test() (gas: 23877) +RemovalQueue_getTotalBalanceFromRemovalQueue:test_100xRemovalsOfTheDifferentVintages() (gas: 895764) +RemovalQueue_getTotalBalanceFromRemovalQueue:test_100xRemovalsOfTheSameVintage() (gas: 620277) RemovalQueue_insertRemovalByVintage:test_insertRemovalOnce() (gas: 119613) RemovalQueue_insertRemovalByVintage:test_insertRemovalTwice() (gas: 121125) Removal__beforeTokenTransfer:test() (gas: 18010) @@ -124,56 +124,56 @@ Removal__createRemovalData:test_reverts_InvalidData() (gas: 25711) Removal__createRemovalDataBatch:test() (gas: 29572) Removal__createRemovalDataBatch:test_reverts_InvalidData2() (gas: 36714) Removal__isValidTransferAmount:testFuzz_ReturnFalse_NonMultiplesOf1e14(uint256) (runs: 256, μ: 13891, ~: 13847) -Removal__isValidTransferAmount:testFuzz_ReturnTrue_MultiplesOf1e14(uint256) (runs: 256, μ: 14380, ~: 14497) +Removal__isValidTransferAmount:testFuzz_ReturnTrue_MultiplesOf1e14(uint256) (runs: 256, μ: 14369, ~: 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_AmountIsZeroAndToIsTheCertificate() (gas: 4683) 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_addBalance:test() (gas: 60269) +Removal_addBalance_reverts_RemovalNotYetMinted:test() (gas: 31093) Removal_batchGetHoldbackPercentages_multipleIds:test() (gas: 11098) Removal_batchGetHoldbackPercentages_singleId:test() (gas: 10346) -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: 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_consign_revertsForSoldRemovals:test() (gas: 1172950) +Removal_getMarketBalance:test() (gas: 1185991) +Removal_getOwnedTokenIds:test_multiple_tokens_with_transfer() (gas: 1079154) +Removal_getOwnedTokenIds:test_no_tokens() (gas: 18772) +Removal_getProjectId:test() (gas: 19395) +Removal_grantRole:test_reverts_when_paused() (gas: 26250) +Removal_mintBatch:test() (gas: 348622) +Removal_mintBatch_list:test() (gas: 557933) +Removal_mintBatch_list_sequential:test() (gas: 749180) +Removal_mintBatch_multiple:test_16() (gas: 3083718) +Removal_mintBatch_multiple:test_2() (gas: 726271) +Removal_mintBatch_multiple:test_32() (gas: 5779051) +Removal_mintBatch_multiple:test_4() (gas: 1063010) +Removal_mintBatch_multiple:test_8() (gas: 1736493) Removal_mintBatch_revertsInvalidHoldbackPercentage:test() (gas: 56204) Removal_mintBatch_reverts_mint_to_wrong_address:test() (gas: 89002) -Removal_mintBatch_zero_amount_removal:test() (gas: 311185) +Removal_mintBatch_zero_amount_removal:test() (gas: 311196) Removal_mintBatch_zero_amount_removal_to_market_reverts:test() (gas: 85435) -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) -Removal_release_retired_burned:testDecrementsCertificateDiscrepancy() (gas: 88947) -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: 237568) -Removal_renounceRole:test_reverts_when_paused() (gas: 19688) +Removal_multicall:test_balanceOfBatch() (gas: 491983) +Removal_release_listed:test() (gas: 486522) +Removal_release_listed_isRemovedFromMarket:test() (gas: 486876) +Removal_release_partial_listed:test() (gas: 79615) +Removal_release_retired:test() (gas: 92381) +Removal_release_retired_2x:test() (gas: 98467) +Removal_release_retired_burned:test() (gas: 94861) +Removal_release_retired_burned:testDecrementsCertificateDiscrepancy() (gas: 88925) +Removal_release_retired_oneHundredCertificates:test() (gas: 89491) +Removal_release_reverts_AccessControl:test() (gas: 48735) +Removal_release_unlisted:test() (gas: 48600) +Removal_release_unlisted_listed_and_retired:test() (gas: 237533) +Removal_renounceRole:test_reverts_when_paused() (gas: 19666) +Removal_retire:test() (gas: 989180) +Removal_retire_gasLimit:test() (gas: 15361939) +Removal_retire_revertsIfRemovalBalanceSumDifferentFromCertificateAmount:test() (gas: 1002910) Removal_revokeRole:test_reverts_when_paused() (gas: 26840) -Removal_safeBatchTransferFrom_reverts_ForbiddenTransfer:test() (gas: 32073) +Removal_safeBatchTransferFrom_reverts_ForbiddenTransfer:test() (gas: 32162) Removal_safeTransferFrom_reverts_ForbiddenTransfer:test() (gas: 27738) Removal_setHoldbackPercentage:test() (gas: 37877) Removal_setHoldbackPercentage:test_reverts_InvalidHoldbackPercentage() (gas: 29163) @@ -181,11 +181,11 @@ RestrictedNORI__validateSchedule:test_startTimeNotZero() (gas: 269) RestrictedNORI__validateSchedule_reverts:test_restrictionDurationZero() (gas: 3362) RestrictedNORI__validateSchedule_reverts:test_startTimeZero() (gas: 3384) RestrictedNORI_createSchedule:test_RevertWhen_MethodologyVersionHasNoDurationSet() (gas: 26650) -RestrictedNORI_createSchedule:test_RevertWhen_ScheduleExists() (gas: 43800) +RestrictedNORI_createSchedule:test_RevertWhen_ScheduleExists() (gas: 43888) RestrictedNORI_getUnderlyingTokenAddress:test() (gas: 17124) RestrictedNORI_initialize:test() (gas: 21827) RestrictedNORI_linearReleaseAmountAvailable:test() (gas: 11920) -RestrictedNORI_revokeUnreleasedTokens:test() (gas: 246318) +RestrictedNORI_revokeUnreleasedTokens:test() (gas: 246406) RestrictedNORI_scheduleExists:test() (gas: 15177) RestrictedNORI_scheduleExists:test_doesntExist() (gas: 15156) RestrictedNORI_transfers_revert:testSafeBatchTransferFromReverts() (gas: 29158) diff --git a/contracts/Removal.sol b/contracts/Removal.sol index 3041a89fc..891324384 100644 --- a/contracts/Removal.sol +++ b/contracts/Removal.sol @@ -211,6 +211,22 @@ contract Removal is uint256[] removalAmounts ); + /** + * @notice Emitted when removals are directly retired into a certificate by Nori. + * @param certificateRecipient The recipient of the certificate. + * @param certificateAmount The total amount of the certificate to mint (denominated in RTs). + * @param certificateId The ID of the certificate being minted. + * @param removalIds The removal IDs to use to mint the certificate. + * @param removalAmounts The amounts to retire from each corresponding removal ID. + */ + event Retire( + address indexed certificateRecipient, + uint256 indexed certificateAmount, + uint256 indexed certificateId, + uint256[] removalIds, + uint256[] removalAmounts + ); + /** * @notice Locks the contract, preventing any future re-initialization. * @dev See more [here](https://docs.openzeppelin.com/contracts/4.x/api/proxy#Initializable-_disableInitializers--). @@ -381,19 +397,18 @@ contract Removal is /** * @notice Transfers the provided `amounts` (denominated in NRTs) of the specified removal `ids` directly to the - * Certificate contract to mint a legacy certificate. This function provides Nori the ability to execute a one-off - * migration of legacy certificates and removals (legacy certificates and removals are those which existed prior to - * our deployment to Polygon and covers all historic issuances and purchases up until the date that we start using the - * Market contract). + * Certificate contract to mint a certificate. This function provides Nori the ability to retire removals directly + * into the Certificate contract and to specify exactly which removals will be retired. * @dev The Certificate contract implements `onERC1155BatchReceived`, which is invoked upon receipt of a batch of - * removals (triggered via `_safeBatchTransferFrom`). This function circumvents the market contract's lifecycle by + * removals (triggered via `_safeBatchTransferFrom`). This function circumvents the market contract by * transferring the removals from an account with the `CONSIGNOR_ROLE` role. + * Emits a `Retire` event. * * It is necessary that the consignor holds the removals because of the following: * - `ids` can be composed of a list of removal IDs that belong to one or more suppliers. * - `_safeBatchTransferFrom` only accepts one `from` address. * - `Certificate.onERC1155BatchReceived` will mint a *new* certificate every time an additional batch is received, so - * we must ensure that all the removals comprising the certificate to be migrated come from a single batch. + * we must ensure that all the removals comprising the certificate come from a single batch. * * ##### Requirements: * - The caller must have the `CONSIGNOR_ROLE` role. @@ -407,13 +422,13 @@ contract Removal is * @param certificateRecipient The recipient of the certificate to be minted. * @param certificateAmount The total amount of the certificate. */ - function migrate( + function retire( uint256[] calldata ids, uint256[] calldata amounts, address certificateRecipient, uint256 certificateAmount ) external onlyRole(CONSIGNOR_ROLE) { - emit Migrate({ + emit Retire({ certificateRecipient: certificateRecipient, certificateAmount: certificateAmount, certificateId: _certificate.totalMinted(), diff --git a/docs/Removal.md b/docs/Removal.md index 28e4ec529..72b710105 100644 --- a/docs/Removal.md +++ b/docs/Removal.md @@ -187,6 +187,24 @@ Emitted when legacy removals are minted and then immediately used to migrate a l | removalAmounts | uint256[] | The amounts for each corresponding removal ID to use to mint the certificate via migration. | +### Retire + +```solidity +event Retire(address certificateRecipient, uint256 certificateAmount, uint256 certificateId, uint256[] removalIds, uint256[] removalAmounts) +``` + +Emitted when removals are directly retired into a certificate by Nori. + + +| Name | Type | Description | +| ---- | ---- | ----------- | +| certificateRecipient | address | The recipient of the certificate. | +| certificateAmount | uint256 | The total amount of the certificate to mint (denominated in RTs). | +| certificateId | uint256 | The ID of the certificate being minted. | +| removalIds | uint256[] | The removal IDs to use to mint the certificate. | +| removalAmounts | uint256[] | The amounts to retire from each corresponding removal ID. | + + ### constructor ```solidity @@ -323,27 +341,26 @@ this contract, and handles the mechanics of listing this token for sale. | amount | uint256 | The balance of this token ID to transfer to the Market contract | -### migrate +### retire ```solidity -function migrate(uint256[] ids, uint256[] amounts, address certificateRecipient, uint256 certificateAmount) external +function retire(uint256[] ids, uint256[] amounts, address certificateRecipient, uint256 certificateAmount) external ``` Transfers the provided `amounts` (denominated in NRTs) of the specified removal `ids` directly to the -Certificate contract to mint a legacy certificate. This function provides Nori the ability to execute a one-off -migration of legacy certificates and removals (legacy certificates and removals are those which existed prior to -our deployment to Polygon and covers all historic issuances and purchases up until the date that we start using the -Market contract). +Certificate contract to mint a certificate. This function provides Nori the ability to retire removals directly +into the Certificate contract and to specify exactly which removals will be retired. The Certificate contract implements `onERC1155BatchReceived`, which is invoked upon receipt of a batch of -removals (triggered via `_safeBatchTransferFrom`). This function circumvents the market contract's lifecycle by +removals (triggered via `_safeBatchTransferFrom`). This function circumvents the market contract by transferring the removals from an account with the `CONSIGNOR_ROLE` role. +Emits a `Retire` event. It is necessary that the consignor holds the removals because of the following: - `ids` can be composed of a list of removal IDs that belong to one or more suppliers. - `_safeBatchTransferFrom` only accepts one `from` address. - `Certificate.onERC1155BatchReceived` will mint a *new* certificate every time an additional batch is received, so -we must ensure that all the removals comprising the certificate to be migrated come from a single batch. +we must ensure that all the removals comprising the certificate come from a single batch. ##### Requirements: - The caller must have the `CONSIGNOR_ROLE` role. diff --git a/tasks/migrate-certificates.ts b/tasks/migrate-certificates.ts index c9f9b6f02..1a187628c 100644 --- a/tasks/migrate-certificates.ts +++ b/tasks/migrate-certificates.ts @@ -123,7 +123,7 @@ const validateMigrateEvents = ({ }[] = parseTransactionLogs({ contractInstance: removalContract, txReceipt: txResult, - eventNames: ['Migrate'], + eventNames: ['Retire'], }).map((log) => ({ certificateId: log.args.certificateId.toNumber(), removalAmounts: log.args.removalAmounts.map((a: BigNumber) => @@ -503,7 +503,7 @@ export const GET_MIGRATE_CERTIFICATES_TASK = () => ) .div(1_000_000) .toString(); - return removalContract.interface.encodeFunctionData('migrate', [ + return removalContract.interface.encodeFunctionData('retire', [ ids, amounts, recipient, @@ -588,7 +588,7 @@ export const GET_MIGRATE_CERTIFICATES_TASK = () => tokenIds = parseTransactionLogs({ contractInstance: removalContract, txReceipt, - eventNames: ['Migrate'], + eventNames: ['Retire'], }).map((log) => log.args.certificateId.toNumber()); if (tokenIds.length !== batch.length) { throw new Error( diff --git a/test/Removal.t.sol b/test/Removal.t.sol index cf2dd76b5..c5ebfb05e 100644 --- a/test/Removal.t.sol +++ b/test/Removal.t.sol @@ -11,7 +11,7 @@ using AddressArrayLib for address[]; // todo fuzz RemovalIdLib // todo test that checks Removal.consign can happen using multi call with mix-match project IDs -contract Removal_migrate_revertsIfRemovalBalanceSumDifferentFromCertificateAmount is +contract Removal_retire_revertsIfRemovalBalanceSumDifferentFromCertificateAmount is UpgradeableMarket { /*////////////////////////////////////////////////////////////// @@ -34,7 +34,7 @@ contract Removal_migrate_revertsIfRemovalBalanceSumDifferentFromCertificateAmoun address[] suppliers; function setUp() external { - // todo reuse setup in Removal_migrate_gasLimit + // todo reuse setup in Removal_retire_gasLimit _removal.grantRole({ role: _removal.CONSIGNOR_ROLE(), account: _namedAccounts.admin @@ -67,7 +67,7 @@ contract Removal_migrate_revertsIfRemovalBalanceSumDifferentFromCertificateAmoun function test() external { vm.prank(_namedAccounts.admin); vm.expectRevert("Incorrect supply allocation"); - _removal.migrate({ + _removal.retire({ ids: idsForAllSuppliers, amounts: amountsForAllSuppliers, certificateRecipient: _namedAccounts.buyer, @@ -135,7 +135,7 @@ contract Removal_consign_revertsForSoldRemovals is UpgradeableMarket { } } -contract Removal_migrate is UpgradeableMarket { +contract Removal_retire is UpgradeableMarket { /*////////////////////////////////////////////////////////////// INPUTS //////////////////////////////////////////////////////////////*/ @@ -156,7 +156,7 @@ contract Removal_migrate is UpgradeableMarket { address[] suppliers; function setUp() external { - // todo reuse setup in Removal_migrate_gasLimit + // todo reuse setup in Removal_retire_gasLimit _removal.grantRole({ role: _removal.CONSIGNOR_ROLE(), account: _namedAccounts.admin @@ -186,7 +186,7 @@ contract Removal_migrate is UpgradeableMarket { } } - event Migrate( + event Retire( address indexed certificateRecipient, uint256 indexed certificateAmount, uint256 indexed certificateId, @@ -197,7 +197,7 @@ contract Removal_migrate is UpgradeableMarket { function test() external { vm.prank(_namedAccounts.admin); vm.recordLogs(); - _removal.migrate({ + _removal.retire({ ids: idsForAllSuppliers, amounts: amountsForAllSuppliers, certificateRecipient: _namedAccounts.buyer, @@ -215,7 +215,7 @@ contract Removal_migrate is UpgradeableMarket { assertEq(entries.length, 4); assertEq( entries[0].topics[0], - keccak256("Migrate(address,uint256,uint256,uint256[],uint256[])") // todo if we move contract events to interfaces we can use IRemoval.Migrate.selector instead + keccak256("Retire(address,uint256,uint256,uint256[],uint256[])") // todo if we move contract events to interfaces we can use IRemoval.Retire.selector instead ); assertEq(entries[0].topics.length, 4); assertEq( @@ -233,7 +233,7 @@ contract Removal_migrate is UpgradeableMarket { } } -contract Removal_migrate_gasLimit is UpgradeableMarket { +contract Removal_retire_gasLimit is UpgradeableMarket { /*////////////////////////////////////////////////////////////// INPUTS //////////////////////////////////////////////////////////////*/ @@ -286,7 +286,7 @@ contract Removal_migrate_gasLimit is UpgradeableMarket { function test() external { vm.prank(_namedAccounts.admin); uint256 initialGas = gasleft(); - _removal.migrate({ + _removal.retire({ ids: idsForAllSuppliers, amounts: amountsForAllSuppliers, certificateRecipient: _namedAccounts.buyer, diff --git a/test/tasks/vesting.test.ts b/test/tasks/vesting.test.ts index 8ccc96b44..00ade44db 100644 --- a/test/tasks/vesting.test.ts +++ b/test/tasks/vesting.test.ts @@ -139,7 +139,7 @@ const MOCK_GITHUB_VESTING_GRANTS = [ '0xBd6E6A75c7A51cfdf08DDf2f538ceB221835839b', 'daf2922b-c96d-442e-a267-e977758961f1', '50000', - '2023-03-03T00:00:00Z', + dateLessThanOneYearAgoFromTodayISOString, '2027-03-03T00:00:00Z', '2028-03-03T00:00:00Z', '2024-03-03T00:00:00Z', @@ -171,7 +171,7 @@ const MOCK_GITHUB_VESTING_GRANTS = [ '0x6029424b26feFfe2879E88C62e8130dC418e64D9', '6a36b897-b38d-4938-9537-32a5ee4f33b8', '52000', - '2023-03-10T00:00:00Z', + dateLessThanOneYearAgoFromTodayISOString, '2028-03-10T00:00:00Z', '2029-03-10T00:00:00Z', '2025-03-10T00:00:00Z', @@ -980,7 +980,10 @@ describe('vesting task', () => { }, '0xBd6E6A75c7A51cfdf08DDf2f538ceB221835839b': { vestEndTime: { __old: 0, __new: 1_804_032_000 }, - startTime: { __old: 0, __new: 1_677_801_600 }, + startTime: { + __old: 0, + __new: utcToEvmTime(dateLessThanOneYearAgoFromTodayUnix), + }, originalAmount: { __old: '0', __new: '50000000000000000000000', @@ -1028,7 +1031,10 @@ describe('vesting task', () => { }, '0x6029424b26feFfe2879E88C62e8130dC418e64D9': { vestEndTime: { __old: 0, __new: 1_836_259_200 }, - startTime: { __old: 0, __new: 1_678_406_400 }, + startTime: { + __old: 0, + __new: utcToEvmTime(dateLessThanOneYearAgoFromTodayUnix), + }, originalAmount: { __old: '0', __new: '52000000000000000000000', diff --git a/types/events/Removal.ts b/types/events/Removal.ts index 751543c4b..cac2ed502 100644 --- a/types/events/Removal.ts +++ b/types/events/Removal.ts @@ -5,6 +5,7 @@ import type { ApprovalForAllEvent, InitializedEvent, MigrateEvent, + RetireEvent, PausedEvent, ReleaseRemovalEvent, RoleAdminChangedEvent, @@ -33,6 +34,10 @@ export interface RemovalEventMap { name: 'Migrate'; args: MigrateEvent['args']; }; + Retire: { + name: 'Retire'; + args: RetireEvent['args']; + }; Paused: { name: 'Paused'; args: PausedEvent['args'];