Skip to content

Commit

Permalink
Merge pull request #32 from xWerk/feat/upgradeable-payment-module
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielstoica authored Nov 22, 2024
2 parents 2c8c872 + c66f8b9 commit 1a5e725
Show file tree
Hide file tree
Showing 23 changed files with 464 additions and 185 deletions.
11 changes: 4 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,10 @@ jobs:
- name: "Install the Node.js dependencies"
run: "bun install --frozen-lockfile"

- name: "Run Forge build"
run: |
forge --version
forge build
- name: "Build the contracts"
run: "FOUNDRY_PROFILE=optimized forge build"
id: build

- name: "Run Forge tests"
run: |
forge test -vvv
- name: "Run the tests"
run: "FOUNDRY_PROFILE=optimized forge test -vvv"
id: test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Directories
cache/
out/
out-optimized/
node_modules

# Coverage
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "lib/openzeppelin-foundry-upgrades"]
path = lib/openzeppelin-foundry-upgrades
url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades
37 changes: 34 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ tests-coverage :; ./script/coverage.sh
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)
--sig "run(string,address,string,string)" --rpc-url {RPC_URL} --private-key $(PRIVATE_KEY) --etherscan-api-key $(ETHERSCAN_API_KEY)
--broadcast --verify

# Deploys the {ModuleKeeper} contract deterministically
Expand All @@ -40,12 +40,43 @@ deploy-deterministic-module-keeper:
# Deploys the {StationRegistry} contract deterministically
# Update the following configs before running the script:
# - {INITIAL_OWNER} with the address of the initial owner
# - {ENTRYPOINT} with the address of the {Entrypoiny} contract (currently v6)
# - {ENTRYPOINT} with the address of the {Entrypoint} 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/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) \
--broadcast --verify
--broadcast --verify

# Deploys the {PaymentModule} contract deterministically
#
# 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
# - {INITIAL_OWNER} with the address of the initial admin of the {PaymentModule}
# - {BROKER_ACCOUNT} with the address of the account responsible for collecting the broker fees (multisig vault)
# - {RPC_URL} with the network RPC used for deployment
deploy-payment-module:
forge script script/DeployDeterministicPaymentModule.s.sol:DeployDeterministicPaymentModule \
$(CREATE2SALT) {SABLIER_LOCKUP_LINEAR} {SABLIER_LOCKUP_TRANCHED} {INITIAL_OWNER} {BROKER_ACCOUNT} \
--sig "run(string,address,address,address,address)" --rpc-url {RPC_URL} --private-key $(PRIVATE_KEY) --etherscan-api-key $(ETHERSCAN_API_KEY)
--broadcast --verify

# Deploys the {PaymentModule} contract deterministically

# Deploys the core contracts deterministically
#
# 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
# - {INITIAL_OWNER} with the address of the initial admin of the {StationRegistry} and {PaymentModule}
# - {BROKER_ACCOUNT} with the address of the account responsible for collecting the broker fees (multisig vault)
# - {ENTRYPOINT} with the address of the {Entrypoint} contract (currently v6)
# - {RPC_URL} with the network RPC used for deployment
deploy-core:
forge script script/DeployDeterministicCore.s.sol:DeployDeterministicCore \
$(CREATE2SALT) {SABLIER_LOCKUP_LINEAR} {SABLIER_LOCKUP_TRANCHED} {INITIAL_OWNER} {BROKER_ACCOUNT} {ENTRYPOINT}\
--sig "run(string,address,address,address,address,address)" --rpc-url {RPC_URL} --private-key $(PRIVATE_KEY) --etherscan-api-key $(ETHERSCAN_API_KEY)
--broadcast --verify
Binary file modified bun.lockb
Binary file not shown.
4 changes: 4 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ extra_output = ["storageLayout"]
max_test_rejects = 500_000
runs = 10_000

[profile.optimized]
out = "out-optimized"
via_ir = true

[fmt]
bracket_spacing = true
int_types = "long"
Expand Down
1 change: 1 addition & 0 deletions lib/openzeppelin-foundry-upgrades
39 changes: 20 additions & 19 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
{
"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",
"prettier": "^3.3.3",
"prettier-plugin-solidity": "^1.4.1",
"solhint": "^5.0.3"
},
"dependencies": {
"@openzeppelin/contracts": "^5.1.0",
"@prb/math": "^4.1.0",
"@sablier/v2-core": "^1.2.0",
"@thirdweb-dev/contracts": "^3.15.0"
}
"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",
"prettier": "^3.3.3",
"prettier-plugin-solidity": "^1.4.1",
"solhint": "^5.0.3"
},
"dependencies": {
"@openzeppelin/contracts": "^5.1.0",
"@openzeppelin/contracts-upgradeable": "^5.1.0",
"@prb/math": "^4.1.0",
"@sablier/v2-core": "^1.2.0",
"@thirdweb-dev/contracts": "^3.15.0"
}
}
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/
@openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/
@sablier/v2-core/=node_modules/@sablier/v2-core/
@prb/math/=node_modules/@prb/math/
@thirdweb/contracts/=node_modules/@thirdweb-dev/contracts/
Expand Down
77 changes: 77 additions & 0 deletions script/DeployDeterministicCore.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.26;

import { BaseScript } from "./Base.s.sol";
import { PaymentModule } from "./../src/modules/payment-module/PaymentModule.sol";
import { StationRegistry } from "./../src/StationRegistry.sol";
import { ModuleKeeper } from "./../src/ModuleKeeper.sol";

import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import { Options } from "./../lib/openzeppelin-foundry-upgrades/src/Options.sol";
import { Core } from "./../lib/openzeppelin-foundry-upgrades/src/internal/Core.sol";
import { ISablierV2LockupLinear } from "@sablier/v2-core/src/interfaces/ISablierV2LockupLinear.sol";
import { ISablierV2LockupTranched } from "@sablier/v2-core/src/interfaces/ISablierV2LockupTranched.sol";
import { IEntryPoint } from "@thirdweb/contracts/prebuilts/account/interface/IEntrypoint.sol";
import { ud } from "@prb/math/src/UD60x18.sol";

/// @notice Deploys at deterministic addresses across chains the core contracts of the Werk Protocol
/// @dev Reverts if any contract has already been deployed
contract DeployDeterministicCore 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,
ISablierV2LockupLinear sablierLockupLinear,
ISablierV2LockupTranched sablierLockupTranched,
address initialOwner,
address brokerAccount,
IEntryPoint entrypoint
)
public
virtual
broadcast
returns (ModuleKeeper moduleKeeper, StationRegistry stationRegistry, PaymentModule paymentModule)
{
bytes32 salt = bytes32(abi.encodePacked(create2Salt));

// Deterministically deploy the {ModuleKeeper} contract
moduleKeeper = new ModuleKeeper{ salt: salt }(initialOwner);

// Deterministically deploy the {StationRegistry} contract
stationRegistry = new StationRegistry{ salt: salt }(initialOwner, entrypoint, moduleKeeper);

// Deterministically deploy the {PaymentModule} module
paymentModule = PaymentModule(
deployDetermisticUUPSProxy(
salt,
abi.encode(sablierLockupLinear, sablierLockupTranched),
"PaymentModule.sol",
abi.encodeCall(PaymentModule.initialize, (initialOwner, brokerAccount, ud(0)))
)
);

// Add the {PaymentModule} module to the allowlist of the {ModuleKeeper}
moduleKeeper.addToAllowlist(address(paymentModule));
}

/// @dev Deploys a UUPS proxy at deterministic addresses across chains based on a provided salt
/// @param salt Salt to use for deterministic deployment
/// @param contractName The name of the implementation contract
/// @param initializerData The ABI encoded call to be made to the initialize method
function deployDetermisticUUPSProxy(
bytes32 salt,
bytes memory constructorData,
string memory contractName,
bytes memory initializerData
)
internal
returns (address)
{
Options memory opts;
opts.constructorData = constructorData;

address impl = Core.deployImplementation(contractName, opts);

return address(new ERC1967Proxy{ salt: salt }(impl, initializerData));
}
}
64 changes: 64 additions & 0 deletions script/DeployDeterministicPaymentModule.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.26;

import { BaseScript } from "./Base.s.sol";
import { PaymentModule } from "./../src/modules/payment-module/PaymentModule.sol";

import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import { Options } from "./../lib/openzeppelin-foundry-upgrades/src/Options.sol";
import { Core } from "./../lib/openzeppelin-foundry-upgrades/src/internal/Core.sol";
import { ISablierV2LockupLinear } from "@sablier/v2-core/src/interfaces/ISablierV2LockupLinear.sol";
import { ISablierV2LockupTranched } from "@sablier/v2-core/src/interfaces/ISablierV2LockupTranched.sol";
import { ud } from "@prb/math/src/UD60x18.sol";

/// @notice Deploys at deterministic addresses across chains an instance of {PaymentModule}
/// @dev Reverts if any contract has already been deployed
contract DeployDeterministicPaymentModule 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,
ISablierV2LockupLinear sablierLockupLinear,
ISablierV2LockupTranched sablierLockupTranched,
address initialOwner,
address brokerAccount
)
public
virtual
broadcast
returns (PaymentModule paymentModule)
{
bytes32 salt = bytes32(abi.encodePacked(create2Salt));

// Deterministically deploy the {PaymentModule} module
paymentModule = PaymentModule(
deployDetermisticUUPSProxy(
salt,
abi.encode(sablierLockupLinear, sablierLockupTranched),
"PaymentModule.sol",
abi.encodeCall(PaymentModule.initialize, (initialOwner, brokerAccount, ud(0)))
)
);
}

/// @dev Deploys a UUPS proxy at deterministic addresses across chains based on a provided salt
/// @param salt Salt to use for deterministic deployment
/// @param contractName The name of the implementation contract
/// @param initializerData The ABI encoded call to be made to the initialize method
function deployDetermisticUUPSProxy(
bytes32 salt,
bytes memory constructorData,
string memory contractName,
bytes memory initializerData
)
internal
returns (address)
{
Options memory opts;
opts.constructorData = constructorData;

address impl = Core.deployImplementation(contractName, opts);

return address(new ERC1967Proxy{ salt: salt }(impl, initializerData));
}
}
10 changes: 8 additions & 2 deletions src/StationRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ contract StationRegistry is IStationRegistry, BaseAccountFactory, PermissionsEnu
address _initialAdmin,
IEntryPoint _entrypoint,
ModuleKeeper _moduleKeeper
) BaseAccountFactory(address(new Space(_entrypoint, address(this))), address(_entrypoint)) {
)
BaseAccountFactory(address(new Space(_entrypoint, address(this))), address(_entrypoint))
{
_setupRole(DEFAULT_ADMIN_ROLE, _initialAdmin);

_stationNextId = 1;
Expand All @@ -56,7 +58,11 @@ contract StationRegistry is IStationRegistry, BaseAccountFactory, PermissionsEnu
function createAccount(
address _admin,
bytes calldata _data
) public override(BaseAccountFactory, IStationRegistry) returns (address) {
)
public
override(BaseAccountFactory, IStationRegistry)
returns (address)
{
// Get the station ID and initial modules array from the calldata
// Note: calldata contains a salt (usually the number of accounts created by an admin),
// station ID and an array with the initial enabled modules on the account
Expand Down
Loading

0 comments on commit 1a5e725

Please sign in to comment.