Skip to content

Commit

Permalink
Merge pull request #18 from metadock/feat/dock-registry
Browse files Browse the repository at this point in the history
Dock Registry implementation
  • Loading branch information
V1d0r authored Aug 8, 2024
2 parents 25f609c + 47d9d99 commit 625b11a
Show file tree
Hide file tree
Showing 36 changed files with 686 additions and 119 deletions.
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

0 comments on commit 625b11a

Please sign in to comment.