Skip to content

Commit

Permalink
Merge pull request #31 from xWerk/refactor/invoice-module
Browse files Browse the repository at this point in the history
Refactor `InvoiceModule` to rely on external `PaymentModule`
  • Loading branch information
gabrielstoica authored Nov 19, 2024
2 parents d05a473 + cd078de commit 2c8c872
Show file tree
Hide file tree
Showing 80 changed files with 3,018 additions and 2,709 deletions.
147 changes: 72 additions & 75 deletions .gas-snapshot

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions .solhint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"extends": "solhint:recommended",
"rules": {
"avoid-low-level-calls": "off",
"code-complexity": ["error", 9],
"compiler-version": ["error", ">=0.8.22"],
"contract-name-camelcase": "off",
"const-name-snakecase": "off",
"func-name-mixedcase": "off",
"func-visibility": ["error", { "ignoreConstructors": true }],
"gas-custom-errors": "off",
"max-line-length": ["error", 124],
"named-parameters-mapping": "warn",
"no-empty-blocks": "off",
"not-rely-on-time": "off",
"one-contract-per-file": "off",
"var-name-mixedcase": "off"
}
}
2 changes: 0 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{
"solidity.packageDefaultDependenciesContractsDirectory": "src",
"solidity.packageDefaultDependenciesDirectory": "lib",
"editor.formatOnSave": true,
"[solidity]": {
"editor.defaultFormatter": "NomicFoundation.hardhat-solidity"
Expand Down
52 changes: 12 additions & 40 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,48 +13,20 @@ clean :; forge clean
# See https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/solidity/coverage.sh
tests-coverage :; ./script/coverage.sh

# Deploy the {InvoiceModule} contract deterministically
# See Sablier V2 deployments: https://docs.sablier.com/contracts/v2/deployments
# Deploys the {InvoiceCollection} peripheral
#
# Update the following configs before running the script:
# - {SABLIER_LOCKUP_LINEAR} with the according {SablierV2LockupLinear} deployment address
# - {SABLIER_LOCKUP_TRANCHED} with the according {SablierV2LockupTranched} deployment address
# - {BROKER_ADMIN} with the address of the account managing the Sablier V2 integration fee
# - {RELAYER} with the address of the Relayer responsible to mint the invoice NFTs
# - {NAME} with the name of the ERC-721 {InvoiceCollection} contract
# - {SYMBOL} with symbol of the ERC-721 {InvoiceCollection} contract
# - {RPC_URL} with the network RPC used for deployment
deploy-deterministic-invoice-module:
forge script script/DeployDeterministicInvoiceModule.s.sol:DeployDeterministicInvoiceModule \
$(CREATE2SALT) {SABLIER_LOCKUP_LINEAR} {SABLIER_LOCKUP_TRANCHED} {BROKER_ADMIN} \
--sig "run(string,address,address,address)" --rpc-url {RPC_URL} --private-key $(PRIVATE_KEY) --etherscan-api-key $(ETHERSCAN_API_KEY)
deploy-invoice-collection:
forge script script/DeployInvoiceCollection.s.sol:DeployInvoiceCollection \
$(CREATE2SALT) {RELAYER} {NAME} {SYMBOL} \
--sig "run(address,string,string)" --rpc-url {RPC_URL} --private-key $(PRIVATE_KEY) --etherscan-api-key $(ETHERSCAN_API_KEY)
--broadcast --verify


# Deploy a {Container} contract deterministically
# Update the following configs before running the script:
# - {INITIAL_OWNER} with the address of the initial owner
# - {MODULE_KEEPER_ADDRESS} with the address of the {ModuleKeeper} deployment
# - {RPC_URL} with the network RPC used for deployment
deploy-deterministic-container:
forge script script/DeployDeterministicContainer.s.sol:DeployDeterministicContainer \
$(CREATE2SALT) {INITIAL_OWNER} {MODULE_KEEPER_ADDRESS} [] \
--sig "run(string,address,address,address[])" --rpc-url {RPC_URL} \
--private-key $(PRIVATE_KEY) --etherscan-api-key $(ETHERSCAN_API_KEY) \
--broadcast --verify

# Deploy a {Container} contract
# Update the following configs before running the script:
# - {INITIAL_OWNER} with the address of the initial owner
# - {DOCK_REGISTRY} with the address of the {DockRegistr} factory
# - {DOCK_ID} with the ID of the dock to which the new {Container} will be deployed
# - {INITIAL_MODULES} with the addresses of the enabled initial modules (array)
# - {RPC_URL} with the network RPC used for deployment
deploy-container:
forge script script/DeployContainer.s.sol:DeployContainer \
{INITIAL_OWNER} {DOCK_REGISTRY} {DOCK_ID} {INITIAL_MODULES} \
--sig "run(address,address,uint256,address[])" --rpc-url {RPC_URL} \
--private-key $(PRIVATE_KEY) --etherscan-api-key $(ETHERSCAN_API_KEY) \
--broadcast --verify

# Deploy the {ModuleKeeper} contract deterministically
# Deploys the {ModuleKeeper} contract deterministically
# Update the following configs before running the script:
# - {INITIAL_OWNER} with the address of the initial owner
# - {RPC_URL} with the network RPC used for deployment
Expand All @@ -65,14 +37,14 @@ deploy-deterministic-module-keeper:
--private-key $(PRIVATE_KEY) --etherscan-api-key $(ETHERSCAN_API_KEY) \
--broadcast --verify

# Deploy the {DockRegistry} contract deterministically
# Deploys the {StationRegistry} 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
# - {ENTRYPOINT} with the address of the {Entrypoiny} contract (currently v6)
# - {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 \
forge script script/DeployDeterministicStationRegistry.s.sol:DeployDeterministicStationRegistry \
$(CREATE2SALT) {INITIAL_OWNER} {ENTRYPOINT} {MODULE_KEEPER} \
--sig "run(string,address,address)" --rpc-url {RPC_URL} \
--private-key $(PRIVATE_KEY) --etherscan-api-key $(ETHERSCAN_API_KEY) \
Expand Down
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ runs = 10_000
bracket_spacing = true
int_types = "long"
line_length = 120
multiline_func_header = "params_first"
multiline_func_header = "all"
number_underscore = "thousands"
quote_style = "double"
tab_width = 4
Expand Down
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
{
"scripts": {
"build": "forge build",
"lint": "bun run lint:sol && bun run prettier:check",
"lint:sol": "forge fmt --check && bun solhint \"{precompiles,script,src,test}/**/*.sol\"",
"prettier:check": "prettier --check --plugin=prettier-plugin-solidity \"**/*.{json,md,svg,yml}\"",
"prettier:write": "prettier --write --plugin=prettier-plugin-solidity \"**/*.{json,md,svg,yml,sol}\""
},
"devDependencies": {
"forge-std": "github:foundry-rs/forge-std#v1.9.4"
"forge-std": "github:foundry-rs/forge-std#v1.9.4",
"prettier": "^3.3.3",
"prettier-plugin-solidity": "^1.4.1",
"solhint": "^5.0.3"
},
"dependencies": {
"@openzeppelin/contracts": "^5.1.0",
Expand Down
4 changes: 2 additions & 2 deletions script/Base.s.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;
pragma solidity ^0.8.22;

import { Script } from "forge-std/Script.sol";

Expand All @@ -19,7 +19,7 @@ contract BaseScript is Script {
deployer = from;
} else {
mnemonic = vm.envOr({ name: "MNEMONIC", defaultValue: TEST_MNEMONIC });
(deployer, ) = deriveRememberKey(mnemonic, 0);
(deployer,) = deriveRememberKey(mnemonic, 0);
}
}

Expand Down
27 changes: 0 additions & 27 deletions script/DeployDeterministicInvoiceModule.s.sol

This file was deleted.

7 changes: 6 additions & 1 deletion script/DeployDeterministicModuleKeeper.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ contract DeployDeterministicModuleKeeper is BaseScript {
function run(
string memory create2Salt,
address initialOwner
) public virtual broadcast returns (ModuleKeeper moduleKeeper) {
)
public
virtual
broadcast
returns (ModuleKeeper moduleKeeper)
{
bytes32 salt = bytes32(abi.encodePacked(create2Salt));

// Deterministically deploy the {ModuleKeeper} contract
Expand Down
7 changes: 6 additions & 1 deletion script/DeployDeterministicStationRegistry.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ contract DeployDeterministicStationRegistry is BaseScript {
address initialAdmin,
IEntryPoint entrypoint,
ModuleKeeper moduleKeeper
) public virtual broadcast returns (StationRegistry stationRegistry) {
)
public
virtual
broadcast
returns (StationRegistry stationRegistry)
{
bytes32 salt = bytes32(abi.encodePacked(create2Salt));

// Deterministically deploy the {StationRegistry} smart account factory
Expand Down
25 changes: 25 additions & 0 deletions script/DeployInvoiceCollection.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.26;

import { BaseScript } from "./Base.s.sol";
import { InvoiceCollection } from "../src/peripherals/invoice-collection/InvoiceCollection.sol";

/// @notice Deploys and initializes the {InvoiceCollection} contract at deterministic addresses across chains
/// @dev Reverts if any contract has already been deployed
contract DeployInvoiceCollection 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(
address relayer,
string memory name,
string memory symbol
)
public
virtual
broadcast
returns (InvoiceCollection invoiceCollection)
{
// Deploy the {InvoiceCollection} contract
invoiceCollection = new InvoiceCollection(relayer, name, symbol);
}
}
7 changes: 6 additions & 1 deletion script/DeploySpace.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ contract DeploySpace is BaseScript {
StationRegistry stationRegistry,
uint256 stationId,
address[] memory initialModules
) public virtual broadcast returns (Space space) {
)
public
virtual
broadcast
returns (Space space)
{
// Get the number of total accounts created by the `initialAdmin` deployer
uint256 totalAccountsOfAdmin = stationRegistry.totalAccountsOfSigner(initialAdmin);

Expand Down
2 changes: 1 addition & 1 deletion src/ModuleKeeper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ contract ModuleKeeper is IModuleKeeper, Ownable {
//////////////////////////////////////////////////////////////////////////*/

/// @dev Initializes the initial owner of the {ModuleKeeper}
constructor(address _initialOwner) Ownable(_initialOwner) { }
constructor(address _initialOwner) Ownable(_initialOwner) {}

/*//////////////////////////////////////////////////////////////////////////
NON-CONSTANT FUNCTIONS
Expand Down
54 changes: 28 additions & 26 deletions src/Space.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ contract Space is ISpace, AccountCore, ERC1271, ModuleManager {
//////////////////////////////////////////////////////////////////////////*/

/// @dev Initializes the address of the EIP 4337 factory and EntryPoint contract
constructor(IEntryPoint _entrypoint, address _factory) AccountCore(_entrypoint, _factory) { }
constructor(IEntryPoint _entrypoint, address _factory) AccountCore(_entrypoint, _factory) {}

/// @notice Initializes the {ModuleKeeper}, enables initial modules and configures the {Space} smart account
function initialize(address _defaultAdmin, bytes calldata _data) public override {
(,, address[] memory initialModules) = abi.decode(_data, (uint256, uint256, address[]));
(, , address[] memory initialModules) = abi.decode(_data, (uint256, uint256, address[]));

// Enable the initial module(s)
ModuleKeeper moduleKeeper = StationRegistry(factory).moduleKeeper();
Expand Down Expand Up @@ -153,24 +153,34 @@ contract Space is ISpace, AccountCore, ERC1271, ModuleManager {
// 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: "" });
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: "" });
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 ISpace
function withdrawNative(
uint256 amount
) public onlyAdminOrEntrypoint {
function withdrawNative(uint256 amount) public onlyAdminOrEntrypoint {
// Checks: the native balance of the space minus the amount locked for operations is greater than the requested amount
if (amount > address(this).balance) revert Errors.InsufficientNativeToWithdraw();

// Interactions: withdraw by transferring the amount to the sender
(bool success,) = msg.sender.call{ value: amount }("");
(bool success, ) = msg.sender.call{ value: amount }("");
// Revert if the call failed
if (!success) revert Errors.NativeWithdrawFailed();

Expand All @@ -179,9 +189,7 @@ contract Space is ISpace, AccountCore, ERC1271, ModuleManager {
}

/// @inheritdoc IModuleManager
function enableModule(
address module
) public override onlyAdminOrEntrypoint {
function enableModule(address module) public override onlyAdminOrEntrypoint {
// Retrieve the address of the {ModuleKeeper}
ModuleKeeper moduleKeeper = StationRegistry(factory).moduleKeeper();

Expand All @@ -190,9 +198,7 @@ contract Space is ISpace, AccountCore, ERC1271, ModuleManager {
}

/// @inheritdoc IModuleManager
function disableModule(
address module
) public override onlyAdminOrEntrypoint {
function disableModule(address module) public override onlyAdminOrEntrypoint {
// Effects: disable the module
_disableModule(module);
}
Expand All @@ -202,10 +208,7 @@ contract Space is ISpace, AccountCore, ERC1271, ModuleManager {
//////////////////////////////////////////////////////////////////////////*/

/// @inheritdoc ERC1271
function isValidSignature(
bytes32 _hash,
bytes memory _signature
) public view override returns (bytes4 magicValue) {
function isValidSignature(bytes32 _hash, bytes memory _signature) public view override returns (bytes4 magicValue) {
// Compute the hash of message the should be signed
bytes32 targetDigest = getMessageHash(_hash);

Expand All @@ -230,20 +233,19 @@ contract Space is ISpace, AccountCore, ERC1271, ModuleManager {
}

/// @inheritdoc ISpace
function getMessageHash(
bytes32 _hash
) public view returns (bytes32) {
function getMessageHash(bytes32 _hash) public view returns (bytes32) {
bytes32 messageHash = keccak256(abi.encode(_hash));
bytes32 typedDataHash = keccak256(abi.encode(MSG_TYPEHASH, messageHash));
return keccak256(abi.encodePacked("\x19\x01", _domainSeparatorV4(), typedDataHash));
}

/// @inheritdoc IERC165
function supportsInterface(
bytes4 interfaceId
) public pure returns (bool) {
return interfaceId == type(ISpace).interfaceId || interfaceId == type(IERC1155Receiver).interfaceId
|| interfaceId == type(IERC721Receiver).interfaceId || interfaceId == type(IERC165).interfaceId;
function supportsInterface(bytes4 interfaceId) public pure returns (bool) {
return
interfaceId == type(ISpace).interfaceId ||
interfaceId == type(IERC1155Receiver).interfaceId ||
interfaceId == type(IERC721Receiver).interfaceId ||
interfaceId == type(IERC165).interfaceId;
}

/// @inheritdoc IERC721Receiver
Expand Down
8 changes: 2 additions & 6 deletions src/StationRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,7 @@ contract StationRegistry is IStationRegistry, BaseAccountFactory, PermissionsEnu
}

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

Expand All @@ -126,9 +124,7 @@ contract StationRegistry is IStationRegistry, BaseAccountFactory, PermissionsEnu
//////////////////////////////////////////////////////////////////////////*/

/// @inheritdoc IStationRegistry
function totalAccountsOfSigner(
address signer
) public view returns (uint256) {
function totalAccountsOfSigner(address signer) public view returns (uint256) {
return accountsOfSigner[signer].length();
}

Expand Down
Loading

0 comments on commit 2c8c872

Please sign in to comment.