diff --git a/contracts/.gas-snapshot b/contracts/.gas-snapshot index 38adab61..472186d5 100644 --- a/contracts/.gas-snapshot +++ b/contracts/.gas-snapshot @@ -1,7 +1,8 @@ -ExaAccountFactoryTest:testFuzz_createAccount_EOAOwners(uint256,address[63]) (runs: 256, μ: 3621366, ~: 3460632) -ExaPluginTest:testFork_crossRepay_repays() (gas: 19686996) -ExaPluginTest:testFork_debitCollateral_collects() (gas: 19776574) -ExaPluginTest:testFork_swap_swaps() (gas: 16929512) +ExaAccountFactoryTest:testFuzz_createAccount_EOAOwners(uint256,address[63]) (runs: 256, μ: 3627424, ~: 3505072) +ExaAccountFactoryTest:test_deploy_deploysToSameAddress() (gas: 26321134) +ExaPluginTest:testFork_crossRepay_repays() (gas: 15277977) +ExaPluginTest:testFork_debitCollateral_collects() (gas: 15367555) +ExaPluginTest:testFork_swap_swaps() (gas: 12543711) ExaPluginTest:test_borrowAtMaturity_reverts_withUnauthorized_whenReceiverNotCollector() (gas: 408975) ExaPluginTest:test_borrow_reverts_withUnauthorized_whenReceiverNotCollector() (gas: 408553) ExaPluginTest:test_collectCredit_collects() (gas: 921083) diff --git a/contracts/script/Deploy.s.sol b/contracts/script/Deploy.s.sol index 4f49404a..97d96e69 100644 --- a/contracts/script/Deploy.s.sol +++ b/contracts/script/Deploy.s.sol @@ -32,12 +32,22 @@ contract DeployScript is BaseScript { acct("keeper") ); - factory = new ExaAccountFactory( - acct("admin"), - WebauthnOwnerPlugin(dependency("webauthn-owner-plugin", "WebauthnOwnerPlugin", "Plugin", 0)), - exaPlugin, - ACCOUNT_IMPL, - ENTRYPOINT + factory = ExaAccountFactory( + payable( + CREATE3_FACTORY.deploy( + keccak256(abi.encode(exaPlugin.NAME(), exaPlugin.VERSION())), + abi.encodePacked( + vm.getCode("ExaAccountFactory.sol:ExaAccountFactory"), + abi.encode( + acct("admin"), + WebauthnOwnerPlugin(dependency("webauthn-owner-plugin", "WebauthnOwnerPlugin", "Plugin", 0)), + exaPlugin, + ACCOUNT_IMPL, + ENTRYPOINT + ) + ) + ) + ) ); factory.donateStake{ value: 0.1 ether }(); diff --git a/contracts/test/ExaAccountFactory.t.sol b/contracts/test/ExaAccountFactory.t.sol index 2214e135..6effe460 100644 --- a/contracts/test/ExaAccountFactory.t.sol +++ b/contracts/test/ExaAccountFactory.t.sol @@ -1,28 +1,27 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.0; // solhint-disable-line one-contract-per-file -import { Test } from "forge-std/Test.sol"; +import { ForkTest } from "./Fork.t.sol"; import { EntryPoint } from "account-abstraction/core/EntryPoint.sol"; import { UpgradeableModularAccount } from "modular-account/src/account/UpgradeableModularAccount.sol"; -import { IEntryPoint } from "modular-account/src/interfaces/erc4337/IEntryPoint.sol"; import { ERC20 } from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import { ERC4626 } from "openzeppelin-contracts/contracts/token/ERC20/extensions/ERC4626.sol"; import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol"; +import { ACCOUNT_IMPL, ENTRYPOINT } from "webauthn-owner-plugin/../script/Factory.s.sol"; import { MAX_OWNERS } from "webauthn-owner-plugin/IWebauthnOwnerPlugin.sol"; import { OwnersLib } from "webauthn-owner-plugin/OwnersLib.sol"; import { WebauthnOwnerPlugin } from "webauthn-owner-plugin/WebauthnOwnerPlugin.sol"; +import { DeployScript } from "../script/Deploy.s.sol"; import { ExaAccountFactory, ExaAccountInitialized } from "../src/ExaAccountFactory.sol"; -import { ExaPlugin, IBalancerVault, IDebtManager, IInstallmentsRouter } from "../src/ExaPlugin.sol"; -import { IAuditor, IMarket } from "../src/IExaAccount.sol"; -import { IssuerChecker } from "../src/IssuerChecker.sol"; +import { ExaPlugin } from "../src/ExaPlugin.sol"; -contract ExaAccountFactoryTest is Test { +contract ExaAccountFactoryTest is ForkTest { using FixedPointMathLib for uint256; using OwnersLib for address[]; @@ -32,24 +31,30 @@ contract ExaAccountFactoryTest is Test { function setUp() external { ownerPlugin = new WebauthnOwnerPlugin(); - exaPlugin = new ExaPlugin( - address(this), - IAuditor(address(0)), - IMarket(address(new MockERC4626(new MockERC20()))), - IMarket(address(new MockERC4626(new MockERC20()))), - IBalancerVault(address(this)), - IDebtManager(address(this)), - IInstallmentsRouter(address(this)), - IssuerChecker(address(this)), - address(this), - address(this), - address(this) - ); - - IEntryPoint entryPoint = IEntryPoint(address(new EntryPoint())); - factory = new ExaAccountFactory( - address(this), ownerPlugin, exaPlugin, address(new UpgradeableModularAccount(entryPoint)), entryPoint - ); + vm.etch(address(ENTRYPOINT), address(new EntryPoint()).code); + vm.etch(ACCOUNT_IMPL, address(new UpgradeableModularAccount(ENTRYPOINT)).code); + vm.store(address(this), keccak256(abi.encode("deployer")), bytes32(uint256(uint160(address(this))))); + + DeployScript d = new DeployScript(); + set("Auditor", address(this)); + set("MarketUSDC", address(new MockERC4626(new MockERC20()))); + set("MarketWETH", address(new MockERC4626(new MockERC20()))); + set("BalancerVault", address(this)); + set("DebtManager", address(this)); + set("InstallmentsRouter", address(this)); + set("IssuerChecker", address(this)); + set("WebauthnOwnerPlugin", address(ownerPlugin)); + d.run(); + unset("Auditor"); + unset("MarketUSDC"); + unset("MarketWETH"); + unset("BalancerVault"); + unset("DebtManager"); + unset("InstallmentsRouter"); + unset("IssuerChecker"); + unset("WebauthnOwnerPlugin"); + exaPlugin = d.exaPlugin(); + factory = d.factory(); } // solhint-disable func-name-mixedcase @@ -88,6 +93,20 @@ contract ExaAccountFactoryTest is Test { } } + function test_deploy_deploysToSameAddress() external { + address[] memory owners = new address[](1); + owners[0] = address(this); + address account = factory.createAccount(0, owners.toPublicKeys()); + + vm.createSelectFork("optimism", 127_050_624); + + DeployScript d = new DeployScript(); + d.run(); + + assertEq(address(d.factory()), address(factory), "different factory address"); + assertEq(factory.getAddress(0, owners.toPublicKeys()), account, "different account address"); + } + // solhint-enable func-name-mixedcase function _sorted(address[] memory owners) internal pure returns (address[] memory) { diff --git a/contracts/test/Fork.t.sol b/contracts/test/Fork.t.sol index aafa0918..39a8b590 100644 --- a/contracts/test/Fork.t.sol +++ b/contracts/test/Fork.t.sol @@ -7,10 +7,34 @@ import { Vm } from "forge-std/Vm.sol"; // solhint-disable-line no-unused-import import { LibString } from "solady/utils/LibString.sol"; abstract contract ForkTest is Test { + using LibString for address; using LibString for uint256; using LibString for string; + using LibString for bytes; using stdJson for string; + ICREATE3Factory internal immutable CREATE3_FACTORY; + + constructor() { + CREATE3_FACTORY = ICREATE3Factory( + block.chainid == 11_155_420 // TODO remove after https://github.com/lifinance/create3-factory/issues/14 + ? 0xcc3f41204a1324DD91F1Dbfc46208535293A371e + : 0x93FEC2C00BfE902F733B57c5a6CeeD7CD1384AE1 + ); + vm.label(address(CREATE3_FACTORY), "CREATE3Factory"); + if (block.chainid == getChain("anvil").chainId) { + bytes memory code = + hex"6080604052600436106100295760003560e01c806350f1c4641461002e578063cdcb760a14610077575b600080fd5b34801561003a57600080fd5b5061004e610049366004610489565b61008a565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b61004e6100853660046104fd565b6100ee565b6040517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606084901b166020820152603481018290526000906054016040516020818303038152906040528051906020012091506100e78261014c565b9392505050565b6040517fffffffffffffffffffffffffffffffffffffffff0000000000000000000000003360601b166020820152603481018390526000906054016040516020818303038152906040528051906020012092506100e78383346102b2565b604080518082018252601081527f67363d3d37363d34f03d5260086018f30000000000000000000000000000000060209182015290517fff00000000000000000000000000000000000000000000000000000000000000918101919091527fffffffffffffffffffffffffffffffffffffffff0000000000000000000000003060601b166021820152603581018290527f21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f60558201526000908190610228906075015b6040516020818303038152906040528051906020012090565b6040517fd69400000000000000000000000000000000000000000000000000000000000060208201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606083901b1660228201527f010000000000000000000000000000000000000000000000000000000000000060368201529091506100e79060370161020f565b6000806040518060400160405280601081526020017f67363d3d37363d34f03d5260086018f30000000000000000000000000000000081525090506000858251602084016000f5905073ffffffffffffffffffffffffffffffffffffffff811661037d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f4445504c4f594d454e545f4641494c454400000000000000000000000000000060448201526064015b60405180910390fd5b6103868661014c565b925060008173ffffffffffffffffffffffffffffffffffffffff1685876040516103b091906105d6565b60006040518083038185875af1925050503d80600081146103ed576040519150601f19603f3d011682016040523d82523d6000602084013e6103f2565b606091505b50509050808015610419575073ffffffffffffffffffffffffffffffffffffffff84163b15155b61047f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f494e495449414c495a4154494f4e5f4641494c454400000000000000000000006044820152606401610374565b5050509392505050565b6000806040838503121561049c57600080fd5b823573ffffffffffffffffffffffffffffffffffffffff811681146104c057600080fd5b946020939093013593505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000806040838503121561051057600080fd5b82359150602083013567ffffffffffffffff8082111561052f57600080fd5b818501915085601f83011261054357600080fd5b813581811115610555576105556104ce565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561059b5761059b6104ce565b816040528281528860208487010111156105b457600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b6000825160005b818110156105f757602081860181015185830152016105dd565b50600092019182525091905056fea26469706673582212201ff95c2aafa102481fdd22c59ee7f98a92a9662a6566ab5e0498e8bb47a5f30c64736f6c63430008110033"; + vm.etch(address(CREATE3_FACTORY), code); + try vm.activeFork() { + vm.rpc( + "anvil_setCode", + string.concat('["', address(CREATE3_FACTORY).toHexString(), '","', code.toHexString(), '"]') // solhint-disable-line quotes + ); + } catch { } // solhint-disable-line no-empty-blocks + } + } + function set(string memory name, address addr) internal { vm.store(address(this), keccak256(abi.encode(name)), bytes32(uint256(uint160(addr)))); } @@ -87,3 +111,19 @@ abstract contract ForkTest is Test { } } } + +interface ICREATE3Factory { + /// @notice Deploys a contract using CREATE3 + /// @dev The provided salt is hashed together with msg.sender to generate the final salt + /// @param salt The deployer-specific salt for determining the deployed contract's address + /// @param creationCode The creation code of the contract to deploy + /// @return deployed The address of the deployed contract + function deploy(bytes32 salt, bytes memory creationCode) external payable returns (address deployed); + + /// @notice Predicts the address of a deployed contract + /// @dev The provided salt is hashed together with the deployer address to generate the final salt + /// @param deployer The deployer account that will call deploy() + /// @param salt The deployer-specific salt for determining the deployed contract's address + /// @return deployed The address of the contract that will be deployed + function getDeployed(address deployer, bytes32 salt) external view returns (address deployed); +}