diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index 0a25c1940..281550b71 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 0a25c1940ca220686588c4af3ec526f725fe2582 +Subproject commit 281550b71c3df9a83e6b80ceefc700852c287570 diff --git a/src/contracts/atlas/Atlas.sol b/src/contracts/atlas/Atlas.sol index 98f28a4f8..7128b2794 100644 --- a/src/contracts/atlas/Atlas.sol +++ b/src/contracts/atlas/Atlas.sol @@ -54,13 +54,10 @@ contract Atlas is Escrow { { uint256 gasMarker = gasleft(); - // TODO: Combine this w/ call to get executionEnvironment - DAppConfig memory dConfig = IDAppControl(userOp.control).getDAppConfig(userOp); - - // Get the execution environment - address executionEnvironment = IAtlasFactory(FACTORY).getExecutionEnvironmentCustom( - userOp.from, dAppOp.control.codehash, userOp.control, dConfig.callConfig - ); + // Get or create the execution environment + address executionEnvironment; + DAppConfig memory dConfig; + (executionEnvironment, dConfig) = IAtlasFactory(FACTORY).getOrCreateExecutionEnvironment(userOp); // Gracefully return if not valid. This allows signature data to be stored, which helps prevent // replay attacks. diff --git a/src/contracts/atlas/AtlasFactory.sol b/src/contracts/atlas/AtlasFactory.sol index a1d5ebfba..c2a0f4311 100644 --- a/src/contracts/atlas/AtlasFactory.sol +++ b/src/contracts/atlas/AtlasFactory.sol @@ -5,6 +5,7 @@ import { IDAppControl } from "../interfaces/IDAppControl.sol"; import { Mimic } from "./Mimic.sol"; import { DAppConfig } from "src/contracts/types/DAppApprovalTypes.sol"; import { ExecutionEnvironment } from "./ExecutionEnvironment.sol"; +import { UserOperation } from "../types/UserCallTypes.sol"; // TODO make sure no cases of address(this) when Atlas address is intended @@ -29,7 +30,7 @@ contract AtlasFactory { // ------------------ // function createExecutionEnvironment( - address account, + address user, address dAppControl ) external @@ -37,7 +38,22 @@ contract AtlasFactory { { // Must call createExecutionEnvironment on Atlas contract to properly initialize nonce tracking require(msg.sender == atlas, "AtlasFactory: Only Atlas can create execution environments"); - executionEnvironment = _setExecutionEnvironment(dAppControl, account, dAppControl.codehash); + uint32 callConfig = IDAppControl(dAppControl).callConfig(); + executionEnvironment = _setExecutionEnvironment(dAppControl, user, callConfig, dAppControl.codehash); + } + + function getOrCreateExecutionEnvironment(UserOperation calldata userOp) + external + returns (address executionEnvironment, DAppConfig memory dConfig) + { + // Must call getOrCreateExecutionEnvironment on Atlas contract to properly initialize nonce tracking + require(msg.sender == atlas, "AtlasFactory: Only Atlas can create execution environments"); + + address control = userOp.control; + + dConfig = IDAppControl(control).getDAppConfig(userOp); + + executionEnvironment = _setExecutionEnvironment(control, userOp.from, dConfig.callConfig, control.codehash); } function getExecutionEnvironment( @@ -91,13 +107,12 @@ contract AtlasFactory { function _setExecutionEnvironment( address dAppControl, address user, + uint32 callConfig, bytes32 controlCodeHash ) internal returns (address executionEnvironment) { - uint32 callConfig = IDAppControl(dAppControl).callConfig(); - bytes memory creationCode = _getMimicCreationCode(dAppControl, callConfig, user, controlCodeHash); executionEnvironment = address( @@ -116,7 +131,7 @@ contract AtlasFactory { executionEnvironment := create2(0, add(creationCode, 32), mload(creationCode), memSalt) } - emit NewExecutionEnvironment(executionEnvironment, user, dAppControl, callConfig); + // emit NewExecutionEnvironment(executionEnvironment, user, dAppControl, callConfig); } } diff --git a/src/contracts/atlas/AtlasVerification.sol b/src/contracts/atlas/AtlasVerification.sol index ebb81e349..487bf2235 100644 --- a/src/contracts/atlas/AtlasVerification.sol +++ b/src/contracts/atlas/AtlasVerification.sol @@ -358,9 +358,14 @@ contract AtlasVerification is EIP712, DAppIntegration { return false; } - if (nonce == 0 && !isSimulation) { - // Allow 0 nonce for simulations to avoid unnecessary init txs - return false; + if (!isSimulation) { + if (nonce == 0) { + // Allow 0 nonce for simulations to avoid unnecessary init txs + return false; + } else if (nonce == 1) { + // Check if nonce needs to be initialized - do so if necessary. + _initializeNonce(account); + } } uint256 bitmapIndex = (nonce / 240) + 1; // +1 because highestFullBitmap initializes at 0 diff --git a/src/contracts/atlas/DAppIntegration.sol b/src/contracts/atlas/DAppIntegration.sol index 33126b145..cfd7b7501 100644 --- a/src/contracts/atlas/DAppIntegration.sol +++ b/src/contracts/atlas/DAppIntegration.sol @@ -68,7 +68,7 @@ contract DAppIntegration { signatories[signatoryKey] = true; - initializeNonce(msg.sender); + _initializeNonce(msg.sender); } function addSignatory(address controller, address signatory) external { @@ -84,7 +84,7 @@ contract DAppIntegration { signatories[signatoryKey] = true; - initializeNonce(signatory); + _initializeNonce(signatory); emit NewDAppSignatory(controller, govData.governance, signatory, govData.callConfig); } @@ -125,7 +125,11 @@ contract DAppIntegration { delete dapps[key]; } - function initializeNonce(address account) public { + function initializeNonce(address account) external { + _initializeNonce(account); + } + + function _initializeNonce(address account) internal returns (bool initialized) { if (asyncNonceBitIndex[account].LowestEmptyBitmap == uint128(0)) { unchecked { asyncNonceBitIndex[account].LowestEmptyBitmap = 2; @@ -134,6 +138,7 @@ contract DAppIntegration { // to skip the 0 nonce asyncNonceBitmap[bitmapKey] = NonceBitmap({ highestUsedNonce: uint8(1), bitmap: 0 }); + initialized = true; } } diff --git a/src/contracts/interfaces/IAtlasFactory.sol b/src/contracts/interfaces/IAtlasFactory.sol index 268694fd1..7d9e44b43 100644 --- a/src/contracts/interfaces/IAtlasFactory.sol +++ b/src/contracts/interfaces/IAtlasFactory.sol @@ -1,6 +1,9 @@ //SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.21; +import { DAppConfig } from "src/contracts/types/DAppApprovalTypes.sol"; +import { UserOperation } from "../types/UserCallTypes.sol"; + interface IAtlasFactory { function createExecutionEnvironment( address account, @@ -9,6 +12,10 @@ interface IAtlasFactory { external returns (address executionEnvironment); + function getOrCreateExecutionEnvironment(UserOperation calldata userOp) + external + returns (address executionEnvironment, DAppConfig memory dConfig); + function getExecutionEnvironment( address user, address dAppControl diff --git a/test/MainTest.t.sol b/test/MainTest.t.sol index ebbc5d460..97f1085b5 100644 --- a/test/MainTest.t.sol +++ b/test/MainTest.t.sol @@ -331,6 +331,38 @@ contract MainTest is BaseTest { ); } + function testExecutionEnvironmentAutoCreation() public { + uint8 v; + bytes32 r; + bytes32 s; + + UserOperation memory userOp = helper.buildUserOperation(POOL_ONE, POOL_TWO, userEOA, TOKEN_ONE); + // User does not sign their own operation when bundling + + SolverOperation[] memory solverOps = new SolverOperation[](1); + bytes memory solverOpData = helper.buildV2SolverOperationData(POOL_TWO, POOL_ONE); + solverOps[0] = helper.buildSolverOperation(userOp, solverOpData, solverOneEOA, address(solverOne), 2e17); + (v, r, s) = vm.sign(solverOnePK, atlasVerification.getSolverPayload(solverOps[0])); + solverOps[0].signature = abi.encodePacked(r, s, v); + + DAppOperation memory dAppOp = helper.buildDAppOperation(governanceEOA, userOp, solverOps); + (v, r, s) = vm.sign(governancePK, atlasVerification.getDAppOperationPayload(dAppOp)); + dAppOp.signature = abi.encodePacked(r, s, v); + + // Execution environment should not exist yet + (,, bool exists) = atlasFactory.getExecutionEnvironment(userEOA, address(control)); + assertFalse(exists, "ExecutionEnvironment already exists"); + + vm.startPrank(userEOA); + ERC20(TOKEN_ONE).approve(address(atlas), type(uint256).max); + atlas.metacall(userOp, solverOps, dAppOp); + vm.stopPrank(); + + // Execution environment should exist now + (,, exists) = atlasFactory.getExecutionEnvironment(userEOA, address(control)); + assertTrue(exists, "ExecutionEnvironment wasn't created"); + } + function testTestUserOperation() public { uint8 v; bytes32 r;