Skip to content

Commit

Permalink
Public transfer endpoint for CONSIGNOR to move tokens (#735)
Browse files Browse the repository at this point in the history
* might need different endpoint entirely

* separate endpoint and tests

* docs, snapshot, docgen

* snapshot

* snapshot

* correct bad merge

* snapshot, docgen

---------

Co-authored-by: amiecorso <amie@nori.com>
  • Loading branch information
amiecorso and amiecorso authored Mar 7, 2024
1 parent 7a03b43 commit 599f58e
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 65 deletions.
133 changes: 68 additions & 65 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -20,37 +20,37 @@ 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: 470325)
Checkout_buyingFromOneRemoval_byApproval:test() (gas: 408716)
Checkout_buyingFromTenRemovals:test() (gas: 1549469)
Checkout_buyingFromTenRemovals_singleSupplier:test() (gas: 1309335)
Checkout_buyingFromTenRemovals_singleSupplier_withoutFee:test() (gas: 1304535)
Checkout_buyingFromTenRemovals_withoutFee:test() (gas: 1317286)
Checkout_buyingFromTenSuppliers:test() (gas: 1818246)
Checkout_buyingWithAlternateERC20:test() (gas: 540975)
Checkout_buyingWithAlternateERC20_floatingPointPriceMultiple:test() (gas: 506941)
Checkout_swapRevertsWhenBuyerIsMissingSANCTION_ALLOWLIST_ROLE:test() (gas: 168693)
Checkout_swapRevertsWithDifferentPermitSignerAndMsgSender:test() (gas: 168705)
Checkout_swapWithoutFeeSpecialOrder:test() (gas: 380608)
Checkout_swapWithoutFeeSpecialOrder_specificSupplier:test() (gas: 375263)
Checkout_buyingFromOneRemoval:test() (gas: 470304)
Checkout_buyingFromOneRemoval_byApproval:test() (gas: 408695)
Checkout_buyingFromTenRemovals:test() (gas: 1550316)
Checkout_buyingFromTenRemovals_singleSupplier:test() (gas: 1310226)
Checkout_buyingFromTenRemovals_singleSupplier_withoutFee:test() (gas: 1305426)
Checkout_buyingFromTenRemovals_withoutFee:test() (gas: 1318133)
Checkout_buyingFromTenSuppliers:test() (gas: 1818941)
Checkout_buyingWithAlternateERC20:test() (gas: 540866)
Checkout_buyingWithAlternateERC20_floatingPointPriceMultiple:test() (gas: 506920)
Checkout_swapRevertsWhenBuyerIsMissingSANCTION_ALLOWLIST_ROLE:test() (gas: 168649)
Checkout_swapRevertsWithDifferentPermitSignerAndMsgSender:test() (gas: 168661)
Checkout_swapWithoutFeeSpecialOrder:test() (gas: 380653)
Checkout_swapWithoutFeeSpecialOrder_specificSupplier:test() (gas: 375352)
Checkout_swapWithoutFeeSpecialOrder_specificSupplier:test_revertsWhenSupplierDoesNotExistInMarket() (gas: 57753)
Checkout_swapWithoutFeeSpecialOrder_specificVintages:test_basicFulfillment() (gas: 642456)
Checkout_swapWithoutFeeSpecialOrder_specificVintages:test_revertsWhenNoRemovalsFromSpecifiedVintages() (gas: 91044)
Checkout_swapWithoutFeeSpecialOrder_specificVintagesSpecificSupplier:test_basicFulfillment() (gas: 491885)
GasBenchmark_buyingFromManyRemovals_singleSupplier:test() (gas: 29850469)
GasBenchmark_buyingFromOneRemoval_singleSupplier:test() (gas: 322878)
Checkout_swapWithoutFeeSpecialOrder_specificVintages:test_basicFulfillment() (gas: 642523)
Checkout_swapWithoutFeeSpecialOrder_specificVintages:test_revertsWhenNoRemovalsFromSpecifiedVintages() (gas: 91022)
Checkout_swapWithoutFeeSpecialOrder_specificVintagesSpecificSupplier:test_basicFulfillment() (gas: 491974)
GasBenchmark_buyingFromManyRemovals_singleSupplier:test() (gas: 29850540)
GasBenchmark_buyingFromOneRemoval_singleSupplier:test() (gas: 322967)
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: 4266)
MarketInvariantTest:invariant_sumOfPurchaseAmounts() (runs: 400, calls: 6000, reverts: 4270)
MarketSupplierSelectionNotUsingUpSuppliersLastRemoval:test() (gas: 644634)
MarketInvariantTest:invariant_callSummary() (runs: 400, calls: 6000, reverts: 4258)
MarketInvariantTest:invariant_sumOfPurchaseAmounts() (runs: 400, calls: 6000, reverts: 4265)
MarketSupplierSelectionNotUsingUpSuppliersLastRemoval:test() (gas: 644679)
Market_ALLOWLIST_ROLE:test() (gas: 12799)
Market_SANCTION_ALLOWLIST_ROLE:test() (gas: 12897)
Market_USDC_swap_respects_decimal_mismatch:test() (gas: 786927)
Market_USDC_swap_respects_decimal_mismatch:test() (gas: 786972)
Market__addActiveRemoval:test() (gas: 183344)
Market__addActiveRemoval:test__lis2VintagesFor1SupplierFor2SubIdentifiers() (gas: 242879)
Market__addActiveRemoval:test__list1VintageFor1Supplier() (gas: 188309)
Expand Down Expand Up @@ -87,26 +87,26 @@ Market_onERC1155BatchReceived_reverts_SenderNotRemovalContract:test() (gas: 3317
Market_onERC1155Received:test() (gas: 206036)
Market_onERC1155Received_reverts_SenderNotRemovalContract:test() (gas: 159022)
Market_purchasingTokenAddress:test() (gas: 17080)
Market_replace:test() (gas: 278002)
Market_replace:test() (gas: 278047)
Market_replace_reverts_CertificateNotYetMinted:test() (gas: 49559)
Market_replace_reverts_ReplacementAmountExceedsNrtDeficit:test() (gas: 52590)
Market_replace_reverts_ReplacementAmountMismatch:test() (gas: 86397)
Market_replace_reverts_ReplacementAmountMismatch:test() (gas: 86353)
Market_setNoriFeePercentage_revertsInvalidPercentage:test() (gas: 20276)
Market_setPriorityRestrictedThreshold:test() (gas: 157448)
Market_setPriorityRestrictedThreshold:test_zeroAvailable() (gas: 152423)
Market_setPriorityRestrictedThreshold:test() (gas: 157360)
Market_setPriorityRestrictedThreshold:test_zeroAvailable() (gas: 152335)
Market_setPurchasingTokenAndPriceMultiple:test() (gas: 1026610)
Market_setPurchasingTokenAndPriceMultiple_revertsIfNotAdmin:test() (gas: 50813)
Market_supplierSelectionUsingUpSuppliersLastRemoval:test() (gas: 641343)
Market_swap_revertsWhenUnsafeERC20TransferFails:test() (gas: 189648)
Market_supplierSelectionUsingUpSuppliersLastRemoval:test() (gas: 641388)
Market_swap_revertsWhenUnsafeERC20TransferFails:test() (gas: 189604)
Market_validates_certificate_amount:test() (gas: 596800)
Market_withdraw_1x3_center:test() (gas: 340825)
Market_withdraw_2x1_back:test() (gas: 345507)
Market_withdraw_2x1_front:test() (gas: 333842)
Market_withdraw_2x1_front_relist:test() (gas: 381799)
Market_withdraw_as_DEFAULT_ADMIN_ROLE:test() (gas: 276535)
Market_withdraw_as_operator:test() (gas: 285680)
Market_withdraw_as_supplier:test() (gas: 274676)
Market_withdraw_reverts:test() (gas: 138709)
Market_withdraw_1x3_center:test() (gas: 340737)
Market_withdraw_2x1_back:test() (gas: 345397)
Market_withdraw_2x1_front:test() (gas: 333754)
Market_withdraw_2x1_front_relist:test() (gas: 381689)
Market_withdraw_as_DEFAULT_ADMIN_ROLE:test() (gas: 276447)
Market_withdraw_as_operator:test() (gas: 285592)
Market_withdraw_as_supplier:test() (gas: 274588)
Market_withdraw_reverts:test() (gas: 138621)
NORI_name:test() (gas: 17205)
NORI_permit:test() (gas: 92382)
NoriUSDC_permit:test() (gas: 122061)
Expand All @@ -119,10 +119,10 @@ Removal__beforeTokenTransfer:test() (gas: 18010)
Removal__beforeTokenTransfer:test_paused_reverts_Paused() (gas: 29432)
Removal__createRemovalData:test() (gas: 22593)
Removal__createRemovalData:test_reverts_InvalidData() (gas: 25689)
Removal__createRemovalDataBatch:test() (gas: 29594)
Removal__createRemovalDataBatch:test_reverts_InvalidData2() (gas: 36802)
Removal__createRemovalDataBatch:test() (gas: 29572)
Removal__createRemovalDataBatch:test_reverts_InvalidData2() (gas: 36736)
Removal__isValidTransferAmount:testFuzz_ReturnFalse_NonMultiplesOf1e14(uint256) (runs: 256, μ: 13913, ~: 13869)
Removal__isValidTransferAmount:testFuzz_ReturnTrue_MultiplesOf1e14(uint256) (runs: 256, μ: 14394, ~: 14519)
Removal__isValidTransferAmount:testFuzz_ReturnTrue_MultiplesOf1e14(uint256) (runs: 256, μ: 14397, ~: 14519)
Removal__isValidTransferAmount:testFuzz_ReturnTrue_SmallestGranularity() (gas: 6854)
Removal__isValidTransferAmount:test_ReturnFalse_AmountIsTooGranular() (gas: 6832)
Removal__isValidTransferAmount:test_ReturnFalse_AmountIsTooGranularAndToIsTheCertificate() (gas: 4767)
Expand All @@ -131,15 +131,18 @@ Removal__isValidTransferAmount:test_ReturnFalse_AmountIsZeroAndToIsTheCertificat
Removal__isValidTransferAmount:test_ReturnFalse_AmountIsZeroAndToIsTheMarket() (gas: 2543)
Removal__isValidTransferAmount:test_ReturnTrue_AmountIsZeroAndToIsNeitherTheMarketNorCertificate() (gas: 6852)
Removal__validateRemoval:test() (gas: 2491)
Removal__validateRemoval:test_reverts_InvalidData() (gas: 5373)
Removal_addBalance:test() (gas: 60291)
Removal_addBalance_reverts_RemovalNotYetMinted:test() (gas: 31115)
Removal_consign_revertsForSoldRemovals:test() (gas: 896812)
Removal_getMarketBalance:test() (gas: 907245)
Removal_getOwnedTokenIds:test_multiple_tokens_with_transfer() (gas: 916702)
Removal_getOwnedTokenIds:test_no_tokens() (gas: 18683)
Removal_getProjectId:test() (gas: 19307)
Removal_grantRole:test_reverts_when_paused() (gas: 26183)
Removal__validateRemoval:test_reverts_InvalidData() (gas: 5351)
Removal_addBalance:test() (gas: 60269)
Removal_addBalance_reverts_RemovalNotYetMinted:test() (gas: 31093)
Removal_consign_revertsForSoldRemovals:test() (gas: 896760)
Removal_consignorBatchTransfer:test() (gas: 297347)
Removal_consignorBatchTransfer:test_reverts_whenReceiverIsNotConsignor() (gas: 128791)
Removal_consignorBatchTransfer:test_reverts_whenSenderIsNotConsignor() (gas: 65690)
Removal_getMarketBalance:test() (gas: 907193)
Removal_getOwnedTokenIds:test_multiple_tokens_with_transfer() (gas: 917081)
Removal_getOwnedTokenIds:test_no_tokens() (gas: 18772)
Removal_getProjectId:test() (gas: 19395)
Removal_grantRole:test_reverts_when_paused() (gas: 26250)
Removal_mintBatch:test() (gas: 177546)
Removal_mintBatch_list:test() (gas: 395816)
Removal_mintBatch_list_sequential:test() (gas: 581633)
Expand All @@ -151,24 +154,24 @@ Removal_mintBatch_multiple:test_8() (gas: 1574376)
Removal_mintBatch_reverts_mint_to_wrong_address:test() (gas: 65369)
Removal_mintBatch_zero_amount_removal:test() (gas: 140120)
Removal_mintBatch_zero_amount_removal_to_market_reverts:test() (gas: 61802)
Removal_multicall:test_balanceOfBatch() (gas: 320976)
Removal_release_listed:test() (gas: 356811)
Removal_release_listed_isRemovedFromMarket:test() (gas: 357165)
Removal_release_partial_listed:test() (gas: 79593)
Removal_release_retired:test() (gas: 92447)
Removal_release_retired_2x:test() (gas: 98511)
Removal_release_retired_burned:test() (gas: 94927)
Removal_release_retired_burned:testDecrementsCertificateDiscrepancy() (gas: 88881)
Removal_release_retired_oneHundredCertificates:test() (gas: 89557)
Removal_release_reverts_AccessControl:test() (gas: 48757)
Removal_release_unlisted:test() (gas: 48617)
Removal_release_unlisted_listed_and_retired:test() (gas: 237498)
Removal_renounceRole:test_reverts_when_paused() (gas: 19599)
Removal_retire:test() (gas: 989202)
Removal_retire_gasLimit:test() (gas: 15361961)
Removal_retire_revertsIfRemovalBalanceSumDifferentFromCertificateAmount:test() (gas: 1002932)
Removal_multicall:test_balanceOfBatch() (gas: 320910)
Removal_release_listed:test() (gas: 356793)
Removal_release_listed_isRemovedFromMarket:test() (gas: 357148)
Removal_release_partial_listed:test() (gas: 79571)
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: 88859)
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: 237463)
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: 26818)
Removal_safeBatchTransferFrom_reverts_ForbiddenTransfer:test() (gas: 32073)
Removal_safeBatchTransferFrom_reverts_ForbiddenTransfer:test() (gas: 32162)
Removal_safeTransferFrom_reverts_ForbiddenTransfer:test() (gas: 27716)
UInt256ArrayLib_fill:test() (gas: 120236)
UInt256ArrayLib_fill:test_gas() (gas: 101580)
Expand Down
31 changes: 31 additions & 0 deletions contracts/Removal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,37 @@ contract Removal is
});
}

/**
* @notice Allows an address with the `CONSIGNOR_ROLE` to transfer tokens.
* @dev Emits a `TransferBatch` event.
*
* ##### Requirements:
*
* - Can only be called by an address with the `CONSIGNOR_ROLE`.
* - Respects the rules of `Removal._beforeTokenTransfer`.
* - `ids` and `amounts` must have the same length.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
* acceptance magic value.
* @param from The address to transfer from.
* @param to The address to transfer to.
* @param ids The removal IDs to transfer.
* @param amounts The amounts of removals to transfer.
*/
function consignorBatchTransfer(
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts
) public onlyRole(CONSIGNOR_ROLE) {
super._safeBatchTransferFrom({
from: from,
to: to,
ids: ids,
amounts: amounts,
data: ""
});
}

/**
* @notice Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`.
* @dev Emits an `ApprovalForAll` event.
Expand Down
26 changes: 26 additions & 0 deletions docs/Removal.md
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,32 @@ acceptance magic value.</i>
| data | bytes | The data to pass to the receiver contract. |


### consignorBatchTransfer

```solidity
function consignorBatchTransfer(address from, address to, uint256[] ids, uint256[] amounts) public
```

Allows an address with the `CONSIGNOR_ROLE` to transfer tokens.

<i>Emits a `TransferBatch` event.

##### Requirements:

- Can only be called by an address with the `CONSIGNOR_ROLE`.
- Respects the rules of `Removal._beforeTokenTransfer`.
- `ids` and `amounts` must have the same length.
- If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
acceptance magic value.</i>

| Name | Type | Description |
| ---- | ---- | ----------- |
| from | address | The address to transfer from. |
| to | address | The address to transfer to. |
| ids | uint256[] | The removal IDs to transfer. |
| amounts | uint256[] | The amounts of removals to transfer. |


### setApprovalForAll

```solidity
Expand Down
64 changes: 64 additions & 0 deletions test/Removal.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1250,6 +1250,70 @@ contract Removal_safeBatchTransferFrom_reverts_ForbiddenTransfer is
}
}

contract Removal_consignorBatchTransfer is UpgradeableMarket {
uint256[] private _removalIds;

function setUp() external {
_removalIds = _seedRemovals({
to: _namedAccounts.supplier,
count: 2,
list: false
});
}

function test() external {
_removal.grantRole({
role: _removal.CONSIGNOR_ROLE(),
account: _namedAccounts.admin
});
assert(_removal.hasRole(_removal.CONSIGNOR_ROLE(), _namedAccounts.admin));
vm.prank(_namedAccounts.admin);
_removal.consignorBatchTransfer({
from: _namedAccounts.supplier,
to: _namedAccounts.admin,
ids: _removalIds,
amounts: new uint256[](2).fill(1 ether)
});
assertEq(_removal.balanceOf(_namedAccounts.admin, _removalIds[0]), 1 ether);
assertEq(_removal.balanceOf(_namedAccounts.admin, _removalIds[1]), 1 ether);
}

function test_reverts_whenSenderIsNotConsignor() external {
assert(!_removal.hasRole(_removal.CONSIGNOR_ROLE(), _namedAccounts.admin));
vm.expectRevert(
bytes(
string.concat(
"AccessControl: account 0x05127efcd2fc6a781bfed49188da1081670b22d8 is missing role ",
"0xa269776b75ac4c5fa422bb11bec3ed3cee626848d07687372583174b209261fb"
)
)
);
vm.prank(_namedAccounts.admin);
_removal.consignorBatchTransfer({
from: _namedAccounts.supplier,
to: _namedAccounts.admin,
ids: _removalIds,
amounts: new uint256[](2).fill(1 ether)
});
}

function test_reverts_whenReceiverIsNotConsignor() external {
_removal.grantRole({
role: _removal.CONSIGNOR_ROLE(),
account: _namedAccounts.admin
});
assert(_removal.hasRole(_removal.CONSIGNOR_ROLE(), _namedAccounts.admin));
vm.expectRevert(ForbiddenTransfer.selector);
vm.prank(_namedAccounts.admin);
_removal.consignorBatchTransfer({
from: _namedAccounts.supplier,
to: _namedAccounts.supplier2,
ids: _removalIds,
amounts: new uint256[](2).fill(1 ether)
});
}
}

contract Removal_grantRole is UpgradeableMarket {
function setUp() external {
_removal.pause();
Expand Down

0 comments on commit 599f58e

Please sign in to comment.