diff --git a/.env.example b/.env.example index b25c38b7..5fa4150b 100644 --- a/.env.example +++ b/.env.example @@ -40,6 +40,9 @@ ACCOUNT_FACTORY_SALT= # Factory staking setup +# Needs a new factory address variable, to address a foundry test suite issue with setEnv +ACCOUNT_FACTORY_TO_STAKE= + # 0.1 ether required by bundlers REQUIRED_STAKE_AMOUNT_WEI=100000000000000000 # 1 day required by bundlers diff --git a/script/StakeFactory.s.sol b/script/StakeFactory.s.sol new file mode 100644 index 00000000..8bcc83f9 --- /dev/null +++ b/script/StakeFactory.s.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; +import {IStakeManager} from "@eth-infinitism/account-abstraction/interfaces/IStakeManager.sol"; +import {console} from "forge-std/console.sol"; + +import {AccountFactory} from "../src/factory/AccountFactory.sol"; + +import {ScriptBase} from "./ScriptBase.sol"; + +contract StakeFactoryScript is ScriptBase { + AccountFactory internal _accountFactory; + + IEntryPoint internal _entryPoint; + + function setUp() public { + _entryPoint = _getEntryPoint(); + + // Has to use a different env var name to avoid conflicts with the one in DeployFactoryScript, because + // vm.setEnv in tests doesn't isolate and it would result in a race condition. + _accountFactory = AccountFactory(vm.envOr("ACCOUNT_FACTORY_TO_STAKE", address(0))); + } + + function run() public { + console.log("******** Staking Account Factory *********"); + + if (address(_accountFactory) == address(0)) { + console.log("Account Factory not found or invalid"); + revert(); + } + + console.log("Using AccountFactory at address: ", address(_accountFactory)); + + (uint256 stakeAmountWei, uint256 unstakeDelay) = _getStakeParams(); + + uint256 stakeNeeded = _checkCurrentStake(stakeAmountWei); + + vm.startBroadcast(); + + _accountFactory.addStake{value: stakeNeeded}(uint32(unstakeDelay)); + + vm.stopBroadcast(); + + console.log("******** Done Staking Account Factory *********"); + } + + function _getStakeParams() internal view returns (uint256 stakeAmountWei, uint256 unstakeDelaySec) { + stakeAmountWei = vm.envOr("REQUIRED_STAKE_AMOUNT_WEI", uint256(0)); + + if (stakeAmountWei == 0) { + console.log("Env Variable 'REQUIRED_STAKE_AMOUNT_WEI' not found or invalid."); + revert(); + } + + console.log("Using user-defined stake amount: ", stakeAmountWei); + + unstakeDelaySec = vm.envOr("UNSTAKE_DELAY_SEC", uint256(0)); + + if (unstakeDelaySec == 0) { + console.log("Env Variable 'UNSTAKE_DELAY_SEC' not found or invalid."); + revert(); + } + + console.log("Using user-defined unstake delay: ", unstakeDelaySec); + } + + function _checkCurrentStake(uint256 requiredStakeAmountWei) internal view returns (uint256 stakeNeeded) { + IStakeManager.DepositInfo memory factoryDepositInfo = _entryPoint.getDepositInfo(address(_accountFactory)); + + uint256 currentStake = factoryDepositInfo.stake; + + if (currentStake > requiredStakeAmountWei) { + console.log("Factory already has enough stake: ", currentStake); + stakeNeeded = 0; + } else { + stakeNeeded = requiredStakeAmountWei - currentStake; + console.log("Adding stake to factory: ", stakeNeeded); + } + + return stakeNeeded; + } +} diff --git a/test/script/DeployAccounts.s.t.sol b/test/script/DeployAccounts.s.t.sol index 07f088c8..c8727d0c 100644 --- a/test/script/DeployAccounts.s.t.sol +++ b/test/script/DeployAccounts.s.t.sol @@ -1,17 +1,16 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.26; -import {Test} from "forge-std/Test.sol"; - import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; import {DeployAccountsScript} from "../../script/DeployAccounts.s.sol"; import {ModularAccount} from "../../src/account/ModularAccount.sol"; - import {SemiModularAccount7702} from "../../src/account/SemiModularAccount7702.sol"; import {SemiModularAccountBytecode} from "../../src/account/SemiModularAccountBytecode.sol"; -contract DeployAccountsTest is Test { +import {OptimizedTest} from "../utils/OptimizedTest.sol"; + +contract DeployAccountsTest is OptimizedTest { DeployAccountsScript internal _deployAccountsScript; address public entryPoint; @@ -25,7 +24,7 @@ contract DeployAccountsTest is Test { bytes32 zeroSalt = bytes32(0); - entryPoint = makeAddr("Entrypoint"); + entryPoint = address(_deployEntryPoint070()); executionInstallDelegate = makeAddr("ExecutionInstallDelegate"); diff --git a/test/script/DeployFactory.s.t.sol b/test/script/DeployFactory.s.t.sol index 687cf614..489b85ff 100644 --- a/test/script/DeployFactory.s.t.sol +++ b/test/script/DeployFactory.s.t.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.26; -import {Test} from "forge-std/Test.sol"; - import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; import {DeployFactoryScript} from "../../script/DeployFactory.s.sol"; import {AccountFactory} from "../../src/factory/AccountFactory.sol"; -contract DeployFactoryTest is Test { +import {OptimizedTest} from "../utils/OptimizedTest.sol"; + +contract DeployFactoryTest is OptimizedTest { DeployFactoryScript internal _deployFactoryScript; address public entryPoint; @@ -25,7 +25,7 @@ contract DeployFactoryTest is Test { bytes32 zeroSalt = bytes32(0); - entryPoint = makeAddr("Entrypoint"); + entryPoint = address(_deployEntryPoint070()); modularAccountImpl = makeAddr("Modular Account Impl"); semiModularAccountBytecodeImpl = makeAddr("Semi Modular Account Bytecode Impl"); singleSignerValidationModule = makeAddr("Single Signer Validation Module"); diff --git a/test/script/DeploySmaStorage.s.t.sol b/test/script/DeploySmaStorage.s.t.sol index 9395ac72..a357709b 100644 --- a/test/script/DeploySmaStorage.s.t.sol +++ b/test/script/DeploySmaStorage.s.t.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.26; -import {Test} from "forge-std/Test.sol"; - import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; import {DeploySmaStorageScript} from "../../script/DeploySmaStorage.s.sol"; import {SemiModularAccountStorageOnly} from "../../src/account/SemiModularAccountStorageOnly.sol"; -contract DeploySmaStorageTest is Test { +import {OptimizedTest} from "../utils/OptimizedTest.sol"; + +contract DeploySmaStorageTest is OptimizedTest { DeploySmaStorageScript internal _deploySmaStorageScript; address public entryPoint; @@ -20,7 +20,7 @@ contract DeploySmaStorageTest is Test { bytes32 zeroSalt = bytes32(0); - entryPoint = makeAddr("Entrypoint"); + entryPoint = address(_deployEntryPoint070()); executionInstallDelegate = makeAddr("ExecutionInstallDelegate"); diff --git a/test/script/StakeFactory.s.t.sol b/test/script/StakeFactory.s.t.sol new file mode 100644 index 00000000..6d16286c --- /dev/null +++ b/test/script/StakeFactory.s.t.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; +import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; + +import {StakeFactoryScript} from "../../script/StakeFactory.s.sol"; +import {AccountFactory} from "../../src/factory/AccountFactory.sol"; +import {ExecutionInstallDelegate} from "../../src/helpers/ExecutionInstallDelegate.sol"; +import {WebAuthnValidationModule} from "../../src/modules/validation/WebAuthnValidationModule.sol"; + +import {OptimizedTest} from "../utils/OptimizedTest.sol"; + +contract StakeFactoryTest is OptimizedTest { + StakeFactoryScript internal _stakeFactoryScript; + + EntryPoint internal _entryPoint; + + AccountFactory internal _accountFactory; + + uint256 internal constant _REQUIRED_STAKE_AMOUNT_WEI = 100_000_000_000_000_000; + uint32 internal constant _UNSTAKE_DELAY_SEC = 86_400; + + function setUp() public { + _stakeFactoryScript = new StakeFactoryScript(); + + _entryPoint = _deployEntryPoint070(); + + ExecutionInstallDelegate executionInstallDelegate = _deployExecutionInstallDelegate(); + + _accountFactory = new AccountFactory( + _entryPoint, + _deployModularAccount(_entryPoint, executionInstallDelegate), + _deploySemiModularAccountBytecode(_entryPoint, executionInstallDelegate), + address(_deploySingleSignerValidationModule()), + address(new WebAuthnValidationModule()), + DEFAULT_SENDER + ); + + vm.setEnv("REQUIRED_STAKE_AMOUNT_WEI", vm.toString(_REQUIRED_STAKE_AMOUNT_WEI)); + vm.setEnv("UNSTAKE_DELAY_SEC", vm.toString(_UNSTAKE_DELAY_SEC)); + vm.setEnv("ACCOUNT_FACTORY_TO_STAKE", vm.toString(address(_accountFactory))); + } + + function test_stakeFactoryScript_fromZero() public { + IEntryPoint.DepositInfo memory factoryDepositInfo = _entryPoint.getDepositInfo(address(_accountFactory)); + + assertFalse(factoryDepositInfo.staked, "Factory should not be staked"); + assertEq(factoryDepositInfo.stake, 0, "Factory Stake should be 0"); + assertEq(factoryDepositInfo.unstakeDelaySec, 0, "Factory Unstake Delay should be 0"); + + _stakeFactoryScript.setUp(); + + _stakeFactoryScript.run(); + + factoryDepositInfo = _entryPoint.getDepositInfo(address(_accountFactory)); + + assertTrue(factoryDepositInfo.staked, "Factory should be staked"); + assertEq(factoryDepositInfo.stake, _REQUIRED_STAKE_AMOUNT_WEI, "Factory Stake should be 1000"); + assertEq(factoryDepositInfo.unstakeDelaySec, _UNSTAKE_DELAY_SEC, "Factory Unstake Delay should be 86_400"); + } + + function test_stakeFactoryScript_fromNonZero() public { + vm.prank(DEFAULT_SENDER); + _accountFactory.addStake{value: _REQUIRED_STAKE_AMOUNT_WEI / 2}(uint32(_UNSTAKE_DELAY_SEC / 2)); + + IEntryPoint.DepositInfo memory factoryDepositInfo = _entryPoint.getDepositInfo(address(_accountFactory)); + + assertTrue(factoryDepositInfo.staked, "Factory should be staked"); + assertEq(factoryDepositInfo.stake, _REQUIRED_STAKE_AMOUNT_WEI / 2, "Factory Stake should be 500"); + assertEq( + factoryDepositInfo.unstakeDelaySec, _UNSTAKE_DELAY_SEC / 2, "Factory Unstake Delay should be 43_200" + ); + + _stakeFactoryScript.setUp(); + + _stakeFactoryScript.run(); + + factoryDepositInfo = _entryPoint.getDepositInfo(address(_accountFactory)); + + assertTrue(factoryDepositInfo.staked, "Factory should be staked"); + assertEq(factoryDepositInfo.stake, _REQUIRED_STAKE_AMOUNT_WEI, "Factory Stake should be 1000"); + assertEq(factoryDepositInfo.unstakeDelaySec, _UNSTAKE_DELAY_SEC, "Factory Unstake Delay should be 86_400"); + } +}