Skip to content

Commit

Permalink
Merge pull request #35 from xWerk/base-sepolia-deployment
Browse files Browse the repository at this point in the history
Fix `Space` self-execution bug and deploy to Base Sepolia
  • Loading branch information
gabrielstoica authored Nov 28, 2024
2 parents 1a5e725 + 0476fff commit 0ae1b90
Show file tree
Hide file tree
Showing 22 changed files with 896 additions and 171 deletions.
7 changes: 2 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
MAINNET_RPC_URL=""
SEPOLIA_RPC_URL="https://rpc.sepolia.org"
BASE_SEPOLIA_RPC_URL="https://sepolia.base.org"
BASESCAN_API_KEY=""
CREATE2SALT="""
DEPLOYER=""
PRIVATE_KEY="0x{YOUR_PRIVATE_KEY}"
MNEMONIC=""
ETHERSCAN_API_KEY=""
CREATE2SALT="""
24 changes: 12 additions & 12 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ tests-coverage :; ./script/coverage.sh
# - {RPC_URL} with the network RPC used for deployment
deploy-invoice-collection:
forge script script/DeployInvoiceCollection.s.sol:DeployInvoiceCollection \
$(CREATE2SALT) {RELAYER} {NAME} {SYMBOL} \
--sig "run(string,address,string,string)" --rpc-url {RPC_URL} --private-key $(PRIVATE_KEY) --etherscan-api-key $(ETHERSCAN_API_KEY)
{RELAYER} {NAME} {SYMBOL} \
--sig "run(address,string,string)" --rpc-url {RPC_URL} --account dev --etherscan-api-key $(ETHERSCAN_API_KEY)
--broadcast --verify

# Deploys the {ModuleKeeper} contract deterministically
Expand All @@ -34,7 +34,7 @@ deploy-deterministic-module-keeper:
forge script script/DeployDeterministicModuleKeeper.s.sol:DeployDeterministicModuleKeeper \
$(CREATE2SALT) {INITIAL_OWNER} \
--sig "run(string,address)" --rpc-url {RPC_URL} \
--private-key $(PRIVATE_KEY) --etherscan-api-key $(ETHERSCAN_API_KEY) \
--account dev --etherscan-api-key $(ETHERSCAN_API_KEY) \
--broadcast --verify

# Deploys the {StationRegistry} contract deterministically
Expand All @@ -43,12 +43,12 @@ deploy-deterministic-module-keeper:
# - {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:
deploy-deterministic-station-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
--sig "run(string,address,address,address)" --rpc-url {RPC_URL} \
--account dev --etherscan-api-key $(ETHERSCAN_API_KEY) \
--broadcast --verify --ffi

# Deploys the {PaymentModule} contract deterministically
#
Expand All @@ -61,10 +61,10 @@ deploy-deterministic-dock-registry:
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)
--sig "run(string,address,address,address,address)" --rpc-url {RPC_URL} --account dev --etherscan-api-key $(ETHERSCAN_API_KEY)
--broadcast --verify

# Deploys the {PaymentModule} contract deterministically
# Deploys the {PaymentModule} contract deterministically

# Deploys the core contracts deterministically
#
Expand All @@ -77,6 +77,6 @@ deploy-payment-module:
# - {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
$(CREATE2SALT) {SABLIER_LOCKUP_LINEAR} {SABLIER_LOCKUP_TRANCHED} {INITIAL_OWNER} {BROKER_ACCOUNT} {ENTRYPOINT} \
--sig "run(string,address,address,address,address,address)" --rpc-url {RPC_URL} --account dev \
--broadcast --verify --etherscan-api-key $(ETHERSCAN_API_KEY) --ffi
352 changes: 352 additions & 0 deletions broadcast/DeployDeterministicCore.s.sol/84532/run-1732820030.json

Large diffs are not rendered by default.

352 changes: 352 additions & 0 deletions broadcast/DeployDeterministicCore.s.sol/84532/run-latest.json

Large diffs are not rendered by default.

21 changes: 1 addition & 20 deletions script/Base.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,8 @@ pragma solidity ^0.8.22;
import { Script } from "forge-std/Script.sol";

contract BaseScript is Script {
/// @dev Junk mnemonic seed phrase use as a fallback in case there is no mnemonic set in the `.env` file
string internal constant TEST_MNEMONIC = "test test test test test test test test test test test junk";

/// @dev Used to derive the deployer's address
string internal mnemonic;

/// @dev Stores the deployer address
address deployer;

constructor() {
address from = vm.envOr({ name: "DEPLOYER", defaultValue: address(0) });
if (from != address(0)) {
deployer = from;
} else {
mnemonic = vm.envOr({ name: "MNEMONIC", defaultValue: TEST_MNEMONIC });
(deployer,) = deriveRememberKey(mnemonic, 0);
}
}

modifier broadcast() {
vm.startBroadcast(deployer);
vm.startBroadcast();
_;
vm.stopBroadcast();
}
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
121 changes: 61 additions & 60 deletions src/Space.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { AccountCore } from "@thirdweb/contracts/prebuilts/account/utils/Account
import { IEntryPoint } from "@thirdweb/contracts/prebuilts/account/interface/IEntrypoint.sol";
import { ERC1271 } from "@thirdweb/contracts/eip/ERC1271.sol";
import { EnumerableSet } from "@thirdweb/contracts/external-deps/openzeppelin/utils/structs/EnumerableSet.sol";
import { AccountCoreStorage } from "@thirdweb/contracts/prebuilts/account/utils/AccountCoreStorage.sol";

import { ISpace } from "./interfaces/ISpace.sol";
import { ModuleManager } from "./abstracts/ModuleManager.sol";
Expand All @@ -37,20 +36,18 @@ 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();
_initializeModuleManager(moduleKeeper, initialModules);

// Initialize the {Space} smart contract
super.initialize(_defaultAdmin, _data);

_registerOnFactory();
}

/*//////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -87,7 +84,11 @@ contract Space is ISpace, AccountCore, ERC1271, ModuleManager {
address module,
uint256 value,
bytes calldata data
) public onlyAdminOrEntrypoint returns (bool success) {
)
public
onlyAdminOrEntrypoint
returns (bool success)
{
// Checks: the `module` module is enabled on the smart account
_checkIfModuleIsEnabled(module);

Expand All @@ -100,7 +101,10 @@ contract Space is ISpace, AccountCore, ERC1271, ModuleManager {
address[] calldata modules,
uint256[] calldata values,
bytes[] calldata data
) external onlyAdminOrEntrypoint {
)
external
onlyAdminOrEntrypoint
{
// Cache the length of the modules array
uint256 modulesLength = modules.length;

Expand All @@ -118,74 +122,66 @@ contract Space is ISpace, AccountCore, ERC1271, ModuleManager {
}

/// @inheritdoc ISpace
function withdrawERC20(IERC20 asset, uint256 amount) public onlyAdminOrEntrypoint {
function withdrawERC20(address to, IERC20 asset, uint256 amount) public onlyAdminOrEntrypoint {
// Checks: the available ERC20 balance of the space is greater enough to support the withdrawal
if (amount > asset.balanceOf(address(this))) revert Errors.InsufficientERC20ToWithdraw();

// Interactions: withdraw by transferring the amount to the sender
asset.safeTransfer({ to: msg.sender, value: amount });
// Interactions: withdraw by transferring the `amount` to the `to` address
asset.safeTransfer({ to: to, value: amount });

// Log the successful ERC-20 token withdrawal
emit AssetWithdrawn({ to: msg.sender, asset: address(asset), amount: amount });
emit AssetWithdrawn({ to: to, asset: address(asset), amount: amount });
}

/// @inheritdoc ISpace
function withdrawERC721(IERC721 collection, uint256 tokenId) public onlyAdminOrEntrypoint {
// Checks, Effects, Interactions: withdraw by transferring the token to the space owner
function withdrawERC721(address to, IERC721 collection, uint256 tokenId) public onlyAdminOrEntrypoint {
// Checks, Effects, Interactions: withdraw by transferring the `tokenId` token to the `to` address
// Notes:
// - we're using `safeTransferFrom` as the owner can be an ERC-4337 smart account
// - we're using `safeTransferFrom` as the owner can be a smart contract
// therefore the `onERC721Received` hook must be implemented
collection.safeTransferFrom(address(this), msg.sender, tokenId);
collection.safeTransferFrom({ from: address(this), to: to, tokenId: tokenId });

// Log the successful ERC-721 token withdrawal
emit ERC721Withdrawn({ to: msg.sender, collection: address(collection), tokenId: tokenId });
emit ERC721Withdrawn({ to: to, collection: address(collection), tokenId: tokenId });
}

/// @inheritdoc ISpace
function withdrawERC1155(
address to,
IERC1155 collection,
uint256[] memory ids,
uint256[] memory amounts
) public onlyAdminOrEntrypoint {
// Checks, Effects, Interactions: withdraw by transferring the tokens to the space owner
)
public
onlyAdminOrEntrypoint
{
// Checks, Effects, Interactions: withdraw by transferring the tokens to the `to` address
// Notes:
// - we're using `safeTransferFrom` as the owner can be an ERC-4337 smart account
// - we're using `safeTransferFrom` as the owner can be a smart contract
// 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);
emit ERC1155Withdrawn({ to: to, collection: address(collection), ids: ids, values: amounts });
}

/// @inheritdoc ISpace
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
function withdrawNative(address to, uint256 amount) public onlyAdminOrEntrypoint {
// Checks: the native balance of the space is greater enough to support the withdrawal
if (amount > address(this).balance) revert Errors.InsufficientNativeToWithdraw();

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

// Log the successful native token withdrawal
emit AssetWithdrawn({ to: msg.sender, asset: address(0), amount: amount });
emit AssetWithdrawn({ to: to, asset: address(0), amount: amount });
}

/// @inheritdoc IModuleManager
Expand All @@ -208,7 +204,15 @@ 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 Down Expand Up @@ -241,11 +245,8 @@ contract Space is ISpace, AccountCore, ERC1271, ModuleManager {

/// @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;
return interfaceId == type(ISpace).interfaceId || interfaceId == type(IERC1155Receiver).interfaceId
|| interfaceId == type(IERC721Receiver).interfaceId || interfaceId == type(IERC165).interfaceId;
}

/// @inheritdoc IERC721Receiver
Expand All @@ -254,7 +255,11 @@ contract Space is ISpace, AccountCore, ERC1271, ModuleManager {
address from,
uint256 tokenId,
bytes memory
) public override returns (bytes4) {
)
public
override
returns (bytes4)
{
// Silence unused variable warning
operator = operator;

Expand All @@ -271,7 +276,11 @@ contract Space is ISpace, AccountCore, ERC1271, ModuleManager {
uint256 id,
uint256 value,
bytes memory
) public override returns (bytes4) {
)
public
override
returns (bytes4)
{
// Silence unused variable warning
operator = operator;

Expand All @@ -288,7 +297,11 @@ contract Space is ISpace, AccountCore, ERC1271, ModuleManager {
uint256[] memory ids,
uint256[] memory values,
bytes memory
) public override returns (bytes4) {
)
public
override
returns (bytes4)
{
for (uint256 i; i < ids.length; ++i) {
// Log the successful ERC-1155 token receipt
emit ERC1155Received(from, ids[i], values[i]);
Expand All @@ -301,18 +314,6 @@ contract Space is ISpace, AccountCore, ERC1271, ModuleManager {
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @dev Registers the account on the factory if it hasn't been registered yet
function _registerOnFactory() internal {
// Get the address of the factory contract
StationRegistry factoryContract = StationRegistry(factory);

// Checks: the smart account is registered on the factory contract
if (!factoryContract.isRegistered(address(this))) {
// Otherwise register it
factoryContract.onRegister(AccountCoreStorage.data().creationSalt);
}
}

/// @dev Executes a low-level call on the `module` contract with the `data` data forwarding the `value` value
function _call(address module, uint256 value, bytes calldata data) internal returns (bool success) {
// Execute the call via assembly
Expand Down
4 changes: 4 additions & 0 deletions src/abstracts/ModuleManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ abstract contract ModuleManager is IModuleManager {

/// @dev Initializes the initial module(s) enabled on the space
function _initializeModuleManager(ModuleKeeper moduleKeeper, address[] memory _initialModules) internal {
// Effects: enable the smart account itself as a module to allow self-execution
isModuleEnabled[address(this)] = true;

// Effects: enable the initial module(s)
_enableBatchModules(moduleKeeper, _initialModules);
}

Expand Down
Loading

0 comments on commit 0ae1b90

Please sign in to comment.