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

Make PaymentModule UUPSUpgradeable compatible #32

Merged
merged 14 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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