From 2efcbf07690ddce9987cc38450354aa220d56134 Mon Sep 17 00:00:00 2001 From: gabrielstoica Date: Mon, 2 Sep 2024 17:07:13 +0300 Subject: [PATCH 01/10] feat: make 'Container' ERC721 received compatible --- src/Container.sol | 34 ++++++++++++++++++++++++---- src/interfaces/IContainer.sol | 42 ++++++++++++++++++++++++++--------- 2 files changed, 62 insertions(+), 14 deletions(-) diff --git a/src/Container.sol b/src/Container.sol index 92e0123..68906d6 100644 --- a/src/Container.sol +++ b/src/Container.sol @@ -2,8 +2,10 @@ pragma solidity ^0.8.26; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import { ExcessivelySafeCall } from "@nomad-xyz/excessively-safe-call/src/ExcessivelySafeCall.sol"; import { IContainer } from "./interfaces/IContainer.sol"; @@ -80,7 +82,18 @@ contract Container is IContainer, ModuleManager { asset.safeTransfer({ to: msg.sender, value: amount }); // Log the successful ERC-20 token withdrawal - emit AssetWithdrawn({ sender: msg.sender, asset: address(asset), amount: amount }); + emit AssetWithdrawn({ to: msg.sender, asset: address(asset), amount: amount }); + } + + /// @inheritdoc IContainer + function withdrawERC721(IERC721 collection, uint256 tokenId) public onlyOwner { + // Interactions: withdraw by transferring the token to the sender + // We're using `safeTransferFrom` as the owner can be an ERC-4337 smart account + // therefore the `onERC721Received` hook must be implemented + collection.safeTransferFrom(address(this), msg.sender, tokenId); + + // Log the successful ERC-721 token withdrawal + emit ERC721Withdrawn({ to: msg.sender, collection: address(collection), tokenId: tokenId }); } /// @inheritdoc IContainer @@ -89,12 +102,12 @@ contract Container is IContainer, ModuleManager { if (amount > address(this).balance) revert Errors.InsufficientNativeToWithdraw(); // Interactions: withdraw by transferring the amount to the sender - (bool success,) = payable(msg.sender).call{ value: amount }(""); + (bool success,) = msg.sender.call{ value: amount }(""); // Revert if the call failed if (!success) revert Errors.NativeWithdrawFailed(); // Log the successful native token withdrawal - emit AssetWithdrawn({ sender: msg.sender, asset: address(0), amount: amount }); + emit AssetWithdrawn({ to: msg.sender, asset: address(0), amount: amount }); } /// @inheritdoc IModuleManager @@ -110,7 +123,7 @@ contract Container is IContainer, ModuleManager { /// @dev Allow container to receive native token (ETH) receive() external payable { // Log the successful native token deposit - emit NativeDeposited({ sender: msg.sender, amount: msg.value }); + emit NativeReceived({ from: msg.sender, amount: msg.value }); } /*////////////////////////////////////////////////////////////////////////// @@ -121,4 +134,17 @@ contract Container is IContainer, ModuleManager { function supportsInterface(bytes4 interfaceId) public pure override returns (bool) { return interfaceId == type(IContainer).interfaceId || interfaceId == type(IERC165).interfaceId; } + + /// @inheritdoc IERC721Receiver + function onERC721Received( + address, + address from, + uint256 tokenId, + bytes calldata + ) external override returns (bytes4) { + // Log the successful ERC-721 token receipt + emit ERC721Received(from, tokenId); + + return this.onERC721Received.selector; + } } diff --git a/src/interfaces/IContainer.sol b/src/interfaces/IContainer.sol index 861059b..eaa1e7f 100644 --- a/src/interfaces/IContainer.sol +++ b/src/interfaces/IContainer.sol @@ -2,30 +2,43 @@ pragma solidity ^0.8.26; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; /// @title IContainer /// @notice Contract that provides functionalities to store native token (ETH) value and any ERC-20 tokens, allowing /// external modules to be executed by extending its core functionalities -interface IContainer is IERC165 { +interface IContainer is IERC165, IERC721Receiver { /*////////////////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////////////////*/ /// @notice Emitted when an `amount` amount of `asset` native tokens (ETH) is deposited on the container - /// @param sender The address of the depositor + /// @param from The address of the depositor /// @param amount The amount of the deposited ERC-20 token - event NativeDeposited(address indexed sender, uint256 amount); + event NativeReceived(address indexed from, uint256 amount); - /// @notice Emitted when an `amount` amount of `asset` ERC-20 asset is withdrawn from the container - /// @param sender The address to which the tokens were transferred - /// @param asset The address of the withdrawn ERC-20 token - /// @param amount The amount of the withdrawn ERC-20 token - event AssetWithdrawn(address indexed sender, address indexed asset, uint256 amount); + /// @notice Emitted when an ERC-721 token is received by the container + /// @param from The address of the depositor + /// @param tokenId The ID of the received token + event ERC721Received(address indexed from, uint256 indexed tokenId); + + /// @notice Emitted when an `amount` amount of `asset` ERC-20 asset or native ETH is withdrawn from the container + /// @param to The address to which the tokens were transferred + /// @param asset The address of the ERC-20 token or zero-address for native ETH + /// @param amount The withdrawn amount + event AssetWithdrawn(address indexed to, address indexed asset, uint256 amount); + + /// @notice Emitted when an ERC-721 token is withdrawn from the container + /// @param to The address that received the token + /// @param collection The address of the ERC-721 collection + /// @param tokenId The ID of the token + event ERC721Withdrawn(address indexed to, address indexed collection, uint256 tokenId); /// @notice Emitted when a module execution is successful - /// @param module The address of the module that was executed - /// @param value The value sent to the module address required for the call + /// @param module The address of the module + /// @param value The value sent to the module required for the call /// @param data The ABI-encoded method called on the module event ModuleExecutionSucceded(address indexed module, uint256 value, bytes data); @@ -48,6 +61,15 @@ interface IContainer is IERC165 { /// @param amount The amount of the ERC-20 token to withdraw function withdrawERC20(IERC20 asset, uint256 amount) external; + /// @notice Withdraws the `tokenId` token from the ERC-721 `collection` collection + /// + /// Requirements: + /// - `msg.sender` must be the owner of the container + /// + /// @param collection The address of the ERC-721 collection + /// @param tokenId The ID of the token to withdraw + function withdrawERC721(IERC721 collection, uint256 tokenId) external; + /// @notice Withdraws an `amount` amount of native token (ETH) from the container /// /// Requirements: From 4a47df16da40b52ae1f1ca18667c633d3389d587 Mon Sep 17 00:00:00 2001 From: gabrielstoica Date: Mon, 2 Sep 2024 17:26:33 +0300 Subject: [PATCH 02/10] feat: make 'Container' ERC-1155 receiver compatible --- src/Container.sol | 31 +++++++++++++++++++++++++++++++ src/interfaces/IContainer.sol | 17 +++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/Container.sol b/src/Container.sol index 68906d6..aab1d2d 100644 --- a/src/Container.sol +++ b/src/Container.sol @@ -6,6 +6,7 @@ import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; +import { IERC1155Receiver } from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; import { ExcessivelySafeCall } from "@nomad-xyz/excessively-safe-call/src/ExcessivelySafeCall.sol"; import { IContainer } from "./interfaces/IContainer.sol"; @@ -147,4 +148,34 @@ contract Container is IContainer, ModuleManager { return this.onERC721Received.selector; } + + /// @inheritdoc IERC1155Receiver + function onERC1155Received( + address, + address from, + uint256 id, + uint256 value, + bytes calldata + ) external override returns (bytes4) { + // Log the successful ERC-1155 token receipt + emit ERC1155Received(from, id, value); + + return this.onERC1155Received.selector; + } + + /// @inheritdoc IERC1155Receiver + function onERC1155BatchReceived( + address, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata + ) external override returns (bytes4) { + for (uint256 i; i < ids.length; ++i) { + // Log the successful ERC-1155 token receipt + emit ERC1155Received(from, ids[i], values[i]); + } + + return this.onERC1155BatchReceived.selector; + } } diff --git a/src/interfaces/IContainer.sol b/src/interfaces/IContainer.sol index eaa1e7f..b88cc67 100644 --- a/src/interfaces/IContainer.sol +++ b/src/interfaces/IContainer.sol @@ -5,11 +5,12 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; +import { IERC1155Receiver } from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; /// @title IContainer /// @notice Contract that provides functionalities to store native token (ETH) value and any ERC-20 tokens, allowing /// external modules to be executed by extending its core functionalities -interface IContainer is IERC165, IERC721Receiver { +interface IContainer is IERC165, IERC721Receiver, IERC1155Receiver { /*////////////////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////////////////*/ @@ -24,6 +25,12 @@ interface IContainer is IERC165, IERC721Receiver { /// @param tokenId The ID of the received token event ERC721Received(address indexed from, uint256 indexed tokenId); + /// @notice Emitted when an ERC-1155 token is received by the container + /// @param from The address of the depositor + /// @param id The ID of the received token + /// @param value The amount of tokens received + event ERC1155Received(address indexed from, uint256 indexed id, uint256 value); + /// @notice Emitted when an `amount` amount of `asset` ERC-20 asset or native ETH is withdrawn from the container /// @param to The address to which the tokens were transferred /// @param asset The address of the ERC-20 token or zero-address for native ETH @@ -31,11 +38,17 @@ interface IContainer is IERC165, IERC721Receiver { event AssetWithdrawn(address indexed to, address indexed asset, uint256 amount); /// @notice Emitted when an ERC-721 token is withdrawn from the container - /// @param to The address that received the token + /// @param to The address to which the token was transferred /// @param collection The address of the ERC-721 collection /// @param tokenId The ID of the token event ERC721Withdrawn(address indexed to, address indexed collection, uint256 tokenId); + /// @notice Emitted when an ERC-1155 token is withdrawn from the container + /// @param to The address to which the tokens were transferred + /// @param id The ID of the token + /// @param value The amount of the tokens withdrawn + event ERC1155Withdrawn(address indexed to, address indexed collection, uint256 id, uint256 value); + /// @notice Emitted when a module execution is successful /// @param module The address of the module /// @param value The value sent to the module required for the call From fda03488926a3ad2dd6e94458833ef951c490594 Mon Sep 17 00:00:00 2001 From: gabrielstoica Date: Mon, 2 Sep 2024 17:29:13 +0300 Subject: [PATCH 03/10] test: update 'Container'-related events --- .../concrete/container/receive/receive.t.sol | 6 +-- .../concrete/container/receive/receive.tree | 2 +- .../withdraw-erc20/withdrawERC20.t.sol | 2 +- .../withdraw-native/withdrawNative.t.sol | 2 +- test/utils/Events.sol | 43 ++++++++++++++----- 5 files changed, 39 insertions(+), 16 deletions(-) diff --git a/test/unit/concrete/container/receive/receive.t.sol b/test/unit/concrete/container/receive/receive.t.sol index f281a51..f1525ba 100644 --- a/test/unit/concrete/container/receive/receive.t.sol +++ b/test/unit/concrete/container/receive/receive.t.sol @@ -13,12 +13,12 @@ contract Receive_Unit_Concrete_Test is Container_Unit_Concrete_Test { // Make Bob the caller for this test suite vm.startPrank({ msgSender: users.bob }); - // Expect the {NativeDeposited} event to be emitted upon ETH deposit + // Expect the {NativeReceived} event to be emitted upon ETH deposit vm.expectEmit(); - emit Events.NativeDeposited({ sender: users.bob, amount: 1 ether }); + emit Events.NativeReceived({ from: users.bob, amount: 1 ether }); // Run the test - (bool success, ) = address(container).call{ value: 1 ether }(""); + (bool success,) = address(container).call{ value: 1 ether }(""); if (!success) revert(); // Assert the {Container} contract balance diff --git a/test/unit/concrete/container/receive/receive.tree b/test/unit/concrete/container/receive/receive.tree index 8f16c0d..e762629 100644 --- a/test/unit/concrete/container/receive/receive.tree +++ b/test/unit/concrete/container/receive/receive.tree @@ -1,2 +1,2 @@ receive.t.sol -└── it should emit an {NativeDeposited} event \ No newline at end of file +└── it should emit an {NativeReceived} event \ No newline at end of file diff --git a/test/unit/concrete/container/withdraw-erc20/withdrawERC20.t.sol b/test/unit/concrete/container/withdraw-erc20/withdrawERC20.t.sol index 81558b0..ae1a425 100644 --- a/test/unit/concrete/container/withdraw-erc20/withdrawERC20.t.sol +++ b/test/unit/concrete/container/withdraw-erc20/withdrawERC20.t.sol @@ -51,7 +51,7 @@ contract WithdrawERC20_Unit_Concrete_Test is Container_Unit_Concrete_Test { // Expect the {AssetWithdrawn} event to be emitted vm.expectEmit(); - emit Events.AssetWithdrawn({ sender: users.eve, asset: address(usdt), amount: 10e6 }); + emit Events.AssetWithdrawn({ to: users.eve, asset: address(usdt), amount: 10e6 }); // Run the test container.withdrawERC20({ asset: IERC20(address(usdt)), amount: 10e6 }); diff --git a/test/unit/concrete/container/withdraw-native/withdrawNative.t.sol b/test/unit/concrete/container/withdraw-native/withdrawNative.t.sol index 9847077..523259d 100644 --- a/test/unit/concrete/container/withdraw-native/withdrawNative.t.sol +++ b/test/unit/concrete/container/withdraw-native/withdrawNative.t.sol @@ -85,7 +85,7 @@ contract WithdrawNative_Unit_Concrete_Test is Container_Unit_Concrete_Test { // Expect the {AssetWithdrawn} event to be emitted vm.expectEmit(); - emit Events.AssetWithdrawn({ sender: users.eve, asset: address(0x0), amount: ethToWithdraw }); + emit Events.AssetWithdrawn({ to: users.eve, asset: address(0x0), amount: ethToWithdraw }); // Run the test container.withdrawNative({ amount: ethToWithdraw }); diff --git a/test/utils/Events.sol b/test/utils/Events.sol index 10836e6..68269c7 100644 --- a/test/utils/Events.sol +++ b/test/utils/Events.sol @@ -48,19 +48,42 @@ abstract contract Events { //////////////////////////////////////////////////////////////////////////*/ /// @notice Emitted when an `amount` amount of `asset` native tokens (ETH) is deposited on the container - /// @param sender The address of the depositor + /// @param from The address of the depositor /// @param amount The amount of the deposited ERC-20 token - event NativeDeposited(address indexed sender, uint256 amount); - - /// @notice Emitted when an `amount` amount of `asset` ERC-20 asset is withdrawn from the container - /// @param sender The address to which the tokens were transferred - /// @param asset The address of the withdrawn ERC-20 token - /// @param amount The amount of the withdrawn ERC-20 token - event AssetWithdrawn(address indexed sender, address indexed asset, uint256 amount); + event NativeReceived(address indexed from, uint256 amount); + + /// @notice Emitted when an ERC-721 token is received by the container + /// @param from The address of the depositor + /// @param tokenId The ID of the received token + event ERC721Received(address indexed from, uint256 indexed tokenId); + + /// @notice Emitted when an ERC-1155 token is received by the container + /// @param from The address of the depositor + /// @param id The ID of the received token + /// @param value The amount of tokens received + event ERC1155Received(address indexed from, uint256 indexed id, uint256 value); + + /// @notice Emitted when an `amount` amount of `asset` ERC-20 asset or native ETH is withdrawn from the container + /// @param to The address to which the tokens were transferred + /// @param asset The address of the ERC-20 token or zero-address for native ETH + /// @param amount The withdrawn amount + event AssetWithdrawn(address indexed to, address indexed asset, uint256 amount); + + /// @notice Emitted when an ERC-721 token is withdrawn from the container + /// @param to The address to which the token was transferred + /// @param collection The address of the ERC-721 collection + /// @param tokenId The ID of the token + event ERC721Withdrawn(address indexed to, address indexed collection, uint256 tokenId); + + /// @notice Emitted when an ERC-1155 token is withdrawn from the container + /// @param to The address to which the tokens were transferred + /// @param id The ID of the token + /// @param value The amount of the tokens withdrawn + event ERC1155Withdrawn(address indexed to, address indexed collection, uint256 id, uint256 value); /// @notice Emitted when a module execution is successful - /// @param module The address of the module that was executed - /// @param value The value sent to the module address required for the call + /// @param module The address of the module + /// @param value The value sent to the module required for the call /// @param data The ABI-encoded method called on the module event ModuleExecutionSucceded(address indexed module, uint256 value, bytes data); From 615d6a861e68814398b616f1b524038b85102a37 Mon Sep 17 00:00:00 2001 From: gabrielstoica Date: Tue, 3 Sep 2024 14:14:15 +0300 Subject: [PATCH 04/10] test: add 'withdrawERC721' unit concrete tests --- test/Base.t.sol | 3 + test/mocks/MockERC721Collection.sol | 34 +++++++++++ .../withdraw-erc721/withdrawERC721.t.sol | 57 +++++++++++++++++++ .../withdraw-erc721/withdrawERC721.tree | 9 +++ test/utils/Errors.sol | 3 + 5 files changed, 106 insertions(+) create mode 100644 test/mocks/MockERC721Collection.sol create mode 100644 test/unit/concrete/container/withdraw-erc721/withdrawERC721.t.sol create mode 100644 test/unit/concrete/container/withdraw-erc721/withdrawERC721.tree diff --git a/test/Base.t.sol b/test/Base.t.sol index 26da9fb..12bc7ce 100644 --- a/test/Base.t.sol +++ b/test/Base.t.sol @@ -12,6 +12,7 @@ import { Container } from "./../src/Container.sol"; import { ModuleKeeper } from "./../src/ModuleKeeper.sol"; import { DockRegistry } from "./../src/DockRegistry.sol"; import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { MockERC721Collection } from "./mocks/MockERC721Collection.sol"; abstract contract Base_Test is Test, Events { /*////////////////////////////////////////////////////////////////////////// @@ -31,6 +32,7 @@ abstract contract Base_Test is Test, Events { MockModule internal mockModule; MockNonCompliantContainer internal mockNonCompliantContainer; MockBadReceiver internal mockBadReceiver; + MockERC721Collection internal mockERC721; /*////////////////////////////////////////////////////////////////////////// TEST STORAGE @@ -59,6 +61,7 @@ abstract contract Base_Test is Test, Events { mockModule = new MockModule(); mockNonCompliantContainer = new MockNonCompliantContainer({ _owner: users.admin }); mockBadReceiver = new MockBadReceiver(); + mockERC721 = new MockERC721Collection("MockERC721Collection", "MC"); // Create a mock modules array mockModules.push(address(mockModule)); diff --git a/test/mocks/MockERC721Collection.sol b/test/mocks/MockERC721Collection.sol new file mode 100644 index 0000000..71949f8 --- /dev/null +++ b/test/mocks/MockERC721Collection.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +/// @notice A mock implementation of ERC-721 that implements the {IERC721} interface +contract MockERC721Collection is ERC721 { + uint256 private _tokenIdCounter; + + constructor(string memory name, string memory symbol) ERC721(name, symbol) { + // Start the token ID counter at 1 + _tokenIdCounter = 1; + } + + function mint(address to) public returns (uint256) { + // Generate a new token ID + uint256 tokenId = _tokenIdCounter; + + // Mint the token to the specified address + _safeMint(to, tokenId); + + // Increment the token ID counter + unchecked { + _tokenIdCounter++; + } + + // Return the token ID + return tokenId; + } + + function burn(uint256 tokenId) public { + _burn(tokenId); + } +} diff --git a/test/unit/concrete/container/withdraw-erc721/withdrawERC721.t.sol b/test/unit/concrete/container/withdraw-erc721/withdrawERC721.t.sol new file mode 100644 index 0000000..3346d84 --- /dev/null +++ b/test/unit/concrete/container/withdraw-erc721/withdrawERC721.t.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +import { Container_Unit_Concrete_Test } from "../Container.t.sol"; +import { Errors } from "../../../../utils/Errors.sol"; +import { Events } from "../../../../utils/Events.sol"; +import { IERC721 } from "@openzeppelin/contracts/interfaces/IERC721.sol"; + +contract WithdrawERC721_Unit_Concrete_Test is Container_Unit_Concrete_Test { + function setUp() public virtual override { + Container_Unit_Concrete_Test.setUp(); + } + + function test_RevertWhen_CallerNotOwner() external { + // Make Bob the caller for this test suite who is not the owner of the container + vm.startPrank({ msgSender: users.bob }); + + // Expect the next call to revert with the {CallerNotContainerOwner} error + vm.expectRevert(Errors.CallerNotContainerOwner.selector); + + // Run the test + container.withdrawERC721({ collection: IERC721(address(0x0)), tokenId: 1 }); + } + + modifier whenCallerOwner() { + // Make Eve the caller for the next test suite as she's the owner of the container + vm.startPrank({ msgSender: users.eve }); + _; + } + + function test_RevertWhen_NonexistentERC721Token() external whenCallerOwner { + // Expect the next call to revert with the {ERC721NonexistentToken} error + vm.expectRevert(abi.encodeWithSelector(Errors.ERC721NonexistentToken.selector, 1)); + + // Run the test by attempting to withdraw a nonexistent ERC721 token + container.withdrawERC721({ collection: mockERC721, tokenId: 1 }); + } + + modifier whenExistingERC721Token() { + // Mint an ERC721 token to the container contract + mockERC721.mint({ to: address(container) }); + _; + } + + function test_WithdrawERC721() external whenCallerOwner whenExistingERC721Token { + // Expect the {ERC721Withdrawn} event to be emitted + vm.expectEmit(); + emit Events.ERC721Withdrawn({ to: users.eve, collection: address(mockERC721), tokenId: 1 }); + + // Run the test + container.withdrawERC721({ collection: mockERC721, tokenId: 1 }); + + // Assert the actual and expected owner of the ERC721 token + address actualOwner = mockERC721.ownerOf(1); + assertEq(actualOwner, users.eve); + } +} diff --git a/test/unit/concrete/container/withdraw-erc721/withdrawERC721.tree b/test/unit/concrete/container/withdraw-erc721/withdrawERC721.tree new file mode 100644 index 0000000..91ac7f6 --- /dev/null +++ b/test/unit/concrete/container/withdraw-erc721/withdrawERC721.tree @@ -0,0 +1,9 @@ +withdrawERC721.t.sol +├── when the caller IS NOT the container owner +│ └── it should revert with the {CallerNotContainerOwner} error +└── when the caller IS the container owner + ├── when there is no existing ERC-721 token to be transferred + │ └── it should revert with the {ERC721WithdrawalFailed} error + └── when there is an existing ERC-721 token to be transferred + ├── it should transfer the token to the caller + └── it should emit an {ERC721Withdrawn} event diff --git a/test/utils/Errors.sol b/test/utils/Errors.sol index 033075a..f3506dc 100644 --- a/test/utils/Errors.sol +++ b/test/utils/Errors.sol @@ -33,6 +33,9 @@ library Errors { /// @notice Thrown when the deposited ERC-20 token amount is zero error InvalidAssetZeroAmount(); + /// @notice Thrown when the ERC-721 token ID does not exist + error ERC721NonexistentToken(uint256 tokenId); + /*////////////////////////////////////////////////////////////////////////// MODULE-MANAGER //////////////////////////////////////////////////////////////////////////*/ From 574c10d73cfacefad6e493595053970b45ba4756 Mon Sep 17 00:00:00 2001 From: gabrielstoica Date: Tue, 3 Sep 2024 16:36:46 +0300 Subject: [PATCH 05/10] feat: add 'withdrawERC1155' method and update docs --- src/Container.sol | 23 +++++++++++++++++++++-- src/interfaces/IContainer.sol | 20 ++++++++++++++++---- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/Container.sol b/src/Container.sol index aab1d2d..a8e55bc 100644 --- a/src/Container.sol +++ b/src/Container.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.26; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { IERC1155 } from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; @@ -88,8 +89,9 @@ contract Container is IContainer, ModuleManager { /// @inheritdoc IContainer function withdrawERC721(IERC721 collection, uint256 tokenId) public onlyOwner { - // Interactions: withdraw by transferring the token to the sender - // We're using `safeTransferFrom` as the owner can be an ERC-4337 smart account + // Checks, Effects, Interactions: withdraw by transferring the token to the container owner + // Notes: + // - we're using `safeTransferFrom` as the owner can be an ERC-4337 smart account // therefore the `onERC721Received` hook must be implemented collection.safeTransferFrom(address(this), msg.sender, tokenId); @@ -97,6 +99,23 @@ contract Container is IContainer, ModuleManager { emit ERC721Withdrawn({ to: msg.sender, collection: address(collection), tokenId: tokenId }); } + /// @inheritdoc IContainer + function withdrawERC1155(IERC1155 collection, uint256[] memory ids, uint256[] memory amounts) public onlyOwner { + // Checks, Effects, Interactions: withdraw by transferring the tokens to the container owner + // Notes: + // - we're using `safeTransferFrom` as the owner can be an ERC-4337 smart account + // therefore the `onERC1155Received` hook must be implemented + // - depending on the length of the `ids` array, we're using `safeBatchTransferFrom` or `safeTransferFrom` + if (ids.length > 1) { + collection.safeBatchTransferFrom({ from: address(this), to: msg.sender, ids: ids, values: amounts, data: "" }); + } else { + collection.safeTransferFrom({ from: address(this), to: msg.sender, id: ids[0], value: amounts[0], data: "" }); + } + + // Log the successful ERC-1155 token withdrawal + emit ERC1155Withdrawn(msg.sender, address(collection), ids, amounts); + } + /// @inheritdoc IContainer function withdrawNative(uint256 amount) public onlyOwner { // Checks: the native balance of the container minus the amount locked for operations is greater than the requested amount diff --git a/src/interfaces/IContainer.sol b/src/interfaces/IContainer.sol index b88cc67..88883b9 100644 --- a/src/interfaces/IContainer.sol +++ b/src/interfaces/IContainer.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.26; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { IERC1155 } from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import { IERC1155Receiver } from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; @@ -43,11 +44,12 @@ interface IContainer is IERC165, IERC721Receiver, IERC1155Receiver { /// @param tokenId The ID of the token event ERC721Withdrawn(address indexed to, address indexed collection, uint256 tokenId); - /// @notice Emitted when an ERC-1155 token is withdrawn from the container + /// @notice Emitted when a `value` amount of ERC-1155 `id` tokens are withdrawn from the container /// @param to The address to which the tokens were transferred - /// @param id The ID of the token - /// @param value The amount of the tokens withdrawn - event ERC1155Withdrawn(address indexed to, address indexed collection, uint256 id, uint256 value); + /// @param collection The address of the ERC-1155 collection + /// @param ids The IDs of the tokens + /// @param values The amounts of the token types withdrawn + event ERC1155Withdrawn(address indexed to, address indexed collection, uint256[] ids, uint256[] values); /// @notice Emitted when a module execution is successful /// @param module The address of the module @@ -83,6 +85,16 @@ interface IContainer is IERC165, IERC721Receiver, IERC1155Receiver { /// @param tokenId The ID of the token to withdraw function withdrawERC721(IERC721 collection, uint256 tokenId) external; + /// @notice Withdraws an `amount` amount of ERC-1155 `id` token from the container + /// + /// Requirements: + /// - `msg.sender` must be the owner of the container + /// + /// @param collection The address of the ERC-1155 collection + /// @param ids The IDs of tokens to withdraw + /// @param amounts The amounts of tokens to withdraw + function withdrawERC1155(IERC1155 collection, uint256[] memory ids, uint256[] memory amounts) external; + /// @notice Withdraws an `amount` amount of native token (ETH) from the container /// /// Requirements: From bfe8461424ecd5c1175a9c5e53dbdc28bf3b2ee9 Mon Sep 17 00:00:00 2001 From: gabrielstoica Date: Tue, 3 Sep 2024 16:37:08 +0300 Subject: [PATCH 06/10] test: add 'withdrawERC1155' unit concrete tests --- test/Base.t.sol | 3 + test/mocks/MockERC1155Collection.sol | 60 ++++++++++++ .../withdraw-erc1155/withdrawERC1155.t.sol | 98 +++++++++++++++++++ .../withdraw-erc1155/withdrawERC1155.tree | 9 ++ test/utils/Errors.sol | 3 + test/utils/Events.sol | 6 +- 6 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 test/mocks/MockERC1155Collection.sol create mode 100644 test/unit/concrete/container/withdraw-erc1155/withdrawERC1155.t.sol create mode 100644 test/unit/concrete/container/withdraw-erc1155/withdrawERC1155.tree diff --git a/test/Base.t.sol b/test/Base.t.sol index 12bc7ce..7be6728 100644 --- a/test/Base.t.sol +++ b/test/Base.t.sol @@ -13,6 +13,7 @@ import { ModuleKeeper } from "./../src/ModuleKeeper.sol"; import { DockRegistry } from "./../src/DockRegistry.sol"; import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import { MockERC721Collection } from "./mocks/MockERC721Collection.sol"; +import { MockERC1155Collection } from "./mocks/MockERC1155Collection.sol"; abstract contract Base_Test is Test, Events { /*////////////////////////////////////////////////////////////////////////// @@ -33,6 +34,7 @@ abstract contract Base_Test is Test, Events { MockNonCompliantContainer internal mockNonCompliantContainer; MockBadReceiver internal mockBadReceiver; MockERC721Collection internal mockERC721; + MockERC1155Collection internal mockERC1155; /*////////////////////////////////////////////////////////////////////////// TEST STORAGE @@ -62,6 +64,7 @@ abstract contract Base_Test is Test, Events { mockNonCompliantContainer = new MockNonCompliantContainer({ _owner: users.admin }); mockBadReceiver = new MockBadReceiver(); mockERC721 = new MockERC721Collection("MockERC721Collection", "MC"); + mockERC1155 = new MockERC1155Collection("https://nft.com/0x1.json"); // Create a mock modules array mockModules.push(address(mockModule)); diff --git a/test/mocks/MockERC1155Collection.sol b/test/mocks/MockERC1155Collection.sol new file mode 100644 index 0000000..02962ef --- /dev/null +++ b/test/mocks/MockERC1155Collection.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +import { ERC1155 } from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; + +/// @notice A mock implementation of ERC-1155 that implements the {IERC1155} interface +contract MockERC1155Collection is ERC1155 { + uint256 private _tokenTypeIdCounter; + + constructor(string memory uri) ERC1155(uri) { + // Start the token type ID counter at 1 + _tokenTypeIdCounter = 1; + } + + function mint(address to, uint256 amount) public returns (uint256) { + // Generate a new token ID + uint256 tokenId = _tokenTypeIdCounter; + + // Mint the token to the specified address + _mint(to, tokenId, amount, ""); + + // Increment the token ID counter + unchecked { + _tokenTypeIdCounter++; + } + + // Return the token ID + return tokenId; + } + + function mintBatch(address to, uint256[] memory amounts) public returns (uint256[] memory) { + // Create a new array to store the token IDs + uint256 cachedAmount = amounts.length; + uint256[] memory tokenIds = new uint256[](cachedAmount); + + for (uint256 i; i < cachedAmount; ++i) { + // Generate a new token ID for each amount + tokenIds[i] = _tokenTypeIdCounter; + + // Increment the token ID counter + unchecked { + ++_tokenTypeIdCounter; + } + } + + // Mint the tokens to the specified address + _mintBatch(to, tokenIds, amounts, ""); + + // Return the token IDs + return tokenIds; + } + + function burn(uint256 tokenId, uint256 amount) public { + _burn(msg.sender, tokenId, amount); + } + + function burnBatch(uint256[] memory tokenIds, uint256[] memory amounts) public { + _burnBatch(msg.sender, tokenIds, amounts); + } +} diff --git a/test/unit/concrete/container/withdraw-erc1155/withdrawERC1155.t.sol b/test/unit/concrete/container/withdraw-erc1155/withdrawERC1155.t.sol new file mode 100644 index 0000000..bd555ba --- /dev/null +++ b/test/unit/concrete/container/withdraw-erc1155/withdrawERC1155.t.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +import { Container_Unit_Concrete_Test } from "../Container.t.sol"; +import { Errors } from "../../../../utils/Errors.sol"; +import { Events } from "../../../../utils/Events.sol"; +import { IERC1155 } from "@openzeppelin/contracts/interfaces/IERC1155.sol"; + +contract WithdrawERC1155_Unit_Concrete_Test is Container_Unit_Concrete_Test { + uint256[] ids; + uint256[] amounts; + + function setUp() public virtual override { + Container_Unit_Concrete_Test.setUp(); + + ids = new uint256[](2); + amounts = new uint256[](2); + + ids[0] = 1; + ids[1] = 2; + amounts[0] = 100; + amounts[1] = 200; + } + + function test_RevertWhen_CallerNotOwner() external { + // Make Bob the caller for this test suite who is not the owner of the container + vm.startPrank({ msgSender: users.bob }); + + // Expect the next call to revert with the {CallerNotContainerOwner} error + vm.expectRevert(Errors.CallerNotContainerOwner.selector); + + // Run the test + container.withdrawERC1155({ collection: IERC1155(address(0x0)), ids: ids, amounts: amounts }); + } + + modifier whenCallerOwner() { + // Make Eve the caller for the next test suite as she's the owner of the container + vm.startPrank({ msgSender: users.eve }); + _; + } + + function test_RevertWhen_InsufficientERC1155Balance() external whenCallerOwner { + // Expect the next call to revert with the {ERC1155InsufficientBalance} error + vm.expectRevert( + abi.encodeWithSelector( + Errors.ERC1155InsufficientBalance.selector, address(container), 0, amounts[0], ids[0] + ) + ); + + // Run the test by attempting to withdraw a nonexistent ERC1155 token + container.withdrawERC1155({ collection: mockERC1155, ids: ids, amounts: amounts }); + } + + modifier whenExistingERC1155Token() { + // Mint 100 ERC1155 tokens to the container contract + mockERC1155.mintBatch({ to: address(container), amounts: amounts }); + _; + } + + function test_WithdrawERC1155() external whenCallerOwner whenExistingERC1155Token { + uint256[] memory idsToWithdraw = new uint256[](1); + uint256[] memory amountsToWithdraw = new uint256[](1); + idsToWithdraw[0] = 1; + amountsToWithdraw[0] = 100; + + // Expect the {ERC721Withdrawn} event to be emitted + vm.expectEmit(); + emit Events.ERC1155Withdrawn({ + to: users.eve, + collection: address(mockERC1155), + ids: idsToWithdraw, + amounts: amountsToWithdraw + }); + + // Run the test + container.withdrawERC1155({ collection: mockERC1155, ids: idsToWithdraw, amounts: amountsToWithdraw }); + + // Assert the actual and expected token type 1 ERC1155 balance of Eve + uint256 actualBalanceOfEve = mockERC1155.balanceOf(users.eve, idsToWithdraw[0]); + assertEq(actualBalanceOfEve, amountsToWithdraw[0]); + } + + function test_WithdrawERC1155_Batch() external whenCallerOwner whenExistingERC1155Token { + // Expect the {ERC721Withdrawn} event to be emitted + vm.expectEmit(); + emit Events.ERC1155Withdrawn({ to: users.eve, collection: address(mockERC1155), ids: ids, amounts: amounts }); + + // Run the test + container.withdrawERC1155({ collection: mockERC1155, ids: ids, amounts: amounts }); + + // Assert the actual and expected balance of any ERC1155 tokens + uint256 numberOfTokens = ids.length; + for (uint256 i; i < numberOfTokens; ++i) { + uint256 actualBalanceOfEve = mockERC1155.balanceOf(users.eve, ids[i]); + assertEq(actualBalanceOfEve, amounts[i]); + } + } +} diff --git a/test/unit/concrete/container/withdraw-erc1155/withdrawERC1155.tree b/test/unit/concrete/container/withdraw-erc1155/withdrawERC1155.tree new file mode 100644 index 0000000..cb85f99 --- /dev/null +++ b/test/unit/concrete/container/withdraw-erc1155/withdrawERC1155.tree @@ -0,0 +1,9 @@ +withdrawERC1155.t.sol +├── when the caller IS NOT the container owner +│ └── it should revert with the {CallerNotContainerOwner} error +└── when the caller IS the container owner + ├── when there the ERC-1155 balance IS NOT sufficient + │ └── it should revert with the {ERC1155InsufficientBalance} error + └── when there the ERC-1155 balance IS sufficient + ├── it should transfer the token(s) to the caller + └── it should emit an {ERC1155Withdrawn} event diff --git a/test/utils/Errors.sol b/test/utils/Errors.sol index f3506dc..6068dea 100644 --- a/test/utils/Errors.sol +++ b/test/utils/Errors.sol @@ -36,6 +36,9 @@ library Errors { /// @notice Thrown when the ERC-721 token ID does not exist error ERC721NonexistentToken(uint256 tokenId); + /// @notice Thrown when the balance of the sender is insufficient to perform an ERC-1155 transfer + error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); + /*////////////////////////////////////////////////////////////////////////// MODULE-MANAGER //////////////////////////////////////////////////////////////////////////*/ diff --git a/test/utils/Events.sol b/test/utils/Events.sol index 68269c7..b397c30 100644 --- a/test/utils/Events.sol +++ b/test/utils/Events.sol @@ -77,9 +77,9 @@ abstract contract Events { /// @notice Emitted when an ERC-1155 token is withdrawn from the container /// @param to The address to which the tokens were transferred - /// @param id The ID of the token - /// @param value The amount of the tokens withdrawn - event ERC1155Withdrawn(address indexed to, address indexed collection, uint256 id, uint256 value); + /// @param ids The IDs of the tokens + /// @param amounts The amounts of the tokens + event ERC1155Withdrawn(address indexed to, address indexed collection, uint256[] ids, uint256[] amounts); /// @notice Emitted when a module execution is successful /// @param module The address of the module From 647a45b1af816928504323100073d427ed7450c5 Mon Sep 17 00:00:00 2001 From: gabrielstoica Date: Wed, 4 Sep 2024 17:03:50 +0300 Subject: [PATCH 07/10] docs: update 'IContainer.sol' docs --- src/interfaces/IContainer.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/interfaces/IContainer.sol b/src/interfaces/IContainer.sol index 88883b9..e53473b 100644 --- a/src/interfaces/IContainer.sol +++ b/src/interfaces/IContainer.sol @@ -67,7 +67,7 @@ interface IContainer is IERC165, IERC721Receiver, IERC1155Receiver { /// @param data The ABI-encode definition of the method (+inputs) to call function execute(address module, uint256 value, bytes memory data) external returns (bool success); - /// @notice Withdraws an `amount` amount of `asset` ERC-20 token from the container + /// @notice Withdraws an `amount` amount of `asset` ERC-20 token /// /// Requirements: /// - `msg.sender` must be the owner of the container @@ -76,7 +76,7 @@ interface IContainer is IERC165, IERC721Receiver, IERC1155Receiver { /// @param amount The amount of the ERC-20 token to withdraw function withdrawERC20(IERC20 asset, uint256 amount) external; - /// @notice Withdraws the `tokenId` token from the ERC-721 `collection` collection + /// @notice Withdraws the `tokenId` token of the ERC-721 `collection` collection /// /// Requirements: /// - `msg.sender` must be the owner of the container @@ -85,7 +85,7 @@ interface IContainer is IERC165, IERC721Receiver, IERC1155Receiver { /// @param tokenId The ID of the token to withdraw function withdrawERC721(IERC721 collection, uint256 tokenId) external; - /// @notice Withdraws an `amount` amount of ERC-1155 `id` token from the container + /// @notice Withdraws an `amount` amount of the ERC-1155 `id` token /// /// Requirements: /// - `msg.sender` must be the owner of the container @@ -95,7 +95,7 @@ interface IContainer is IERC165, IERC721Receiver, IERC1155Receiver { /// @param amounts The amounts of tokens to withdraw function withdrawERC1155(IERC1155 collection, uint256[] memory ids, uint256[] memory amounts) external; - /// @notice Withdraws an `amount` amount of native token (ETH) from the container + /// @notice Withdraws an `amount` amount of native token (ETH) /// /// Requirements: /// - `msg.sender` must be the owner of the container From 54ad72c77877affa828fe6bca257e0ba477c8e56 Mon Sep 17 00:00:00 2001 From: gabrielstoica Date: Thu, 5 Sep 2024 13:47:50 +0300 Subject: [PATCH 08/10] feat: add fallback function to 'Container' --- src/Container.sol | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Container.sol b/src/Container.sol index a8e55bc..b21d03e 100644 --- a/src/Container.sol +++ b/src/Container.sol @@ -35,6 +35,21 @@ contract Container is IContainer, ModuleManager { dockRegistry = _dockRegistry; } + /*////////////////////////////////////////////////////////////////////////// + RECEIVE & FALLBACK + //////////////////////////////////////////////////////////////////////////*/ + + /// @dev Allow container to receive native token (ETH) + receive() external payable { + // Log the successful native token deposit + emit NativeReceived({ from: msg.sender, amount: msg.value }); + } + + /// @dev Fallback function to handle incoming calls with data + fallback() external payable { + emit NativeReceived({ from: msg.sender, amount: msg.value }); + } + /*////////////////////////////////////////////////////////////////////////// MODIFIERS //////////////////////////////////////////////////////////////////////////*/ @@ -140,12 +155,6 @@ contract Container is IContainer, ModuleManager { super.disableModule(module); } - /// @dev Allow container to receive native token (ETH) - receive() external payable { - // Log the successful native token deposit - emit NativeReceived({ from: msg.sender, amount: msg.value }); - } - /*////////////////////////////////////////////////////////////////////////// CONSTANT FUNCTIONS //////////////////////////////////////////////////////////////////////////*/ From 3d9e4ec369f3cdb1b3b0ba5e6189361ebfb52b52 Mon Sep 17 00:00:00 2001 From: gabrielstoica Date: Thu, 5 Sep 2024 13:48:18 +0300 Subject: [PATCH 09/10] test(Container): add unit concrete test for 'fallback' method --- .../container/fallback/fallback.t.sol | 27 +++++++++++++++++++ .../concrete/container/fallback/fallback.tree | 2 ++ 2 files changed, 29 insertions(+) create mode 100644 test/unit/concrete/container/fallback/fallback.t.sol create mode 100644 test/unit/concrete/container/fallback/fallback.tree diff --git a/test/unit/concrete/container/fallback/fallback.t.sol b/test/unit/concrete/container/fallback/fallback.t.sol new file mode 100644 index 0000000..07705e3 --- /dev/null +++ b/test/unit/concrete/container/fallback/fallback.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +import { Container_Unit_Concrete_Test } from "../Container.t.sol"; +import { Events } from "../../../../utils/Events.sol"; + +contract Fallback_Unit_Concrete_Test is Container_Unit_Concrete_Test { + function setUp() public virtual override { + Container_Unit_Concrete_Test.setUp(); + } + + function test_Fallback() external { + // Make Bob the caller for this test suite + vm.startPrank({ msgSender: users.bob }); + + // Expect the {NativeReceived} event to be emitted upon ETH deposit with data + vm.expectEmit(); + emit Events.NativeReceived({ from: users.bob, amount: 1 ether }); + + // Run the test + (bool success,) = address(container).call{ value: 1 ether }("test"); + if (!success) revert(); + + // Assert the {Container} contract balance + assertEq(address(container).balance, 1 ether); + } +} diff --git a/test/unit/concrete/container/fallback/fallback.tree b/test/unit/concrete/container/fallback/fallback.tree new file mode 100644 index 0000000..46d45ea --- /dev/null +++ b/test/unit/concrete/container/fallback/fallback.tree @@ -0,0 +1,2 @@ +fallback.t.sol +└── it should emit an {NativeReceived} event \ No newline at end of file From fcca13362f7333348aa24c37c0c16002d27e66b6 Mon Sep 17 00:00:00 2001 From: gabrielstoica Date: Fri, 6 Sep 2024 14:21:01 +0300 Subject: [PATCH 10/10] chore: update gas snapshot --- .gas-snapshot | 124 +++++++++++++++++++++++++++----------------------- 1 file changed, 66 insertions(+), 58 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 4f68341..3027a2d 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,6 +1,6 @@ -AddToAllowlist_Unit_Concrete_Test:test_AddToAllowlist() (gas: 241256) -AddToAllowlist_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 13752) -AddToAllowlist_Unit_Concrete_Test:test_RevertWhen_InvalidZeroCodeModule() (gas: 13925) +AddToAllowlist_Unit_Concrete_Test:test_AddToAllowlist() (gas: 178178) +AddToAllowlist_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 12954) +AddToAllowlist_Unit_Concrete_Test:test_RevertWhen_InvalidZeroCodeModule() (gas: 13122) CancelInvoice_Integration_Concret_Test:test_CancelInvoice_PaymentMethodLinearStream_StatusOngoing() (gas: 446811) CancelInvoice_Integration_Concret_Test:test_CancelInvoice_PaymentMethodLinearStream_StatusPending() (gas: 30578) CancelInvoice_Integration_Concret_Test:test_CancelInvoice_PaymentMethodTranchedStream_StatusOngoing() (gas: 446790) @@ -13,38 +13,39 @@ CancelInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodLinearStream CancelInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodTranchedStream_StatusOngoing_SenderNoInitialtStreamSender() (gas: 404628) CancelInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodTranchedStream_StatusPending_SenderNotInvoiceRecipient() (gas: 20611) CancelInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodTransfer_SenderNotInvoiceRecipient() (gas: 20553) -ComputeNumberOfPayments_Helpers_Test:test_ComputeNumberOfPayments_Monthly() (gas: 4156) -ComputeNumberOfPayments_Helpers_Test:test_ComputeNumberOfPayments_Weekly() (gas: 4082) -ComputeNumberOfPayments_Helpers_Test:test_ComputeNumberOfPayments_Yearly() (gas: 4266) -Constructor_DockRegistry_Test:test_Constructor() (gas: 3353665) -Constructor_ModuleKeeper_Test:test_Constructor() (gas: 416041) +ComputeNumberOfPayments_Helpers_Test:test_ComputeNumberOfPayments_Monthly() (gas: 3530) +ComputeNumberOfPayments_Helpers_Test:test_ComputeNumberOfPayments_Weekly() (gas: 3483) +ComputeNumberOfPayments_Helpers_Test:test_ComputeNumberOfPayments_Yearly() (gas: 3613) +Constructor_DockRegistry_Test:test_Constructor() (gas: 2577196) +Constructor_ModuleKeeper_Test:test_Constructor() (gas: 241794) Constructor_StreamManager_Integration_Concret_Test:test_Constructor() (gas: 1615852) -CreateContainer_Unit_Concrete_Test:test_CreateContainer_DockIdNonZero() (gas: 3030474) -CreateContainer_Unit_Concrete_Test:test_CreateContainer_DockIdZero() (gas: 1548403) -CreateContainer_Unit_Concrete_Test:test_RevertWhen_CallerNotDockOwner() (gas: 1561938) -CreateInvoice_Integration_Concret_Test:test_CreateInvoice_LinearStream() (gas: 258963) -CreateInvoice_Integration_Concret_Test:test_CreateInvoice_PaymentMethodOneOffTransfer() (gas: 259167) -CreateInvoice_Integration_Concret_Test:test_CreateInvoice_RecurringTransfer() (gas: 260318) -CreateInvoice_Integration_Concret_Test:test_CreateInvoice_Tranched() (gas: 260591) +CreateContainer_Unit_Concrete_Test:test_CreateContainer_DockIdNonZero() (gas: 3153626) +CreateContainer_Unit_Concrete_Test:test_CreateContainer_DockIdZero() (gas: 1607335) +CreateContainer_Unit_Concrete_Test:test_RevertWhen_CallerNotDockOwner() (gas: 1624571) +CreateInvoice_Integration_Concret_Test:test_CreateInvoice_LinearStream() (gas: 259080) +CreateInvoice_Integration_Concret_Test:test_CreateInvoice_PaymentMethodOneOffTransfer() (gas: 259284) +CreateInvoice_Integration_Concret_Test:test_CreateInvoice_RecurringTransfer() (gas: 260435) +CreateInvoice_Integration_Concret_Test:test_CreateInvoice_Tranched() (gas: 260708) CreateInvoice_Integration_Concret_Test:test_RevertWhen_CallerNotContract() (gas: 89299) -CreateInvoice_Integration_Concret_Test:test_RevertWhen_EndTimeInThePast() (gas: 107235) +CreateInvoice_Integration_Concret_Test:test_RevertWhen_EndTimeInThePast() (gas: 107352) CreateInvoice_Integration_Concret_Test:test_RevertWhen_NonCompliantContainer() (gas: 92554) -CreateInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodLinearStream_PaymentAssetNativeToken() (gas: 107353) -CreateInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodRecurringTransfer_PaymentIntervalTooShortForSelectedRecurrence() (gas: 108043) -CreateInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodTranchedStream_PaymentAssetNativeToken() (gas: 108523) -CreateInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodTranchedStream_PaymentIntervalTooShortForSelectedRecurrence() (gas: 108087) -CreateInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodTranchedStream_RecurrenceSetToOneOff() (gas: 106731) -CreateInvoice_Integration_Concret_Test:test_RevertWhen_StartTimeGreaterThanEndTime() (gas: 106559) -CreateInvoice_Integration_Concret_Test:test_RevertWhen_ZeroPaymentAmount() (gas: 85918) -CreateInvoice_Integration_Fuzz_Test:testFuzz_CreateInvoice(uint8,uint8,address,uint40,uint40,uint128) (runs: 10001, μ: 202392, ~: 259073) -DisableModule_Unit_Concrete_Test:test_DisableModule() (gas: 249454) -DisableModule_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 22806) -EnableModule_Unit_Concrete_Test:test_EnableModule() (gas: 42771) -EnableModule_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 22785) -EnableModule_Unit_Concrete_Test:test_RevertWhen_ModuleNotAllowlisted() (gas: 32658) -Execute_Unit_Concrete_Test:test_Execute() (gas: 96131) -Execute_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 26420) -Execute_Unit_Concrete_Test:test_RevertWhen_ModuleNotEnabled() (gas: 26485) +CreateInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodLinearStream_PaymentAssetNativeToken() (gas: 107470) +CreateInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodRecurringTransfer_PaymentIntervalTooShortForSelectedRecurrence() (gas: 108160) +CreateInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodTranchedStream_PaymentAssetNativeToken() (gas: 108640) +CreateInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodTranchedStream_PaymentIntervalTooShortForSelectedRecurrence() (gas: 108204) +CreateInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodTranchedStream_RecurrenceSetToOneOff() (gas: 106848) +CreateInvoice_Integration_Concret_Test:test_RevertWhen_StartTimeGreaterThanEndTime() (gas: 106676) +CreateInvoice_Integration_Concret_Test:test_RevertWhen_ZeroPaymentAmount() (gas: 86035) +CreateInvoice_Integration_Fuzz_Test:testFuzz_CreateInvoice(uint8,uint8,address,uint40,uint40,uint128) (runs: 10001, μ: 202482, ~: 259190) +DisableModule_Unit_Concrete_Test:test_DisableModule() (gas: 183277) +DisableModule_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 21350) +EnableModule_Unit_Concrete_Test:test_EnableModule() (gas: 39197) +EnableModule_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 21302) +EnableModule_Unit_Concrete_Test:test_RevertWhen_ModuleNotAllowlisted() (gas: 29881) +Execute_Unit_Concrete_Test:test_Execute() (gas: 89623) +Execute_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 23959) +Execute_Unit_Concrete_Test:test_RevertWhen_ModuleNotEnabled() (gas: 24032) +Fallback_Unit_Concrete_Test:test_Fallback() (gas: 20887) PayInvoice_Integration_Concret_Test:test_PayInvoice_PaymentMethodLinearStream() (gas: 309509) PayInvoice_Integration_Concret_Test:test_PayInvoice_PaymentMethodTranchedStream() (gas: 436650) PayInvoice_Integration_Concret_Test:test_PayInvoice_PaymentMethodTransfer_ERC20Token_Recurring() (gas: 87178) @@ -52,34 +53,41 @@ PayInvoice_Integration_Concret_Test:test_PayInvoice_PaymentMethodTransfer_Native PayInvoice_Integration_Concret_Test:test_RevertWhen_InvoiceAlreadyPaid() (gas: 62436) PayInvoice_Integration_Concret_Test:test_RevertWhen_InvoiceCanceled() (gas: 29609) PayInvoice_Integration_Concret_Test:test_RevertWhen_InvoiceNull() (gas: 17829) -PayInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodTransfer_NativeTokenTransferFails() (gas: 170827) +PayInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodTransfer_NativeTokenTransferFails() (gas: 170944) PayInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodTransfer_PaymentAmountLessThanInvoiceValue() (gas: 31912) -PayInvoice_Integration_Fuzz_Test:testFuzz_PayInvoice(uint8,uint8,uint40,uint40,uint128) (runs: 10002, μ: 369458, ~: 342362) -Receive_Unit_Concrete_Test:test_Receive() (gas: 21868) -RemoveFromAllowlist_Unit_Concrete_Test:test_AddToAllowlist() (gas: 23859) -RemoveFromAllowlist_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 13752) -TransferContainerOwnership_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 1561103) -TransferContainerOwnership_Unit_Concrete_Test:test_RevertWhen_InvalidOwnerZeroAddress() (gas: 1559040) -TransferContainerOwnership_Unit_Concrete_Test:test_TransferContainerOwnership() (gas: 1569104) -TransferDockOwnership_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 1560963) -TransferDockOwnership_Unit_Concrete_Test:test_TransferDockOwnership() (gas: 1568645) -TransferOwnership_Unit_Concrete_Test:test_RevertWhen_CallerNotCurrentOwner() (gas: 15849) -TransferOwnership_Unit_Concrete_Test:test_RevertWhen_NewOwnerZeroAddress() (gas: 13765) -TransferOwnership_Unit_Concrete_Test:test_TransferOwnership() (gas: 24347) -TransferOwnership_Unit_Fuzz_Test:testFuzz_RevertWhen_CallerNotCurrentOwner(address) (runs: 10002, μ: 14624, ~: 14624) -TransferOwnership_Unit_Fuzz_Test:testFuzz_TransferOwnership(address) (runs: 10002, μ: 22748, ~: 22748) -UpdateModuleKeeper_Unit_Concrete_Test:test_RevertWhen_CallerNotRegistryOwner() (gas: 20219) -UpdateModuleKeeper_Unit_Concrete_Test:test_UpdateModuleKeeper() (gas: 29457) +PayInvoice_Integration_Fuzz_Test:testFuzz_PayInvoice(uint8,uint8,uint40,uint40,uint128) (runs: 10002, μ: 370886, ~: 342456) +Receive_Unit_Concrete_Test:test_Receive() (gas: 20733) +RemoveFromAllowlist_Unit_Concrete_Test:test_AddToAllowlist() (gas: 22211) +RemoveFromAllowlist_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 12981) +TransferContainerOwnership_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 1624113) +TransferContainerOwnership_Unit_Concrete_Test:test_RevertWhen_InvalidOwnerZeroAddress() (gas: 1622072) +TransferContainerOwnership_Unit_Concrete_Test:test_TransferContainerOwnership() (gas: 1630377) +TransferDockOwnership_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 1623891) +TransferDockOwnership_Unit_Concrete_Test:test_TransferDockOwnership() (gas: 1629839) +TransferOwnership_Unit_Concrete_Test:test_RevertWhen_CallerNotCurrentOwner() (gas: 15031) +TransferOwnership_Unit_Concrete_Test:test_RevertWhen_NewOwnerZeroAddress() (gas: 12972) +TransferOwnership_Unit_Concrete_Test:test_TransferOwnership() (gas: 22589) +TransferOwnership_Unit_Fuzz_Test:testFuzz_RevertWhen_CallerNotCurrentOwner(address) (runs: 10002, μ: 13560, ~: 13560) +TransferOwnership_Unit_Fuzz_Test:testFuzz_TransferOwnership(address) (runs: 10002, μ: 20802, ~: 20803) +UpdateModuleKeeper_Unit_Concrete_Test:test_RevertWhen_CallerNotRegistryOwner() (gas: 18570) +UpdateModuleKeeper_Unit_Concrete_Test:test_UpdateModuleKeeper() (gas: 26803) UpdateStreamBrokerFee_Integration_Concret_Test:test_RevertWhen_CallerNotOwner() (gas: 12864) UpdateStreamBrokerFee_Integration_Concret_Test:test_UpdateStreamBrokerFee() (gas: 38936) -UpgradeToAndCall_DockRegistry_Test:test_RevertWhen_CallerNotRegistryOwner() (gas: 23388) -UpgradeToAndCall_DockRegistry_Test:test_UpgradeToAndCall() (gas: 35430) -WithdrawERC20_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 23342) -WithdrawERC20_Unit_Concrete_Test:test_RevertWhen_InsufficientERC20ToWithdraw() (gas: 31627) -WithdrawERC20_Unit_Concrete_Test:test_WithdrawERC20() (gas: 104376) +UpgradeToAndCall_DockRegistry_Test:test_RevertWhen_CallerNotRegistryOwner() (gas: 21343) +UpgradeToAndCall_DockRegistry_Test:test_UpgradeToAndCall() (gas: 32559) +WithdrawERC1155_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 35766) +WithdrawERC1155_Unit_Concrete_Test:test_RevertWhen_InsufficientERC1155Balance() (gas: 47878) +WithdrawERC1155_Unit_Concrete_Test:test_WithdrawERC1155() (gas: 119637) +WithdrawERC1155_Unit_Concrete_Test:test_WithdrawERC1155_Batch() (gas: 138047) +WithdrawERC20_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 21352) +WithdrawERC20_Unit_Concrete_Test:test_RevertWhen_InsufficientERC20ToWithdraw() (gas: 28992) +WithdrawERC20_Unit_Concrete_Test:test_WithdrawERC20() (gas: 95442) +WithdrawERC721_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 21367) +WithdrawERC721_Unit_Concrete_Test:test_RevertWhen_NonexistentERC721Token() (gas: 34534) +WithdrawERC721_Unit_Concrete_Test:test_WithdrawERC721() (gas: 99513) WithdrawLinearStream_Integration_Concret_Test:test_WithdrawStream_LinearStream() (gas: 295994) WithdrawLinearStream_Integration_Concret_Test:test_WithdrawStream_TranchedStream() (gas: 424090) -WithdrawNative_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 22882) -WithdrawNative_Unit_Concrete_Test:test_RevertWhen_InsufficientNativeToWithdraw() (gas: 22871) -WithdrawNative_Unit_Concrete_Test:test_RevertWhen_NativeWithdrawFailed() (gas: 38894) -WithdrawNative_Unit_Concrete_Test:test_WithdrawNative() (gas: 45302) \ No newline at end of file +WithdrawNative_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 21281) +WithdrawNative_Unit_Concrete_Test:test_RevertWhen_InsufficientNativeToWithdraw() (gas: 21271) +WithdrawNative_Unit_Concrete_Test:test_RevertWhen_NativeWithdrawFailed() (gas: 36766) +WithdrawNative_Unit_Concrete_Test:test_WithdrawNative() (gas: 42337) \ No newline at end of file