Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dock Registry implementation #18

Merged
merged 16 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
AddToAllowlist_Unit_Concrete_Test:test_AddToAllowlist() (gas: 178178)
AddToAllowlist_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 12955)
AddToAllowlist_Unit_Concrete_Test:test_RevertWhen_InvalidZeroCodeModule() (gas: 13123)
CancelInvoice_Integration_Concret_Test:test_CancelInvoice_PaymentMethodLinearStream_StatusOngoing() (gas: 326597)
CancelInvoice_Integration_Concret_Test:test_CancelInvoice_PaymentMethodLinearStream_StatusPending() (gas: 30566)
CancelInvoice_Integration_Concret_Test:test_CancelInvoice_PaymentMethodTranchedStream_StatusOngoing() (gas: 445110)
CancelInvoice_Integration_Concret_Test:test_CancelInvoice_PaymentMethodTranchedStream_StatusPending() (gas: 30566)
CancelInvoice_Integration_Concret_Test:test_CancelInvoice_PaymentMethodTransfer() (gas: 30497)
CancelInvoice_Integration_Concret_Test:test_RevertWhen_InvoiceIsCanceled() (gas: 27177)
CancelInvoice_Integration_Concret_Test:test_RevertWhen_InvoiceIsPaid() (gas: 49663)
CancelInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodLinearStream_StatusOngoing_SenderNoInitialtStreamSender() (gas: 285668)
CancelInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodLinearStream_StatusPending_SenderNotInvoiceRecipient() (gas: 20666)
CancelInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodTranchedStream_StatusOngoing_SenderNoInitialtStreamSender() (gas: 402986)
CancelInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodTranchedStream_StatusPending_SenderNotInvoiceRecipient() (gas: 20642)
CancelInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodTransfer_SenderNotInvoiceRecipient() (gas: 20575)
CreateContainer_Unit_Concrete_Test:test_CreateContainer_DockIdNonZero() (gas: 2079188)
CreateContainer_Unit_Concrete_Test:test_CreateContainer_DockIdZero() (gas: 1066370)
CreateContainer_Unit_Concrete_Test:test_RevertWhen_CallerNotDockOwner() (gas: 1084452)
CreateInvoice_Integration_Concret_Test:test_CreateInvoice_LinearStream() (gas: 251262)
CreateInvoice_Integration_Concret_Test:test_CreateInvoice_PaymentMethodOneOffTransfer() (gas: 251466)
CreateInvoice_Integration_Concret_Test:test_CreateInvoice_RecurringTransfer() (gas: 252617)
CreateInvoice_Integration_Concret_Test:test_CreateInvoice_Tranched() (gas: 252890)
CreateInvoice_Integration_Concret_Test:test_RevertWhen_CallerNotContract() (gas: 89343)
CreateInvoice_Integration_Concret_Test:test_RevertWhen_EndTimeInThePast() (gas: 102376)
CreateInvoice_Integration_Concret_Test:test_RevertWhen_NonCompliantContainer() (gas: 92598)
CreateInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodLinearStream_PaymentAssetNativeToken() (gas: 102494)
CreateInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodRecurringTransfer_PaymentIntervalTooShortForSelectedRecurrence() (gas: 103184)
CreateInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodTranchedStream_PaymentAssetNativeToken() (gas: 103664)
CreateInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodTranchedStream_PaymentIntervalTooShortForSelectedRecurrence() (gas: 103228)
CreateInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodTranchedStream_RecurrenceSetToOneOff() (gas: 101872)
CreateInvoice_Integration_Concret_Test:test_RevertWhen_StartTimeGreaterThanEndTime() (gas: 101700)
CreateInvoice_Integration_Concret_Test:test_RevertWhen_ZeroPaymentAmount() (gas: 81059)
DisableModule_Unit_Concrete_Test:test_DisableModule() (gas: 177591)
DisableModule_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 16425)
EnableModule_Unit_Concrete_Test:test_EnableModule() (gas: 33937)
EnableModule_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 16422)
EnableModule_Unit_Concrete_Test:test_RevertWhen_ModuleNotAllowlisted() (gas: 24667)
Execute_Unit_Concrete_Test:test_Execute() (gas: 84136)
Execute_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 18992)
Execute_Unit_Concrete_Test:test_RevertWhen_ModuleNotEnabled() (gas: 19065)
PayInvoice_Integration_Concret_Test:test_PayInvoice_PaymentMethodLinearStream() (gas: 309543)
PayInvoice_Integration_Concret_Test:test_PayInvoice_PaymentMethodTranchedStream() (gas: 434948)
PayInvoice_Integration_Concret_Test:test_PayInvoice_PaymentMethodTransfer_ERC20Token_Recurring() (gas: 87177)
PayInvoice_Integration_Concret_Test:test_PayInvoice_PaymentMethodTransfer_NativeToken_OneOff() (gas: 63518)
PayInvoice_Integration_Concret_Test:test_RevertWhen_InvoiceAlreadyPaid() (gas: 62518)
PayInvoice_Integration_Concret_Test:test_RevertWhen_InvoiceCanceled() (gas: 29675)
PayInvoice_Integration_Concret_Test:test_RevertWhen_InvoiceNull() (gas: 17874)
PayInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodTransfer_NativeTokenTransferFails() (gas: 166006)
PayInvoice_Integration_Concret_Test:test_RevertWhen_PaymentMethodTransfer_PaymentAmountLessThanInvoiceValue() (gas: 31961)
Receive_Unit_Concrete_Test:test_Receive() (gas: 20732)
RemoveFromAllowlist_Unit_Concrete_Test:test_AddToAllowlist() (gas: 22211)
RemoveFromAllowlist_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 12982)
TransferContainerOwnership_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 1083749)
TransferContainerOwnership_Unit_Concrete_Test:test_RevertWhen_InvalidOwnerZeroAddress() (gas: 1081714)
TransferContainerOwnership_Unit_Concrete_Test:test_transferContainerOwnership() (gas: 1089571)
WithdrawERC20_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 16382)
WithdrawERC20_Unit_Concrete_Test:test_RevertWhen_InsufficientERC20ToWithdraw() (gas: 24022)
WithdrawERC20_Unit_Concrete_Test:test_WithdrawERC20() (gas: 90472)
WithdrawLinearStream_Integration_Concret_Test:test_WithdrawLinearStream() (gas: 317093)
WithdrawNative_Unit_Concrete_Test:test_RevertWhen_CallerNotOwner() (gas: 16402)
WithdrawNative_Unit_Concrete_Test:test_RevertWhen_InsufficientNativeToWithdraw() (gas: 16391)
WithdrawNative_Unit_Concrete_Test:test_WithdrawNative() (gas: 37436)
WithdrawTranchedStream_Integration_Concret_Test:test_WithdrawTranchedStream() (gas: 437924)
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@
[submodule "lib/nomad-xyz/excessively-safe-call"]
path = lib/nomad-xyz/excessively-safe-call
url = https://github.com/nomad-xyz/ExcessivelySafeCall
[submodule "lib/openzeppelin-contracts-upgradeable"]
path = lib/openzeppelin-contracts-upgradeable
url = https://github.com/openzeppelin/openzeppelin-contracts-upgradeable
[submodule "lib/openzeppelin-foundry-upgrades"]
path = lib/openzeppelin-foundry-upgrades
url = https://github.com/openzeppelin/openzeppelin-foundry-upgrades
12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,16 @@ deploy-deterministic-module-keeper:
$(CREATE2SALT) {INITIAL_OWNER} \
--sig "run(string,address)" --rpc-url {RPC_URL} \
--private-key $(PRIVATE_KEY) --etherscan-api-key $(ETHERSCAN_API_KEY) \
--broadcast --verify

# Deploy the {DockRegistry} contract deterministically
# Update the following configs before running the script:
# - {INITIAL_OWNER} with the address of the initial owner
# - {MODULE_KEEPER} with the address of the {ModuleKeeper} deployment
# - {RPC_URL} with the network RPC used for deployment
deploy-deterministic-dock-registry:
forge script script/DeployDeterministicDockRegistry.s.sol:DeployDeterministicDockRegistry \
$(CREATE2SALT) {INITIAL_OWNER} {MODULE_KEEPER} \
--sig "run(string,address,address)" --rpc-url {RPC_URL} \
--private-key $(PRIVATE_KEY) --etherscan-api-key $(ETHERSCAN_API_KEY) \
--broadcast --verify
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ out = "out"
libs = ["lib"]
optimizer = true
optimizer_runs = 1000
gas_reports = ["ModuleKeeper", "DockRegistry", "Container"]

[fmt]
bracket_spacing = true
Expand Down
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts-upgradeable
1 change: 1 addition & 0 deletions lib/openzeppelin-foundry-upgrades
9 changes: 5 additions & 4 deletions script/DeployContainer.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ pragma solidity ^0.8.26;

import { BaseScript } from "./Base.s.sol";
import { Container } from "../src/Container.sol";
import { ModuleKeeper } from "./../src/ModuleKeeper.sol";
import { DockRegistry } from "./../src/DockRegistry.sol";

/// @notice Deploys an instance of {Container} and enables initial module(s)
contract DeployContainer is BaseScript {
function run(
DockRegistry dockRegistry,
address initialOwner,
ModuleKeeper moduleKeeper,
uint256 dockId,
address[] memory initialModules
) public virtual broadcast returns (Container container) {
// Ddeploy the {InvoiceModule} contracts
container = new Container(initialOwner, moduleKeeper, initialModules);
// Deploy a new {Container} through the {DockRegistry}
container = Container(payable(dockRegistry.createContainer(dockId, initialOwner, initialModules)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,22 @@
pragma solidity ^0.8.26;

import { BaseScript } from "./Base.s.sol";
import { Container } from "../src/Container.sol";
import { DockRegistry } from "./../src/DockRegistry.sol";
import { ModuleKeeper } from "./../src/ModuleKeeper.sol";

/// @notice Deploys at deterministic addresses across chains an instance of {Container} and enables initial module(s)
/// @notice Deploys at deterministic addresses across chains an instance of {DockRegistry}
/// @dev Reverts if any contract has already been deployed
contract DeployDeterministicContainer is BaseScript {
contract DeployDeterministicDockRegistry is BaseScript {
/// @dev By using a salt, Forge will deploy the contract via a deterministic CREATE2 factory
/// https://book.getfoundry.sh/tutorials/create2-tutorial?highlight=deter#deterministic-deployment-using-create2
function run(
string memory create2Salt,
address initialOwner,
ModuleKeeper moduleKeeper,
address[] memory initialModules
) public virtual broadcast returns (Container container) {
ModuleKeeper moduleKeeper
) public virtual broadcast returns (DockRegistry dockRegistry) {
bytes32 salt = bytes32(abi.encodePacked(create2Salt));

// Deterministically deploy a {Container} contract
container = new Container{ salt: salt }(initialOwner, moduleKeeper, initialModules);
// Deterministically deploy a {DockRegistry} contract
dockRegistry = new DockRegistry{ salt: salt }(initialOwner, moduleKeeper);
}
}
21 changes: 16 additions & 5 deletions src/Container.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import { ExcessivelySafeCall } from "@nomad-xyz/excessively-safe-call/src/Excess

import { IContainer } from "./interfaces/IContainer.sol";
import { ModuleManager } from "./abstracts/ModuleManager.sol";
import { Ownable } from "./abstracts/Ownable.sol";
import { IModuleManager } from "./interfaces/IModuleManager.sol";
import { Errors } from "./libraries/Errors.sol";
import { ModuleKeeper } from "./ModuleKeeper.sol";
import { DockRegistry } from "./DockRegistry.sol";

/// @title Container
/// @notice See the documentation in {IContainer}
contract Container is IContainer, Ownable, ModuleManager {
contract Container is IContainer, ModuleManager {
using SafeERC20 for IERC20;
using ExcessivelySafeCall for address;

Expand All @@ -25,10 +25,21 @@ contract Container is IContainer, Ownable, ModuleManager {

/// @dev Initializes the address of the {Container} owner, {ModuleKeeper} and enables the initial module(s)
constructor(
address _owner,
ModuleKeeper _moduleKeeper,
DockRegistry _dockRegistry,
address[] memory _initialModules
) Ownable(_owner) ModuleManager(_moduleKeeper, _initialModules) { }
) ModuleManager(_dockRegistry, _initialModules) {
dockRegistry = _dockRegistry;
}

/*//////////////////////////////////////////////////////////////////////////
MODIFIERS
//////////////////////////////////////////////////////////////////////////*/

/// @notice Reverts if the `msg.sender` is not the owner of the {Container} assigned in the registry
modifier onlyOwner() {
if (msg.sender != dockRegistry.ownerOfContainer(address(this))) revert Errors.CallerNotContainerOwner();
_;
}

/*//////////////////////////////////////////////////////////////////////////
NON-CONSTANT FUNCTIONS
Expand Down
133 changes: 133 additions & 0 deletions src/DockRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.26;

import { Ownable } from "./abstracts/Ownable.sol";
import { IDockRegistry } from "./interfaces/IDockRegistry.sol";
import { Container } from "./Container.sol";
import { ModuleKeeper } from "./ModuleKeeper.sol";
import { Errors } from "./libraries/Errors.sol";

/// @title DockRegistry
/// @notice See the documentation in {IDockRegistry}
contract DockRegistry is IDockRegistry, Ownable {
/*//////////////////////////////////////////////////////////////////////////
PUBLIC STORAGE
//////////////////////////////////////////////////////////////////////////*/

/// @inheritdoc IDockRegistry
ModuleKeeper public override moduleKeeper;

/// @inheritdoc IDockRegistry
mapping(uint256 dockId => address owner) public override ownerOfDock;

/// @inheritdoc IDockRegistry
mapping(address container => uint256 dockId) public override dockIdOfContainer;

/// @inheritdoc IDockRegistry
mapping(address container => address owner) public override ownerOfContainer;

/*//////////////////////////////////////////////////////////////////////////
PRIVATE STORAGE
//////////////////////////////////////////////////////////////////////////*/

/// @dev Counter to keep track of the next dock ID
uint256 private _dockNextId;

/*//////////////////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////////////////*/

/// @dev Initializes the address of the {ModuleKeeper} contract, registry owner and sets the next dock ID to start from 1
constructor(address _initialOwner, ModuleKeeper _moduleKeeper) Ownable(_initialOwner) {
_dockNextId = 1;
moduleKeeper = _moduleKeeper;
}

/*//////////////////////////////////////////////////////////////////////////
NON-CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @inheritdoc IDockRegistry
function createContainer(
uint256 dockId,
address owner,
address[] calldata initialModules
) public returns (address container) {
// Checks: a new dock must be created first
if (dockId == 0) {
// Store the ID of the next dock
dockId = _dockNextId;

// Effects: set the owner of the freshly created dock
ownerOfDock[dockId] = msg.sender;

// Effects: increment the next dock ID
// Use unchecked because the dock ID cannot realistically overflow
unchecked {
_dockNextId++;
}
} else {
// Checks: `msg.sender` is the dock owner
if (ownerOfDock[dockId] != msg.sender) {
revert Errors.CallerNotDockOwner();
}
}

// Interactions: deploy a new {Container}
container =
address(new Container({ _dockRegistry: DockRegistry(address(this)), _initialModules: initialModules }));

// Assign the ID of the dock to which the new container belongs
dockIdOfContainer[container] = dockId;

// Assign the owner of the container
ownerOfContainer[container] = owner;

// Log the {Container} creation
emit ContainerCreated(owner, dockId, container, initialModules);
}

/// @inheritdoc IDockRegistry
function transferContainerOwnership(address container, address newOwner) external {
// Checks: `msg.sender` is the current owner of the {Container}
address currentOwner = ownerOfContainer[container];
if (msg.sender != currentOwner) {
revert Errors.CallerNotContainerOwner();
}

// Checks: the new owner is not the zero address
if (newOwner == address(0)) {
revert Errors.InvalidOwnerZeroAddress();
}

// Effects: update container's ownership
ownerOfContainer[container] = newOwner;

// Log the ownership transfer
emit ContainerOwnershipTransferred({ container: container, oldOwner: currentOwner, newOwner: newOwner });
}

/// @inheritdoc IDockRegistry
function transferDockOwnership(uint256 dockId, address newOwner) external {
// Checks: `msg.sender` is the current owner of the dock
address currentOwner = ownerOfDock[dockId];
if (msg.sender != currentOwner) {
revert Errors.CallerNotDockOwner();
}

// Effects: update dock's ownership
ownerOfDock[dockId] = newOwner;

// Log the ownership transfer
emit DockOwnershipTransferred({ dockId: dockId, oldOwner: currentOwner, newOwner: newOwner });
}

/// @inheritdoc IDockRegistry
function updateModuleKeeper(ModuleKeeper newModuleKeeper) external onlyOwner {
// Effects: update the {ModuleKeeper} address
moduleKeeper = newModuleKeeper;

// Log the update
emit ModuleKeeperUpdated(newModuleKeeper);
}
}
9 changes: 6 additions & 3 deletions src/abstracts/ModuleManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.26;

import { IModuleManager } from "./../interfaces/IModuleManager.sol";
import { DockRegistry } from "./../DockRegistry.sol";
import { ModuleKeeper } from "./../ModuleKeeper.sol";
import { Errors } from "./../libraries/Errors.sol";

Expand All @@ -13,7 +14,7 @@ abstract contract ModuleManager is IModuleManager {
//////////////////////////////////////////////////////////////////////////*/

/// @inheritdoc IModuleManager
ModuleKeeper public immutable override moduleKeeper;
DockRegistry public immutable override dockRegistry;

/// @inheritdoc IModuleManager
mapping(address module => bool) public override isModuleEnabled;
Expand All @@ -23,8 +24,8 @@ abstract contract ModuleManager is IModuleManager {
//////////////////////////////////////////////////////////////////////////*/

/// @dev Initializes the {ModuleKeeper} address and initial module(s) enabled on the container
constructor(ModuleKeeper _moduleKeeper, address[] memory _initialModules) {
moduleKeeper = _moduleKeeper;
constructor(DockRegistry _dockRegistry, address[] memory _initialModules) {
dockRegistry = _dockRegistry;
_enableBatchModules(_initialModules);
}

Expand Down Expand Up @@ -67,6 +68,8 @@ abstract contract ModuleManager is IModuleManager {

/// @dev Enables one single module at a time
function _enableModule(address module) internal {
ModuleKeeper moduleKeeper = dockRegistry.moduleKeeper();

// Check: module is in the allowlist
if (!moduleKeeper.isAllowlisted(module)) {
revert Errors.ModuleNotAllowlisted();
Expand Down
Loading