diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 15405687..adc0d2f9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,21 +18,25 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Install Foundry + - name: "Install Foundry" uses: foundry-rs/foundry-toolchain@v1 with: version: nightly - - name: Run Forge build + - name: "Install Bun" + uses: "oven-sh/setup-bun@v1" + + - name: "Install the Node.js dependencies" + run: "bun install --frozen-lockfile" + + - name: "Run Forge build" run: | forge --version forge build id: build - - name: Run Forge tests + - name: "Run Forge tests" run: | forge test -vvv id: test diff --git a/.gitignore b/.gitignore index 40ecee46..ff529669 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ -# Compiler files +# Directories cache/ out/ +node_modules # Coverage coverage/ @@ -15,4 +16,9 @@ coverage/ # Others .npmrc -*lcov.info \ No newline at end of file +.DS_Store +.pnp.* +*lcov.info +package-lock.json +pnpm-lock.yaml +yarn.lock \ No newline at end of file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index d3ba6c77..00000000 --- a/.gitmodules +++ /dev/null @@ -1,24 +0,0 @@ -[submodule "lib/forge-std"] - path = lib/forge-std - url = https://github.com/foundry-rs/forge-std -[submodule "lib/openzeppelin-contracts"] - path = lib/openzeppelin-contracts - url = https://github.com/OpenZeppelin/openzeppelin-contracts -[submodule "lib/v2-core"] - path = lib/v2-core - url = https://github.com/sablier-labs/v2-core -[submodule "lib/prb-math"] - path = lib/prb-math - url = https://github.com/PaulRBerg/prb-math -[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 -[submodule "lib/thirdweb-contracts"] - path = lib/thirdweb-contracts - url = https://github.com/thirdweb-dev/contracts -[submodule "lib/contracts"] - path = lib/contracts - url = https://github.com/thirdweb-dev/contracts diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 00000000..5446f411 Binary files /dev/null and b/bun.lockb differ diff --git a/foundry.toml b/foundry.toml index dd5b48e8..b6d7abec 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,11 +1,11 @@ [profile.default] src = "src" out = "out" -libs = ["lib"] +script = "script" +test = "test" optimizer = true optimizer_runs = 1000 gas_reports = ["ModuleKeeper", "DockRegistry", "Container"] -ffi = true ast = true build_info = true extra_output = ["storageLayout"] diff --git a/lib/contracts b/lib/contracts deleted file mode 160000 index 5c96c223..00000000 --- a/lib/contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5c96c223f53a6b3e9c9db2c0a8d1766727d901c1 diff --git a/lib/forge-std b/lib/forge-std deleted file mode 160000 index 978ac6fa..00000000 --- a/lib/forge-std +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 978ac6fadb62f5f0b723c996f64be52eddba6801 diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts deleted file mode 160000 index dbb6104c..00000000 --- a/lib/openzeppelin-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable deleted file mode 160000 index 723f8cab..00000000 --- a/lib/openzeppelin-contracts-upgradeable +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 723f8cab09cdae1aca9ec9cc1cfa040c2d4b06c1 diff --git a/lib/openzeppelin-foundry-upgrades b/lib/openzeppelin-foundry-upgrades deleted file mode 160000 index 4cd15fc5..00000000 --- a/lib/openzeppelin-foundry-upgrades +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4cd15fc50b141c77d8cc9ff8efb44d00e841a299 diff --git a/lib/prb-math b/lib/prb-math deleted file mode 160000 index 39eec818..00000000 --- a/lib/prb-math +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 39eec818282a29df7406b8280b29c084c9a3f3b5 diff --git a/lib/v2-core b/lib/v2-core deleted file mode 160000 index 73356945..00000000 --- a/lib/v2-core +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 73356945b53e8dd4112f34f3e2c63c278c4a5239 diff --git a/package.json b/package.json new file mode 100644 index 00000000..b8c9aca6 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "devDependencies": { + "forge-std": "github:foundry-rs/forge-std#v1.9.4" + }, + "dependencies": { + "@openzeppelin/contracts": "^5.1.0", + "@prb/math": "^4.1.0", + "@sablier/v2-core": "^1.2.0", + "@thirdweb-dev/contracts": "^3.15.0" + } +} diff --git a/remappings.txt b/remappings.txt index 23b805d8..07ab4b14 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,8 +1,5 @@ -@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ -@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ -@sablier/v2-core/=lib/v2-core/ -@prb/math/=lib/prb-math/ -@nomad-xyz/excessively-safe-call/=lib/nomad-xyz/excessively-safe-call/ -@thirdweb/contracts/=lib/contracts/contracts/ -ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/ -forge-std/=lib/forge-std/src/ +@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/ +@sablier/v2-core/=node_modules/@sablier/v2-core/ +@prb/math/=node_modules/@prb/math/ +@thirdweb/contracts/=node_modules/@thirdweb-dev/contracts/ +forge-std/=node_modules/forge-std/src/ diff --git a/script/DeployDeterministicStationRegistry.s.sol b/script/DeployDeterministicStationRegistry.s.sol index 25deec1d..07f1b592 100644 --- a/script/DeployDeterministicStationRegistry.s.sol +++ b/script/DeployDeterministicStationRegistry.s.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.26; import { BaseScript } from "./Base.s.sol"; import { StationRegistry } from "./../src/StationRegistry.sol"; import { ModuleKeeper } from "./../src/ModuleKeeper.sol"; -import { EntryPoint } from "@thirdweb/contracts/prebuilts/account/utils/Entrypoint.sol"; +import { IEntryPoint } from "@thirdweb/contracts/prebuilts/account/interface/IEntrypoint.sol"; /// @notice Deploys at deterministic addresses across chains an instance of {StationRegistry} /// @dev Reverts if any contract has already been deployed @@ -14,7 +14,7 @@ contract DeployDeterministicStationRegistry is BaseScript { function run( string memory create2Salt, address initialAdmin, - EntryPoint entrypoint, + IEntryPoint entrypoint, ModuleKeeper moduleKeeper ) public virtual broadcast returns (StationRegistry stationRegistry) { bytes32 salt = bytes32(abi.encodePacked(create2Salt)); diff --git a/src/Space.sol b/src/Space.sol index ca5c1b1f..a97833ec 100644 --- a/src/Space.sol +++ b/src/Space.sol @@ -163,7 +163,9 @@ contract Space is ISpace, AccountCore, ERC1271, ModuleManager { } /// @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(); @@ -177,7 +179,9 @@ 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(); @@ -186,7 +190,9 @@ 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); } @@ -224,14 +230,18 @@ 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) { + 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; } diff --git a/src/StationRegistry.sol b/src/StationRegistry.sol index d7315d44..c19b6387 100644 --- a/src/StationRegistry.sol +++ b/src/StationRegistry.sol @@ -1,15 +1,15 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.26; -import { BaseAccountFactory } from "@thirdweb/contracts/prebuilts/account/utils/BaseAccountFactory.sol"; import { IEntryPoint } from "@thirdweb/contracts/prebuilts/account/interface/IEntrypoint.sol"; import { PermissionsEnumerable } from "@thirdweb/contracts/extension/PermissionsEnumerable.sol"; import { EnumerableSet } from "@thirdweb/contracts/external-deps/openzeppelin/utils/structs/EnumerableSet.sol"; -import { IStationRegistry } from "./interfaces/IStationRegistry.sol"; import { Space } from "./Space.sol"; import { ModuleKeeper } from "./ModuleKeeper.sol"; import { Errors } from "./libraries/Errors.sol"; +import { IStationRegistry } from "./interfaces/IStationRegistry.sol"; +import { BaseAccountFactory } from "./utils/BaseAccountFactory.sol"; /// @title StationRegistry /// @notice See the documentation in {IStationRegistry} @@ -111,7 +111,9 @@ 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; @@ -124,7 +126,9 @@ 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(); } diff --git a/src/utils/BaseAccountFactory.sol b/src/utils/BaseAccountFactory.sol new file mode 100644 index 00000000..6892c945 --- /dev/null +++ b/src/utils/BaseAccountFactory.sol @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.12; + +// Utils +import "@thirdweb-dev/contracts/extension/Multicall.sol"; +import "@thirdweb-dev/contracts/external-deps/openzeppelin/proxy/Clones.sol"; +import "@thirdweb-dev/contracts/external-deps/openzeppelin/utils/structs/EnumerableSet.sol"; +import "@thirdweb-dev/contracts/lib/BytesLib.sol"; + +// Interface +import "@thirdweb-dev/contracts/prebuilts/account/interface/IAccountFactory.sol"; + +// $$\ $$\ $$\ $$\ $$\ +// $$ | $$ | \__| $$ | $$ | +// $$$$$$\ $$$$$$$\ $$\ $$$$$$\ $$$$$$$ |$$\ $$\ $$\ $$$$$$\ $$$$$$$\ +// \_$$ _| $$ __$$\ $$ |$$ __$$\ $$ __$$ |$$ | $$ | $$ |$$ __$$\ $$ __$$\ +// $$ | $$ | $$ |$$ |$$ | \__|$$ / $$ |$$ | $$ | $$ |$$$$$$$$ |$$ | $$ | +// $$ |$$\ $$ | $$ |$$ |$$ | $$ | $$ |$$ | $$ | $$ |$$ ____|$$ | $$ | +// \$$$$ |$$ | $$ |$$ |$$ | \$$$$$$$ |\$$$$$\$$$$ |\$$$$$$$\ $$$$$$$ | +// \____/ \__| \__|\__|\__| \_______| \_____\____/ \_______|\_______/ + +/// Note: Fork of the thirdweb `BaseAccountFactory.sol` contract which allows the `createAccount` +/// method to be overriden in child contracts +abstract contract BaseAccountFactory is IAccountFactory, Multicall { + using EnumerableSet for EnumerableSet.AddressSet; + + /*/////////////////////////////////////////////////////////////// + State + //////////////////////////////////////////////////////////////*/ + + address public immutable accountImplementation; + address public immutable entrypoint; + + EnumerableSet.AddressSet private allAccounts; + mapping(address => EnumerableSet.AddressSet) internal accountsOfSigner; + + /*/////////////////////////////////////////////////////////////// + Constructor + //////////////////////////////////////////////////////////////*/ + + constructor(address _accountImpl, address _entrypoint) { + accountImplementation = _accountImpl; + entrypoint = _entrypoint; + } + + /*/////////////////////////////////////////////////////////////// + Public functions + //////////////////////////////////////////////////////////////*/ + + /// @notice Deploys a new Account for admin. + function createAccount(address _admin, bytes calldata _data) public virtual override returns (address) { + address impl = accountImplementation; + bytes32 salt = _generateSalt(_admin, _data); + address account = Clones.predictDeterministicAddress(impl, salt); + + if (account.code.length > 0) { + return account; + } + + account = Clones.cloneDeterministic(impl, salt); + + if (msg.sender != entrypoint) { + require(allAccounts.add(account), "AccountFactory: account already registered"); + } + + _initializeAccount(account, _admin, _data); + + emit AccountCreated(account, _admin); + + return account; + } + + /*/////////////////////////////////////////////////////////////// + External functions + //////////////////////////////////////////////////////////////*/ + + /// @notice Callback function for an Account to register itself on the factory. + function onRegister( + bytes32 _salt + ) external { + address account = msg.sender; + require(_isAccountOfFactory(account, _salt), "AccountFactory: not an account."); + + require(allAccounts.add(account), "AccountFactory: account already registered"); + } + + function onSignerAdded(address _signer, bytes32 _salt) external { + address account = msg.sender; + require(_isAccountOfFactory(account, _salt), "AccountFactory: not an account."); + + bool isNewSigner = accountsOfSigner[_signer].add(account); + + if (isNewSigner) { + emit SignerAdded(account, _signer); + } + } + + /// @notice Callback function for an Account to un-register its signers. + function onSignerRemoved(address _signer, bytes32 _salt) external { + address account = msg.sender; + require(_isAccountOfFactory(account, _salt), "AccountFactory: not an account."); + + bool isAccount = accountsOfSigner[_signer].remove(account); + + if (isAccount) { + emit SignerRemoved(account, _signer); + } + } + + /*/////////////////////////////////////////////////////////////// + View functions + //////////////////////////////////////////////////////////////*/ + + /// @notice Returns whether an account is registered on this factory. + function isRegistered( + address _account + ) external view returns (bool) { + return allAccounts.contains(_account); + } + + /// @notice Returns the total number of accounts. + function totalAccounts() external view returns (uint256) { + return allAccounts.length(); + } + + /// @notice Returns all accounts between the given indices. + function getAccounts(uint256 _start, uint256 _end) external view returns (address[] memory accounts) { + require(_start < _end && _end <= allAccounts.length(), "BaseAccountFactory: invalid indices"); + + uint256 len = _end - _start; + accounts = new address[](_end - _start); + + for (uint256 i = 0; i < len; i += 1) { + accounts[i] = allAccounts.at(i + _start); + } + } + + /// @notice Returns all accounts created on the factory. + function getAllAccounts() external view returns (address[] memory) { + return allAccounts.values(); + } + + /// @notice Returns the address of an Account that would be deployed with the given admin signer. + function getAddress(address _adminSigner, bytes calldata _data) public view returns (address) { + bytes32 salt = _generateSalt(_adminSigner, _data); + return Clones.predictDeterministicAddress(accountImplementation, salt); + } + + /// @notice Returns all accounts that the given address is a signer of. + function getAccountsOfSigner( + address signer + ) external view returns (address[] memory accounts) { + return accountsOfSigner[signer].values(); + } + + /*/////////////////////////////////////////////////////////////// + Internal functions + //////////////////////////////////////////////////////////////*/ + + /// @dev Returns whether the caller is an account deployed by this factory. + function _isAccountOfFactory(address _account, bytes32 _salt) internal view virtual returns (bool) { + address predicted = Clones.predictDeterministicAddress(accountImplementation, _salt); + return _account == predicted; + } + + function _getImplementation( + address cloneAddress + ) internal view returns (address) { + bytes memory code = cloneAddress.code; + return BytesLib.toAddress(code, 10); + } + + /// @dev Returns the salt used when deploying an Account. + function _generateSalt(address _admin, bytes memory _data) internal view virtual returns (bytes32) { + return keccak256(abi.encode(_admin, _data)); + } + + /// @dev Called in `createAccount`. Initializes the account contract created in `createAccount`. + function _initializeAccount(address _account, address _admin, bytes calldata _data) internal virtual; +} diff --git a/test/Base.t.sol b/test/Base.t.sol index 88349347..bafd1dfc 100644 --- a/test/Base.t.sol +++ b/test/Base.t.sol @@ -11,11 +11,11 @@ import { MockBadReceiver } from "./mocks/MockBadReceiver.sol"; import { Space } from "./../src/Space.sol"; import { ModuleKeeper } from "./../src/ModuleKeeper.sol"; import { StationRegistry } from "./../src/StationRegistry.sol"; -import { EntryPoint } from "@thirdweb/contracts/prebuilts/account/utils/Entrypoint.sol"; import { MockERC721Collection } from "./mocks/MockERC721Collection.sol"; import { MockERC1155Collection } from "./mocks/MockERC1155Collection.sol"; import { MockBadSpace } from "./mocks/MockBadSpace.sol"; import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol"; +import { IEntryPoint } from "@thirdweb/contracts/prebuilts/account/interface/IEntrypoint.sol"; abstract contract Base_Test is Test, Events { /*////////////////////////////////////////////////////////////////////////// @@ -28,7 +28,7 @@ abstract contract Base_Test is Test, Events { TEST CONTRACTS //////////////////////////////////////////////////////////////////////////*/ - EntryPoint internal entrypoint; + address internal entrypoint; StationRegistry internal stationRegistry; Space internal space; ModuleKeeper internal moduleKeeper; @@ -58,11 +58,11 @@ abstract contract Base_Test is Test, Events { users = Users({ admin: createUser("admin"), eve: createUser("eve"), bob: createUser("bob") }); // Deploy test contracts - entrypoint = new EntryPoint(); + //entrypoint = new EntryPoint(); moduleKeeper = new ModuleKeeper({ _initialOwner: users.admin }); - stationRegistry = new StationRegistry(users.admin, entrypoint, moduleKeeper); - containerImplementation = address(new Space(entrypoint, address(stationRegistry))); + stationRegistry = new StationRegistry(users.admin, IEntryPoint(entrypoint), moduleKeeper); + containerImplementation = address(new Space(IEntryPoint(entrypoint), address(stationRegistry))); mockModule = new MockModule(); mockNonCompliantSpace = new MockNonCompliantSpace({ _owner: users.admin }); @@ -126,7 +126,9 @@ abstract contract Base_Test is Test, Events { vm.stopPrank(); } - function allowlistModule(address _module) internal { + function allowlistModule( + address _module + ) internal { moduleKeeper.addToAllowlist({ module: _module }); } @@ -135,7 +137,9 @@ abstract contract Base_Test is Test, Events { //////////////////////////////////////////////////////////////////////////*/ /// @dev Generates a user, labels its address, and funds it with test assets - function createUser(string memory name) internal returns (address payable) { + function createUser( + string memory name + ) internal returns (address payable) { address payable user = payable(makeAddr(name)); vm.deal({ account: user, newBalance: 100 ether }); deal({ token: address(usdt), to: user, give: 10_000_000e18 }); diff --git a/test/integration/Integration.t.sol b/test/integration/Integration.t.sol index 275252f8..181d3d5d 100644 --- a/test/integration/Integration.t.sol +++ b/test/integration/Integration.t.sol @@ -5,7 +5,7 @@ import { Base_Test } from "../Base.t.sol"; import { InvoiceModule } from "./../../src/modules/invoice-module/InvoiceModule.sol"; import { SablierV2LockupLinear } from "@sablier/v2-core/src/SablierV2LockupLinear.sol"; import { SablierV2LockupTranched } from "@sablier/v2-core/src/SablierV2LockupTranched.sol"; -import { NFTDescriptorMock } from "@sablier/v2-core/test/mocks/NFTDescriptorMock.sol"; +import { MockNFTDescriptor } from "../mocks/MockNFTDescriptor.sol"; import { MockStreamManager } from "../mocks/MockStreamManager.sol"; import { MockBadSpace } from "../mocks/MockBadSpace.sol"; import { Space } from "./../../src/Space.sol"; @@ -17,7 +17,7 @@ abstract contract Integration_Test is Base_Test { InvoiceModule internal invoiceModule; // Sablier V2 related test contracts - NFTDescriptorMock internal mockNFTDescriptor; + MockNFTDescriptor internal mockNFTDescriptor; SablierV2LockupLinear internal sablierV2LockupLinear; SablierV2LockupTranched internal sablierV2LockupTranched; MockStreamManager internal mockStreamManager; @@ -60,7 +60,7 @@ abstract contract Integration_Test is Base_Test { /// @dev Deploys the {InvoiceModule} module by initializing the Sablier v2-required contracts first function deployInvoiceModule() internal { - mockNFTDescriptor = new NFTDescriptorMock(); + mockNFTDescriptor = new MockNFTDescriptor(); sablierV2LockupLinear = new SablierV2LockupLinear({ initialAdmin: users.admin, initialNFTDescriptor: mockNFTDescriptor }); sablierV2LockupTranched = new SablierV2LockupTranched({ diff --git a/test/mocks/MockNFTDescriptor.sol b/test/mocks/MockNFTDescriptor.sol new file mode 100644 index 00000000..a09f5568 --- /dev/null +++ b/test/mocks/MockNFTDescriptor.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.22; + +import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; + +import { NFTSVG } from "@sablier/v2-core/src/libraries/NFTSVG.sol"; +import { SVGElements } from "@sablier/v2-core/src/libraries/SVGElements.sol"; +import { Lockup } from "@sablier/v2-core/src/types/DataTypes.sol"; +import { SablierV2NFTDescriptor } from "@sablier/v2-core/src/SablierV2NFTDescriptor.sol"; + +/// @dev This mock is needed for: +/// - Running the tests against the `--via-ir` precompiles +/// - Testing reverts: https://github.com/foundry-rs/foundry/issues/864 +contract MockNFTDescriptor is SablierV2NFTDescriptor { + function abbreviateAmount_(uint256 amount, uint256 decimals) external pure returns (string memory) { + return abbreviateAmount(amount, decimals); + } + + function calculateDurationInDays_(uint256 startTime, uint256 endTime) external pure returns (string memory) { + return calculateDurationInDays(startTime, endTime); + } + + function calculatePixelWidth_(string memory text, bool largeFont) external pure returns (uint256) { + return SVGElements.calculatePixelWidth(text, largeFont); + } + + function calculateStreamedPercentage_( + uint128 streamedAmount, + uint128 depositedAmount + ) external pure returns (uint256) { + return calculateStreamedPercentage(streamedAmount, depositedAmount); + } + + function generateAccentColor_(address sablier, uint256 streamId) external view returns (string memory) { + return generateAccentColor(sablier, streamId); + } + + function generateAttributes_( + string memory assetSymbol, + string memory sender, + string memory status + ) external pure returns (string memory) { + return generateAttributes(assetSymbol, sender, status); + } + + function generateDescription_( + string memory sablierModel, + string memory assetSymbol, + string memory sablierAddress, + string memory assetAddress, + string memory streamId, + bool isTransferable + ) external pure returns (string memory) { + return generateDescription(sablierModel, assetSymbol, sablierAddress, assetAddress, streamId, isTransferable); + } + + function generateName_(string memory sablierModel, string memory streamId) external pure returns (string memory) { + return generateName(sablierModel, streamId); + } + + function generateSVG_(NFTSVG.SVGParams memory params) external pure returns (string memory) { + return NFTSVG.generateSVG(params); + } + + function hourglass_(string memory status) external pure returns (string memory) { + return SVGElements.hourglass(status); + } + + function isAllowedCharacter_(string memory symbol) external pure returns (bool) { + return isAllowedCharacter(symbol); + } + + function mapSymbol_(IERC721Metadata nft) external view returns (string memory) { + return mapSymbol(nft); + } + + function safeAssetDecimals_(address asset) external view returns (uint8) { + return safeAssetDecimals(asset); + } + + function safeAssetSymbol_(address asset) external view returns (string memory) { + return safeAssetSymbol(asset); + } + + function stringifyCardType_(SVGElements.CardType cardType) external pure returns (string memory) { + return SVGElements.stringifyCardType(cardType); + } + + function stringifyFractionalAmount_(uint256 fractionalAmount) external pure returns (string memory) { + return stringifyFractionalAmount(fractionalAmount); + } + + function stringifyPercentage_(uint256 percentage) external pure returns (string memory) { + return stringifyPercentage(percentage); + } + + function stringifyStatus_(Lockup.Status status) external pure returns (string memory) { + return stringifyStatus(status); + } +} diff --git a/test/unit/concrete/station-registry/constructor.t.sol b/test/unit/concrete/station-registry/constructor.t.sol index b7891150..e88086b1 100644 --- a/test/unit/concrete/station-registry/constructor.t.sol +++ b/test/unit/concrete/station-registry/constructor.t.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.26; import { StationRegistry } from "./../../../../src/StationRegistry.sol"; import { Base_Test } from "../../../Base.t.sol"; import { Constants } from "../../../utils/Constants.sol"; +import { IEntryPoint } from "@thirdweb/contracts/prebuilts/account/interface/IEntrypoint.sol"; contract Constructor_StationRegistry_Test is Base_Test { function setUp() public virtual override { @@ -12,7 +13,11 @@ contract Constructor_StationRegistry_Test is Base_Test { function test_Constructor() external { // Run the test - new StationRegistry({ _initialAdmin: users.admin, _entrypoint: entrypoint, _moduleKeeper: moduleKeeper }); + new StationRegistry({ + _initialAdmin: users.admin, + _entrypoint: IEntryPoint(entrypoint), + _moduleKeeper: moduleKeeper + }); // Assert the actual and expected {ModuleKeeper} address address actualModuleKeeper = address(stationRegistry.moduleKeeper());