From 8d735eafccfbfbd64884206cd64f5985ae95ff41 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Thu, 26 Oct 2023 19:32:23 +0200 Subject: [PATCH 01/38] Start refactoring errors to custom errs --- src/contracts/atlas/AtlETH.sol | 5 ++++- src/contracts/atlas/Atlas.sol | 8 ++++---- src/contracts/atlas/Escrow.sol | 5 ++--- src/contracts/atlas/SafetyLocks.sol | 4 +++- src/contracts/types/Emissions.sol | 16 ++++++++++++++++ 5 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/contracts/atlas/AtlETH.sol b/src/contracts/atlas/AtlETH.sol index a0ba7274..8d837fde 100644 --- a/src/contracts/atlas/AtlETH.sol +++ b/src/contracts/atlas/AtlETH.sol @@ -94,7 +94,10 @@ abstract contract AtlETH is Permit69 { // Redeem atlETH for ETH. function withdraw(uint256 amount) external onlyWhenUnlocked tokenTransferChecks(msg.sender) { - require(_escrowAccountData[msg.sender].balance >= amount, "ERR-E078 InsufficientBalance"); + if (_escrowAccountData[msg.sender].balance < amount){ + revert InsufficientBalance(); + } + _burn(msg.sender, amount); SafeTransferLib.safeTransferETH(msg.sender, amount); } diff --git a/src/contracts/atlas/Atlas.sol b/src/contracts/atlas/Atlas.sol index d89303c7..633951b4 100644 --- a/src/contracts/atlas/Atlas.sol +++ b/src/contracts/atlas/Atlas.sol @@ -117,7 +117,7 @@ contract Atlas is Test, Factory { key = key.holdPreOpsLock(dConfig.to); (callSuccessful, returnData) = _executePreOpsCall(userOp, executionEnvironment, key.pack()); if (!callSuccessful) { - if (key.isSimulation) { revert PreOpsSimFail(); } else { revert("ERR-E001 PreOpsFail"); } + if (key.isSimulation) { revert PreOpsSimFail(); } else { revert PreOpsFail(); } } } @@ -126,7 +126,7 @@ contract Atlas is Test, Factory { bytes memory userReturnData; (callSuccessful, userReturnData) = _executeUserOperation(userOp, executionEnvironment, key.pack()); if (!callSuccessful) { - if (key.isSimulation) { revert UserOpSimFail(); } else { revert("ERR-E002 UserFail"); } + if (key.isSimulation) { revert UserOpSimFail(); } else { revert UserOpFail(); } } if (CallBits.needsPreOpsReturnData(dConfig.callConfig)) { @@ -164,7 +164,7 @@ contract Atlas is Test, Factory { key = key.holdDAppOperationLock(address(this)); callSuccessful = _executePostOpsCall(returnData, executionEnvironment, key.pack()); if (!callSuccessful) { - if (key.isSimulation) { revert PostOpsSimFail(); } else { revert("ERR-E005 PostOpsFail"); } + if (key.isSimulation) { revert PostOpsSimFail(); } else { revert PostOpsFail(); } } } return (auctionWon, uint256(key.gasRefund), winningSearcherIndex); @@ -353,7 +353,7 @@ contract Atlas is Test, Factory { revert UserNotFulfilled(); } if (callConfig.allowsReuseUserOps()) { - revert("ERR-F07 RevertToReuse"); + revert RevertToReuse(); } } } diff --git a/src/contracts/atlas/Escrow.sol b/src/contracts/atlas/Escrow.sol index f1c275df..280e2ff9 100644 --- a/src/contracts/atlas/Escrow.sol +++ b/src/contracts/atlas/Escrow.sol @@ -15,7 +15,6 @@ import "../types/UserCallTypes.sol"; import {DAppConfig} from "../types/DAppApprovalTypes.sol"; import "../types/EscrowTypes.sol"; import "../types/LockTypes.sol"; -import {FastLaneErrorsEvents} from "../types/Emissions.sol"; import {EscrowBits} from "../libraries/EscrowBits.sol"; import {CallBits} from "../libraries/CallBits.sol"; @@ -23,7 +22,7 @@ import {SafetyBits} from "../libraries/SafetyBits.sol"; import "forge-std/Test.sol"; -abstract contract Escrow is AtlETH, DAppVerification, FastLaneErrorsEvents { +abstract contract Escrow is AtlETH, DAppVerification { using ECDSA for bytes32; using EscrowBits for uint256; using CallBits for uint32; @@ -182,7 +181,7 @@ abstract contract Escrow is AtlETH, DAppVerification, FastLaneErrorsEvents { } else if (result & EscrowBits._NO_USER_REFUND != 0) { // pass } else { - revert("ERR-SE72 UncoveredResult"); + revert UncoveredResult(); } if (gasRebate != 0) { diff --git a/src/contracts/atlas/SafetyLocks.sol b/src/contracts/atlas/SafetyLocks.sol index 60088c09..cfe22c65 100644 --- a/src/contracts/atlas/SafetyLocks.sol +++ b/src/contracts/atlas/SafetyLocks.sol @@ -12,9 +12,11 @@ import "../types/EscrowTypes.sol"; import "../types/LockTypes.sol"; +import {FastLaneErrorsEvents} from "../types/Emissions.sol"; + import "forge-std/Test.sol"; -contract SafetyLocks { +contract SafetyLocks is FastLaneErrorsEvents { using SafetyBits for EscrowKey; using CallBits for uint32; using PartyMath for Party; diff --git a/src/contracts/types/Emissions.sol b/src/contracts/types/Emissions.sol index f0dafaa7..c49cf983 100644 --- a/src/contracts/types/Emissions.sol +++ b/src/contracts/types/Emissions.sol @@ -57,6 +57,22 @@ contract FastLaneErrorsEvents { error ValidCalls(ValidCallsResult); + // NEW Custom Errors to replace string errors + + // NEW - Atlas + error PreOpsFail(); + error UserOpFail(); + // error SolverFail(); // Only sim version of err is used + error PostOpsFail(); + error RevertToReuse(); + + // NEW - Escrow + error UncoveredResult(); + + // NEW - AtlETH + error InsufficientBalance(); + + /* event NewDAppIntegration( address indexed environment, From c77005d04e0c4f3697d675dfff5d2e29d94c1133 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Thu, 26 Oct 2023 19:56:25 +0200 Subject: [PATCH 02/38] Refactor DAppIntegration to custom errors --- src/contracts/atlas/DAppIntegration.sol | 24 +++++++++++++++--------- src/contracts/types/Emissions.sol | 8 ++++++++ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/contracts/atlas/DAppIntegration.sol b/src/contracts/atlas/DAppIntegration.sol index b43a9a30..a964d324 100644 --- a/src/contracts/atlas/DAppIntegration.sol +++ b/src/contracts/atlas/DAppIntegration.sol @@ -7,6 +7,8 @@ import {CallBits} from "../libraries/CallBits.sol"; import "../types/GovernanceTypes.sol"; +import {FastLaneErrorsEvents} from "../types/Emissions.sol"; + contract DAppIntegration { using CallBits for uint32; @@ -53,11 +55,11 @@ contract DAppIntegration { function initializeGovernance(address controller) external { address govAddress = IDAppControl(controller).getDAppSignatory(); - require(msg.sender == govAddress, "ERR-V50 OnlyGovernance"); + if(msg.sender != govAddress) revert FastLaneErrorsEvents.OnlyGovernance(); bytes32 signatoryKey = keccak256(abi.encode(msg.sender, msg.sender)); - require(!signatories[signatoryKey], "ERR-V49 OwnerActive"); + if(signatories[signatoryKey]) revert FastLaneErrorsEvents.OwnerActive(); uint32 callConfig = CallBits.buildCallConfig(controller); @@ -73,11 +75,13 @@ contract DAppIntegration { function addSignatory(address controller, address signatory) external { GovernanceData memory govData = governance[controller]; - require(msg.sender == govData.governance, "ERR-V50 OnlyGovernance"); + if(msg.sender != govData.governance) revert FastLaneErrorsEvents.OnlyGovernance(); bytes32 signatoryKey = keccak256(abi.encode(msg.sender, signatory)); - require(!signatories[signatoryKey], "ERR-V49 SignatoryActive"); + if(signatories[signatoryKey]){ + revert FastLaneErrorsEvents.SignatoryActive(); + } signatories[signatoryKey] = true; @@ -94,11 +98,13 @@ contract DAppIntegration { function removeSignatory(address controller, address signatory) external { GovernanceData memory govData = governance[controller]; - require(msg.sender == govData.governance || msg.sender == signatory, "ERR-V51 InvalidCaller"); + if(msg.sender != govData.governance && msg.sender != signatory){ + revert FastLaneErrorsEvents.InvalidCaller(); + } bytes32 signatoryKey = keccak256(abi.encode(msg.sender, signatory)); - require(signatories[signatoryKey], "ERR-V52 InvalidDAppControl"); + if(!signatories[signatoryKey]) revert FastLaneErrorsEvents.InvalidDAppControl(); delete signatories[signatoryKey]; } @@ -106,7 +112,7 @@ contract DAppIntegration { function integrateDApp(address dAppControl) external { GovernanceData memory govData = governance[dAppControl]; - require(msg.sender == govData.governance, "ERR-V50 OnlyGovernance"); + if(msg.sender != govData.governance) revert FastLaneErrorsEvents.OnlyGovernance(); bytes32 key = keccak256(abi.encode(dAppControl, govData.governance, govData.callConfig)); @@ -123,7 +129,7 @@ contract DAppIntegration { function disableDApp(address dAppControl) external { GovernanceData memory govData = governance[dAppControl]; - require(msg.sender == govData.governance, "ERR-V50 OnlyGovernance"); + if(msg.sender != govData.governance) revert FastLaneErrorsEvents.OnlyGovernance(); bytes32 key = keccak256(abi.encode(dAppControl, govData.governance, govData.callConfig)); @@ -147,7 +153,7 @@ contract DAppIntegration { function getGovFromControl(address dAppControl) external view returns (address governanceAddress) { GovernanceData memory govData = governance[dAppControl]; - require(govData.lastUpdate != uint64(0), "ERR-V52 DAppNotEnabled"); + if(govData.lastUpdate == 0) revert FastLaneErrorsEvents.DAppNotEnabled(); governanceAddress = govData.governance; } } diff --git a/src/contracts/types/Emissions.sol b/src/contracts/types/Emissions.sol index c49cf983..e456e3e4 100644 --- a/src/contracts/types/Emissions.sol +++ b/src/contracts/types/Emissions.sol @@ -71,6 +71,14 @@ contract FastLaneErrorsEvents { // NEW - AtlETH error InsufficientBalance(); + + // NEW - DAppIntegration + error OnlyGovernance(); + error OwnerActive(); + error SignatoryActive(); + error InvalidCaller(); + error InvalidDAppControl(); + error DAppNotEnabled(); /* From 72124b96e6715b7a610d2e247d42ec2bc0acf859 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Thu, 26 Oct 2023 20:25:00 +0200 Subject: [PATCH 03/38] Refactored AtlETH to custom errors --- src/contracts/atlas/AtlETH.sol | 13 ++++++------- src/contracts/types/Emissions.sol | 3 +++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/contracts/atlas/AtlETH.sol b/src/contracts/atlas/AtlETH.sol index 8d837fde..e7fa9616 100644 --- a/src/contracts/atlas/AtlETH.sol +++ b/src/contracts/atlas/AtlETH.sol @@ -71,7 +71,9 @@ abstract contract AtlETH is Permit69 { // Interactions (transfers, withdrawals) are allowed only after the owner last interaction // with Atlas was at least `escrowDuration` blocks ago. modifier tokenTransferChecks(address account) { - require(_escrowAccountData[account].lastAccessed + escrowDuration < block.number, "EscrowActive"); + if(block.number <= _escrowAccountData[account].lastAccessed + escrowDuration) { + revert EscrowLockActive(); + } _; } @@ -94,10 +96,7 @@ abstract contract AtlETH is Permit69 { // Redeem atlETH for ETH. function withdraw(uint256 amount) external onlyWhenUnlocked tokenTransferChecks(msg.sender) { - if (_escrowAccountData[msg.sender].balance < amount){ - revert InsufficientBalance(); - } - + if (_escrowAccountData[msg.sender].balance < amount) revert InsufficientBalance(); _burn(msg.sender, amount); SafeTransferLib.safeTransferETH(msg.sender, amount); } @@ -139,7 +138,7 @@ abstract contract AtlETH is Permit69 { function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public { - require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); + if(deadline < block.timestamp) revert PermitDeadlineExpired(); // Unchecked because the only math done is incrementing // the owner's nonce which cannot realistically overflow. unchecked { @@ -166,7 +165,7 @@ abstract contract AtlETH is Permit69 { r, s ); - require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); + if(recoveredAddress == address(0) || recoveredAddress != owner) revert InvalidSigner(); allowance[recoveredAddress][spender] = value; } emit Approval(owner, spender, value); diff --git a/src/contracts/types/Emissions.sol b/src/contracts/types/Emissions.sol index e456e3e4..043db5d1 100644 --- a/src/contracts/types/Emissions.sol +++ b/src/contracts/types/Emissions.sol @@ -71,6 +71,9 @@ contract FastLaneErrorsEvents { // NEW - AtlETH error InsufficientBalance(); + error PermitDeadlineExpired(); + error InvalidSigner(); + error EscrowLockActive(); // NEW - DAppIntegration error OnlyGovernance(); From 96e32993e47ffcd6e2c18f08d0126553f8666f82 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Thu, 26 Oct 2023 20:40:24 +0200 Subject: [PATCH 04/38] Refactor Permit69 to custom errors --- src/contracts/common/Permit69.sol | 15 ++++++++------- src/contracts/types/Emissions.sol | 5 +++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/contracts/common/Permit69.sol b/src/contracts/common/Permit69.sol index d6c38e06..a186099b 100644 --- a/src/contracts/common/Permit69.sol +++ b/src/contracts/common/Permit69.sol @@ -83,7 +83,7 @@ abstract contract Permit69 is GasAccounting { function requestGasFrom(Party donor, Party recipient, uint256 amt, uint16 lockState) external { // Verify the parties - require(_validParties(msg.sender, donor, recipient), "ERR-T003 InvalidEnvironment"); + if(!_validParties(msg.sender, donor, recipient)) revert InvalidEnvironment(); // Verify the lock state _verifyLockState({ @@ -96,7 +96,7 @@ abstract contract Permit69 is GasAccounting { function contributeGasTo(Party donor, Party recipient, uint256 amt, uint16 lockState) external { // Verify the parties - require(_validParties(msg.sender, donor, recipient), "ERR-T004 InvalidEnvironment"); + if(!_validParties(msg.sender, donor, recipient)) revert InvalidEnvironment(); // Verify the lock state _verifyLockState({ @@ -112,14 +112,15 @@ abstract contract Permit69 is GasAccounting { address controller, uint32 callConfig ) internal view { - require( - msg.sender == _getExecutionEnvironmentCustom(user, controller.codehash, controller, callConfig), - "ERR-T001 EnvironmentMismatch" - ); + if(msg.sender != _getExecutionEnvironmentCustom(user, controller.codehash, controller, callConfig)){ + revert EnvironmentMismatch(); + } } function _verifyLockState(uint16 lockState, uint16 safeExecutionPhaseSet) internal pure { - require(lockState & safeExecutionPhaseSet != 0, "ERR-T002 InvalidLockState"); + if(lockState & safeExecutionPhaseSet == 0){ + revert InvalidLockState(); + } // TODO: Do we need the below require? // Intuition is that we'd need to block all reentry into EE to bypass this check // require(msg.sender == activeEnvironment, "ERR-T003 EnvironmentNotActive"); diff --git a/src/contracts/types/Emissions.sol b/src/contracts/types/Emissions.sol index 043db5d1..49f45a25 100644 --- a/src/contracts/types/Emissions.sol +++ b/src/contracts/types/Emissions.sol @@ -82,6 +82,11 @@ contract FastLaneErrorsEvents { error InvalidCaller(); error InvalidDAppControl(); error DAppNotEnabled(); + + // NEW - Permit69 + error InvalidEnvironment(); + error EnvironmentMismatch(); + error InvalidLockState(); /* From ed7dd00a04bf61d0d41b004ea351f3a2beb971a4 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Thu, 26 Oct 2023 20:46:00 +0200 Subject: [PATCH 05/38] Refactored SafetyLocks to custom errors --- src/contracts/atlas/SafetyLocks.sol | 4 ++-- src/contracts/types/Emissions.sol | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/contracts/atlas/SafetyLocks.sol b/src/contracts/atlas/SafetyLocks.sol index cfe22c65..7ed550f4 100644 --- a/src/contracts/atlas/SafetyLocks.sol +++ b/src/contracts/atlas/SafetyLocks.sol @@ -136,7 +136,7 @@ contract SafetyLocks is FastLaneErrorsEvents { ) internal view returns (EscrowKey memory self) { // TODO: can we bypass this check? - require(lock.activeEnvironment == executionEnvironment, "ERR-SL004 NotInitialized"); + if(lock.activeEnvironment != executionEnvironment) revert NotInitialized(); self = self.initializeEscrowLock( dConfig.callConfig.needsPreOpsCall(), solverOpCount, executionEnvironment, isSimulation @@ -172,7 +172,7 @@ contract SafetyLocks is FastLaneErrorsEvents { } modifier onlyWhenUnlocked() { - require(lock.activeEnvironment == UNLOCKED, "ERR-SL003 AlreadyInitialized"); + if(lock.activeEnvironment != UNLOCKED) revert AlreadyInitialized(); _; } diff --git a/src/contracts/types/Emissions.sol b/src/contracts/types/Emissions.sol index 49f45a25..34329332 100644 --- a/src/contracts/types/Emissions.sol +++ b/src/contracts/types/Emissions.sol @@ -87,6 +87,14 @@ contract FastLaneErrorsEvents { error InvalidEnvironment(); error EnvironmentMismatch(); error InvalidLockState(); + + // NEW - GasAccounting + // TODO check with thogard if we need unique IDs for each LedgerFinalized revert + + // NEW - SafetyLocks + error NotInitialized(); + error AlreadyInitialized(); + /* From aec732c099cbc2fa8086d6c62d11377c0ac9ad79 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Fri, 27 Oct 2023 15:48:13 +0200 Subject: [PATCH 06/38] Refactored GasAcc to custom errors --- src/contracts/atlas/GasAccounting.sol | 44 ++++++++++++++------------- src/contracts/types/Emissions.sol | 9 ++++-- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/contracts/atlas/GasAccounting.sol b/src/contracts/atlas/GasAccounting.sol index 92025aa3..b38d4cd4 100644 --- a/src/contracts/atlas/GasAccounting.sol +++ b/src/contracts/atlas/GasAccounting.sol @@ -29,7 +29,7 @@ abstract contract GasAccounting is SafetyLocks { (Ledger memory partyLedger, uint256 partyIndex) = _getLedger(party); - require(partyLedger.status != LedgerStatus.Finalized, "ERR-GA002, LedgerFinalized"); + if (partyLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(1); int64 depositAmount = int64(uint64(amt / tx.gasprice)); @@ -46,7 +46,7 @@ abstract contract GasAccounting is SafetyLocks { (Ledger memory partyLedger, uint256 partyIndex) = _getLedger(party); - require(uint256(partyLedger.status) < uint256(LedgerStatus.Balancing), "ERR-GA003a, LedgerFinalized"); + if(partyLedger.status >= LedgerStatus.Borrowing) revert LedgerFinalized(2); int64 borrowAmount = int64(uint64(amt / tx.gasprice))+1; partyLedger.balance -= borrowAmount; @@ -61,8 +61,8 @@ abstract contract GasAccounting is SafetyLocks { (Ledger memory partyLedger, uint256 partyIndex) = _getLedger(party); - require(partyLedger.status == LedgerStatus.Borrowing, "ERR-GA003b, LedgerFinalized"); - + if(partyLedger.status != LedgerStatus.Borrowing) revert LedgerFinalized(3); + int64 borrowAmount = int64(uint64(amt / tx.gasprice))+1; partyLedger.balance += borrowAmount; partyLedger.status = LedgerStatus.Active; @@ -74,7 +74,7 @@ abstract contract GasAccounting is SafetyLocks { (Ledger memory partyLedger, uint256 partyIndex) = _getLedger(party); - require(uint256(partyLedger.status) < uint256(LedgerStatus.Balancing), "ERR-GA004, LedgerBalancing"); + if(partyLedger.status >= LedgerStatus.Balancing) revert LedgerBalancing(1); int64 amount = int64(uint64(amt / tx.gasprice))+1; @@ -121,7 +121,7 @@ abstract contract GasAccounting is SafetyLocks { return; } - revert("ERR-GA022 InsufficientFunds"); + revert InsufficientFunds(); } function _requestFrom(Party donor, Party recipient, uint256 amt) internal { @@ -129,10 +129,10 @@ abstract contract GasAccounting is SafetyLocks { // We need to add a phase check to verify this. (Ledger memory donorLedger, uint256 donorIndex) = _getLedger(donor); - require(uint256(donorLedger.status) < uint256(LedgerStatus.Balancing), "ERR-GA004, LedgerBalancing"); + if(donorLedger.status >= LedgerStatus.Balancing) revert LedgerBalancing(2); (Ledger memory recipientLedger, uint256 recipientIndex) = _getLedger(recipient); - require(recipientLedger.status != LedgerStatus.Finalized, "ERR-GA005, LedgerFinalized"); + if(recipientLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(4); int64 amount = int64(uint64(amt / tx.gasprice)); @@ -146,10 +146,10 @@ abstract contract GasAccounting is SafetyLocks { function _contributeTo(Party donor, Party recipient, uint256 amt) internal { (Ledger memory donorLedger, uint256 donorIndex) = _getLedger(donor); - require(donorLedger.status != LedgerStatus.Finalized, "ERR-GA006, LedgerFinalized"); + if(donorLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(5); (Ledger memory recipientLedger, uint256 recipientIndex) = _getLedger(recipient); - require(recipientLedger.status != LedgerStatus.Finalized, "ERR-GA007, LedgerFinalized"); + if(recipientLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(6); int64 amount = int64(uint64(amt / tx.gasprice)); @@ -277,7 +277,7 @@ abstract contract GasAccounting is SafetyLocks { Ledger memory partyLedger = ledgers[i]; - require(partyLedger.contributed >= 0, "ERR-GA099 NoUnfilledRequests"); + if(partyLedger.contributed < 0) revert NoUnfilledRequests(); // Only tally totals from non-proxies if (uint256(partyLedger.proxy) == i) { @@ -320,7 +320,7 @@ abstract contract GasAccounting is SafetyLocks { totalContributions -= gasRemainder; totalBalanceDelta += gasRebate; - require(totalRequests + totalContributions >= 0, "ERR-GA014a MissingFunds"); + if(totalRequests + totalContributions < 0) revert MissingFunds(1); // TODO: Adjust to accomodate the direction of rounding errors. int64 atlasBalanceDelta = int64(uint64(address(this).balance / tx.gasprice)) - int64(startingGasBal); @@ -341,7 +341,7 @@ abstract contract GasAccounting is SafetyLocks { } if (atlasBalanceDelta < totalBalanceDelta + totalContributions + totalRequests) { - revert("ERR-GA014b MissingFunds"); + revert MissingFunds(2); } return (parties, totalRequests, totalContributions, totalBalanceDelta); @@ -804,13 +804,14 @@ abstract contract GasAccounting is SafetyLocks { } function contribute(Party recipient) external payable { - require(_validParty(msg.sender, recipient), "ERR-GA020 InvalidEnvironment"); + if(!_validParty(msg.sender, recipient)) revert InvalidEnvironment(); int64 amount = int64(uint64((msg.value) / tx.gasprice)); (Ledger memory partyLedger, uint256 pIndex) = _getLedger(recipient); - require(partyLedger.status != LedgerStatus.Finalized, "ERR-GA021, LedgerFinalized"); + if(partyLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(7); + if (partyLedger.status == LedgerStatus.Inactive) partyLedger.status = LedgerStatus.Active; if (partyLedger.requested < 0) { @@ -832,14 +833,15 @@ abstract contract GasAccounting is SafetyLocks { } function deposit(Party party) external payable { - require(_validParty(msg.sender, party), "ERR-GA022 InvalidEnvironment"); + if(!_validParty(msg.sender, party)) revert InvalidEnvironment(); int64 amount = int64(uint64((msg.value) / tx.gasprice)); uint256 pIndex = uint256(party); Ledger memory partyLedger = ledgers[pIndex]; - require(partyLedger.status != LedgerStatus.Finalized, "ERR-GA023 LedgerFinalized"); + if(partyLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(8); + if (partyLedger.status == LedgerStatus.Inactive) partyLedger.status = LedgerStatus.Active; partyLedger.balance += amount; @@ -851,18 +853,18 @@ abstract contract GasAccounting is SafetyLocks { // the approved amounts will only lead to a revert. // Bundlers must make sure the DApp hasn't maliciously upgraded their contract to avoid wasting gas. function contributeTo(Party donor, Party recipient, uint256 amt) external { - require(_validParties(msg.sender, donor, recipient), "ERR-GA021 InvalidEnvironment"); + if(!_validParties(msg.sender, donor, recipient)) revert InvalidEnvironment(); _contributeTo(donor, recipient, amt); } function requestFrom(Party donor, Party recipient, uint256 amt) external { - require(_validParties(msg.sender, donor, recipient), "ERR-GA022 InvalidEnvironment"); + if(!_validParties(msg.sender, donor, recipient)) revert InvalidEnvironment(); _requestFrom(donor, recipient, amt); } function finalize(Party party, address partyAddress) external returns (bool) { - require(_validParty(msg.sender, party), "ERR-GA024 InvalidEnvironment"); - require(party != Party.Solver, "ERR-GA025 SolverMustReconcile"); + if(!_validParty(msg.sender, party)) revert InvalidEnvironment(); + if(party == Party.Solver) revert SolverMustReconcile(); uint256 pIndex = uint256(party); Ledger memory partyLedger = ledgers[pIndex]; diff --git a/src/contracts/types/Emissions.sol b/src/contracts/types/Emissions.sol index 34329332..d85b6b4e 100644 --- a/src/contracts/types/Emissions.sol +++ b/src/contracts/types/Emissions.sol @@ -89,8 +89,13 @@ contract FastLaneErrorsEvents { error InvalidLockState(); // NEW - GasAccounting - // TODO check with thogard if we need unique IDs for each LedgerFinalized revert - + error LedgerFinalized(uint8 id); + error LedgerBalancing(uint8 id); + error MissingFunds(uint8 id); + error InsufficientFunds(); + error NoUnfilledRequests(); + error SolverMustReconcile(); + // NEW - SafetyLocks error NotInitialized(); error AlreadyInitialized(); From c84168e5abbfeb94984595e0e2ea6267957dd86f Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Fri, 27 Oct 2023 17:03:20 +0200 Subject: [PATCH 07/38] Start planning contract split: AtlETH and Factory --- src/contracts/atlas/AtlETH2.sol | 15 +++++++++++++++ src/contracts/atlas/AtlasFactory.sol | 13 +++++++++++++ src/contracts/atlas/GasAccounting.sol | 3 --- 3 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 src/contracts/atlas/AtlETH2.sol create mode 100644 src/contracts/atlas/AtlasFactory.sol diff --git a/src/contracts/atlas/AtlETH2.sol b/src/contracts/atlas/AtlETH2.sol new file mode 100644 index 00000000..f28934ba --- /dev/null +++ b/src/contracts/atlas/AtlETH2.sol @@ -0,0 +1,15 @@ +//SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.18; + + +// NOTE: Experimental - Splitting contracts into [AtlETH, Atlas, AtlasFactory] + +// AtlETH needs: +// ERC20 stuff - for the AtlETH token +// Permit2 integration - for external AtlETH use +// Permit69 - for internal approval between Atlas, Exec Envs, etc +// Escrow - locked down during phases of Atlas execution, or time locked +// GasAccounting - maybe?? Maybe in Atlas if msg.value needed +contract AtlETH2 { + +} \ No newline at end of file diff --git a/src/contracts/atlas/AtlasFactory.sol b/src/contracts/atlas/AtlasFactory.sol new file mode 100644 index 00000000..ce67d497 --- /dev/null +++ b/src/contracts/atlas/AtlasFactory.sol @@ -0,0 +1,13 @@ +//SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.18; + + +// NOTE: Experimental - Splitting contracts into [AtlETH, Atlas, AtlasFactory] + +// AtlasFactory needs: +// Factory - everything for creating new Execution Environments +// Exec Env template deployed separately, no internal deploy functions + +contract AtlasFactory { + +} \ No newline at end of file diff --git a/src/contracts/atlas/GasAccounting.sol b/src/contracts/atlas/GasAccounting.sol index b38d4cd4..3a67ce70 100644 --- a/src/contracts/atlas/GasAccounting.sol +++ b/src/contracts/atlas/GasAccounting.sol @@ -1,8 +1,6 @@ //SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.16; -import {SafeTransferLib, ERC20} from "solmate/utils/SafeTransferLib.sol"; - import {SafetyLocks} from "../atlas/SafetyLocks.sol"; import "../types/EscrowTypes.sol"; @@ -15,7 +13,6 @@ import {PartyMath, LEDGER_LENGTH} from "../libraries/GasParties.sol"; import "forge-std/Test.sol"; abstract contract GasAccounting is SafetyLocks { - using SafeTransferLib for ERC20; using PartyMath for Party; using PartyMath for uint256; using PartyMath for Ledger[LEDGER_LENGTH]; From 6e40e34f78bd7443c8ef3fffed739f274d3ac57a Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Mon, 30 Oct 2023 17:16:06 +0200 Subject: [PATCH 08/38] Split out Factory to AtlasFactory --- src/contracts/atlas/AtlasFactory.sol | 114 +++++++++++++++++++++++++++ src/contracts/types/Emissions.sol | 1 + 2 files changed, 115 insertions(+) diff --git a/src/contracts/atlas/AtlasFactory.sol b/src/contracts/atlas/AtlasFactory.sol index ce67d497..de38d9bd 100644 --- a/src/contracts/atlas/AtlasFactory.sol +++ b/src/contracts/atlas/AtlasFactory.sol @@ -1,6 +1,8 @@ //SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.18; +import {IDAppControl} from "../interfaces/IDAppControl.sol"; +import {Mimic} from "./Mimic.sol"; // NOTE: Experimental - Splitting contracts into [AtlETH, Atlas, AtlasFactory] @@ -9,5 +11,117 @@ pragma solidity ^0.8.18; // Exec Env template deployed separately, no internal deploy functions contract AtlasFactory { + event NewExecutionEnvironment( + address indexed environment, + address indexed user, + address indexed controller, + uint32 callConfig + ); + + bytes32 public immutable salt; + address public immutable executionTemplate; + + constructor(address _executionTemplate) { + salt = keccak256(abi.encodePacked(block.chainid, address(this), "AtlasFactory 1.0")); + executionTemplate = _executionTemplate; + } + + // ------------------ // + // EXTERNAL FUNCTIONS // + // ------------------ // + + function createExecutionEnvironment(address dAppControl) external returns (address executionEnvironment) { + executionEnvironment = _setExecutionEnvironment(dAppControl, msg.sender, dAppControl.codehash); + // _initializeNonce(msg.sender); // NOTE: called separately by Atlas after calling createExecEnv + } + + function getMimicCreationCode(address controller, uint32 callConfig, address user, bytes32 controlCodeHash) + external + view + returns (bytes memory creationCode) + { + creationCode = _getMimicCreationCode(controller, callConfig, user, controlCodeHash); + } + + // ------------------ // + // INTERNAL FUNCTIONS // + // ------------------ // + + function _setExecutionEnvironment(address dAppControl, address user, bytes32 controlCodeHash) + internal + returns (address executionEnvironment) + { + uint32 callConfig = IDAppControl(dAppControl).callConfig(); + + bytes memory creationCode = _getMimicCreationCode(dAppControl, callConfig, user, controlCodeHash); + + executionEnvironment = address( + uint160( + uint256( + keccak256( + abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(abi.encodePacked(creationCode))) + ) + ) + ) + ); + + if (executionEnvironment.codehash == bytes32(0)) { + bytes32 memSalt = salt; + assembly { + executionEnvironment := create2(0, add(creationCode, 32), mload(creationCode), memSalt) + } + + emit NewExecutionEnvironment(executionEnvironment, user, dAppControl, callConfig); + } + } + + + function _getMimicCreationCode(address controller, uint32 callConfig, address user, bytes32 controlCodeHash) + internal + view + returns (bytes memory creationCode) + { + address executionLib = executionTemplate; + // NOTE: Changing compiler settings or solidity versions can break this. + creationCode = type(Mimic).creationCode; + + // TODO: unpack the SHL and reorient + assembly { + mstore( + add(creationCode, 85), + or( + and( + mload(add(creationCode, 85)), + not(shl(96, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) + ), + shl(96, executionLib) + ) + ) + + mstore( + add(creationCode, 118), + or( + and( + mload(add(creationCode, 118)), + not(shl(96, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) + ), + shl(96, user) + ) + ) + + mstore( + add(creationCode, 139), + or( + and( + mload(add(creationCode, 139)), + not(shl(56, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFF)) + ), + add(shl(96, controller), add(shl(88, 0x63), shl(56, callConfig))) + ) + ) + + mstore(add(creationCode, 165), controlCodeHash) + } + } } \ No newline at end of file diff --git a/src/contracts/types/Emissions.sol b/src/contracts/types/Emissions.sol index d85b6b4e..a99cbc95 100644 --- a/src/contracts/types/Emissions.sol +++ b/src/contracts/types/Emissions.sol @@ -24,6 +24,7 @@ contract FastLaneErrorsEvents { address indexed controller, uint32 callConfig, address bidToken, uint256 bidAmount ); + // TODO remove after AtlasFactory split-out event NewExecutionEnvironment( address indexed environment, address indexed user, From 97d24dba0e5755672636c2a6db6f2fe9d34f53b4 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Mon, 30 Oct 2023 17:38:48 +0200 Subject: [PATCH 09/38] More AtlasFactory progress --- src/contracts/atlas/AtlasFactory.sol | 20 ++++++++++++++++++-- test/base/BaseTest.t.sol | 14 +++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/contracts/atlas/AtlasFactory.sol b/src/contracts/atlas/AtlasFactory.sol index de38d9bd..8f1c3e5f 100644 --- a/src/contracts/atlas/AtlasFactory.sol +++ b/src/contracts/atlas/AtlasFactory.sol @@ -3,6 +3,8 @@ pragma solidity ^0.8.18; import {IDAppControl} from "../interfaces/IDAppControl.sol"; import {Mimic} from "./Mimic.sol"; +import {DAppConfig} from "src/contracts/types/DAppApprovalTypes.sol"; +import {ExecutionEnvironment} from "./ExecutionEnvironment.sol"; // NOTE: Experimental - Splitting contracts into [AtlETH, Atlas, AtlasFactory] @@ -20,10 +22,13 @@ contract AtlasFactory { bytes32 public immutable salt; address public immutable executionTemplate; + address public immutable atlas; - constructor(address _executionTemplate) { + constructor(address _executionTemplate, address _atlas) { salt = keccak256(abi.encodePacked(block.chainid, address(this), "AtlasFactory 1.0")); - executionTemplate = _executionTemplate; + atlas = _atlas; + + executionTemplate = _deployExecutionEnvironmentTemplate(_atlas); } // ------------------ // @@ -47,6 +52,17 @@ contract AtlasFactory { // INTERNAL FUNCTIONS // // ------------------ // + function _deployExecutionEnvironmentTemplate(address _atlas) + internal + returns (address executionEnvironment) + { + ExecutionEnvironment _environment = new ExecutionEnvironment{ + salt: salt + }(_atlas); + + executionEnvironment = address(_environment); + } + function _setExecutionEnvironment(address dAppControl, address user, bytes32 controlCodeHash) internal returns (address executionEnvironment) diff --git a/test/base/BaseTest.t.sol b/test/base/BaseTest.t.sol index b2f64209..921958a1 100644 --- a/test/base/BaseTest.t.sol +++ b/test/base/BaseTest.t.sol @@ -3,15 +3,17 @@ pragma solidity ^0.8.18; import "forge-std/Test.sol"; -import {IDAppIntegration} from "../../src/contracts/interfaces/IDAppIntegration.sol"; +import {IDAppIntegration} from "src/contracts/interfaces/IDAppIntegration.sol"; -import {Atlas} from "../../src/contracts/atlas/Atlas.sol"; -import {Sorter} from "../../src/contracts/helpers/Sorter.sol"; -import {Simulator} from "../../src/contracts/helpers/Simulator.sol"; +import {Atlas} from "src/contracts/atlas/Atlas.sol"; +import {AtlasFactory} from "src/contracts/atlas/AtlasFactory.sol"; + +import {Sorter} from "src/contracts/helpers/Sorter.sol"; +import {Simulator} from "src/contracts/helpers/Simulator.sol"; import {Solver} from "src/contracts/solver/src/TestSolver.sol"; -import {V2DAppControl} from "../../src/contracts/examples/v2-example/V2DAppControl.sol"; +import {V2DAppControl} from "src/contracts/examples/v2-example/V2DAppControl.sol"; import {TestConstants} from "./TestConstants.sol"; @@ -35,6 +37,8 @@ contract BaseTest is Test, TestConstants { address public userEOA = vm.addr(userPK); Atlas public atlas; + AtlasFactory public atlasFactory; + Simulator public simulator; Sorter public sorter; From 83ec7c22aeea31ccb2553147f9a7030df19789d4 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Mon, 30 Oct 2023 18:43:12 +0200 Subject: [PATCH 10/38] Add code to precompute Factory deploy address --- script/deploy-atlas.s.sol | 6 +++--- src/contracts/atlas/Atlas.sol | 5 ++++- src/contracts/atlas/AtlasFactory.sol | 4 +++- test/base/BaseTest.t.sol | 10 +++++++++- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/script/deploy-atlas.s.sol b/script/deploy-atlas.s.sol index e4e4de0b..0c23390f 100644 --- a/script/deploy-atlas.s.sol +++ b/script/deploy-atlas.s.sol @@ -26,7 +26,7 @@ contract DeployAtlasScript is DeployBaseScript { vm.startBroadcast(deployerPrivateKey); simulator = new Simulator(); - atlas = new Atlas(64, address(simulator)); + atlas = new Atlas(64, address(simulator), address(0)); //TODO update to Factory addr arg vm.stopBroadcast(); @@ -56,7 +56,7 @@ contract DeployAtlasAndSwapIntentDAppControlScript is DeployBaseScript { // Deploy the Atlas contract simulator = new Simulator(); - atlas = new Atlas(64, address(simulator)); + atlas = new Atlas(64, address(simulator), address(0)); //TODO update to Factory addr arg // Deploy the SwapIntent DAppControl contract swapIntentControl = new SwapIntentController(address(atlas)); @@ -96,7 +96,7 @@ contract DeployAtlasAndSwapIntentDAppControlAndTxBuilderScript is DeployBaseScri // Deploy the Atlas contract simulator = new Simulator(); - atlas = new Atlas(64, address(simulator)); + atlas = new Atlas(64, address(simulator), address(0)); //TODO update to Factory addr arg // Deploy the SwapIntent DAppControl contract swapIntentControl = new SwapIntentController(address(atlas)); diff --git a/src/contracts/atlas/Atlas.sol b/src/contracts/atlas/Atlas.sol index 633951b4..8e90ce29 100644 --- a/src/contracts/atlas/Atlas.sol +++ b/src/contracts/atlas/Atlas.sol @@ -27,8 +27,11 @@ contract Atlas is Test, Factory { using SafetyBits for EscrowKey; uint256 private constant _MAX_GAS = 1_500_000; + address public immutable FACTORY; - constructor(uint32 _escrowDuration, address _simulator) Factory(_escrowDuration, _simulator) {} + constructor(uint32 _escrowDuration, address _simulator, address _factory) Factory(_escrowDuration, _simulator) { + FACTORY = _factory; + } function metacall( // <- Entrypoint Function UserOperation calldata userOp, // set by user diff --git a/src/contracts/atlas/AtlasFactory.sol b/src/contracts/atlas/AtlasFactory.sol index 8f1c3e5f..72d0b85c 100644 --- a/src/contracts/atlas/AtlasFactory.sol +++ b/src/contracts/atlas/AtlasFactory.sol @@ -12,6 +12,8 @@ import {ExecutionEnvironment} from "./ExecutionEnvironment.sol"; // Factory - everything for creating new Execution Environments // Exec Env template deployed separately, no internal deploy functions +// TODO make sure no cases of address(this) when Atlas address is intended + contract AtlasFactory { event NewExecutionEnvironment( address indexed environment, @@ -24,7 +26,7 @@ contract AtlasFactory { address public immutable executionTemplate; address public immutable atlas; - constructor(address _executionTemplate, address _atlas) { + constructor(address _atlas) { salt = keccak256(abi.encodePacked(block.chainid, address(this), "AtlasFactory 1.0")); atlas = _atlas; diff --git a/test/base/BaseTest.t.sol b/test/base/BaseTest.t.sol index 921958a1..eefe3f7f 100644 --- a/test/base/BaseTest.t.sol +++ b/test/base/BaseTest.t.sol @@ -69,7 +69,15 @@ contract BaseTest is Test, TestConstants { simulator = new Simulator(); - atlas = new Atlas(64, address(simulator)); + // Computes the address at which AtlasFactory will be deployed + address expectedAtlasFactoryAddr = computeCreateAddress( + payee, + vm.getNonce(payee) + 1 + ); + + atlas = new Atlas(64, address(simulator), expectedAtlasFactoryAddr); + atlasFactory = new AtlasFactory(address(atlas)); + simulator.setAtlas(address(atlas)); escrow = atlas.getEscrowAddress(); From 64a824c2925323553fba2336036d40476a7c5da1 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Mon, 30 Oct 2023 21:35:26 +0200 Subject: [PATCH 11/38] More AtlasFactory porting and interface added --- src/contracts/atlas/AtlasFactory.sol | 45 ++++++++++++++++++++++ src/contracts/interfaces/IAtlasFactory.sol | 18 +++++++++ 2 files changed, 63 insertions(+) create mode 100644 src/contracts/interfaces/IAtlasFactory.sol diff --git a/src/contracts/atlas/AtlasFactory.sol b/src/contracts/atlas/AtlasFactory.sol index 72d0b85c..bdf75e5a 100644 --- a/src/contracts/atlas/AtlasFactory.sol +++ b/src/contracts/atlas/AtlasFactory.sol @@ -42,6 +42,16 @@ contract AtlasFactory { // _initializeNonce(msg.sender); // NOTE: called separately by Atlas after calling createExecEnv } + function getExecutionEnvironment(address user, address dAppControl) + external + view + returns (address executionEnvironment, uint32 callConfig, bool exists) + { + callConfig = IDAppControl(dAppControl).callConfig(); + executionEnvironment = _getExecutionEnvironmentCustom(user, dAppControl.codehash, dAppControl, callConfig); + exists = executionEnvironment.codehash != bytes32(0); + } + function getMimicCreationCode(address controller, uint32 callConfig, address user, bytes32 controlCodeHash) external view @@ -93,6 +103,41 @@ contract AtlasFactory { } } + function _getExecutionEnvironment(address user, bytes32 controlCodeHash, address controller) + internal + view + returns (address executionEnvironment) + { + uint32 callConfig = IDAppControl(controller).callConfig(); + executionEnvironment = _getExecutionEnvironmentCustom(user, controlCodeHash, controller, callConfig); + } + + // NOTE: This func is used to generate the address of user ExecutionEnvironments that have + // been deprecated due to DAppControl changes of callConfig. + function _getExecutionEnvironmentCustom( + address user, + bytes32 controlCodeHash, + address controller, + uint32 callConfig + ) internal view returns (address executionEnvironment) { + executionEnvironment = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + bytes1(0xff), + address(this), + salt, + keccak256( + abi.encodePacked(_getMimicCreationCode(controller, callConfig, user, controlCodeHash)) + ) + ) + ) + ) + ) + ); + } + function _getMimicCreationCode(address controller, uint32 callConfig, address user, bytes32 controlCodeHash) internal diff --git a/src/contracts/interfaces/IAtlasFactory.sol b/src/contracts/interfaces/IAtlasFactory.sol new file mode 100644 index 00000000..ab1b0dc0 --- /dev/null +++ b/src/contracts/interfaces/IAtlasFactory.sol @@ -0,0 +1,18 @@ +//SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.18; + +interface AtlasFactory { + + function createExecutionEnvironment(address dAppControl) external returns (address executionEnvironment); + + function getExecutionEnvironment(address user, address dAppControl) + external + view + returns (address executionEnvironment, uint32 callConfig, bool exists); + + function getMimicCreationCode(address controller, uint32 callConfig, address user, bytes32 controlCodeHash) + external + view + returns (bytes memory creationCode); + +} \ No newline at end of file From bad0344381d2f11235f1797df63eb4e85345c0e1 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Tue, 31 Oct 2023 17:48:53 +0200 Subject: [PATCH 12/38] Factory split out to separate contract --- src/contracts/atlas/Atlas.sol | 20 ++++-- src/contracts/atlas/AtlasFactory.sol | 9 +++ src/contracts/atlas/Factory.sol | 2 +- src/contracts/common/Permit69.sol | 15 +--- src/contracts/interfaces/IAtlasFactory.sol | 11 ++- test/Accounting.t.sol | 2 +- test/Factory.t.sol | 80 +++++++++++----------- test/Permit69.t.sol | 9 +-- test/SwapIntent.t.sol | 4 +- test/base/BaseTest.t.sol | 2 +- 10 files changed, 86 insertions(+), 68 deletions(-) diff --git a/src/contracts/atlas/Atlas.sol b/src/contracts/atlas/Atlas.sol index 8e90ce29..97f09c5d 100644 --- a/src/contracts/atlas/Atlas.sol +++ b/src/contracts/atlas/Atlas.sol @@ -3,8 +3,10 @@ pragma solidity ^0.8.16; import {IExecutionEnvironment} from "../interfaces/IExecutionEnvironment.sol"; import {IDAppControl} from "../interfaces/IDAppControl.sol"; +import {IAtlasFactory} from "../interfaces/IAtlasFactory.sol"; + +import {Escrow} from "./Escrow.sol"; -import {Factory} from "./Factory.sol"; import {UserSimulationFailed, UserUnexpectedSuccess, UserSimulationSucceeded} from "../types/Emissions.sol"; import {FastLaneErrorsEvents} from "../types/Emissions.sol"; @@ -21,7 +23,7 @@ import {SafetyBits} from "../libraries/SafetyBits.sol"; import "forge-std/Test.sol"; -contract Atlas is Test, Factory { +contract Atlas is Escrow { using CallVerification for UserOperation; using CallBits for uint32; using SafetyBits for EscrowKey; @@ -29,7 +31,7 @@ contract Atlas is Test, Factory { uint256 private constant _MAX_GAS = 1_500_000; address public immutable FACTORY; - constructor(uint32 _escrowDuration, address _simulator, address _factory) Factory(_escrowDuration, _simulator) { + constructor(uint32 _escrowDuration, address _simulator, address _factory) Escrow(_escrowDuration, _simulator) { FACTORY = _factory; } @@ -45,7 +47,7 @@ contract Atlas is Test, Factory { DAppConfig memory dConfig = IDAppControl(userOp.control).getDAppConfig(userOp); // Get the execution environment - address executionEnvironment = _getExecutionEnvironmentCustom(userOp.from, dAppOp.control.codehash, userOp.control, dConfig.callConfig); + address executionEnvironment = IAtlasFactory(FACTORY).getExecutionEnvironmentCustom(userOp.from, dAppOp.control.codehash, userOp.control, dConfig.callConfig); // Gracefully return if not valid. This allows signature data to be stored, which helps prevent // replay attacks. @@ -359,4 +361,14 @@ contract Atlas is Test, Factory { revert RevertToReuse(); } } + + function _verifyCallerIsExecutionEnv( + address user, + address controller, + uint32 callConfig + ) internal override { + if(msg.sender != IAtlasFactory(FACTORY).getExecutionEnvironmentCustom(user, controller.codehash, controller, callConfig)){ + revert EnvironmentMismatch(); + } + } } diff --git a/src/contracts/atlas/AtlasFactory.sol b/src/contracts/atlas/AtlasFactory.sol index bdf75e5a..d4228137 100644 --- a/src/contracts/atlas/AtlasFactory.sol +++ b/src/contracts/atlas/AtlasFactory.sol @@ -52,6 +52,15 @@ contract AtlasFactory { exists = executionEnvironment.codehash != bytes32(0); } + function getExecutionEnvironmentCustom( + address user, + bytes32 controlCodeHash, + address controller, + uint32 callConfig + ) external view returns (address executionEnvironment) { + executionEnvironment = _getExecutionEnvironmentCustom(user, controlCodeHash, controller, callConfig); + } + function getMimicCreationCode(address controller, uint32 callConfig, address user, bytes32 controlCodeHash) external view diff --git a/src/contracts/atlas/Factory.sol b/src/contracts/atlas/Factory.sol index 8fe483bf..2dd7b106 100644 --- a/src/contracts/atlas/Factory.sol +++ b/src/contracts/atlas/Factory.sol @@ -70,7 +70,7 @@ contract Factory is Escrow { bytes32 controlCodeHash, address controller, uint32 callConfig - ) internal view override returns (address executionEnvironment) { + ) internal view returns (address executionEnvironment) { executionEnvironment = address( uint160( uint256( diff --git a/src/contracts/common/Permit69.sol b/src/contracts/common/Permit69.sol index a186099b..286d3785 100644 --- a/src/contracts/common/Permit69.sol +++ b/src/contracts/common/Permit69.sol @@ -27,12 +27,11 @@ abstract contract Permit69 is GasAccounting { constructor(address _simulator) GasAccounting(_simulator) {} // Virtual Functions defined by other Atlas modules - function _getExecutionEnvironmentCustom( + function _verifyCallerIsExecutionEnv( address user, - bytes32 controlCodeHash, address controller, uint32 callConfig - ) internal view virtual returns (address environment); + ) internal virtual {} // Transfer functions function transferUserERC20( @@ -107,16 +106,6 @@ abstract contract Permit69 is GasAccounting { _contributeTo(donor, recipient, amt); } - function _verifyCallerIsExecutionEnv( - address user, - address controller, - uint32 callConfig - ) internal view { - if(msg.sender != _getExecutionEnvironmentCustom(user, controller.codehash, controller, callConfig)){ - revert EnvironmentMismatch(); - } - } - function _verifyLockState(uint16 lockState, uint16 safeExecutionPhaseSet) internal pure { if(lockState & safeExecutionPhaseSet == 0){ revert InvalidLockState(); diff --git a/src/contracts/interfaces/IAtlasFactory.sol b/src/contracts/interfaces/IAtlasFactory.sol index ab1b0dc0..7b58e789 100644 --- a/src/contracts/interfaces/IAtlasFactory.sol +++ b/src/contracts/interfaces/IAtlasFactory.sol @@ -1,8 +1,7 @@ //SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.18; -interface AtlasFactory { - +interface IAtlasFactory { function createExecutionEnvironment(address dAppControl) external returns (address executionEnvironment); function getExecutionEnvironment(address user, address dAppControl) @@ -10,9 +9,15 @@ interface AtlasFactory { view returns (address executionEnvironment, uint32 callConfig, bool exists); + function getExecutionEnvironmentCustom( + address user, + bytes32 controlCodeHash, + address controller, + uint32 callConfig + ) external view returns (address executionEnvironment); + function getMimicCreationCode(address controller, uint32 callConfig, address user, bytes32 controlCodeHash) external view returns (bytes memory creationCode); - } \ No newline at end of file diff --git a/test/Accounting.t.sol b/test/Accounting.t.sol index e93ffebc..573aecf0 100644 --- a/test/Accounting.t.sol +++ b/test/Accounting.t.sol @@ -160,7 +160,7 @@ contract AccountingTest is BaseTest { solverOps = new SolverOperation[](1); vm.startPrank(userEOA); - address executionEnvironment = atlas.createExecutionEnvironment(txBuilder.control()); + address executionEnvironment = atlasFactory.createExecutionEnvironment(txBuilder.control()); vm.stopPrank(); vm.label(address(executionEnvironment), "EXECUTION ENV"); diff --git a/test/Factory.t.sol b/test/Factory.t.sol index 2e21ccf0..18314d46 100644 --- a/test/Factory.t.sol +++ b/test/Factory.t.sol @@ -43,49 +43,51 @@ contract DummyDAppControl is DAppControl { contract FactoryTest is BaseTest { DummyDAppControl public dAppControl; - function setUp() public virtual override { - BaseTest.setUp(); + // TODO fix this to test AtlasFactory instead + + // function setUp() public virtual override { + // BaseTest.setUp(); - governancePK = 666; - governanceEOA = vm.addr(governancePK); - vm.startPrank(governanceEOA); - dAppControl = new DummyDAppControl(escrow); - vm.stopPrank(); - } + // governancePK = 666; + // governanceEOA = vm.addr(governancePK); + // vm.startPrank(governanceEOA); + // dAppControl = new DummyDAppControl(escrow); + // vm.stopPrank(); + // } - function testExecutionEnvironmentAddress() public { - UserOperation memory userOp = UserOperation({ - from: address(this), - to: address(atlas), - deadline: 12, - gas: 34, - nonce: 56, - maxFeePerGas: 78, - value: 90, - dapp: address(0x2), - control: address(0x3), - data: "data", - signature: "signature" - }); + // function testExecutionEnvironmentAddress() public { + // UserOperation memory userOp = UserOperation({ + // from: address(this), + // to: address(atlas), + // deadline: 12, + // gas: 34, + // nonce: 56, + // maxFeePerGas: 78, + // value: 90, + // dapp: address(0x2), + // control: address(0x3), + // data: "data", + // signature: "signature" + // }); - address expectedExecutionEnvironment = - TestUtils.computeExecutionEnvironment(payable(atlas), userOp, address(dAppControl)); + // address expectedExecutionEnvironment = + // TestUtils.computeExecutionEnvironment(payable(atlas), userOp, address(dAppControl)); - assertEq( - atlas.createExecutionEnvironment(address(dAppControl)), - expectedExecutionEnvironment, - "Create exec env address not same as predicted" - ); + // assertEq( + // atlas.createExecutionEnvironment(address(dAppControl)), + // expectedExecutionEnvironment, + // "Create exec env address not same as predicted" + // ); - (address executionEnvironment,,) = atlas.getExecutionEnvironment(userOp.from, address(dAppControl)); - assertEq( - executionEnvironment, - expectedExecutionEnvironment, - "atlas.getExecEnv address not same as predicted" - ); - } + // (address executionEnvironment,,) = atlas.getExecutionEnvironment(userOp.from, address(dAppControl)); + // assertEq( + // executionEnvironment, + // expectedExecutionEnvironment, + // "atlas.getExecEnv address not same as predicted" + // ); + // } - function testGetEscrowAddress() public { - assertEq(atlas.getEscrowAddress(), address(atlas)); - } + // function testGetEscrowAddress() public { + // assertEq(atlas.getEscrowAddress(), address(atlas)); + // } } diff --git a/test/Permit69.t.sol b/test/Permit69.t.sol index 627a5948..58340265 100644 --- a/test/Permit69.t.sol +++ b/test/Permit69.t.sol @@ -279,13 +279,14 @@ contract MockAtlasForPermit69Tests is Permit69 { } // Overriding the virtual functions in Permit69 - function _getExecutionEnvironmentCustom( + function _verifyCallerIsExecutionEnv( address user, - bytes32 controlCodeHash, address controller, uint32 callConfig - ) internal view virtual override returns (address activeEnvironment) { - activeEnvironment = _environment; + ) internal override { + if(msg.sender != _environment) { + revert("ERR-T001 EnvironmentMismatch"); + } } // Implemented in Factory.sol in the canonical Atlas system diff --git a/test/SwapIntent.t.sol b/test/SwapIntent.t.sol index a093531e..4ac040b6 100644 --- a/test/SwapIntent.t.sol +++ b/test/SwapIntent.t.sol @@ -122,7 +122,7 @@ contract SwapIntentTest is BaseTest { DAppOperation memory dAppOp; vm.startPrank(userEOA); - address executionEnvironment = atlas.createExecutionEnvironment(txBuilder.control()); + address executionEnvironment = atlasFactory.createExecutionEnvironment(txBuilder.control()); console.log("executionEnvironment",executionEnvironment); vm.stopPrank(); vm.label(address(executionEnvironment), "EXECUTION ENV"); @@ -247,7 +247,7 @@ contract SwapIntentTest is BaseTest { DAppOperation memory dAppOp; vm.startPrank(userEOA); - address executionEnvironment = atlas.createExecutionEnvironment(txBuilder.control()); + address executionEnvironment = atlasFactory.createExecutionEnvironment(txBuilder.control()); console.log("executionEnvironment a",executionEnvironment); vm.stopPrank(); vm.label(address(executionEnvironment), "EXECUTION ENV"); diff --git a/test/base/BaseTest.t.sol b/test/base/BaseTest.t.sol index eefe3f7f..bd2ff38b 100644 --- a/test/base/BaseTest.t.sol +++ b/test/base/BaseTest.t.sol @@ -80,7 +80,7 @@ contract BaseTest is Test, TestConstants { simulator.setAtlas(address(atlas)); - escrow = atlas.getEscrowAddress(); + escrow = address(atlas); sorter = new Sorter(address(atlas), escrow); vm.stopPrank(); From 4134f0c7667f6a6c3dd54128a63b0bb95c61b857 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Tue, 31 Oct 2023 17:58:09 +0200 Subject: [PATCH 13/38] Fix interface bugs around Atlas vs AtlasFactory in tests --- src/contracts/interfaces/IAtlas.sol | 4 --- test/MainTest.t.sol | 38 ++++++++++++++--------------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/contracts/interfaces/IAtlas.sol b/src/contracts/interfaces/IAtlas.sol index 91ab8c3c..4ee7656f 100644 --- a/src/contracts/interfaces/IAtlas.sol +++ b/src/contracts/interfaces/IAtlas.sol @@ -12,15 +12,11 @@ interface IAtlas { DAppOperation calldata verification ) external payable returns (bool auctionWon); - function createExecutionEnvironment(address control) external returns (address environment); - function withdrawERC20(address token, uint256 amount, DAppConfig memory dConfig) external; function withdrawEther(uint256 amount, DAppConfig memory dConfig) external; function getEscrowAddress() external view returns (address escrowAddress); - function getExecutionEnvironment(address user, address dAppControl) external view returns (address executionEnvironment); - function userDirectVerifyDApp( address userOpFrom, address userOpTo, diff --git a/test/MainTest.t.sol b/test/MainTest.t.sol index d260d032..37e812ce 100644 --- a/test/MainTest.t.sol +++ b/test/MainTest.t.sol @@ -49,7 +49,7 @@ contract MainTest is BaseTest { UserOperation memory userOp = helper.buildUserOperation(POOL_ONE, POOL_TWO, userEOA, TOKEN_ONE); // user does not sign their own operation when bundling - // (v, r, s) = vm.sign(userPK, IAtlas(address(atlas)).getUserOperationPayload(userOp)); + // (v, r, s) = vm.sign(userPK, atlas.getUserOperationPayload(userOp)); // userOp.signature = abi.encodePacked(r, s, v); SolverOperation[] memory solverOps = new SolverOperation[](2); @@ -62,7 +62,7 @@ contract MainTest is BaseTest { solverOps[1] = helper.buildSolverOperation(userOp, solverOpData, solverOneEOA, address(solverOne), WETH.balanceOf(address(solverOne)) / 20); - (v, r, s) = vm.sign(solverOnePK, IAtlas(address(atlas)).getSolverPayload(solverOps[1])); + (v, r, s) = vm.sign(solverOnePK, atlas.getSolverPayload(solverOps[1])); solverOps[1].signature = abi.encodePacked(r, s, v); console.log("solverTwoEOA WETH:", WETH.balanceOf(address(solverTwoEOA))); @@ -72,7 +72,7 @@ contract MainTest is BaseTest { solverOps[0] = helper.buildSolverOperation(userOp, solverOpData, solverTwoEOA, address(solverTwo), WETH.balanceOf(address(solverTwo)) / 3000); - (v, r, s) = vm.sign(solverTwoPK, IAtlas(address(atlas)).getSolverPayload(solverOps[0])); + (v, r, s) = vm.sign(solverTwoPK, atlas.getSolverPayload(solverOps[0])); solverOps[0].signature = abi.encodePacked(r, s, v); console.log("topBid before sorting",solverOps[0].bidAmount); @@ -85,13 +85,13 @@ contract MainTest is BaseTest { DAppOperation memory dAppOp = helper.buildDAppOperation(governanceEOA, userOp, solverOps); - (v, r, s) = vm.sign(governancePK, IAtlas(address(atlas)).getDAppOperationPayload(dAppOp)); + (v, r, s) = vm.sign(governancePK, atlas.getDAppOperationPayload(dAppOp)); dAppOp.signature = abi.encodePacked(r, s, v); vm.startPrank(userEOA); - address executionEnvironment = IAtlas(address(atlas)).createExecutionEnvironment(userOp.control); + address executionEnvironment = atlasFactory.createExecutionEnvironment(userOp.control); vm.label(address(executionEnvironment), "EXECUTION ENV"); console.log("userEOA", userEOA); @@ -248,27 +248,27 @@ contract MainTest is BaseTest { solverOps[0] = helper.buildSolverOperation(userOp, solverOneEOA, address(solverOne), POOL_ONE, POOL_TWO, 2e17); - (v, r, s) = vm.sign(solverOnePK, IAtlas(address(atlas)).getSolverPayload(solverOps[0])); + (v, r, s) = vm.sign(solverOnePK, atlas.getSolverPayload(solverOps[0])); solverOps[0].signature = abi.encodePacked(r, s, v); // Second SolverOperation solverOps[1] = helper.buildSolverOperation(userOp, solverTwoEOA, address(solverTwo), POOL_TWO, POOL_ONE, 1e17); - (v, r, s) = vm.sign(solverTwoPK, IAtlas(address(atlas)).getSolverPayload(solverOps[1])); + (v, r, s) = vm.sign(solverTwoPK, atlas.getSolverPayload(solverOps[1])); solverOps[1].signature = abi.encodePacked(r, s, v); // DAppOperation call dAppOp = helper.buildDAppOperation(governanceEOA, userOp, solverOps); - (v, r, s) = vm.sign(governancePK, IAtlas(address(atlas)).getDAppOperationPayload(dAppOp)); + (v, r, s) = vm.sign(governancePK, atlas.getDAppOperationPayload(dAppOp)); dAppOp.signature = abi.encodePacked(r, s, v); vm.startPrank(userEOA); - executionEnvironment = IAtlas(address(atlas)).getExecutionEnvironment(userOp.from, address(control)); + executionEnvironment = atlas.getExecutionEnvironment(userOp.from, address(control)); userBalance = userEOA.balance; @@ -322,8 +322,8 @@ contract MainTest is BaseTest { vm.startPrank(userEOA); - IAtlas(address(atlas)).createExecutionEnvironment(address(control)); - address newEnvironment = IAtlas(address(atlas)).createExecutionEnvironment(address(control)); + atlasFactory.createExecutionEnvironment(address(control)); + address newEnvironment = atlasFactory.createExecutionEnvironment(address(control)); vm.stopPrank(); assertTrue(IExecutionEnvironment(newEnvironment).getUser() == userEOA, "Mimic Error - User Mismatch"); @@ -339,11 +339,11 @@ contract MainTest is BaseTest { bytes32 s; UserOperation memory userOp = helper.buildUserOperation(POOL_ONE, POOL_TWO, userEOA, TOKEN_ONE); - (v, r, s) = vm.sign(userPK, IAtlas(address(atlas)).getUserOperationPayload(userOp)); + (v, r, s) = vm.sign(userPK, atlas.getUserOperationPayload(userOp)); userOp.signature = abi.encodePacked(r, s, v); vm.startPrank(userEOA); - IAtlas(address(atlas)).createExecutionEnvironment(userOp.control); + atlasFactory.createExecutionEnvironment(userOp.control); // Failure case, user hasn't approved Atlas for TOKEN_ONE, operation must fail assertFalse(simulator.simUserOperation(userOp), "UserOperation tested true"); @@ -363,7 +363,7 @@ contract MainTest is BaseTest { console.log("TOKEN_ONE",TOKEN_ONE); UserOperation memory userOp = helper.buildUserOperation(POOL_ONE, POOL_TWO, userEOA, TOKEN_ONE); - (v, r, s) = vm.sign(userPK, IAtlas(address(atlas)).getUserOperationPayload(userOp)); + (v, r, s) = vm.sign(userPK, atlas.getUserOperationPayload(userOp)); userOp.signature = abi.encodePacked(r, s, v); SolverOperation[] memory solverOps = new SolverOperation[](1); @@ -373,14 +373,14 @@ contract MainTest is BaseTest { solverOps[0] = helper.buildSolverOperation( userOp, solverOpData, solverOneEOA, address(solverOne), 2e17 ); - (v, r, s) = vm.sign(solverOnePK, IAtlas(address(atlas)).getSolverPayload(solverOps[0])); + (v, r, s) = vm.sign(solverOnePK, atlas.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, IAtlas(address(atlas)).getDAppOperationPayload(dAppOp)); + (v, r, s) = vm.sign(governancePK, atlas.getDAppOperationPayload(dAppOp)); dAppOp.signature = abi.encodePacked(r, s, v); vm.startPrank(userEOA); - IAtlas(address(atlas)).createExecutionEnvironment(userOp.control); + atlasFactory.createExecutionEnvironment(userOp.control); ERC20(TOKEN_ONE).approve(address(atlas), type(uint256).max); (bool success, bytes memory data) = address(simulator).call( abi.encodeWithSelector( @@ -397,10 +397,10 @@ contract MainTest is BaseTest { solverOps[0] = helper.buildSolverOperation( userOp, solverOpData, solverOneEOA, address(solverOne), 2e17 ); - (v, r, s) = vm.sign(solverOnePK, IAtlas(address(atlas)).getSolverPayload(solverOps[0])); + (v, r, s) = vm.sign(solverOnePK, atlas.getSolverPayload(solverOps[0])); solverOps[0].signature = abi.encodePacked(r, s, v); dAppOp = helper.buildDAppOperation(governanceEOA, userOp, solverOps); - (v, r, s) = vm.sign(governancePK, IAtlas(address(atlas)).getDAppOperationPayload(dAppOp)); + (v, r, s) = vm.sign(governancePK, atlas.getDAppOperationPayload(dAppOp)); dAppOp.signature = abi.encodePacked(r, s, v); vm.startPrank(userEOA); (success, data) = address(simulator).call( From bb004f41b22f98e2d2d620ba102dea6f66ea9855 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Thu, 2 Nov 2023 11:32:38 +0200 Subject: [PATCH 14/38] Change split strategy to AtlasVerification over AtlETH --- src/contracts/atlas/AtlETH2.sol | 15 -- src/contracts/atlas/AtlasVerification.sol | 294 ++++++++++++++++++++++ 2 files changed, 294 insertions(+), 15 deletions(-) delete mode 100644 src/contracts/atlas/AtlETH2.sol create mode 100644 src/contracts/atlas/AtlasVerification.sol diff --git a/src/contracts/atlas/AtlETH2.sol b/src/contracts/atlas/AtlETH2.sol deleted file mode 100644 index f28934ba..00000000 --- a/src/contracts/atlas/AtlETH2.sol +++ /dev/null @@ -1,15 +0,0 @@ -//SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.18; - - -// NOTE: Experimental - Splitting contracts into [AtlETH, Atlas, AtlasFactory] - -// AtlETH needs: -// ERC20 stuff - for the AtlETH token -// Permit2 integration - for external AtlETH use -// Permit69 - for internal approval between Atlas, Exec Envs, etc -// Escrow - locked down during phases of Atlas execution, or time locked -// GasAccounting - maybe?? Maybe in Atlas if msg.value needed -contract AtlETH2 { - -} \ No newline at end of file diff --git a/src/contracts/atlas/AtlasVerification.sol b/src/contracts/atlas/AtlasVerification.sol new file mode 100644 index 00000000..0e73f57e --- /dev/null +++ b/src/contracts/atlas/AtlasVerification.sol @@ -0,0 +1,294 @@ +//SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.16; + +import "openzeppelin-contracts/contracts/utils/cryptography/EIP712.sol"; + +import {CallBits} from "../libraries/CallBits.sol"; + +import "../types/UserCallTypes.sol"; +import "../types/GovernanceTypes.sol"; + +import "../types/DAppApprovalTypes.sol"; + +import {DAppIntegration} from "./DAppIntegration.sol"; + +import "forge-std/Test.sol"; // TODO remove + +// NOTE: AtlasVerification is the separate contract version of the DappVerification/DAppIntegration +// inheritance slice of the original Atlas design + +// This contract exists so that dapp frontends can sign and confirm the +// calldata for users. Users already trust the frontends to build and verify +// their calldata. This allows users to know that any CallData sourced via +// an external relay (such as FastLane) has been verified by the already-trusted +// frontend +contract AtlasVerification is EIP712, DAppIntegration { + using ECDSA for bytes32; + using CallBits for uint32; + + constructor() EIP712("ProtoCallHandler", "0.0.1") {} + + + // + // DAPP VERIFICATION + // + + // Verify that the dapp's front end generated the preOps + // information and that it matches the on-chain data. + // Verify that the dapp's front end's data is based on + // the data submitted by the user and by the solvers. + // NOTE: the dapp's front end is the last party in + // the supply chain to submit data. If any other party + // (user, solver, FastLane, or a collusion between + // all of them) attempts to alter it, this check will fail + function _verifyDApp(DAppConfig memory dConfig, DAppOperation calldata dAppOp) + internal + returns (bool) + { + // Verify the signature before storing any data to avoid + // spoof transactions clogging up dapp nonces + if (!_verifyDAppSignature(dAppOp)) { + return false; + } + + // NOTE: to avoid replay attacks arising from key management errors, + // the state changes below must be *saved* even if they render the + // transaction invalid. + // TODO: consider dapp-owned gas escrow. Enshrined account + // abstraction may render that redundant at a large scale, but + // allocating different parts of the tx to different parties + // will allow for optimized trustlessness. This could lead to + // users not having to trust the front end at all - a huge + // improvement over the current experience. + + GovernanceData memory govData = governance[dConfig.to]; + + // Verify that the dapp is onboarded and that the call config is + // genuine. + bytes32 dAppKey = keccak256(abi.encode(dConfig.to, govData.governance, dConfig.callConfig)); + + // Make sure the signer is currently enabled by dapp owner + if (!signatories[keccak256(abi.encode(govData.governance, dAppOp.from))]) { + return (false); + } + + if (dAppOp.control != dConfig.to) { + return (false); + } + + // NOTE: This check does not work if DAppControl is a proxy contract. + // To avoid exposure to social engineering vulnerabilities, disgruntled + // former employees, or beneficiary uncertainty during intra-DAO conflict, + // governance should refrain from using a proxy contract for DAppControl. + if (dConfig.to.codehash == bytes32(0) || dapps[dAppKey] != dConfig.to.codehash) { + return (false); + } + + // If the dapp indicated that they only accept sequenced nonces + // (IE for FCFS execution), check and make sure the order is correct + // NOTE: allowing only sequenced nonces could create a scenario in + // which builders or validators may be able to profit via censorship. + // DApps are encouraged to rely on the deadline parameter. + if (!_handleNonces(dAppOp.from, dAppOp.nonce, dConfig.callConfig.needsSequencedNonces())) { + return (false); + } + + return (true); + } + + function _handleNonces(address account, uint256 nonce, bool async) internal returns (bool validNonce) { + if (nonce > type(uint128).max - 1) { + return (false); + } + + if (nonce == 0) { + return (false); + } + + uint256 bitmapIndex = (nonce / 240) + 1; // +1 because highestFullBitmap initializes at 0 + uint256 bitmapNonce = (nonce % 240) + 1; + + bytes32 bitmapKey = keccak256(abi.encode(account, bitmapIndex)); + + NonceBitmap memory nonceBitmap = asyncNonceBitmap[bitmapKey]; + + uint256 bitmap = uint256(nonceBitmap.bitmap); + if (bitmap & (1 << bitmapNonce) != 0) { + return (false); + } + + bitmap |= 1 << bitmapNonce; + nonceBitmap.bitmap = uint240(bitmap); + + uint256 highestUsedBitmapNonce = uint256(nonceBitmap.highestUsedNonce); + if (bitmapNonce > highestUsedBitmapNonce) { + nonceBitmap.highestUsedNonce = uint8(bitmapNonce); + } + + // Update the nonceBitmap + asyncNonceBitmap[bitmapKey] = nonceBitmap; + + // Update the nonce tracker + return _updateNonceTracker(account, highestUsedBitmapNonce, bitmapIndex, bitmapNonce, async); + } + + function _updateNonceTracker( + address account, uint256 highestUsedBitmapNonce, uint256 bitmapIndex, uint256 bitmapNonce, bool async + ) + internal + returns (bool) + { + NonceTracker memory nonceTracker = asyncNonceBitIndex[account]; + + uint256 highestFullBitmap = uint256(nonceTracker.HighestFullBitmap); + uint256 lowestEmptyBitmap = uint256(nonceTracker.LowestEmptyBitmap); + + // Handle non-async nonce logic + if (!async) { + if (bitmapIndex != highestFullBitmap + 1) { + return (false); + } + + if (bitmapNonce != highestUsedBitmapNonce +1) { + return (false); + } + } + + if (bitmapNonce > uint256(239) || !async) { + bool updateTracker; + + if (bitmapIndex > highestFullBitmap) { + updateTracker = true; + highestFullBitmap = bitmapIndex; + } + + if (bitmapIndex + 2 > lowestEmptyBitmap) { + updateTracker = true; + lowestEmptyBitmap = (lowestEmptyBitmap > bitmapIndex ? lowestEmptyBitmap + 1 : bitmapIndex + 2); + } + + if (updateTracker) { + asyncNonceBitIndex[account] = NonceTracker({ + HighestFullBitmap: uint128(highestFullBitmap), + LowestEmptyBitmap: uint128(lowestEmptyBitmap) + }); + } + } + return true; + } + + function _getProofHash(DAppOperation memory approval) internal pure returns (bytes32 proofHash) { + proofHash = keccak256( + abi.encode( + DAPP_TYPE_HASH, + approval.from, + approval.to, + approval.value, + approval.gas, + approval.maxFeePerGas, + approval.nonce, + approval.deadline, + approval.control, + approval.userOpHash, + approval.callChainHash + ) + ); + } + + function _verifyDAppSignature(DAppOperation calldata dAppOp) internal view returns (bool) { + if (dAppOp.signature.length == 0) { return false; } + address signer = _hashTypedDataV4(_getProofHash(dAppOp)).recover(dAppOp.signature); + + return signer == dAppOp.from; + // return true; + } + + function getDAppOperationPayload(DAppOperation memory dAppOp) public view returns (bytes32 payload) { + payload = _hashTypedDataV4(_getProofHash(dAppOp)); + } + + function getDomainSeparator() external view returns (bytes32 domainSeparator) { + domainSeparator = _domainSeparatorV4(); + } + + // + // USER VERIFICATION + // + + // Verify the user's meta transaction + function _verifyUser(DAppConfig memory dConfig, UserOperation calldata userOp) + internal + returns (bool) + { + + // Verify the signature before storing any data to avoid + // spoof transactions clogging up dapp userNonces + if (!_verifyUserSignature(userOp)) { + return false; + } + + if (userOp.control != dConfig.to) { + return (false); + } + + // If the dapp indicated that they only accept sequenced userNonces + // (IE for FCFS execution), check and make sure the order is correct + // NOTE: allowing only sequenced userNonces could create a scenario in + // which builders or validators may be able to profit via censorship. + // DApps are encouraged to rely on the deadline parameter + // to prevent replay attacks. + if (!_handleNonces(userOp.from, userOp.nonce, dConfig.callConfig.needsSequencedNonces())) { + return (false); + } + + return (true); + } + + function _getProofHash(UserOperation memory userOp) internal pure returns (bytes32 proofHash) { + proofHash = keccak256( + abi.encode( + USER_TYPE_HASH, + userOp.from, + userOp.to, + userOp.value, + userOp.gas, + userOp.maxFeePerGas, + userOp.nonce, + userOp.deadline, + userOp.dapp, + userOp.control, + keccak256(userOp.data) + ) + ); + } + + function _verifyUserSignature(UserOperation calldata userOp) internal view returns (bool) { + if (userOp.signature.length == 0) { return false; } + address signer = _hashTypedDataV4(_getProofHash(userOp)).recover(userOp.signature); + + return signer == userOp.from; + } + + function getUserOperationPayload(UserOperation memory userOp) public view returns (bytes32 payload) { + payload = _hashTypedDataV4(_getProofHash(userOp)); + } + + function getNextNonce(address account) external view returns (uint256 nextNonce) { + NonceTracker memory nonceTracker = asyncNonceBitIndex[account]; + + uint256 nextBitmapIndex = uint256(nonceTracker.HighestFullBitmap) + 1; + uint256 lowestEmptyBitmap = uint256(nonceTracker.LowestEmptyBitmap); + + if (lowestEmptyBitmap == 0) { + return 1; // uninitialized + } + + bytes32 bitmapKey = keccak256(abi.encode(account, nextBitmapIndex)); + + NonceBitmap memory nonceBitmap = asyncNonceBitmap[bitmapKey]; + + uint256 highestUsedNonce = uint256(nonceBitmap.highestUsedNonce); // has a +1 offset + + nextNonce = ((nextBitmapIndex - 1) * 240) + highestUsedNonce; + } +} From d3fc5f0efc0048eb29bbcf1b3455f4836e1e9236 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Thu, 2 Nov 2023 17:44:10 +0200 Subject: [PATCH 15/38] Save progress on splitting out verification --- src/contracts/atlas/AtlasVerification.sol | 133 ++++++++- src/contracts/atlas/Escrow.sol | 255 +++++++++--------- .../interfaces/IAtlasVerification.sol | 9 + 3 files changed, 272 insertions(+), 125 deletions(-) create mode 100644 src/contracts/interfaces/IAtlasVerification.sol diff --git a/src/contracts/atlas/AtlasVerification.sol b/src/contracts/atlas/AtlasVerification.sol index 0e73f57e..2ef2b2aa 100644 --- a/src/contracts/atlas/AtlasVerification.sol +++ b/src/contracts/atlas/AtlasVerification.sol @@ -1,14 +1,18 @@ //SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.16; +pragma solidity ^0.8.18; import "openzeppelin-contracts/contracts/utils/cryptography/EIP712.sol"; import {CallBits} from "../libraries/CallBits.sol"; +import "../types/SolverCallTypes.sol"; import "../types/UserCallTypes.sol"; import "../types/GovernanceTypes.sol"; import "../types/DAppApprovalTypes.sol"; +import "../types/EscrowTypes.sol"; + +import {EscrowBits} from "../libraries/EscrowBits.sol"; import {DAppIntegration} from "./DAppIntegration.sol"; @@ -28,6 +32,133 @@ contract AtlasVerification is EIP712, DAppIntegration { constructor() EIP712("ProtoCallHandler", "0.0.1") {} + // PORTED FROM ESCROW - TODO reorder + + function verify(SolverOperation calldata solverOp, EscrowAccountData memory solverEscrow, uint256 gasWaterMark, bool auctionAlreadyComplete) + external + view + returns (uint256 result, uint256 gasLimit, EscrowAccountData memory) + { + // verify solver's signature + if (_verifySignature(solverOp)) { + // verify the solver has correct usercalldata and the solver escrow checks + (result, gasLimit, solverEscrow) = _verifySolverOperation(solverOp, solverEscrow); + } else { + (result, gasLimit) = (1 << uint256(SolverOutcome.InvalidSignature), 0); + // solverEscrow returns null + } + + result = _solverOpPreCheck(result, gasWaterMark, tx.gasprice, solverOp.maxFeePerGas, auctionAlreadyComplete); + return (result, gasLimit, solverEscrow); + } + + function _getSolverPayload(SolverOperation calldata solverOp) internal view returns (bytes32 payload) { + payload = _hashTypedDataV4(_getSolverHash(solverOp)); + } + + function _verifySignature(SolverOperation calldata solverOp) internal view returns (bool) { + address signer = _hashTypedDataV4(_getSolverHash(solverOp)).recover(solverOp.signature); + return signer == solverOp.from; + } + + // TODO Revisit the EscrowAccountData memory solverEscrow arg. Needs to be passed through from Atlas, through callstack + function _verifySolverOperation(SolverOperation calldata solverOp, EscrowAccountData memory solverEscrow) + internal + view + returns (uint256 result, uint256 gasLimit, EscrowAccountData memory) + { + // TODO big unchecked block - audit/review carefully + unchecked { + if (solverOp.to != address(this)) { + result |= 1 << uint256(SolverOutcome.InvalidTo); + } + + if (solverOp.nonce <= uint256(solverEscrow.nonce)) { + result |= 1 << uint256(SolverOutcome.InvalidNonceUnder); + } else if (solverOp.nonce > uint256(solverEscrow.nonce) + 1) { + result |= 1 << uint256(SolverOutcome.InvalidNonceOver); + + // TODO: reconsider the jump up for gapped nonces? Intent is to mitigate dmg + // potential inflicted by a hostile solver/builder. + solverEscrow.nonce = uint32(solverOp.nonce); + } else { + ++solverEscrow.nonce; + } + + if (solverEscrow.lastAccessed >= uint64(block.number)) { + result |= 1 << uint256(SolverOutcome.PerBlockLimit); + } else { + solverEscrow.lastAccessed = uint64(block.number); + } + + gasLimit = (100) * (solverOp.gas < EscrowBits.SOLVER_GAS_LIMIT ? solverOp.gas : EscrowBits.SOLVER_GAS_LIMIT) + / (100 + EscrowBits.SOLVER_GAS_BUFFER) + EscrowBits.FASTLANE_GAS_BUFFER; + + uint256 gasCost = (tx.gasprice * gasLimit) + (solverOp.data.length * CALLDATA_LENGTH_PREMIUM * tx.gasprice); + + // see if solver's escrow can afford tx gascost + if (gasCost > solverEscrow.balance) { + // charge solver for calldata so that we can avoid vampire attacks from solver onto user + result |= 1 << uint256(SolverOutcome.InsufficientEscrow); + } + + // Verify that we can lend the solver their tx value + if (solverOp.value > address(this).balance - (gasLimit * tx.gasprice)) { + result |= 1 << uint256(SolverOutcome.CallValueTooHigh); + } + + // subtract out the gas buffer since the solver's metaTx won't use it + gasLimit -= EscrowBits.FASTLANE_GAS_BUFFER; + } + + return (result, gasLimit, solverEscrow); + } + + function _getSolverHash(SolverOperation calldata solverOp) internal pure returns (bytes32 solverHash) { + return keccak256( + abi.encode( + SOLVER_TYPE_HASH, + solverOp.from, + solverOp.to, + solverOp.value, + solverOp.gas, + solverOp.maxFeePerGas, + solverOp.nonce, + solverOp.deadline, + solverOp.solver, + solverOp.control, + solverOp.userOpHash, + solverOp.bidToken, + solverOp.bidAmount, + keccak256(solverOp.data) + ) + ); + } + + // BITWISE STUFF + function _solverOpPreCheck( + uint256 result, + uint256 gasWaterMark, + uint256 txGasPrice, + uint256 maxFeePerGas, + bool auctionAlreadyComplete + ) internal pure returns (uint256) { + if (auctionAlreadyComplete) { + result |= 1 << uint256(SolverOutcome.LostAuction); + } + + if (gasWaterMark < EscrowBits.VALIDATION_GAS_LIMIT + EscrowBits.SOLVER_GAS_LIMIT) { + // Make sure to leave enough gas for dApp validation calls + result |= 1 << uint256(SolverOutcome.UserOutOfGas); + } + + if (txGasPrice > maxFeePerGas) { + result |= 1 << uint256(SolverOutcome.GasPriceOverCap); + } + + return result; + } + // // DAPP VERIFICATION diff --git a/src/contracts/atlas/Escrow.sol b/src/contracts/atlas/Escrow.sol index 280e2ff9..eaa5ef41 100644 --- a/src/contracts/atlas/Escrow.sol +++ b/src/contracts/atlas/Escrow.sol @@ -2,12 +2,13 @@ pragma solidity ^0.8.16; import {IExecutionEnvironment} from "../interfaces/IExecutionEnvironment.sol"; +import {IAtlasVerification} from "../interfaces/IAtlasVerification.sol"; import {SafeTransferLib, ERC20} from "solmate/utils/SafeTransferLib.sol"; import "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol"; -import {DAppVerification} from "./DAppVerification.sol"; +import {AtlasVerification} from "./AtlasVerification.sol"; import {AtlETH} from "./AtlETH.sol"; import "../types/SolverCallTypes.sol"; @@ -22,13 +23,17 @@ import {SafetyBits} from "../libraries/SafetyBits.sol"; import "forge-std/Test.sol"; -abstract contract Escrow is AtlETH, DAppVerification { +abstract contract Escrow is AtlETH { using ECDSA for bytes32; using EscrowBits for uint256; using CallBits for uint32; using SafetyBits for EscrowKey; - constructor(uint32 _escrowDuration, address _simulator) AtlETH(_escrowDuration, _simulator) {} + address public immutable VERIFICATION; + + constructor(uint32 _escrowDuration, address _simulator, address _verification) AtlETH(_escrowDuration, _simulator) { + VERIFICATION = _verification; + } /////////////////////////////////////////////////// /// EXTERNAL FUNCTIONS FOR BUNDLER INTERACTION /// @@ -81,7 +86,7 @@ abstract contract Escrow is AtlETH, DAppVerification { // Verify the transaction. (uint256 result, uint256 gasLimit, EscrowAccountData memory solverEscrow) = - _verify(solverOp, gasWaterMark, false); + IAtlasVerification(VERIFICATION).verify(solverOp, gasWaterMark, false); // If there are no errors, attempt to execute if (result.canExecute() && _checkSolverProxy(solverOp.from, bundler)) { @@ -205,104 +210,106 @@ abstract contract Escrow is AtlETH, DAppVerification { } } - function _verify(SolverOperation calldata solverOp, uint256 gasWaterMark, bool auctionAlreadyComplete) - internal - view - returns (uint256 result, uint256 gasLimit, EscrowAccountData memory solverEscrow) - { - // verify solver's signature - if (_verifySignature(solverOp)) { - // verify the solver has correct usercalldata and the solver escrow checks - (result, gasLimit, solverEscrow) = _verifySolverOperation(solverOp); - } else { - (result, gasLimit) = (1 << uint256(SolverOutcome.InvalidSignature), 0); - // solverEscrow returns null - } - - result = _solverOpPreCheck(result, gasWaterMark, tx.gasprice, solverOp.maxFeePerGas, auctionAlreadyComplete); - } - - function _getSolverHash(SolverOperation calldata solverOp) internal pure returns (bytes32 solverHash) { - return keccak256( - abi.encode( - SOLVER_TYPE_HASH, - solverOp.from, - solverOp.to, - solverOp.value, - solverOp.gas, - solverOp.maxFeePerGas, - solverOp.nonce, - solverOp.deadline, - solverOp.solver, - solverOp.control, - solverOp.userOpHash, - solverOp.bidToken, - solverOp.bidAmount, - keccak256(solverOp.data) - ) - ); - } - - function getSolverPayload(SolverOperation calldata solverOp) public view returns (bytes32 payload) { - payload = _hashTypedDataV4(_getSolverHash(solverOp)); - } - - function _verifySignature(SolverOperation calldata solverOp) internal view returns (bool) { - address signer = _hashTypedDataV4(_getSolverHash(solverOp)).recover(solverOp.signature); - return signer == solverOp.from; - } - - function _verifySolverOperation(SolverOperation calldata solverOp) - internal - view - returns (uint256 result, uint256 gasLimit, EscrowAccountData memory solverEscrow) - { - solverEscrow = _escrowAccountData[solverOp.from]; - - // TODO big unchecked block - audit/review carefully - unchecked { - if (solverOp.to != address(this)) { - result |= 1 << uint256(SolverOutcome.InvalidTo); - } - - if (solverOp.nonce <= uint256(solverEscrow.nonce)) { - result |= 1 << uint256(SolverOutcome.InvalidNonceUnder); - } else if (solverOp.nonce > uint256(solverEscrow.nonce) + 1) { - result |= 1 << uint256(SolverOutcome.InvalidNonceOver); - - // TODO: reconsider the jump up for gapped nonces? Intent is to mitigate dmg - // potential inflicted by a hostile solver/builder. - solverEscrow.nonce = uint32(solverOp.nonce); - } else { - ++solverEscrow.nonce; - } - - if (solverEscrow.lastAccessed >= uint64(block.number)) { - result |= 1 << uint256(SolverOutcome.PerBlockLimit); - } else { - solverEscrow.lastAccessed = uint64(block.number); - } - - gasLimit = (100) * (solverOp.gas < EscrowBits.SOLVER_GAS_LIMIT ? solverOp.gas : EscrowBits.SOLVER_GAS_LIMIT) - / (100 + EscrowBits.SOLVER_GAS_BUFFER) + EscrowBits.FASTLANE_GAS_BUFFER; - - uint256 gasCost = (tx.gasprice * gasLimit) + (solverOp.data.length * CALLDATA_LENGTH_PREMIUM * tx.gasprice); - - // see if solver's escrow can afford tx gascost - if (gasCost > _escrowAccountData[solverOp.from].balance) { - // charge solver for calldata so that we can avoid vampire attacks from solver onto user - result |= 1 << uint256(SolverOutcome.InsufficientEscrow); - } - - // Verify that we can lend the solver their tx value - if (solverOp.value > address(this).balance - (gasLimit * tx.gasprice)) { - result |= 1 << uint256(SolverOutcome.CallValueTooHigh); - } - - // subtract out the gas buffer since the solver's metaTx won't use it - gasLimit -= EscrowBits.FASTLANE_GAS_BUFFER; - } - } + // function _verify(SolverOperation calldata solverOp, uint256 gasWaterMark, bool auctionAlreadyComplete) + // internal + // view + // returns (uint256 result, uint256 gasLimit, EscrowAccountData memory solverEscrow) + // { + // // verify solver's signature + // if (_verifySignature(solverOp)) { + // // verify the solver has correct usercalldata and the solver escrow checks + // (result, gasLimit, solverEscrow) = _verifySolverOperation(solverOp); + // } else { + // (result, gasLimit) = (1 << uint256(SolverOutcome.InvalidSignature), 0); + // // solverEscrow returns null + // } + + // result = _solverOpPreCheck(result, gasWaterMark, tx.gasprice, solverOp.maxFeePerGas, auctionAlreadyComplete); + // } + + // function _getSolverHash(SolverOperation calldata solverOp) internal pure returns (bytes32 solverHash) { + // return keccak256( + // abi.encode( + // SOLVER_TYPE_HASH, + // solverOp.from, + // solverOp.to, + // solverOp.value, + // solverOp.gas, + // solverOp.maxFeePerGas, + // solverOp.nonce, + // solverOp.deadline, + // solverOp.solver, + // solverOp.control, + // solverOp.userOpHash, + // solverOp.bidToken, + // solverOp.bidAmount, + // keccak256(solverOp.data) + // ) + // ); + // } + + // TODO take out commented fns after porting + + // function getSolverPayload(SolverOperation calldata solverOp) public view returns (bytes32 payload) { + // payload = _hashTypedDataV4(_getSolverHash(solverOp)); + // } + + // function _verifySignature(SolverOperation calldata solverOp) internal view returns (bool) { + // address signer = _hashTypedDataV4(_getSolverHash(solverOp)).recover(solverOp.signature); + // return signer == solverOp.from; + // } + + // function _verifySolverOperation(SolverOperation calldata solverOp) + // internal + // view + // returns (uint256 result, uint256 gasLimit, EscrowAccountData memory solverEscrow) + // { + // solverEscrow = _escrowAccountData[solverOp.from]; + + // // TODO big unchecked block - audit/review carefully + // unchecked { + // if (solverOp.to != address(this)) { + // result |= 1 << uint256(SolverOutcome.InvalidTo); + // } + + // if (solverOp.nonce <= uint256(solverEscrow.nonce)) { + // result |= 1 << uint256(SolverOutcome.InvalidNonceUnder); + // } else if (solverOp.nonce > uint256(solverEscrow.nonce) + 1) { + // result |= 1 << uint256(SolverOutcome.InvalidNonceOver); + + // // TODO: reconsider the jump up for gapped nonces? Intent is to mitigate dmg + // // potential inflicted by a hostile solver/builder. + // solverEscrow.nonce = uint32(solverOp.nonce); + // } else { + // ++solverEscrow.nonce; + // } + + // if (solverEscrow.lastAccessed >= uint64(block.number)) { + // result |= 1 << uint256(SolverOutcome.PerBlockLimit); + // } else { + // solverEscrow.lastAccessed = uint64(block.number); + // } + + // gasLimit = (100) * (solverOp.gas < EscrowBits.SOLVER_GAS_LIMIT ? solverOp.gas : EscrowBits.SOLVER_GAS_LIMIT) + // / (100 + EscrowBits.SOLVER_GAS_BUFFER) + EscrowBits.FASTLANE_GAS_BUFFER; + + // uint256 gasCost = (tx.gasprice * gasLimit) + (solverOp.data.length * CALLDATA_LENGTH_PREMIUM * tx.gasprice); + + // // see if solver's escrow can afford tx gascost + // if (gasCost > _escrowAccountData[solverOp.from].balance) { + // // charge solver for calldata so that we can avoid vampire attacks from solver onto user + // result |= 1 << uint256(SolverOutcome.InsufficientEscrow); + // } + + // // Verify that we can lend the solver their tx value + // if (solverOp.value > address(this).balance - (gasLimit * tx.gasprice)) { + // result |= 1 << uint256(SolverOutcome.CallValueTooHigh); + // } + + // // subtract out the gas buffer since the solver's metaTx won't use it + // gasLimit -= EscrowBits.FASTLANE_GAS_BUFFER; + // } + // } // Returns a SolverOutcome enum value function _solverOpWrapper( @@ -361,26 +368,26 @@ abstract contract Escrow is AtlETH, DAppVerification { } // BITWISE STUFF - function _solverOpPreCheck( - uint256 result, - uint256 gasWaterMark, - uint256 txGasPrice, - uint256 maxFeePerGas, - bool auctionAlreadyComplete - ) internal pure returns (uint256) { - if (auctionAlreadyComplete) { - result |= 1 << uint256(SolverOutcome.LostAuction); - } - - if (gasWaterMark < EscrowBits.VALIDATION_GAS_LIMIT + EscrowBits.SOLVER_GAS_LIMIT) { - // Make sure to leave enough gas for dApp validation calls - result |= 1 << uint256(SolverOutcome.UserOutOfGas); - } - - if (txGasPrice > maxFeePerGas) { - result |= 1 << uint256(SolverOutcome.GasPriceOverCap); - } - - return result; - } + // function _solverOpPreCheck( + // uint256 result, + // uint256 gasWaterMark, + // uint256 txGasPrice, + // uint256 maxFeePerGas, + // bool auctionAlreadyComplete + // ) internal pure returns (uint256) { + // if (auctionAlreadyComplete) { + // result |= 1 << uint256(SolverOutcome.LostAuction); + // } + + // if (gasWaterMark < EscrowBits.VALIDATION_GAS_LIMIT + EscrowBits.SOLVER_GAS_LIMIT) { + // // Make sure to leave enough gas for dApp validation calls + // result |= 1 << uint256(SolverOutcome.UserOutOfGas); + // } + + // if (txGasPrice > maxFeePerGas) { + // result |= 1 << uint256(SolverOutcome.GasPriceOverCap); + // } + + // return result; + // } } diff --git a/src/contracts/interfaces/IAtlasVerification.sol b/src/contracts/interfaces/IAtlasVerification.sol new file mode 100644 index 00000000..51c5990a --- /dev/null +++ b/src/contracts/interfaces/IAtlasVerification.sol @@ -0,0 +1,9 @@ +//SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.18; + +import "../types/SolverCallTypes.sol"; + +interface IAtlasVerification { + function getSolverPayload(SolverOperation calldata solverOp) external view returns (bytes32 payload); + function verifySignature(SolverOperation calldata solverOp) external view returns (bool); +} \ No newline at end of file From 4563da50a51f23e99f256a3609bd9bf964d0ea11 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Thu, 2 Nov 2023 18:13:56 +0200 Subject: [PATCH 16/38] Rename verify to verifySolverOp and integrate in Escrow --- src/contracts/atlas/AtlasVerification.sol | 2 +- src/contracts/atlas/Escrow.sol | 8 ++++++-- src/contracts/interfaces/IAtlasVerification.sol | 9 +++++++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/contracts/atlas/AtlasVerification.sol b/src/contracts/atlas/AtlasVerification.sol index 2ef2b2aa..ebffccf2 100644 --- a/src/contracts/atlas/AtlasVerification.sol +++ b/src/contracts/atlas/AtlasVerification.sol @@ -34,7 +34,7 @@ contract AtlasVerification is EIP712, DAppIntegration { // PORTED FROM ESCROW - TODO reorder - function verify(SolverOperation calldata solverOp, EscrowAccountData memory solverEscrow, uint256 gasWaterMark, bool auctionAlreadyComplete) + function verifySolverOp(SolverOperation calldata solverOp, EscrowAccountData memory solverEscrow, uint256 gasWaterMark, bool auctionAlreadyComplete) external view returns (uint256 result, uint256 gasLimit, EscrowAccountData memory) diff --git a/src/contracts/atlas/Escrow.sol b/src/contracts/atlas/Escrow.sol index eaa5ef41..a952dcb4 100644 --- a/src/contracts/atlas/Escrow.sol +++ b/src/contracts/atlas/Escrow.sol @@ -84,9 +84,13 @@ abstract contract Escrow is AtlETH { // Set the gas baseline uint256 gasWaterMark = gasleft(); + EscrowAccountData memory solverEscrow = _escrowAccountData[solverOp.from]; + uint256 result; + uint256 gasLimit; + // Verify the transaction. - (uint256 result, uint256 gasLimit, EscrowAccountData memory solverEscrow) = - IAtlasVerification(VERIFICATION).verify(solverOp, gasWaterMark, false); + (result, gasLimit, solverEscrow) = + IAtlasVerification(VERIFICATION).verifySolverOp(solverOp, solverEscrow, gasWaterMark, false); // If there are no errors, attempt to execute if (result.canExecute() && _checkSolverProxy(solverOp.from, bundler)) { diff --git a/src/contracts/interfaces/IAtlasVerification.sol b/src/contracts/interfaces/IAtlasVerification.sol index 51c5990a..00fbe50b 100644 --- a/src/contracts/interfaces/IAtlasVerification.sol +++ b/src/contracts/interfaces/IAtlasVerification.sol @@ -2,8 +2,13 @@ pragma solidity ^0.8.18; import "../types/SolverCallTypes.sol"; +import "../types/EscrowTypes.sol"; interface IAtlasVerification { - function getSolverPayload(SolverOperation calldata solverOp) external view returns (bytes32 payload); - function verifySignature(SolverOperation calldata solverOp) external view returns (bool); + + + function verifySolverOp(SolverOperation calldata solverOp, EscrowAccountData memory solverEscrow, uint256 gasWaterMark, bool auctionAlreadyComplete) + external + view + returns (uint256 result, uint256 gasLimit, EscrowAccountData memory); } \ No newline at end of file From da4963a61b6d77f3cfdcf8318d1c9d5a7ef810d3 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Thu, 2 Nov 2023 19:27:11 +0200 Subject: [PATCH 17/38] Integrate verifyUser and verifyDApp with split --- src/contracts/atlas/Atlas.sol | 13 +++++++------ src/contracts/atlas/AtlasVerification.sol | 8 ++++---- src/contracts/interfaces/IAtlasVerification.sol | 10 +++++++++- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/contracts/atlas/Atlas.sol b/src/contracts/atlas/Atlas.sol index 97f09c5d..493c3134 100644 --- a/src/contracts/atlas/Atlas.sol +++ b/src/contracts/atlas/Atlas.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.16; import {IExecutionEnvironment} from "../interfaces/IExecutionEnvironment.sol"; import {IDAppControl} from "../interfaces/IDAppControl.sol"; import {IAtlasFactory} from "../interfaces/IAtlasFactory.sol"; +import {IAtlasVerification} from "../interfaces/IAtlasVerification.sol"; import {Escrow} from "./Escrow.sol"; @@ -231,7 +232,7 @@ contract Atlas is Escrow { } // check dapp signature - if(!_verifyDApp(dConfig, dAppOp)) { + if(!IAtlasVerification(VERIFICATION).verifyDApp(dConfig, dAppOp)) { bool bypass = isSimulation && dAppOp.signature.length == 0; if (!bypass) { return ValidCallsResult.DAppSignatureInvalid; @@ -245,7 +246,7 @@ contract Atlas is Escrow { } // dapp is bundling - always allowed, check valid user/dapp signature and callchainhash else if(msg.sender == dAppOp.from) { // check dapp signature - if(!_verifyDApp(dConfig, dAppOp)) { + if(!IAtlasVerification(VERIFICATION).verifyDApp(dConfig, dAppOp)) { bool bypass = isSimulation && dAppOp.signature.length == 0; if (!bypass) { return ValidCallsResult.DAppSignatureInvalid; @@ -253,7 +254,7 @@ contract Atlas is Escrow { } // check user signature - if(!_verifyUser(dConfig, userOp)) { + if(!IAtlasVerification(VERIFICATION).verifyUser(dConfig, userOp)) { bool bypass = isSimulation && userOp.signature.length == 0; if (!bypass) { return ValidCallsResult.UserSignatureInvalid; @@ -272,7 +273,7 @@ contract Atlas is Escrow { } // verify user signature - if(!_verifyUser(dConfig, userOp)) { + if(!IAtlasVerification(VERIFICATION).verifyUser(dConfig, userOp)) { bool bypass = isSimulation && userOp.signature.length == 0; if (!bypass) { return ValidCallsResult.UserSignatureInvalid; @@ -288,7 +289,7 @@ contract Atlas is Escrow { } // check if protocol allows unknown bundlers, and verify all signatures if they do else if(dConfig.callConfig.allowsUnknownBundler()) { // check dapp signature - if(!_verifyDApp(dConfig, dAppOp)) { + if(!IAtlasVerification(VERIFICATION).verifyDApp(dConfig, dAppOp)) { bool bypass = isSimulation && dAppOp.signature.length == 0; if (!bypass) { return ValidCallsResult.DAppSignatureInvalid; @@ -296,7 +297,7 @@ contract Atlas is Escrow { } // check user signature - if(!_verifyUser(dConfig, userOp)) { + if(!IAtlasVerification(VERIFICATION).verifyUser(dConfig, userOp)) { bool bypass = isSimulation && userOp.signature.length == 0; if (!bypass) { return ValidCallsResult.UserSignatureInvalid; diff --git a/src/contracts/atlas/AtlasVerification.sol b/src/contracts/atlas/AtlasVerification.sol index ebffccf2..fac822f3 100644 --- a/src/contracts/atlas/AtlasVerification.sol +++ b/src/contracts/atlas/AtlasVerification.sol @@ -172,8 +172,8 @@ contract AtlasVerification is EIP712, DAppIntegration { // the supply chain to submit data. If any other party // (user, solver, FastLane, or a collusion between // all of them) attempts to alter it, this check will fail - function _verifyDApp(DAppConfig memory dConfig, DAppOperation calldata dAppOp) - internal + function verifyDApp(DAppConfig memory dConfig, DAppOperation calldata dAppOp) + external returns (bool) { // Verify the signature before storing any data to avoid @@ -347,8 +347,8 @@ contract AtlasVerification is EIP712, DAppIntegration { // // Verify the user's meta transaction - function _verifyUser(DAppConfig memory dConfig, UserOperation calldata userOp) - internal + function verifyUser(DAppConfig memory dConfig, UserOperation calldata userOp) + external returns (bool) { diff --git a/src/contracts/interfaces/IAtlasVerification.sol b/src/contracts/interfaces/IAtlasVerification.sol index 00fbe50b..8fb73d43 100644 --- a/src/contracts/interfaces/IAtlasVerification.sol +++ b/src/contracts/interfaces/IAtlasVerification.sol @@ -1,12 +1,20 @@ //SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.18; +import "../types/UserCallTypes.sol"; +import "../types/DAppApprovalTypes.sol"; import "../types/SolverCallTypes.sol"; import "../types/EscrowTypes.sol"; interface IAtlasVerification { + function verifyUser(DAppConfig memory dConfig, UserOperation calldata userOp) + external + returns (bool); + + function verifyDApp(DAppConfig memory dConfig, DAppOperation calldata dAppOp) + external + returns (bool); - function verifySolverOp(SolverOperation calldata solverOp, EscrowAccountData memory solverEscrow, uint256 gasWaterMark, bool auctionAlreadyComplete) external view From 85ff2a9dea5b559a39427a01867a966b8089ebdb Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Fri, 3 Nov 2023 07:27:50 +0200 Subject: [PATCH 18/38] Verification split out and compiling, some tests failing --- script/deploy-atlas.s.sol | 29 +- src/contracts/atlas/Atlas.sol | 2 +- src/contracts/atlas/AtlasFactory.sol | 6 - src/contracts/atlas/AtlasVerification.sol | 2 +- src/contracts/atlas/Factory.sol | 384 +++++++++++----------- test/Accounting.t.sol | 8 +- test/MainTest.t.sol | 20 +- test/SwapIntent.t.sol | 12 +- test/base/BaseTest.t.sol | 15 +- test/base/TestUtils.sol | 66 ++-- 10 files changed, 280 insertions(+), 264 deletions(-) diff --git a/script/deploy-atlas.s.sol b/script/deploy-atlas.s.sol index 0c23390f..0e552e4e 100644 --- a/script/deploy-atlas.s.sol +++ b/script/deploy-atlas.s.sol @@ -7,12 +7,17 @@ import "forge-std/Test.sol"; import {DeployBaseScript} from "script/base/deploy-base.s.sol"; import {Atlas} from "src/contracts/atlas/Atlas.sol"; +import {AtlasFactory} from "src/contracts/atlas/AtlasFactory.sol"; +import {AtlasVerification} from "src/contracts/atlas/AtlasVerification.sol"; import {SwapIntentController} from "src/contracts/examples/intents-example/SwapIntent.sol"; import {TxBuilder} from "src/contracts/helpers/TxBuilder.sol"; import {Simulator} from "src/contracts/helpers/Simulator.sol"; contract DeployAtlasScript is DeployBaseScript { + // TODO move commons vars like these to base deploy script Atlas public atlas; + AtlasFactory public atlasFactory; + AtlasVerification public atlasVerification; Simulator public simulator; function run() external { @@ -26,7 +31,9 @@ contract DeployAtlasScript is DeployBaseScript { vm.startBroadcast(deployerPrivateKey); simulator = new Simulator(); - atlas = new Atlas(64, address(simulator), address(0)); //TODO update to Factory addr arg + atlas = new Atlas(64, address(simulator), address(0), address(0)); //TODO update to Factory and Verification addr arg + atlasFactory = new AtlasFactory(address(atlas)); + atlasVerification = new AtlasVerification(); vm.stopBroadcast(); @@ -41,6 +48,8 @@ contract DeployAtlasScript is DeployBaseScript { contract DeployAtlasAndSwapIntentDAppControlScript is DeployBaseScript { Atlas public atlas; + AtlasFactory public atlasFactory; + AtlasVerification public atlasVerification; Simulator public simulator; SwapIntentController public swapIntentControl; @@ -56,14 +65,16 @@ contract DeployAtlasAndSwapIntentDAppControlScript is DeployBaseScript { // Deploy the Atlas contract simulator = new Simulator(); - atlas = new Atlas(64, address(simulator), address(0)); //TODO update to Factory addr arg + atlas = new Atlas(64, address(simulator), address(0), address(0)); //TODO update to Factory and Verification addr arg + atlasFactory = new AtlasFactory(address(atlas)); + atlasVerification = new AtlasVerification(); // Deploy the SwapIntent DAppControl contract swapIntentControl = new SwapIntentController(address(atlas)); // Integrate SwapIntent with Atlas - atlas.initializeGovernance(address(swapIntentControl)); - atlas.integrateDApp(address(swapIntentControl)); + atlasVerification.initializeGovernance(address(swapIntentControl)); + atlasVerification.integrateDApp(address(swapIntentControl)); vm.stopBroadcast(); @@ -80,6 +91,8 @@ contract DeployAtlasAndSwapIntentDAppControlScript is DeployBaseScript { contract DeployAtlasAndSwapIntentDAppControlAndTxBuilderScript is DeployBaseScript { Atlas public atlas; + AtlasFactory public atlasFactory; + AtlasVerification public atlasVerification; Simulator public simulator; SwapIntentController public swapIntentControl; TxBuilder public txBuilder; @@ -96,14 +109,16 @@ contract DeployAtlasAndSwapIntentDAppControlAndTxBuilderScript is DeployBaseScri // Deploy the Atlas contract simulator = new Simulator(); - atlas = new Atlas(64, address(simulator), address(0)); //TODO update to Factory addr arg + atlas = new Atlas(64, address(simulator), address(0), address(0)); //TODO update to Factory and Verification addr arg + atlasFactory = new AtlasFactory(address(atlas)); + atlasVerification = new AtlasVerification(); // Deploy the SwapIntent DAppControl contract swapIntentControl = new SwapIntentController(address(atlas)); // Integrate SwapIntent with Atlas - atlas.initializeGovernance(address(swapIntentControl)); - atlas.integrateDApp(address(swapIntentControl)); + atlasVerification.initializeGovernance(address(swapIntentControl)); + atlasVerification.integrateDApp(address(swapIntentControl)); // Deploy the TxBuilder txBuilder = new TxBuilder(address(swapIntentControl), address(atlas), address(atlas)); diff --git a/src/contracts/atlas/Atlas.sol b/src/contracts/atlas/Atlas.sol index 493c3134..4008ecd9 100644 --- a/src/contracts/atlas/Atlas.sol +++ b/src/contracts/atlas/Atlas.sol @@ -32,7 +32,7 @@ contract Atlas is Escrow { uint256 private constant _MAX_GAS = 1_500_000; address public immutable FACTORY; - constructor(uint32 _escrowDuration, address _simulator, address _factory) Escrow(_escrowDuration, _simulator) { + constructor(uint32 _escrowDuration, address _simulator, address _factory, address _verification) Escrow(_escrowDuration, _simulator, _verification) { FACTORY = _factory; } diff --git a/src/contracts/atlas/AtlasFactory.sol b/src/contracts/atlas/AtlasFactory.sol index d4228137..f4646c4f 100644 --- a/src/contracts/atlas/AtlasFactory.sol +++ b/src/contracts/atlas/AtlasFactory.sol @@ -6,12 +6,6 @@ import {Mimic} from "./Mimic.sol"; import {DAppConfig} from "src/contracts/types/DAppApprovalTypes.sol"; import {ExecutionEnvironment} from "./ExecutionEnvironment.sol"; -// NOTE: Experimental - Splitting contracts into [AtlETH, Atlas, AtlasFactory] - -// AtlasFactory needs: -// Factory - everything for creating new Execution Environments -// Exec Env template deployed separately, no internal deploy functions - // TODO make sure no cases of address(this) when Atlas address is intended contract AtlasFactory { diff --git a/src/contracts/atlas/AtlasVerification.sol b/src/contracts/atlas/AtlasVerification.sol index fac822f3..f31d4529 100644 --- a/src/contracts/atlas/AtlasVerification.sol +++ b/src/contracts/atlas/AtlasVerification.sol @@ -52,7 +52,7 @@ contract AtlasVerification is EIP712, DAppIntegration { return (result, gasLimit, solverEscrow); } - function _getSolverPayload(SolverOperation calldata solverOp) internal view returns (bytes32 payload) { + function getSolverPayload(SolverOperation calldata solverOp) external view returns (bytes32 payload) { payload = _hashTypedDataV4(_getSolverHash(solverOp)); } diff --git a/src/contracts/atlas/Factory.sol b/src/contracts/atlas/Factory.sol index 2dd7b106..2a9ebe2f 100644 --- a/src/contracts/atlas/Factory.sol +++ b/src/contracts/atlas/Factory.sol @@ -1,194 +1,194 @@ -//SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.16; - -import {IDAppControl} from "../interfaces/IDAppControl.sol"; -import {Escrow} from "./Escrow.sol"; - -import {Mimic} from "./Mimic.sol"; -import {ExecutionEnvironment} from "./ExecutionEnvironment.sol"; - -import "../types/SolverCallTypes.sol"; -import "../types/UserCallTypes.sol"; -import "../types/DAppApprovalTypes.sol"; - -import {CallBits} from "../libraries/CallBits.sol"; - -contract Factory is Escrow { - //address immutable public atlas; - using CallBits for uint32; - - bytes32 public immutable salt; - address public immutable executionTemplate; - - constructor(uint32 _escrowDuration, address _simulator) Escrow(_escrowDuration, _simulator) { - //atlas = msg.sender; - salt = keccak256(abi.encodePacked(block.chainid, atlas, "Atlas 1.0")); - - executionTemplate = _deployExecutionEnvironmentTemplate( - address(this), DAppConfig({to: address(0), callConfig: uint32(0), bidToken: address(0)}) - ); - } - - // GETTERS - function getEscrowAddress() external view returns (address escrowAddress) { - escrowAddress = atlas; - } - - function execution() external view returns (address) { - return executionTemplate; - } - - function createExecutionEnvironment(address dAppControl) external returns (address executionEnvironment) { - executionEnvironment = _setExecutionEnvironment(dAppControl, msg.sender, dAppControl.codehash); - _initializeNonce(msg.sender); - } - - function getExecutionEnvironment(address user, address dAppControl) - external - view - returns (address executionEnvironment, uint32 callConfig, bool exists) - { - callConfig = IDAppControl(dAppControl).callConfig(); - executionEnvironment = _getExecutionEnvironmentCustom(user, dAppControl.codehash, dAppControl, callConfig); - exists = executionEnvironment.codehash != bytes32(0); - } - - function _getExecutionEnvironment(address user, bytes32 controlCodeHash, address controller) - internal - view - returns (address executionEnvironment) - { - uint32 callConfig = IDAppControl(controller).callConfig(); - - executionEnvironment = _getExecutionEnvironmentCustom(user, controlCodeHash, controller, callConfig); - } - - // NOTE: This func is used to generate the address of user ExecutionEnvironments that have - // been deprecated due to DAppControl changes of callConfig. - function _getExecutionEnvironmentCustom( - address user, - bytes32 controlCodeHash, - address controller, - uint32 callConfig - ) internal view returns (address executionEnvironment) { - executionEnvironment = address( - uint160( - uint256( - keccak256( - abi.encodePacked( - bytes1(0xff), - address(this), - salt, - keccak256( - abi.encodePacked(_getMimicCreationCode(controller, callConfig, user, controlCodeHash)) - ) - ) - ) - ) - ) - ); - } - - function _setExecutionEnvironment(address dAppControl, address user, bytes32 controlCodeHash) - internal - returns (address executionEnvironment) - { - uint32 callConfig = IDAppControl(dAppControl).callConfig(); - - bytes memory creationCode = _getMimicCreationCode(dAppControl, callConfig, user, controlCodeHash); - - executionEnvironment = address( - uint160( - uint256( - keccak256( - abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(abi.encodePacked(creationCode))) - ) - ) - ) - ); - - if (executionEnvironment.codehash == bytes32(0)) { - bytes32 memSalt = salt; - assembly { - executionEnvironment := create2(0, add(creationCode, 32), mload(creationCode), memSalt) - } - - emit NewExecutionEnvironment(executionEnvironment, user, dAppControl, callConfig); - } - } - - function _deployExecutionEnvironmentTemplate(address, DAppConfig memory) - internal - returns (address executionEnvironment) - { - ExecutionEnvironment _environment = new ExecutionEnvironment{ - salt: salt - }(atlas); - - executionEnvironment = address(_environment); - } - - /* - add( - shl(96, executionLib), - 0xFFFFFFFFFFFFFFFFFFFFFFFFF - ) - ) - */ - - function _getMimicCreationCode(address controller, uint32 callConfig, address user, bytes32 controlCodeHash) - internal - view - returns (bytes memory creationCode) - { - address executionLib = executionTemplate; - // NOTE: Changing compiler settings or solidity versions can break this. - creationCode = type(Mimic).creationCode; - - // TODO: unpack the SHL and reorient - assembly { - mstore( - add(creationCode, 85), - or( - and( - mload(add(creationCode, 85)), - not(shl(96, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) - ), - shl(96, executionLib) - ) - ) +// //SPDX-License-Identifier: BUSL-1.1 +// pragma solidity ^0.8.16; + +// import {IDAppControl} from "../interfaces/IDAppControl.sol"; +// import {Escrow} from "./Escrow.sol"; + +// import {Mimic} from "./Mimic.sol"; +// import {ExecutionEnvironment} from "./ExecutionEnvironment.sol"; + +// import "../types/SolverCallTypes.sol"; +// import "../types/UserCallTypes.sol"; +// import "../types/DAppApprovalTypes.sol"; + +// import {CallBits} from "../libraries/CallBits.sol"; + +// contract Factory is Escrow { +// //address immutable public atlas; +// using CallBits for uint32; + +// bytes32 public immutable salt; +// address public immutable executionTemplate; + +// constructor(uint32 _escrowDuration, address _simulator) Escrow(_escrowDuration, _simulator) { +// //atlas = msg.sender; +// salt = keccak256(abi.encodePacked(block.chainid, atlas, "Atlas 1.0")); + +// executionTemplate = _deployExecutionEnvironmentTemplate( +// address(this), DAppConfig({to: address(0), callConfig: uint32(0), bidToken: address(0)}) +// ); +// } + +// // GETTERS +// function getEscrowAddress() external view returns (address escrowAddress) { +// escrowAddress = atlas; +// } + +// function execution() external view returns (address) { +// return executionTemplate; +// } + +// function createExecutionEnvironment(address dAppControl) external returns (address executionEnvironment) { +// executionEnvironment = _setExecutionEnvironment(dAppControl, msg.sender, dAppControl.codehash); +// _initializeNonce(msg.sender); +// } + +// function getExecutionEnvironment(address user, address dAppControl) +// external +// view +// returns (address executionEnvironment, uint32 callConfig, bool exists) +// { +// callConfig = IDAppControl(dAppControl).callConfig(); +// executionEnvironment = _getExecutionEnvironmentCustom(user, dAppControl.codehash, dAppControl, callConfig); +// exists = executionEnvironment.codehash != bytes32(0); +// } + +// function _getExecutionEnvironment(address user, bytes32 controlCodeHash, address controller) +// internal +// view +// returns (address executionEnvironment) +// { +// uint32 callConfig = IDAppControl(controller).callConfig(); + +// executionEnvironment = _getExecutionEnvironmentCustom(user, controlCodeHash, controller, callConfig); +// } + +// // NOTE: This func is used to generate the address of user ExecutionEnvironments that have +// // been deprecated due to DAppControl changes of callConfig. +// function _getExecutionEnvironmentCustom( +// address user, +// bytes32 controlCodeHash, +// address controller, +// uint32 callConfig +// ) internal view returns (address executionEnvironment) { +// executionEnvironment = address( +// uint160( +// uint256( +// keccak256( +// abi.encodePacked( +// bytes1(0xff), +// address(this), +// salt, +// keccak256( +// abi.encodePacked(_getMimicCreationCode(controller, callConfig, user, controlCodeHash)) +// ) +// ) +// ) +// ) +// ) +// ); +// } + +// function _setExecutionEnvironment(address dAppControl, address user, bytes32 controlCodeHash) +// internal +// returns (address executionEnvironment) +// { +// uint32 callConfig = IDAppControl(dAppControl).callConfig(); + +// bytes memory creationCode = _getMimicCreationCode(dAppControl, callConfig, user, controlCodeHash); + +// executionEnvironment = address( +// uint160( +// uint256( +// keccak256( +// abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(abi.encodePacked(creationCode))) +// ) +// ) +// ) +// ); + +// if (executionEnvironment.codehash == bytes32(0)) { +// bytes32 memSalt = salt; +// assembly { +// executionEnvironment := create2(0, add(creationCode, 32), mload(creationCode), memSalt) +// } + +// emit NewExecutionEnvironment(executionEnvironment, user, dAppControl, callConfig); +// } +// } + +// function _deployExecutionEnvironmentTemplate(address, DAppConfig memory) +// internal +// returns (address executionEnvironment) +// { +// ExecutionEnvironment _environment = new ExecutionEnvironment{ +// salt: salt +// }(atlas); + +// executionEnvironment = address(_environment); +// } + +// /* +// add( +// shl(96, executionLib), +// 0xFFFFFFFFFFFFFFFFFFFFFFFFF +// ) +// ) +// */ + +// function _getMimicCreationCode(address controller, uint32 callConfig, address user, bytes32 controlCodeHash) +// internal +// view +// returns (bytes memory creationCode) +// { +// address executionLib = executionTemplate; +// // NOTE: Changing compiler settings or solidity versions can break this. +// creationCode = type(Mimic).creationCode; + +// // TODO: unpack the SHL and reorient +// assembly { +// mstore( +// add(creationCode, 85), +// or( +// and( +// mload(add(creationCode, 85)), +// not(shl(96, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) +// ), +// shl(96, executionLib) +// ) +// ) - mstore( - add(creationCode, 118), - or( - and( - mload(add(creationCode, 118)), - not(shl(96, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) - ), - shl(96, user) - ) - ) +// mstore( +// add(creationCode, 118), +// or( +// and( +// mload(add(creationCode, 118)), +// not(shl(96, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) +// ), +// shl(96, user) +// ) +// ) - mstore( - add(creationCode, 139), - or( - and( - mload(add(creationCode, 139)), - not(shl(56, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFF)) - ), - add(shl(96, controller), add(shl(88, 0x63), shl(56, callConfig))) - ) - ) - - mstore(add(creationCode, 165), controlCodeHash) - } - } - - function getMimicCreationCode(address controller, uint32 callConfig, address user, bytes32 controlCodeHash) - external - view - returns (bytes memory creationCode) - { - creationCode = _getMimicCreationCode(controller, callConfig, user, controlCodeHash); - } -} +// mstore( +// add(creationCode, 139), +// or( +// and( +// mload(add(creationCode, 139)), +// not(shl(56, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFF)) +// ), +// add(shl(96, controller), add(shl(88, 0x63), shl(56, callConfig))) +// ) +// ) + +// mstore(add(creationCode, 165), controlCodeHash) +// } +// } + +// function getMimicCreationCode(address controller, uint32 callConfig, address user, bytes32 controlCodeHash) +// external +// view +// returns (bytes memory creationCode) +// { +// creationCode = _getMimicCreationCode(controller, callConfig, user, controlCodeHash); +// } +// } diff --git a/test/Accounting.t.sol b/test/Accounting.t.sol index 573aecf0..5baa3b1d 100644 --- a/test/Accounting.t.sol +++ b/test/Accounting.t.sol @@ -54,8 +54,8 @@ contract AccountingTest is BaseTest { // Deploy new SwapIntent Controller from new gov and initialize in Atlas vm.startPrank(governanceEOA); swapIntentController = new SwapIntentController(address(escrow)); - atlas.initializeGovernance(address(swapIntentController)); - atlas.integrateDApp(address(swapIntentController)); + atlasVerification.initializeGovernance(address(swapIntentController)); + atlasVerification.integrateDApp(address(swapIntentController)); vm.stopPrank(); txBuilder = new TxBuilder({ @@ -201,14 +201,14 @@ contract AccountingTest is BaseTest { solverOps[0].value = solverMsgValue; // Solver signs the solverCall - (sig.v, sig.r, sig.s) = vm.sign(solverOnePK, atlas.getSolverPayload(solverOps[0])); + (sig.v, sig.r, sig.s) = vm.sign(solverOnePK, atlasVerification.getSolverPayload(solverOps[0])); solverOps[0].signature = abi.encodePacked(sig.r, sig.s, sig.v); // Frontend creates dApp Operation calldata after seeing rest of data dAppOp = txBuilder.buildDAppOperation(governanceEOA, userOp, solverOps); // Frontend signs the dApp Operation payload - (sig.v, sig.r, sig.s) = vm.sign(governancePK, atlas.getDAppOperationPayload(dAppOp)); + (sig.v, sig.r, sig.s) = vm.sign(governancePK, atlasVerification.getDAppOperationPayload(dAppOp)); dAppOp.signature = abi.encodePacked(sig.r, sig.s, sig.v); // Check user token balances before diff --git a/test/MainTest.t.sol b/test/MainTest.t.sol index 37e812ce..03c18ac3 100644 --- a/test/MainTest.t.sol +++ b/test/MainTest.t.sol @@ -49,7 +49,7 @@ contract MainTest is BaseTest { UserOperation memory userOp = helper.buildUserOperation(POOL_ONE, POOL_TWO, userEOA, TOKEN_ONE); // user does not sign their own operation when bundling - // (v, r, s) = vm.sign(userPK, atlas.getUserOperationPayload(userOp)); + // (v, r, s) = vm.sign(userPK, atlasVerification.getUserOperationPayload(userOp)); // userOp.signature = abi.encodePacked(r, s, v); SolverOperation[] memory solverOps = new SolverOperation[](2); @@ -62,7 +62,7 @@ contract MainTest is BaseTest { solverOps[1] = helper.buildSolverOperation(userOp, solverOpData, solverOneEOA, address(solverOne), WETH.balanceOf(address(solverOne)) / 20); - (v, r, s) = vm.sign(solverOnePK, atlas.getSolverPayload(solverOps[1])); + (v, r, s) = vm.sign(solverOnePK, atlasVerification.getSolverPayload(solverOps[1])); solverOps[1].signature = abi.encodePacked(r, s, v); console.log("solverTwoEOA WETH:", WETH.balanceOf(address(solverTwoEOA))); @@ -72,7 +72,7 @@ contract MainTest is BaseTest { solverOps[0] = helper.buildSolverOperation(userOp, solverOpData, solverTwoEOA, address(solverTwo), WETH.balanceOf(address(solverTwo)) / 3000); - (v, r, s) = vm.sign(solverTwoPK, atlas.getSolverPayload(solverOps[0])); + (v, r, s) = vm.sign(solverTwoPK, atlasVerification.getSolverPayload(solverOps[0])); solverOps[0].signature = abi.encodePacked(r, s, v); console.log("topBid before sorting",solverOps[0].bidAmount); @@ -85,7 +85,7 @@ contract MainTest is BaseTest { DAppOperation memory dAppOp = helper.buildDAppOperation(governanceEOA, userOp, solverOps); - (v, r, s) = vm.sign(governancePK, atlas.getDAppOperationPayload(dAppOp)); + (v, r, s) = vm.sign(governancePK, atlasVerification.getDAppOperationPayload(dAppOp)); dAppOp.signature = abi.encodePacked(r, s, v); @@ -339,7 +339,7 @@ contract MainTest is BaseTest { bytes32 s; UserOperation memory userOp = helper.buildUserOperation(POOL_ONE, POOL_TWO, userEOA, TOKEN_ONE); - (v, r, s) = vm.sign(userPK, atlas.getUserOperationPayload(userOp)); + (v, r, s) = vm.sign(userPK, atlasVerification.getUserOperationPayload(userOp)); userOp.signature = abi.encodePacked(r, s, v); vm.startPrank(userEOA); @@ -363,7 +363,7 @@ contract MainTest is BaseTest { console.log("TOKEN_ONE",TOKEN_ONE); UserOperation memory userOp = helper.buildUserOperation(POOL_ONE, POOL_TWO, userEOA, TOKEN_ONE); - (v, r, s) = vm.sign(userPK, atlas.getUserOperationPayload(userOp)); + (v, r, s) = vm.sign(userPK, atlasVerification.getUserOperationPayload(userOp)); userOp.signature = abi.encodePacked(r, s, v); SolverOperation[] memory solverOps = new SolverOperation[](1); @@ -373,11 +373,11 @@ contract MainTest is BaseTest { solverOps[0] = helper.buildSolverOperation( userOp, solverOpData, solverOneEOA, address(solverOne), 2e17 ); - (v, r, s) = vm.sign(solverOnePK, atlas.getSolverPayload(solverOps[0])); + (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, atlas.getDAppOperationPayload(dAppOp)); + (v, r, s) = vm.sign(governancePK, atlasVerification.getDAppOperationPayload(dAppOp)); dAppOp.signature = abi.encodePacked(r, s, v); vm.startPrank(userEOA); atlasFactory.createExecutionEnvironment(userOp.control); @@ -397,10 +397,10 @@ contract MainTest is BaseTest { solverOps[0] = helper.buildSolverOperation( userOp, solverOpData, solverOneEOA, address(solverOne), 2e17 ); - (v, r, s) = vm.sign(solverOnePK, atlas.getSolverPayload(solverOps[0])); + (v, r, s) = vm.sign(solverOnePK, atlasVerification.getSolverPayload(solverOps[0])); solverOps[0].signature = abi.encodePacked(r, s, v); dAppOp = helper.buildDAppOperation(governanceEOA, userOp, solverOps); - (v, r, s) = vm.sign(governancePK, atlas.getDAppOperationPayload(dAppOp)); + (v, r, s) = vm.sign(governancePK, atlasVerification.getDAppOperationPayload(dAppOp)); dAppOp.signature = abi.encodePacked(r, s, v); vm.startPrank(userEOA); (success, data) = address(simulator).call( diff --git a/test/SwapIntent.t.sol b/test/SwapIntent.t.sol index 4ac040b6..9efd1745 100644 --- a/test/SwapIntent.t.sol +++ b/test/SwapIntent.t.sol @@ -66,8 +66,8 @@ contract SwapIntentTest is BaseTest { // Deploy new SwapIntent Controller from new gov and initialize in Atlas vm.startPrank(governanceEOA); swapIntentController = new SwapIntentController(address(escrow)); - atlas.initializeGovernance(address(swapIntentController)); - atlas.integrateDApp(address(swapIntentController)); + atlasVerification.initializeGovernance(address(swapIntentController)); + atlasVerification.integrateDApp(address(swapIntentController)); vm.stopPrank(); txBuilder = new TxBuilder({ @@ -166,14 +166,14 @@ contract SwapIntentTest is BaseTest { }); // Solver signs the solverOp - (sig.v, sig.r, sig.s) = vm.sign(solverOnePK, atlas.getSolverPayload(solverOps[0])); + (sig.v, sig.r, sig.s) = vm.sign(solverOnePK, atlasVerification.getSolverPayload(solverOps[0])); solverOps[0].signature = abi.encodePacked(sig.r, sig.s, sig.v); // Frontend creates dAppOp calldata after seeing rest of data dAppOp = txBuilder.buildDAppOperation(governanceEOA, userOp, solverOps); // Frontend signs the dAppOp payload - (sig.v, sig.r, sig.s) = vm.sign(governancePK, atlas.getDAppOperationPayload(dAppOp)); + (sig.v, sig.r, sig.s) = vm.sign(governancePK, atlasVerification.getDAppOperationPayload(dAppOp)); dAppOp.signature = abi.encodePacked(sig.r, sig.s, sig.v); // Check user token balances before @@ -291,14 +291,14 @@ contract SwapIntentTest is BaseTest { }); // Solver signs the solverOp - (sig.v, sig.r, sig.s) = vm.sign(solverOnePK, atlas.getSolverPayload(solverOps[0])); + (sig.v, sig.r, sig.s) = vm.sign(solverOnePK, atlasVerification.getSolverPayload(solverOps[0])); solverOps[0].signature = abi.encodePacked(sig.r, sig.s, sig.v); // Frontend creates dAppOp calldata after seeing rest of data dAppOp = txBuilder.buildDAppOperation(governanceEOA, userOp, solverOps); // Frontend signs the dAppOp payload - (sig.v, sig.r, sig.s) = vm.sign(governancePK, atlas.getDAppOperationPayload(dAppOp)); + (sig.v, sig.r, sig.s) = vm.sign(governancePK, atlasVerification.getDAppOperationPayload(dAppOp)); dAppOp.signature = abi.encodePacked(sig.r, sig.s, sig.v); // Check user token balances before diff --git a/test/base/BaseTest.t.sol b/test/base/BaseTest.t.sol index bd2ff38b..759cfe6b 100644 --- a/test/base/BaseTest.t.sol +++ b/test/base/BaseTest.t.sol @@ -7,6 +7,7 @@ import {IDAppIntegration} from "src/contracts/interfaces/IDAppIntegration.sol"; import {Atlas} from "src/contracts/atlas/Atlas.sol"; import {AtlasFactory} from "src/contracts/atlas/AtlasFactory.sol"; +import {AtlasVerification} from "src/contracts/atlas/AtlasVerification.sol"; import {Sorter} from "src/contracts/helpers/Sorter.sol"; import {Simulator} from "src/contracts/helpers/Simulator.sol"; @@ -38,6 +39,7 @@ contract BaseTest is Test, TestConstants { Atlas public atlas; AtlasFactory public atlasFactory; + AtlasVerification public atlasVerification; Simulator public simulator; Sorter public sorter; @@ -69,14 +71,19 @@ contract BaseTest is Test, TestConstants { simulator = new Simulator(); - // Computes the address at which AtlasFactory will be deployed + // Computes the addresses at which AtlasFactory and AtlasVerification will be deployed address expectedAtlasFactoryAddr = computeCreateAddress( payee, vm.getNonce(payee) + 1 ); + address expectedAtlasVerificationAddr = computeCreateAddress( + payee, + vm.getNonce(payee) + 2 + ); - atlas = new Atlas(64, address(simulator), expectedAtlasFactoryAddr); + atlas = new Atlas(64, address(simulator), expectedAtlasFactoryAddr, expectedAtlasVerificationAddr); atlasFactory = new AtlasFactory(address(atlas)); + atlasVerification = new AtlasVerification(); simulator.setAtlas(address(atlas)); @@ -87,8 +94,8 @@ contract BaseTest is Test, TestConstants { vm.startPrank(governanceEOA); control = new V2DAppControl(escrow); - atlas.initializeGovernance(address(control)); - atlas.integrateDApp(address(control)); + atlasVerification.initializeGovernance(address(control)); + atlasVerification.integrateDApp(address(control)); vm.stopPrank(); diff --git a/test/base/TestUtils.sol b/test/base/TestUtils.sol index 67c29cf2..f73c4251 100644 --- a/test/base/TestUtils.sol +++ b/test/base/TestUtils.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.16; import {IDAppControl} from "../../src/contracts/interfaces/IDAppControl.sol"; -import {Factory} from "../../src/contracts/atlas/Factory.sol"; import {Mimic} from "../../src/contracts/atlas/Mimic.sol"; import "../../src/contracts/types/UserCallTypes.sol"; @@ -75,38 +74,39 @@ library TestUtils { return string(output); } - function computeExecutionEnvironment(address payable atlas, UserOperation calldata userOp, address controller) - public - view - returns (address executionEnvironment) - { - DAppConfig memory dConfig = IDAppControl(controller).getDAppConfig(userOp); - - executionEnvironment = address( - uint160( - uint256( - keccak256( - abi.encodePacked( - bytes1(0xff), - atlas, - Factory(atlas).salt(), - keccak256( - abi.encodePacked( - _getMimicCreationCode( - controller, - dConfig.callConfig, - Factory(atlas).execution(), - userOp.from, - controller.codehash - ) - ) - ) - ) - ) - ) - ) - ); - } + // TODO fix this utility fn - after AtlasFactory split + // function computeExecutionEnvironment(address payable atlas, UserOperation calldata userOp, address controller) + // public + // view + // returns (address executionEnvironment) + // { + // DAppConfig memory dConfig = IDAppControl(controller).getDAppConfig(userOp); + + // executionEnvironment = address( + // uint160( + // uint256( + // keccak256( + // abi.encodePacked( + // bytes1(0xff), + // atlas, + // atlasFactory.salt(), + // keccak256( + // abi.encodePacked( + // _getMimicCreationCode( + // controller, + // dConfig.callConfig, + // atlasFactory.execution(), + // userOp.from, + // controller.codehash + // ) + // ) + // ) + // ) + // ) + // ) + // ) + // ); + // } function _getMimicCreationCode( address controller, From 5e2b06649f2ca5318b1490d563a708bc3995ecd1 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Fri, 3 Nov 2023 16:38:45 +0200 Subject: [PATCH 19/38] More AtlasVerification test and interface fixes --- src/contracts/helpers/TxBuilder.sol | 13 ++++---- src/contracts/interfaces/IAtlas.sol | 11 +------ .../interfaces/IAtlasVerification.sol | 10 ++++++ test/Accounting.t.sol | 13 ++------ test/DAppOperationSigner.sol | 33 ------------------- test/MainTest.t.sol | 1 - test/SwapIntent.t.sol | 4 +-- 7 files changed, 22 insertions(+), 63 deletions(-) delete mode 100644 test/DAppOperationSigner.sol diff --git a/src/contracts/helpers/TxBuilder.sol b/src/contracts/helpers/TxBuilder.sol index f494ff9e..ba8d51d3 100644 --- a/src/contracts/helpers/TxBuilder.sol +++ b/src/contracts/helpers/TxBuilder.sol @@ -5,6 +5,7 @@ import {IDAppControl} from "../interfaces/IDAppControl.sol"; import {IDAppIntegration} from "../interfaces/IDAppIntegration.sol"; import {IAtlas} from "../interfaces/IAtlas.sol"; import {IAtlETH} from "../interfaces/IAtlETH.sol"; +import {IAtlasVerification} from "../interfaces/IAtlasVerification.sol"; import "../types/SolverCallTypes.sol"; import "../types/UserCallTypes.sol"; @@ -19,28 +20,28 @@ contract TxBuilder { using CallVerification for UserOperation; address public immutable control; - address public immutable escrow; address public immutable atlas; + address public immutable verification; uint256 public immutable gas; - constructor(address controller, address escrowAddress, address atlasAddress) { + constructor(address controller, address atlasAddress, address _verification) { control = controller; - escrow = escrowAddress; atlas = atlasAddress; + verification = _verification; gas = 1_000_000; } function solverNextNonce(address solverSigner) public view returns (uint256) { - return IAtlETH(escrow).nextAccountNonce(solverSigner); + return IAtlETH(atlas).nextAccountNonce(solverSigner); } function governanceNextNonce(address signatory) public view returns (uint256) { - return IAtlas(atlas).getNextNonce(signatory); + return IAtlasVerification(verification).getNextNonce(signatory); } function userNextNonce(address user) public view returns (uint256) { - return IAtlas(atlas).getNextNonce(user); + return IAtlasVerification(verification).getNextNonce(user); } function getControlCodeHash(address dAppControl) external view returns (bytes32) { diff --git a/src/contracts/interfaces/IAtlas.sol b/src/contracts/interfaces/IAtlas.sol index 4ee7656f..92516b24 100644 --- a/src/contracts/interfaces/IAtlas.sol +++ b/src/contracts/interfaces/IAtlas.sol @@ -15,6 +15,7 @@ interface IAtlas { function withdrawERC20(address token, uint256 amount, DAppConfig memory dConfig) external; function withdrawEther(uint256 amount, DAppConfig memory dConfig) external; + // TODO remove this and inside Atlas - escrow addr is Atlas addr function getEscrowAddress() external view returns (address escrowAddress); function userDirectVerifyDApp( @@ -26,14 +27,4 @@ interface IAtlas { ) external returns (bool); function userDirectReleaseLock(address userOpFrom, bytes32 key, DAppConfig calldata dConfig) external; - - function getDAppOperationPayload(DAppOperation memory verification) external view returns (bytes32 payload); - - function getSolverPayload(SolverOperation calldata solverOp) external view returns (bytes32 payload); - - function getUserOperationPayload(UserOperation memory userOp) external view returns (bytes32 payload); - - function getNextNonce(address account) external view returns (uint256 nextNonce); - - function getDomainSeparator() external view returns (bytes32 domainSeparator); } diff --git a/src/contracts/interfaces/IAtlasVerification.sol b/src/contracts/interfaces/IAtlasVerification.sol index 8fb73d43..8cc4dbc2 100644 --- a/src/contracts/interfaces/IAtlasVerification.sol +++ b/src/contracts/interfaces/IAtlasVerification.sol @@ -19,4 +19,14 @@ interface IAtlasVerification { external view returns (uint256 result, uint256 gasLimit, EscrowAccountData memory); + + + function getUserOperationPayload(UserOperation memory userOp) external view returns (bytes32 payload); + function getNextNonce(address account) external view returns (uint256 nextNonce); + + function initializeGovernance(address controller) external; + function addSignatory(address controller, address signatory) external; + function removeSignatory(address controller, address signatory) external; + function integrateDApp(address dAppControl) external; + function disableDApp(address dAppControl) external; } \ No newline at end of file diff --git a/test/Accounting.t.sol b/test/Accounting.t.sol index 5baa3b1d..cae143af 100644 --- a/test/Accounting.t.sol +++ b/test/Accounting.t.sol @@ -60,20 +60,11 @@ contract AccountingTest is BaseTest { txBuilder = new TxBuilder({ controller: address(swapIntentController), - escrowAddress: address(escrow), - atlasAddress: address(atlas) + atlasAddress: address(atlas), + _verification: address(atlasVerification) }); } - function testDELEET() public { - uint16 lockState = uint16(61711); // phase 4 - HandlingPayments - uint16 phase = SafetyBits.getCurrentExecutionPhase(lockState); - console.log(TestUtils.uint16ToBinaryString(61711)); - console.log("phase", phase); - console.log("Exec Phase", uint8(ExecutionPhase.Releasing)); - } - - function testSolverBorrowRepaySuccessfully() public { // Solver deploys the RFQ solver contract (defined at bottom of this file) diff --git a/test/DAppOperationSigner.sol b/test/DAppOperationSigner.sol deleted file mode 100644 index 22e1dfb4..00000000 --- a/test/DAppOperationSigner.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.18; - -import {IAtlas} from "../src/contracts/interfaces/IAtlas.sol"; - -import "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol"; -import "openzeppelin-contracts/contracts/utils/cryptography/EIP712.sol"; - -import {DAppVerification} from "../src/contracts/atlas/DAppVerification.sol"; - -import "../src/contracts/types/DAppApprovalTypes.sol"; - -import {TestConstants} from "./base/TestConstants.sol"; - -import {CallVerification} from "../src/contracts/libraries/CallVerification.sol"; - -import "forge-std/Test.sol"; - -contract DAppOperationSigner is Test, TestConstants, DAppVerification { - function signDAppOperation(DAppOperation memory verification, address atlas, uint256 privateKey) - public - view - returns (DAppOperation memory) - { - bytes32 payload = IAtlas(atlas).getDAppOperationPayload(verification); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, payload); - - verification.signature = abi.encodePacked(r, s, v); - - return verification; - } -} diff --git a/test/MainTest.t.sol b/test/MainTest.t.sol index 03c18ac3..f467cf12 100644 --- a/test/MainTest.t.sol +++ b/test/MainTest.t.sol @@ -23,7 +23,6 @@ import "../src/contracts/types/DAppApprovalTypes.sol"; import {BaseTest} from "./base/BaseTest.t.sol"; import {V2Helper} from "./V2Helper.sol"; -import {DAppOperationSigner} from "./DAppOperationSigner.sol"; import "forge-std/Test.sol"; diff --git a/test/SwapIntent.t.sol b/test/SwapIntent.t.sol index 9efd1745..35cda5d6 100644 --- a/test/SwapIntent.t.sol +++ b/test/SwapIntent.t.sol @@ -72,8 +72,8 @@ contract SwapIntentTest is BaseTest { txBuilder = new TxBuilder({ controller: address(swapIntentController), - escrowAddress: address(escrow), - atlasAddress: address(atlas) + atlasAddress: address(atlas), + _verification: address(atlasVerification) }); // Deposit ETH from Searcher signer to pay for searcher's gas From 8c0de2a73e0947cb196c62236ddb8ca44ef6ca15 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Fri, 3 Nov 2023 16:57:02 +0200 Subject: [PATCH 20/38] Remove redundant files and code --- src/contracts/atlas/DAppVerification.sol | 291 ----------------------- src/contracts/atlas/Factory.sol | 194 --------------- src/contracts/interfaces/IAtlas.sol | 3 - test/Factory.t.sol | 4 - 4 files changed, 492 deletions(-) delete mode 100644 src/contracts/atlas/DAppVerification.sol delete mode 100644 src/contracts/atlas/Factory.sol diff --git a/src/contracts/atlas/DAppVerification.sol b/src/contracts/atlas/DAppVerification.sol deleted file mode 100644 index 89b5b6c0..00000000 --- a/src/contracts/atlas/DAppVerification.sol +++ /dev/null @@ -1,291 +0,0 @@ -//SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.16; - -import "openzeppelin-contracts/contracts/utils/cryptography/EIP712.sol"; - -import {CallBits} from "../libraries/CallBits.sol"; - -import "../types/UserCallTypes.sol"; -import "../types/GovernanceTypes.sol"; - -import "../types/DAppApprovalTypes.sol"; - -import {DAppIntegration} from "./DAppIntegration.sol"; - -import "forge-std/Test.sol"; // TODO remove - -// This contract exists so that dapp frontends can sign and confirm the -// calldata for users. Users already trust the frontends to build and verify -// their calldata. This allows users to know that any CallData sourced via -// an external relay (such as FastLane) has been verified by the already-trusted -// frontend -contract DAppVerification is EIP712, DAppIntegration { - using ECDSA for bytes32; - using CallBits for uint32; - - constructor() EIP712("ProtoCallHandler", "0.0.1") {} - - - // - // DAPP VERIFICATION - // - - // Verify that the dapp's front end generated the preOps - // information and that it matches the on-chain data. - // Verify that the dapp's front end's data is based on - // the data submitted by the user and by the solvers. - // NOTE: the dapp's front end is the last party in - // the supply chain to submit data. If any other party - // (user, solver, FastLane, or a collusion between - // all of them) attempts to alter it, this check will fail - function _verifyDApp(DAppConfig memory dConfig, DAppOperation calldata dAppOp) - internal - returns (bool) - { - // Verify the signature before storing any data to avoid - // spoof transactions clogging up dapp nonces - if (!_verifyDAppSignature(dAppOp)) { - return false; - } - - // NOTE: to avoid replay attacks arising from key management errors, - // the state changes below must be *saved* even if they render the - // transaction invalid. - // TODO: consider dapp-owned gas escrow. Enshrined account - // abstraction may render that redundant at a large scale, but - // allocating different parts of the tx to different parties - // will allow for optimized trustlessness. This could lead to - // users not having to trust the front end at all - a huge - // improvement over the current experience. - - GovernanceData memory govData = governance[dConfig.to]; - - // Verify that the dapp is onboarded and that the call config is - // genuine. - bytes32 dAppKey = keccak256(abi.encode(dConfig.to, govData.governance, dConfig.callConfig)); - - // Make sure the signer is currently enabled by dapp owner - if (!signatories[keccak256(abi.encode(govData.governance, dAppOp.from))]) { - return (false); - } - - if (dAppOp.control != dConfig.to) { - return (false); - } - - // NOTE: This check does not work if DAppControl is a proxy contract. - // To avoid exposure to social engineering vulnerabilities, disgruntled - // former employees, or beneficiary uncertainty during intra-DAO conflict, - // governance should refrain from using a proxy contract for DAppControl. - if (dConfig.to.codehash == bytes32(0) || dapps[dAppKey] != dConfig.to.codehash) { - return (false); - } - - // If the dapp indicated that they only accept sequenced nonces - // (IE for FCFS execution), check and make sure the order is correct - // NOTE: allowing only sequenced nonces could create a scenario in - // which builders or validators may be able to profit via censorship. - // DApps are encouraged to rely on the deadline parameter. - if (!_handleNonces(dAppOp.from, dAppOp.nonce, dConfig.callConfig.needsSequencedNonces())) { - return (false); - } - - return (true); - } - - function _handleNonces(address account, uint256 nonce, bool async) internal returns (bool validNonce) { - if (nonce > type(uint128).max - 1) { - return (false); - } - - if (nonce == 0) { - return (false); - } - - uint256 bitmapIndex = (nonce / 240) + 1; // +1 because highestFullBitmap initializes at 0 - uint256 bitmapNonce = (nonce % 240) + 1; - - bytes32 bitmapKey = keccak256(abi.encode(account, bitmapIndex)); - - NonceBitmap memory nonceBitmap = asyncNonceBitmap[bitmapKey]; - - uint256 bitmap = uint256(nonceBitmap.bitmap); - if (bitmap & (1 << bitmapNonce) != 0) { - return (false); - } - - bitmap |= 1 << bitmapNonce; - nonceBitmap.bitmap = uint240(bitmap); - - uint256 highestUsedBitmapNonce = uint256(nonceBitmap.highestUsedNonce); - if (bitmapNonce > highestUsedBitmapNonce) { - nonceBitmap.highestUsedNonce = uint8(bitmapNonce); - } - - // Update the nonceBitmap - asyncNonceBitmap[bitmapKey] = nonceBitmap; - - // Update the nonce tracker - return _updateNonceTracker(account, highestUsedBitmapNonce, bitmapIndex, bitmapNonce, async); - } - - function _updateNonceTracker( - address account, uint256 highestUsedBitmapNonce, uint256 bitmapIndex, uint256 bitmapNonce, bool async - ) - internal - returns (bool) - { - NonceTracker memory nonceTracker = asyncNonceBitIndex[account]; - - uint256 highestFullBitmap = uint256(nonceTracker.HighestFullBitmap); - uint256 lowestEmptyBitmap = uint256(nonceTracker.LowestEmptyBitmap); - - // Handle non-async nonce logic - if (!async) { - if (bitmapIndex != highestFullBitmap + 1) { - return (false); - } - - if (bitmapNonce != highestUsedBitmapNonce +1) { - return (false); - } - } - - if (bitmapNonce > uint256(239) || !async) { - bool updateTracker; - - if (bitmapIndex > highestFullBitmap) { - updateTracker = true; - highestFullBitmap = bitmapIndex; - } - - if (bitmapIndex + 2 > lowestEmptyBitmap) { - updateTracker = true; - lowestEmptyBitmap = (lowestEmptyBitmap > bitmapIndex ? lowestEmptyBitmap + 1 : bitmapIndex + 2); - } - - if (updateTracker) { - asyncNonceBitIndex[account] = NonceTracker({ - HighestFullBitmap: uint128(highestFullBitmap), - LowestEmptyBitmap: uint128(lowestEmptyBitmap) - }); - } - } - return true; - } - - function _getProofHash(DAppOperation memory approval) internal pure returns (bytes32 proofHash) { - proofHash = keccak256( - abi.encode( - DAPP_TYPE_HASH, - approval.from, - approval.to, - approval.value, - approval.gas, - approval.maxFeePerGas, - approval.nonce, - approval.deadline, - approval.control, - approval.userOpHash, - approval.callChainHash - ) - ); - } - - function _verifyDAppSignature(DAppOperation calldata dAppOp) internal view returns (bool) { - if (dAppOp.signature.length == 0) { return false; } - address signer = _hashTypedDataV4(_getProofHash(dAppOp)).recover(dAppOp.signature); - - return signer == dAppOp.from; - // return true; - } - - function getDAppOperationPayload(DAppOperation memory dAppOp) public view returns (bytes32 payload) { - payload = _hashTypedDataV4(_getProofHash(dAppOp)); - } - - function getDomainSeparator() external view returns (bytes32 domainSeparator) { - domainSeparator = _domainSeparatorV4(); - } - - // - // USER VERIFICATION - // - - // Verify the user's meta transaction - function _verifyUser(DAppConfig memory dConfig, UserOperation calldata userOp) - internal - returns (bool) - { - - // Verify the signature before storing any data to avoid - // spoof transactions clogging up dapp userNonces - if (!_verifyUserSignature(userOp)) { - return false; - } - - if (userOp.control != dConfig.to) { - return (false); - } - - // If the dapp indicated that they only accept sequenced userNonces - // (IE for FCFS execution), check and make sure the order is correct - // NOTE: allowing only sequenced userNonces could create a scenario in - // which builders or validators may be able to profit via censorship. - // DApps are encouraged to rely on the deadline parameter - // to prevent replay attacks. - if (!_handleNonces(userOp.from, userOp.nonce, dConfig.callConfig.needsSequencedNonces())) { - return (false); - } - - return (true); - } - - function _getProofHash(UserOperation memory userOp) internal pure returns (bytes32 proofHash) { - proofHash = keccak256( - abi.encode( - USER_TYPE_HASH, - userOp.from, - userOp.to, - userOp.value, - userOp.gas, - userOp.maxFeePerGas, - userOp.nonce, - userOp.deadline, - userOp.dapp, - userOp.control, - keccak256(userOp.data) - ) - ); - } - - function _verifyUserSignature(UserOperation calldata userOp) internal view returns (bool) { - if (userOp.signature.length == 0) { return false; } - address signer = _hashTypedDataV4(_getProofHash(userOp)).recover(userOp.signature); - - return signer == userOp.from; - } - - function getUserOperationPayload(UserOperation memory userOp) public view returns (bytes32 payload) { - payload = _hashTypedDataV4(_getProofHash(userOp)); - } - - function getNextNonce(address account) external view returns (uint256 nextNonce) { - NonceTracker memory nonceTracker = asyncNonceBitIndex[account]; - - uint256 nextBitmapIndex = uint256(nonceTracker.HighestFullBitmap) + 1; - uint256 lowestEmptyBitmap = uint256(nonceTracker.LowestEmptyBitmap); - - if (lowestEmptyBitmap == 0) { - return 1; // uninitialized - } - - bytes32 bitmapKey = keccak256(abi.encode(account, nextBitmapIndex)); - - NonceBitmap memory nonceBitmap = asyncNonceBitmap[bitmapKey]; - - uint256 highestUsedNonce = uint256(nonceBitmap.highestUsedNonce); // has a +1 offset - - nextNonce = ((nextBitmapIndex - 1) * 240) + highestUsedNonce; - } -} diff --git a/src/contracts/atlas/Factory.sol b/src/contracts/atlas/Factory.sol deleted file mode 100644 index 2a9ebe2f..00000000 --- a/src/contracts/atlas/Factory.sol +++ /dev/null @@ -1,194 +0,0 @@ -// //SPDX-License-Identifier: BUSL-1.1 -// pragma solidity ^0.8.16; - -// import {IDAppControl} from "../interfaces/IDAppControl.sol"; -// import {Escrow} from "./Escrow.sol"; - -// import {Mimic} from "./Mimic.sol"; -// import {ExecutionEnvironment} from "./ExecutionEnvironment.sol"; - -// import "../types/SolverCallTypes.sol"; -// import "../types/UserCallTypes.sol"; -// import "../types/DAppApprovalTypes.sol"; - -// import {CallBits} from "../libraries/CallBits.sol"; - -// contract Factory is Escrow { -// //address immutable public atlas; -// using CallBits for uint32; - -// bytes32 public immutable salt; -// address public immutable executionTemplate; - -// constructor(uint32 _escrowDuration, address _simulator) Escrow(_escrowDuration, _simulator) { -// //atlas = msg.sender; -// salt = keccak256(abi.encodePacked(block.chainid, atlas, "Atlas 1.0")); - -// executionTemplate = _deployExecutionEnvironmentTemplate( -// address(this), DAppConfig({to: address(0), callConfig: uint32(0), bidToken: address(0)}) -// ); -// } - -// // GETTERS -// function getEscrowAddress() external view returns (address escrowAddress) { -// escrowAddress = atlas; -// } - -// function execution() external view returns (address) { -// return executionTemplate; -// } - -// function createExecutionEnvironment(address dAppControl) external returns (address executionEnvironment) { -// executionEnvironment = _setExecutionEnvironment(dAppControl, msg.sender, dAppControl.codehash); -// _initializeNonce(msg.sender); -// } - -// function getExecutionEnvironment(address user, address dAppControl) -// external -// view -// returns (address executionEnvironment, uint32 callConfig, bool exists) -// { -// callConfig = IDAppControl(dAppControl).callConfig(); -// executionEnvironment = _getExecutionEnvironmentCustom(user, dAppControl.codehash, dAppControl, callConfig); -// exists = executionEnvironment.codehash != bytes32(0); -// } - -// function _getExecutionEnvironment(address user, bytes32 controlCodeHash, address controller) -// internal -// view -// returns (address executionEnvironment) -// { -// uint32 callConfig = IDAppControl(controller).callConfig(); - -// executionEnvironment = _getExecutionEnvironmentCustom(user, controlCodeHash, controller, callConfig); -// } - -// // NOTE: This func is used to generate the address of user ExecutionEnvironments that have -// // been deprecated due to DAppControl changes of callConfig. -// function _getExecutionEnvironmentCustom( -// address user, -// bytes32 controlCodeHash, -// address controller, -// uint32 callConfig -// ) internal view returns (address executionEnvironment) { -// executionEnvironment = address( -// uint160( -// uint256( -// keccak256( -// abi.encodePacked( -// bytes1(0xff), -// address(this), -// salt, -// keccak256( -// abi.encodePacked(_getMimicCreationCode(controller, callConfig, user, controlCodeHash)) -// ) -// ) -// ) -// ) -// ) -// ); -// } - -// function _setExecutionEnvironment(address dAppControl, address user, bytes32 controlCodeHash) -// internal -// returns (address executionEnvironment) -// { -// uint32 callConfig = IDAppControl(dAppControl).callConfig(); - -// bytes memory creationCode = _getMimicCreationCode(dAppControl, callConfig, user, controlCodeHash); - -// executionEnvironment = address( -// uint160( -// uint256( -// keccak256( -// abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(abi.encodePacked(creationCode))) -// ) -// ) -// ) -// ); - -// if (executionEnvironment.codehash == bytes32(0)) { -// bytes32 memSalt = salt; -// assembly { -// executionEnvironment := create2(0, add(creationCode, 32), mload(creationCode), memSalt) -// } - -// emit NewExecutionEnvironment(executionEnvironment, user, dAppControl, callConfig); -// } -// } - -// function _deployExecutionEnvironmentTemplate(address, DAppConfig memory) -// internal -// returns (address executionEnvironment) -// { -// ExecutionEnvironment _environment = new ExecutionEnvironment{ -// salt: salt -// }(atlas); - -// executionEnvironment = address(_environment); -// } - -// /* -// add( -// shl(96, executionLib), -// 0xFFFFFFFFFFFFFFFFFFFFFFFFF -// ) -// ) -// */ - -// function _getMimicCreationCode(address controller, uint32 callConfig, address user, bytes32 controlCodeHash) -// internal -// view -// returns (bytes memory creationCode) -// { -// address executionLib = executionTemplate; -// // NOTE: Changing compiler settings or solidity versions can break this. -// creationCode = type(Mimic).creationCode; - -// // TODO: unpack the SHL and reorient -// assembly { -// mstore( -// add(creationCode, 85), -// or( -// and( -// mload(add(creationCode, 85)), -// not(shl(96, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) -// ), -// shl(96, executionLib) -// ) -// ) - -// mstore( -// add(creationCode, 118), -// or( -// and( -// mload(add(creationCode, 118)), -// not(shl(96, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) -// ), -// shl(96, user) -// ) -// ) - -// mstore( -// add(creationCode, 139), -// or( -// and( -// mload(add(creationCode, 139)), -// not(shl(56, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFF)) -// ), -// add(shl(96, controller), add(shl(88, 0x63), shl(56, callConfig))) -// ) -// ) - -// mstore(add(creationCode, 165), controlCodeHash) -// } -// } - -// function getMimicCreationCode(address controller, uint32 callConfig, address user, bytes32 controlCodeHash) -// external -// view -// returns (bytes memory creationCode) -// { -// creationCode = _getMimicCreationCode(controller, callConfig, user, controlCodeHash); -// } -// } diff --git a/src/contracts/interfaces/IAtlas.sol b/src/contracts/interfaces/IAtlas.sol index 92516b24..878aca48 100644 --- a/src/contracts/interfaces/IAtlas.sol +++ b/src/contracts/interfaces/IAtlas.sol @@ -15,9 +15,6 @@ interface IAtlas { function withdrawERC20(address token, uint256 amount, DAppConfig memory dConfig) external; function withdrawEther(uint256 amount, DAppConfig memory dConfig) external; - // TODO remove this and inside Atlas - escrow addr is Atlas addr - function getEscrowAddress() external view returns (address escrowAddress); - function userDirectVerifyDApp( address userOpFrom, address userOpTo, diff --git a/test/Factory.t.sol b/test/Factory.t.sol index 18314d46..279df57a 100644 --- a/test/Factory.t.sol +++ b/test/Factory.t.sol @@ -86,8 +86,4 @@ contract FactoryTest is BaseTest { // "atlas.getExecEnv address not same as predicted" // ); // } - - // function testGetEscrowAddress() public { - // assertEq(atlas.getEscrowAddress(), address(atlas)); - // } } From 49dc2c9ec4996cb1d340178a9cb1318ae1e059a0 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Sun, 5 Nov 2023 18:49:56 +0200 Subject: [PATCH 21/38] Convert string error to custom err --- src/contracts/atlas/Atlas.sol | 2 +- src/contracts/types/Emissions.sol | 1 + test/SwapIntent.t.sol | 6 ++---- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/contracts/atlas/Atlas.sol b/src/contracts/atlas/Atlas.sol index 4008ecd9..71e98bbf 100644 --- a/src/contracts/atlas/Atlas.sol +++ b/src/contracts/atlas/Atlas.sol @@ -94,7 +94,7 @@ contract Atlas is Escrow { ) external payable returns (bool auctionWon, uint256 accruedGasRebate, uint256 winningSearcherIndex) { // This is a self.call made externally so that it can be used with try/catch - require(msg.sender == address(this), "ERR-F06 InvalidAccess"); + if(msg.sender != address(this)) revert InvalidAccess(); // Build the memory lock EscrowKey memory key = _buildEscrowLock(dConfig, executionEnvironment, uint8(solverOps.length), bundler == simulator); diff --git a/src/contracts/types/Emissions.sol b/src/contracts/types/Emissions.sol index a99cbc95..78ab9b98 100644 --- a/src/contracts/types/Emissions.sol +++ b/src/contracts/types/Emissions.sol @@ -66,6 +66,7 @@ contract FastLaneErrorsEvents { // error SolverFail(); // Only sim version of err is used error PostOpsFail(); error RevertToReuse(); + error InvalidAccess(); // NEW - Escrow error UncoveredResult(); diff --git a/test/SwapIntent.t.sol b/test/SwapIntent.t.sol index 35cda5d6..b038efd3 100644 --- a/test/SwapIntent.t.sol +++ b/test/SwapIntent.t.sol @@ -194,14 +194,12 @@ contract SwapIntentTest is BaseTest { vm.startPrank(userEOA); - assertFalse(simulator.simUserOperation(userOp), "metasimUserOperationcall tested true a"); + // assertFalse(simulator.simUserOperation(userOp), "metasimUserOperationcall tested true a"); WETH.approve(address(atlas), swapIntent.amountUserSells); - assertTrue(simulator.simUserOperation(userOp), "metasimUserOperationcall tested false c"); + // assertTrue(simulator.simUserOperation(userOp), "metasimUserOperationcall tested false c"); - - // NOTE: Should metacall return something? Feels like a lot of data you might want to know about the tx atlas.metacall({ userOp: userOp, solverOps: solverOps, From 75d7407a697d084e44ff55b4b07ce701dbfd5dd7 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Sun, 5 Nov 2023 18:58:36 +0200 Subject: [PATCH 22/38] Fix: Verification checks Atlas address instead of own --- script/deploy-atlas.s.sol | 6 +++--- src/contracts/atlas/AtlasVerification.sol | 6 +++--- src/contracts/atlas/DAppIntegration.sol | 9 ++++++--- test/base/BaseTest.t.sol | 2 +- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/script/deploy-atlas.s.sol b/script/deploy-atlas.s.sol index 0e552e4e..3d6ad6b0 100644 --- a/script/deploy-atlas.s.sol +++ b/script/deploy-atlas.s.sol @@ -33,7 +33,7 @@ contract DeployAtlasScript is DeployBaseScript { simulator = new Simulator(); atlas = new Atlas(64, address(simulator), address(0), address(0)); //TODO update to Factory and Verification addr arg atlasFactory = new AtlasFactory(address(atlas)); - atlasVerification = new AtlasVerification(); + atlasVerification = new AtlasVerification(address(atlas)); vm.stopBroadcast(); @@ -67,7 +67,7 @@ contract DeployAtlasAndSwapIntentDAppControlScript is DeployBaseScript { simulator = new Simulator(); atlas = new Atlas(64, address(simulator), address(0), address(0)); //TODO update to Factory and Verification addr arg atlasFactory = new AtlasFactory(address(atlas)); - atlasVerification = new AtlasVerification(); + atlasVerification = new AtlasVerification(address(atlas)); // Deploy the SwapIntent DAppControl contract swapIntentControl = new SwapIntentController(address(atlas)); @@ -111,7 +111,7 @@ contract DeployAtlasAndSwapIntentDAppControlAndTxBuilderScript is DeployBaseScri simulator = new Simulator(); atlas = new Atlas(64, address(simulator), address(0), address(0)); //TODO update to Factory and Verification addr arg atlasFactory = new AtlasFactory(address(atlas)); - atlasVerification = new AtlasVerification(); + atlasVerification = new AtlasVerification(address(atlas)); // Deploy the SwapIntent DAppControl contract swapIntentControl = new SwapIntentController(address(atlas)); diff --git a/src/contracts/atlas/AtlasVerification.sol b/src/contracts/atlas/AtlasVerification.sol index f31d4529..3bbe63f8 100644 --- a/src/contracts/atlas/AtlasVerification.sol +++ b/src/contracts/atlas/AtlasVerification.sol @@ -30,7 +30,7 @@ contract AtlasVerification is EIP712, DAppIntegration { using ECDSA for bytes32; using CallBits for uint32; - constructor() EIP712("ProtoCallHandler", "0.0.1") {} + constructor(address _atlas) EIP712("ProtoCallHandler", "0.0.1") DAppIntegration(_atlas) {} // PORTED FROM ESCROW - TODO reorder @@ -69,7 +69,7 @@ contract AtlasVerification is EIP712, DAppIntegration { { // TODO big unchecked block - audit/review carefully unchecked { - if (solverOp.to != address(this)) { + if (solverOp.to != ATLAS) { result |= 1 << uint256(SolverOutcome.InvalidTo); } @@ -103,7 +103,7 @@ contract AtlasVerification is EIP712, DAppIntegration { } // Verify that we can lend the solver their tx value - if (solverOp.value > address(this).balance - (gasLimit * tx.gasprice)) { + if (solverOp.value > ATLAS.balance - (gasLimit * tx.gasprice)) { result |= 1 << uint256(SolverOutcome.CallValueTooHigh); } diff --git a/src/contracts/atlas/DAppIntegration.sol b/src/contracts/atlas/DAppIntegration.sol index a964d324..4f10a807 100644 --- a/src/contracts/atlas/DAppIntegration.sol +++ b/src/contracts/atlas/DAppIntegration.sol @@ -29,15 +29,14 @@ contract DAppIntegration { uint128 HighestFullBitmap; } + address public immutable ATLAS; + // from nonceTracker mapping(address => NonceTracker) public asyncNonceBitIndex; // keccak256(from, bitmapNonceIndex) => to mapping(bytes32 => NonceBitmap) public asyncNonceBitmap; - - - // NOTE: To prevent builder censorship, dapp nonces can be // processed in any order so long as they arent duplicated and // as long as the dapp opts in to it @@ -51,6 +50,10 @@ contract DAppIntegration { mapping(bytes32 => bytes32) public dapps; + constructor(address _atlas) { + ATLAS = _atlas; + } + // Permissionlessly integrates a new dapp function initializeGovernance(address controller) external { address govAddress = IDAppControl(controller).getDAppSignatory(); diff --git a/test/base/BaseTest.t.sol b/test/base/BaseTest.t.sol index 759cfe6b..0cbaf668 100644 --- a/test/base/BaseTest.t.sol +++ b/test/base/BaseTest.t.sol @@ -83,7 +83,7 @@ contract BaseTest is Test, TestConstants { atlas = new Atlas(64, address(simulator), expectedAtlasFactoryAddr, expectedAtlasVerificationAddr); atlasFactory = new AtlasFactory(address(atlas)); - atlasVerification = new AtlasVerification(); + atlasVerification = new AtlasVerification(address(atlas)); simulator.setAtlas(address(atlas)); From 8e84210abfa72527a10428fadac56b57bed0ed36 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Sun, 5 Nov 2023 19:03:07 +0200 Subject: [PATCH 23/38] Fixed testMain - V2Helper needed verification addr --- test/MainTest.t.sol | 2 -- test/V2Helper.sol | 4 ++-- test/base/BaseTest.t.sol | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/test/MainTest.t.sol b/test/MainTest.t.sol index f467cf12..66c4bf1d 100644 --- a/test/MainTest.t.sol +++ b/test/MainTest.t.sol @@ -3,8 +3,6 @@ pragma solidity ^0.8.18; import {SafeTransferLib, ERC20} from "solmate/utils/SafeTransferLib.sol"; -import {IEscrow} from "../src/contracts/interfaces/IEscrow.sol"; -import {IAtlas} from "../src/contracts/interfaces/IAtlas.sol"; import {IDAppIntegration} from "../src/contracts/interfaces/IDAppIntegration.sol"; import {IExecutionEnvironment} from "../src/contracts/interfaces/IExecutionEnvironment.sol"; diff --git a/test/V2Helper.sol b/test/V2Helper.sol index 18345443..721738f1 100644 --- a/test/V2Helper.sol +++ b/test/V2Helper.sol @@ -22,8 +22,8 @@ contract V2Helper is Test, TestConstants, TxBuilder { uint256 public immutable maxFeePerGas; - constructor(address controller, address escrowAddress, address atlasAddress) - TxBuilder(controller, escrowAddress, atlasAddress) + constructor(address controller, address atlasAddress, address verification) + TxBuilder(controller, atlasAddress, verification) { maxFeePerGas = tx.gasprice * 2; } diff --git a/test/base/BaseTest.t.sol b/test/base/BaseTest.t.sol index 0cbaf668..62d7a45c 100644 --- a/test/base/BaseTest.t.sol +++ b/test/base/BaseTest.t.sol @@ -121,7 +121,7 @@ contract BaseTest is Test, TestConstants { deal(TOKEN_ZERO, address(solverTwo), 10e24); deal(TOKEN_ONE, address(solverTwo), 10e24); - helper = new V2Helper(address(control), escrow, address(atlas)); + helper = new V2Helper(address(control), address(atlas), address(atlasVerification)); deal(TOKEN_ZERO, address(atlas), 1); deal(TOKEN_ONE, address(atlas), 1); From fe671a3af53832d29e0848edc7a90bda421e50e8 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Mon, 6 Nov 2023 16:37:29 +0200 Subject: [PATCH 24/38] Split out all vars and constants to storage contract --- src/contracts/atlas/Storage.sol | 70 +++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/contracts/atlas/Storage.sol diff --git a/src/contracts/atlas/Storage.sol b/src/contracts/atlas/Storage.sol new file mode 100644 index 00000000..06bcdc5e --- /dev/null +++ b/src/contracts/atlas/Storage.sol @@ -0,0 +1,70 @@ +//SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.16; + +import "../types/EscrowTypes.sol"; +import "../types/LockTypes.sol"; + +contract Storage { + // Atlas constants + uint256 internal constant _MAX_GAS = 1_500_000; + uint256 internal constant LEDGER_LENGTH = 5; // type(Party).max = 5 + address internal constant UNLOCKED = address(1); + + uint256 public immutable ESCROW_DURATION; + address public immutable FACTORY; + address public immutable VERIFICATION; + address public immutable SIMULATOR; + + // AtlETH ERC-20 constants + string public constant name = "Atlas ETH"; + string public constant symbol = "atlETH"; + uint8 public constant decimals = 18; + + // AtlETH EIP-2612 constants + uint256 internal immutable INITIAL_CHAIN_ID; + bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; + + // AtlETH ERC-20 storage + uint256 public totalSupply; + mapping(address => mapping(address => uint256)) public allowance; + mapping(address => uint256) public nonces; + + // Atlas GasAccounting storage + mapping(address => EscrowAccountData) internal _escrowAccountData; + + // Atlas SafetyLocks storage + Lock public lock; + Ledger[LEDGER_LENGTH] public ledgers; + + constructor( + uint32 _escrowDuration, + address _factory, + address _verification, + address _simulator + ) { + ESCROW_DURATION = _escrowDuration; + FACTORY = _factory; + VERIFICATION = _verification; + SIMULATOR = _simulator; + INITIAL_CHAIN_ID = block.chainid; + INITIAL_DOMAIN_SEPARATOR = _computeDomainSeparator(); + lock = Lock({ + activeEnvironment: UNLOCKED, + activeParties: uint16(0), + startingBalance: uint64(0) + }); + + for (uint256 i; i < LEDGER_LENGTH; i++) { + // init the storage vars + ledgers[i] = Ledger({ + balance: 0, + contributed: 0, + requested: 0, + status: LedgerStatus.Inactive, + proxy: Party(i) + }); + } + } + + function _computeDomainSeparator() internal virtual view returns (bytes32) {} +} \ No newline at end of file From 6574ad396edc0553021153d234de9d900be9bf6b Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Mon, 6 Nov 2023 18:08:34 +0200 Subject: [PATCH 25/38] Integrate Storage contract for all Atlas vars and constants --- src/contracts/atlas/AtlETH.sol | 49 +++++------------------ src/contracts/atlas/Atlas.sol | 20 ++++----- src/contracts/atlas/AtlasVerification.sol | 19 +++++---- src/contracts/atlas/Escrow.sol | 11 ++--- src/contracts/atlas/GasAccounting.sol | 9 +++-- src/contracts/atlas/SafetyLocks.sol | 39 ++++-------------- src/contracts/atlas/Storage.sol | 7 +++- src/contracts/common/Permit69.sol | 7 +++- test/Permit69.t.sol | 13 ++++-- 9 files changed, 70 insertions(+), 104 deletions(-) diff --git a/src/contracts/atlas/AtlETH.sol b/src/contracts/atlas/AtlETH.sol index e7fa9616..0acc476f 100644 --- a/src/contracts/atlas/AtlETH.sol +++ b/src/contracts/atlas/AtlETH.sol @@ -21,47 +21,16 @@ abstract contract AtlETH is Permit69 { event Transfer(address indexed from, address indexed to, uint256 amount); event Approval(address indexed owner, address indexed spender, uint256 amount); - /*////////////////////////////////////////////////////////////// - METADATA STORAGE - //////////////////////////////////////////////////////////////*/ - - string public constant name = "Atlas ETH"; - string public constant symbol = "atlETH"; - uint8 public constant decimals = 18; - - /*////////////////////////////////////////////////////////////// - ERC20 STORAGE - //////////////////////////////////////////////////////////////*/ - - uint256 public totalSupply; - mapping(address => mapping(address => uint256)) public allowance; - - /*////////////////////////////////////////////////////////////// - EIP-2612 STORAGE - //////////////////////////////////////////////////////////////*/ - - uint256 internal immutable INITIAL_CHAIN_ID; - bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; - mapping(address => uint256) public nonces; - - /*////////////////////////////////////////////////////////////// - ATLAS STORAGE - //////////////////////////////////////////////////////////////*/ - - // NOTE: these storage vars / maps should only be accessible by *signed* solver transactions - // and only once per solver per block (to avoid user-solver collaborative exploits) - uint256 public immutable escrowDuration; - /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ - constructor(uint32 _escrowDuration, address _simulator) Permit69(_simulator) { - INITIAL_CHAIN_ID = block.chainid; - INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); - - escrowDuration = _escrowDuration; - } + constructor( + uint256 _escrowDuration, + address _factory, + address _verification, + address _simulator + ) Permit69(_escrowDuration, _factory, _verification, _simulator) {} /*////////////////////////////////////////////////////////////// ATLETH @@ -71,7 +40,7 @@ abstract contract AtlETH is Permit69 { // Interactions (transfers, withdrawals) are allowed only after the owner last interaction // with Atlas was at least `escrowDuration` blocks ago. modifier tokenTransferChecks(address account) { - if(block.number <= _escrowAccountData[account].lastAccessed + escrowDuration) { + if(block.number <= _escrowAccountData[account].lastAccessed + ESCROW_DURATION) { revert EscrowLockActive(); } _; @@ -172,10 +141,10 @@ abstract contract AtlETH is Permit69 { } function DOMAIN_SEPARATOR() public view returns (bytes32) { - return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); + return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : _computeDomainSeparator(); } - function computeDomainSeparator() internal view returns (bytes32) { + function _computeDomainSeparator() internal view override returns (bytes32) { return keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), diff --git a/src/contracts/atlas/Atlas.sol b/src/contracts/atlas/Atlas.sol index 71e98bbf..f70ddfa9 100644 --- a/src/contracts/atlas/Atlas.sol +++ b/src/contracts/atlas/Atlas.sol @@ -29,12 +29,12 @@ contract Atlas is Escrow { using CallBits for uint32; using SafetyBits for EscrowKey; - uint256 private constant _MAX_GAS = 1_500_000; - address public immutable FACTORY; - - constructor(uint32 _escrowDuration, address _simulator, address _factory, address _verification) Escrow(_escrowDuration, _simulator, _verification) { - FACTORY = _factory; - } + constructor( + uint256 _escrowDuration, + address _factory, + address _verification, + address _simulator + ) Escrow(_escrowDuration, _factory, _verification, _simulator) {} function metacall( // <- Entrypoint Function UserOperation calldata userOp, // set by user @@ -54,7 +54,7 @@ contract Atlas is Escrow { // replay attacks. ValidCallsResult validCallsResult = _validCalls(dConfig, userOp, solverOps, dAppOp, executionEnvironment); if (validCallsResult != ValidCallsResult.Valid) { - if (msg.sender == simulator) {revert VerificationSimFail();} else { revert ValidCalls(validCallsResult); } + if (msg.sender == SIMULATOR) {revert VerificationSimFail();} else { revert ValidCalls(validCallsResult); } } // Initialize the lock @@ -97,7 +97,7 @@ contract Atlas is Escrow { if(msg.sender != address(this)) revert InvalidAccess(); // Build the memory lock - EscrowKey memory key = _buildEscrowLock(dConfig, executionEnvironment, uint8(solverOps.length), bundler == simulator); + EscrowKey memory key = _buildEscrowLock(dConfig, executionEnvironment, uint8(solverOps.length), bundler == SIMULATOR); // Begin execution (auctionWon, accruedGasRebate, winningSearcherIndex) = _execute(dConfig, userOp, solverOps, executionEnvironment, bundler, key); @@ -205,7 +205,7 @@ contract Atlas is Escrow { // Verify that the calldata injection came from the dApp frontend // and that the signatures are valid. - bool isSimulation = msg.sender == simulator; + bool isSimulation = msg.sender == SIMULATOR; // Some checks are only needed when call is not a simulation if (!isSimulation) { @@ -344,7 +344,7 @@ contract Atlas is Escrow { } function _handleErrors(bytes4 errorSwitch, uint32 callConfig) internal view { - if (msg.sender == simulator) { // Simulation + if (msg.sender == SIMULATOR) { // Simulation if (errorSwitch == PreOpsSimFail.selector) { revert PreOpsSimFail(); } else if (errorSwitch == UserOpSimFail.selector) { diff --git a/src/contracts/atlas/AtlasVerification.sol b/src/contracts/atlas/AtlasVerification.sol index 3bbe63f8..d29a4371 100644 --- a/src/contracts/atlas/AtlasVerification.sol +++ b/src/contracts/atlas/AtlasVerification.sol @@ -229,11 +229,11 @@ contract AtlasVerification is EIP712, DAppIntegration { function _handleNonces(address account, uint256 nonce, bool async) internal returns (bool validNonce) { if (nonce > type(uint128).max - 1) { - return (false); + return false; } if (nonce == 0) { - return (false); + return false; } uint256 bitmapIndex = (nonce / 240) + 1; // +1 because highestFullBitmap initializes at 0 @@ -245,7 +245,7 @@ contract AtlasVerification is EIP712, DAppIntegration { uint256 bitmap = uint256(nonceBitmap.bitmap); if (bitmap & (1 << bitmapNonce) != 0) { - return (false); + return false; } bitmap |= 1 << bitmapNonce; @@ -277,11 +277,11 @@ contract AtlasVerification is EIP712, DAppIntegration { // Handle non-async nonce logic if (!async) { if (bitmapIndex != highestFullBitmap + 1) { - return (false); + return false; } if (bitmapNonce != highestUsedBitmapNonce +1) { - return (false); + return false; } } @@ -355,11 +355,13 @@ contract AtlasVerification is EIP712, DAppIntegration { // Verify the signature before storing any data to avoid // spoof transactions clogging up dapp userNonces if (!_verifyUserSignature(userOp)) { + console.log("invalid user sig"); return false; } if (userOp.control != dConfig.to) { - return (false); + console.log("invalid control"); + return false; } // If the dapp indicated that they only accept sequenced userNonces @@ -369,10 +371,11 @@ contract AtlasVerification is EIP712, DAppIntegration { // DApps are encouraged to rely on the deadline parameter // to prevent replay attacks. if (!_handleNonces(userOp.from, userOp.nonce, dConfig.callConfig.needsSequencedNonces())) { - return (false); + console.log("invalid nonce"); + return false; } - return (true); + return true; } function _getProofHash(UserOperation memory userOp) internal pure returns (bytes32 proofHash) { diff --git a/src/contracts/atlas/Escrow.sol b/src/contracts/atlas/Escrow.sol index a952dcb4..73c01310 100644 --- a/src/contracts/atlas/Escrow.sol +++ b/src/contracts/atlas/Escrow.sol @@ -29,11 +29,12 @@ abstract contract Escrow is AtlETH { using CallBits for uint32; using SafetyBits for EscrowKey; - address public immutable VERIFICATION; - - constructor(uint32 _escrowDuration, address _simulator, address _verification) AtlETH(_escrowDuration, _simulator) { - VERIFICATION = _verification; - } + constructor( + uint256 _escrowDuration, + address _factory, + address _verification, + address _simulator + ) AtlETH(_escrowDuration, _factory, _verification, _simulator) {} /////////////////////////////////////////////////// /// EXTERNAL FUNCTIONS FOR BUNDLER INTERACTION /// diff --git a/src/contracts/atlas/GasAccounting.sol b/src/contracts/atlas/GasAccounting.sol index 3a67ce70..cad2da89 100644 --- a/src/contracts/atlas/GasAccounting.sol +++ b/src/contracts/atlas/GasAccounting.sol @@ -17,9 +17,12 @@ abstract contract GasAccounting is SafetyLocks { using PartyMath for uint256; using PartyMath for Ledger[LEDGER_LENGTH]; - mapping(address => EscrowAccountData) internal _escrowAccountData; - - constructor(address _simulator) SafetyLocks(_simulator) {} + constructor( + uint256 _escrowDuration, + address _factory, + address _verification, + address _simulator + ) SafetyLocks(_escrowDuration, _factory, _verification, _simulator) {} // NOTE: donations are simply deposits that have a different msg.sender than receiving party function _deposit(Party party, uint256 amt) internal returns (uint256 balanceOwed) { diff --git a/src/contracts/atlas/SafetyLocks.sol b/src/contracts/atlas/SafetyLocks.sol index 7ed550f4..86275622 100644 --- a/src/contracts/atlas/SafetyLocks.sol +++ b/src/contracts/atlas/SafetyLocks.sol @@ -12,46 +12,23 @@ import "../types/EscrowTypes.sol"; import "../types/LockTypes.sol"; +import {Storage} from "./Storage.sol"; import {FastLaneErrorsEvents} from "../types/Emissions.sol"; import "forge-std/Test.sol"; -contract SafetyLocks is FastLaneErrorsEvents { +abstract contract SafetyLocks is Storage, FastLaneErrorsEvents { using SafetyBits for EscrowKey; using CallBits for uint32; using PartyMath for Party; using PartyMath for uint256; - address public immutable atlas; - address public immutable simulator; - - address internal constant UNLOCKED = address(1); - - Lock public lock; - - Ledger[LEDGER_LENGTH] public ledgers; - - constructor(address _simulator) { - atlas = address(this); - simulator = _simulator; - - lock = Lock({ - activeEnvironment: UNLOCKED, - activeParties: uint16(0), - startingBalance: uint64(0) - }); - - for (uint256 i; i < LEDGER_LENGTH; i++) { - // init the storage vars - ledgers[i] = Ledger({ - balance: 0, - contributed: 0, - requested: 0, - status: LedgerStatus.Inactive, - proxy: Party(i) - }); - } - } + constructor( + uint256 _escrowDuration, + address _factory, + address _verification, + address _simulator + ) Storage(_escrowDuration, _factory, _verification, _simulator) {} function _initializeEscrowLock(UserOperation calldata userOp, address executionEnvironment, address bundler, uint256 gasLimit) onlyWhenUnlocked internal { diff --git a/src/contracts/atlas/Storage.sol b/src/contracts/atlas/Storage.sol index 06bcdc5e..b8a2ffdc 100644 --- a/src/contracts/atlas/Storage.sol +++ b/src/contracts/atlas/Storage.sol @@ -9,7 +9,7 @@ contract Storage { uint256 internal constant _MAX_GAS = 1_500_000; uint256 internal constant LEDGER_LENGTH = 5; // type(Party).max = 5 address internal constant UNLOCKED = address(1); - + uint256 public immutable ESCROW_DURATION; address public immutable FACTORY; address public immutable VERIFICATION; @@ -30,6 +30,9 @@ contract Storage { mapping(address => uint256) public nonces; // Atlas GasAccounting storage + // NOTE: these storage vars / maps should only be accessible by *signed* solver transactions + // and only once per solver per block (to avoid user-solver collaborative exploits) + // uint256 public immutable escrowDuration; mapping(address => EscrowAccountData) internal _escrowAccountData; // Atlas SafetyLocks storage @@ -37,7 +40,7 @@ contract Storage { Ledger[LEDGER_LENGTH] public ledgers; constructor( - uint32 _escrowDuration, + uint256 _escrowDuration, address _factory, address _verification, address _simulator diff --git a/src/contracts/common/Permit69.sol b/src/contracts/common/Permit69.sol index 286d3785..63849500 100644 --- a/src/contracts/common/Permit69.sol +++ b/src/contracts/common/Permit69.sol @@ -24,7 +24,12 @@ import {EXECUTION_PHASE_OFFSET, SAFE_USER_TRANSFER, SAFE_DAPP_TRANSFER, SAFE_GAS abstract contract Permit69 is GasAccounting { using SafeTransferLib for ERC20; - constructor(address _simulator) GasAccounting(_simulator) {} + constructor( + uint256 _escrowDuration, + address _factory, + address _verification, + address _simulator + ) GasAccounting(_escrowDuration, _factory, _verification, _simulator) {} // Virtual Functions defined by other Atlas modules function _verifyCallerIsExecutionEnv( diff --git a/test/Permit69.t.sol b/test/Permit69.t.sol index 58340265..209088bb 100644 --- a/test/Permit69.t.sol +++ b/test/Permit69.t.sol @@ -44,7 +44,7 @@ contract Permit69Test is BaseTest { callDepth: 0 }); - mockAtlas = new MockAtlasForPermit69Tests(); + mockAtlas = new MockAtlasForPermit69Tests(10,address(0),address(0),address(0)); mockAtlas.setEscrowKey(escrowKey); mockAtlas.setEnvironment(mockExecutionEnvAddress); @@ -249,7 +249,12 @@ contract Permit69Test is BaseTest { // TODO probably refactor some of this stuff to a shared folder of standard implementations // Mock Atlas with standard implementations of Permit69's virtual functions contract MockAtlasForPermit69Tests is Permit69 { - constructor() Permit69(address(0)) {} + constructor( + uint256 _escrowDuration, + address _factory, + address _verification, + address _simulator + ) Permit69(_escrowDuration, _factory, _verification, _simulator) {} // Declared in SafetyLocks.sol in the canonical Atlas system // The only property relevant to testing Permit69 is _escrowKey.lockState (bitwise uint16) @@ -274,8 +279,8 @@ contract MockAtlasForPermit69Tests is Permit69 { _escrowKey = escrowKey; } - function setEnvironment(address activeEnvironment) public { - _environment = activeEnvironment; + function setEnvironment(address _activeEnvironment) public { + _environment = _activeEnvironment; } // Overriding the virtual functions in Permit69 From d70deab9131a0bf2202bfd94b46aafff64f5e03d Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Mon, 6 Nov 2023 18:18:06 +0200 Subject: [PATCH 26/38] Fix Atlas constructor args in tests and deploy script --- script/deploy-atlas.s.sol | 48 ++++++++++++++++++++++++++++++++++++--- test/base/BaseTest.t.sol | 7 +++++- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/script/deploy-atlas.s.sol b/script/deploy-atlas.s.sol index 3d6ad6b0..39c284b7 100644 --- a/script/deploy-atlas.s.sol +++ b/script/deploy-atlas.s.sol @@ -25,13 +25,27 @@ contract DeployAtlasScript is DeployBaseScript { uint256 deployerPrivateKey = vm.envUint("GOV_PRIVATE_KEY"); address deployer = vm.addr(deployerPrivateKey); + // Computes the addresses at which AtlasFactory and AtlasVerification will be deployed + address expectedAtlasFactoryAddr = computeCreateAddress( + deployer, + vm.getNonce(deployer) + 1 + ); + address expectedAtlasVerificationAddr = computeCreateAddress( + deployer, + vm.getNonce(deployer) + 2 + ); console.log("Deployer address: \t\t\t\t", deployer); vm.startBroadcast(deployerPrivateKey); simulator = new Simulator(); - atlas = new Atlas(64, address(simulator), address(0), address(0)); //TODO update to Factory and Verification addr arg + atlas = new Atlas({ + _escrowDuration: 64, + _factory: expectedAtlasFactoryAddr, + _verification: expectedAtlasVerificationAddr, + _simulator: address(simulator) + }); atlasFactory = new AtlasFactory(address(atlas)); atlasVerification = new AtlasVerification(address(atlas)); @@ -58,6 +72,15 @@ contract DeployAtlasAndSwapIntentDAppControlScript is DeployBaseScript { uint256 deployerPrivateKey = vm.envUint("GOV_PRIVATE_KEY"); address deployer = vm.addr(deployerPrivateKey); + // Computes the addresses at which AtlasFactory and AtlasVerification will be deployed + address expectedAtlasFactoryAddr = computeCreateAddress( + deployer, + vm.getNonce(deployer) + 1 + ); + address expectedAtlasVerificationAddr = computeCreateAddress( + deployer, + vm.getNonce(deployer) + 2 + ); console.log("Deployer address: \t\t\t\t", deployer); @@ -65,7 +88,12 @@ contract DeployAtlasAndSwapIntentDAppControlScript is DeployBaseScript { // Deploy the Atlas contract simulator = new Simulator(); - atlas = new Atlas(64, address(simulator), address(0), address(0)); //TODO update to Factory and Verification addr arg + atlas = new Atlas({ + _escrowDuration: 64, + _factory: expectedAtlasFactoryAddr, + _verification: expectedAtlasVerificationAddr, + _simulator: address(simulator) + }); atlasFactory = new AtlasFactory(address(atlas)); atlasVerification = new AtlasVerification(address(atlas)); @@ -102,6 +130,15 @@ contract DeployAtlasAndSwapIntentDAppControlAndTxBuilderScript is DeployBaseScri uint256 deployerPrivateKey = vm.envUint("GOV_PRIVATE_KEY"); address deployer = vm.addr(deployerPrivateKey); + // Computes the addresses at which AtlasFactory and AtlasVerification will be deployed + address expectedAtlasFactoryAddr = computeCreateAddress( + deployer, + vm.getNonce(deployer) + 1 + ); + address expectedAtlasVerificationAddr = computeCreateAddress( + deployer, + vm.getNonce(deployer) + 2 + ); console.log("Deployer address: \t\t\t\t", deployer); @@ -109,7 +146,12 @@ contract DeployAtlasAndSwapIntentDAppControlAndTxBuilderScript is DeployBaseScri // Deploy the Atlas contract simulator = new Simulator(); - atlas = new Atlas(64, address(simulator), address(0), address(0)); //TODO update to Factory and Verification addr arg + atlas = new Atlas({ + _escrowDuration: 64, + _factory: expectedAtlasFactoryAddr, + _verification: expectedAtlasVerificationAddr, + _simulator: address(simulator) + }); atlasFactory = new AtlasFactory(address(atlas)); atlasVerification = new AtlasVerification(address(atlas)); diff --git a/test/base/BaseTest.t.sol b/test/base/BaseTest.t.sol index 62d7a45c..49213671 100644 --- a/test/base/BaseTest.t.sol +++ b/test/base/BaseTest.t.sol @@ -81,7 +81,12 @@ contract BaseTest is Test, TestConstants { vm.getNonce(payee) + 2 ); - atlas = new Atlas(64, address(simulator), expectedAtlasFactoryAddr, expectedAtlasVerificationAddr); + atlas = new Atlas({ + _escrowDuration: 64, + _factory: expectedAtlasFactoryAddr, + _verification: expectedAtlasVerificationAddr, + _simulator: address(simulator) + }); atlasFactory = new AtlasFactory(address(atlas)); atlasVerification = new AtlasVerification(address(atlas)); From 8891514174eb79c2ec3e6dcfafefbe1efac32309 Mon Sep 17 00:00:00 2001 From: jj1980a Date: Tue, 7 Nov 2023 10:58:11 +0800 Subject: [PATCH 27/38] minor refactor to allow user's nonce to be initialized when creating execution environment --- src/contracts/atlas/Atlas.sol | 8 +++++++- src/contracts/atlas/AtlasFactory.sol | 7 ++++--- src/contracts/atlas/DAppIntegration.sol | 6 +++--- src/contracts/interfaces/IAtlas.sol | 1 + src/contracts/interfaces/IAtlasFactory.sol | 2 +- src/contracts/interfaces/IDAppIntegration.sol | 2 ++ test/Accounting.t.sol | 2 +- test/MainTest.t.sol | 10 +++++----- test/SwapIntent.t.sol | 4 ++-- 9 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/contracts/atlas/Atlas.sol b/src/contracts/atlas/Atlas.sol index f70ddfa9..17b4b2f3 100644 --- a/src/contracts/atlas/Atlas.sol +++ b/src/contracts/atlas/Atlas.sol @@ -5,6 +5,7 @@ import {IExecutionEnvironment} from "../interfaces/IExecutionEnvironment.sol"; import {IDAppControl} from "../interfaces/IDAppControl.sol"; import {IAtlasFactory} from "../interfaces/IAtlasFactory.sol"; import {IAtlasVerification} from "../interfaces/IAtlasVerification.sol"; +import {IDAppIntegration} from "../interfaces/IDAppIntegration.sol"; import {Escrow} from "./Escrow.sol"; @@ -36,6 +37,11 @@ contract Atlas is Escrow { address _simulator ) Escrow(_escrowDuration, _factory, _verification, _simulator) {} + function createExecutionEnvironment(address dAppControl) external returns (address executionEnvironment) { + executionEnvironment = IAtlasFactory(FACTORY).createExecutionEnvironment(msg.sender, dAppControl); + IDAppIntegration(VERIFICATION).initializeNonce(msg.sender); + } + function metacall( // <- Entrypoint Function UserOperation calldata userOp, // set by user SolverOperation[] calldata solverOps, // supplied by FastLane via frontend integration @@ -367,7 +373,7 @@ contract Atlas is Escrow { address user, address controller, uint32 callConfig - ) internal override { + ) internal view override { if(msg.sender != IAtlasFactory(FACTORY).getExecutionEnvironmentCustom(user, controller.codehash, controller, callConfig)){ revert EnvironmentMismatch(); } diff --git a/src/contracts/atlas/AtlasFactory.sol b/src/contracts/atlas/AtlasFactory.sol index f4646c4f..9f2d18b3 100644 --- a/src/contracts/atlas/AtlasFactory.sol +++ b/src/contracts/atlas/AtlasFactory.sol @@ -31,9 +31,10 @@ contract AtlasFactory { // EXTERNAL FUNCTIONS // // ------------------ // - function createExecutionEnvironment(address dAppControl) external returns (address executionEnvironment) { - executionEnvironment = _setExecutionEnvironment(dAppControl, msg.sender, dAppControl.codehash); - // _initializeNonce(msg.sender); // NOTE: called separately by Atlas after calling createExecEnv + function createExecutionEnvironment(address account, address dAppControl) external returns (address executionEnvironment) { + // 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); } function getExecutionEnvironment(address user, address dAppControl) diff --git a/src/contracts/atlas/DAppIntegration.sol b/src/contracts/atlas/DAppIntegration.sol index 4f10a807..ce023615 100644 --- a/src/contracts/atlas/DAppIntegration.sol +++ b/src/contracts/atlas/DAppIntegration.sol @@ -71,7 +71,7 @@ contract DAppIntegration { signatories[signatoryKey] = true; - _initializeNonce(msg.sender); + initializeNonce(msg.sender); } @@ -88,7 +88,7 @@ contract DAppIntegration { signatories[signatoryKey] = true; - _initializeNonce(signatory); + initializeNonce(signatory); emit NewDAppSignatory( controller, @@ -139,7 +139,7 @@ contract DAppIntegration { delete dapps[key]; } - function _initializeNonce(address account) internal { + function initializeNonce(address account) public { if (asyncNonceBitIndex[account].LowestEmptyBitmap == uint128(0)) { unchecked { asyncNonceBitIndex[account].LowestEmptyBitmap = 2; diff --git a/src/contracts/interfaces/IAtlas.sol b/src/contracts/interfaces/IAtlas.sol index 878aca48..99cfb3a4 100644 --- a/src/contracts/interfaces/IAtlas.sol +++ b/src/contracts/interfaces/IAtlas.sol @@ -6,6 +6,7 @@ import "../types/UserCallTypes.sol"; import "../types/DAppApprovalTypes.sol"; interface IAtlas { + function createExecutionEnvironment(address dAppControl) external returns (address executionEnvironment); function metacall( UserOperation calldata userOp, SolverOperation[] calldata solverOps, diff --git a/src/contracts/interfaces/IAtlasFactory.sol b/src/contracts/interfaces/IAtlasFactory.sol index 7b58e789..3fe107bd 100644 --- a/src/contracts/interfaces/IAtlasFactory.sol +++ b/src/contracts/interfaces/IAtlasFactory.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.18; interface IAtlasFactory { - function createExecutionEnvironment(address dAppControl) external returns (address executionEnvironment); + function createExecutionEnvironment(address account, address dAppControl) external returns (address executionEnvironment); function getExecutionEnvironment(address user, address dAppControl) external diff --git a/src/contracts/interfaces/IDAppIntegration.sol b/src/contracts/interfaces/IDAppIntegration.sol index 327974b0..57e0aabb 100644 --- a/src/contracts/interfaces/IDAppIntegration.sol +++ b/src/contracts/interfaces/IDAppIntegration.sol @@ -12,6 +12,8 @@ interface IDAppIntegration { function disableDApp(address dAppControl) external; + function initializeNonce(address account) external; + function nextGovernanceNonce(address governanceSignatory) external view returns (uint256 nextNonce); function getGovFromControl(address dAppControl) external view returns (address governanceAddress); diff --git a/test/Accounting.t.sol b/test/Accounting.t.sol index cae143af..ebb2d204 100644 --- a/test/Accounting.t.sol +++ b/test/Accounting.t.sol @@ -151,7 +151,7 @@ contract AccountingTest is BaseTest { solverOps = new SolverOperation[](1); vm.startPrank(userEOA); - address executionEnvironment = atlasFactory.createExecutionEnvironment(txBuilder.control()); + address executionEnvironment = atlas.createExecutionEnvironment(txBuilder.control()); vm.stopPrank(); vm.label(address(executionEnvironment), "EXECUTION ENV"); diff --git a/test/MainTest.t.sol b/test/MainTest.t.sol index 66c4bf1d..ac2bcaf3 100644 --- a/test/MainTest.t.sol +++ b/test/MainTest.t.sol @@ -88,7 +88,7 @@ contract MainTest is BaseTest { vm.startPrank(userEOA); - address executionEnvironment = atlasFactory.createExecutionEnvironment(userOp.control); + address executionEnvironment = atlas.createExecutionEnvironment(userOp.control); vm.label(address(executionEnvironment), "EXECUTION ENV"); console.log("userEOA", userEOA); @@ -319,8 +319,8 @@ contract MainTest is BaseTest { vm.startPrank(userEOA); - atlasFactory.createExecutionEnvironment(address(control)); - address newEnvironment = atlasFactory.createExecutionEnvironment(address(control)); + atlas.createExecutionEnvironment(address(control)); + address newEnvironment = atlas.createExecutionEnvironment(address(control)); vm.stopPrank(); assertTrue(IExecutionEnvironment(newEnvironment).getUser() == userEOA, "Mimic Error - User Mismatch"); @@ -340,7 +340,7 @@ contract MainTest is BaseTest { userOp.signature = abi.encodePacked(r, s, v); vm.startPrank(userEOA); - atlasFactory.createExecutionEnvironment(userOp.control); + atlas.createExecutionEnvironment(userOp.control); // Failure case, user hasn't approved Atlas for TOKEN_ONE, operation must fail assertFalse(simulator.simUserOperation(userOp), "UserOperation tested true"); @@ -377,7 +377,7 @@ contract MainTest is BaseTest { (v, r, s) = vm.sign(governancePK, atlasVerification.getDAppOperationPayload(dAppOp)); dAppOp.signature = abi.encodePacked(r, s, v); vm.startPrank(userEOA); - atlasFactory.createExecutionEnvironment(userOp.control); + atlas.createExecutionEnvironment(userOp.control); ERC20(TOKEN_ONE).approve(address(atlas), type(uint256).max); (bool success, bytes memory data) = address(simulator).call( abi.encodeWithSelector( diff --git a/test/SwapIntent.t.sol b/test/SwapIntent.t.sol index b038efd3..051ca961 100644 --- a/test/SwapIntent.t.sol +++ b/test/SwapIntent.t.sol @@ -122,7 +122,7 @@ contract SwapIntentTest is BaseTest { DAppOperation memory dAppOp; vm.startPrank(userEOA); - address executionEnvironment = atlasFactory.createExecutionEnvironment(txBuilder.control()); + address executionEnvironment = atlas.createExecutionEnvironment(txBuilder.control()); console.log("executionEnvironment",executionEnvironment); vm.stopPrank(); vm.label(address(executionEnvironment), "EXECUTION ENV"); @@ -245,7 +245,7 @@ contract SwapIntentTest is BaseTest { DAppOperation memory dAppOp; vm.startPrank(userEOA); - address executionEnvironment = atlasFactory.createExecutionEnvironment(txBuilder.control()); + address executionEnvironment = atlas.createExecutionEnvironment(txBuilder.control()); console.log("executionEnvironment a",executionEnvironment); vm.stopPrank(); vm.label(address(executionEnvironment), "EXECUTION ENV"); From bdf5c93c0961d5deaf50b508ca6b82b39a9aa273 Mon Sep 17 00:00:00 2001 From: jj1980a Date: Tue, 7 Nov 2023 11:30:21 +0800 Subject: [PATCH 28/38] fix Permit69 tests by using custom errors --- test/Permit69.t.sol | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/test/Permit69.t.sol b/test/Permit69.t.sol index 209088bb..aea4e7c8 100644 --- a/test/Permit69.t.sol +++ b/test/Permit69.t.sol @@ -14,13 +14,11 @@ import {Mimic} from "../src/contracts/atlas/Mimic.sol"; import {EXECUTION_PHASE_OFFSET} from "../src/contracts/libraries/SafetyBits.sol"; import {SAFE_USER_TRANSFER, SAFE_DAPP_TRANSFER} from "../src/contracts/common/Permit69.sol"; +import {FastLaneErrorsEvents} from "../src/contracts/types/Emissions.sol"; + import "../src/contracts/types/LockTypes.sol"; contract Permit69Test is BaseTest { - bytes constant CALLER_IS_NOT_EXECUTION_ENV = bytes("ERR-T001 EnvironmentMismatch"); - bytes constant LOCK_STATE_NOT_VALID = bytes("ERR-T002 InvalidLockState"); - bytes constant CALLER_IS_NOT_ACTIVE = bytes("ERR-T003 EnvironmentNotActive"); - uint16 constant EXEC_PHASE_PRE_OPS = uint16(1 << (EXECUTION_PHASE_OFFSET + uint16(ExecutionPhase.PreOps))); address mockExecutionEnvAddress = address(0x13371337); @@ -55,7 +53,7 @@ contract Permit69Test is BaseTest { function testTransferUserERC20RevertsIsCallerNotExecutionEnv() public { vm.prank(solverOneEOA); - vm.expectRevert(CALLER_IS_NOT_EXECUTION_ENV); + vm.expectRevert(FastLaneErrorsEvents.EnvironmentMismatch.selector); mockAtlas.transferUserERC20(WETH_ADDRESS, solverOneEOA, 10e18, userEOA, address(0), uint16(0), escrowKey.lockState); } @@ -68,7 +66,7 @@ contract Permit69Test is BaseTest { 1 << (mockAtlas.getExecutionPhaseOffset() + uint16(ExecutionPhase.Uninitialized)) ); mockAtlas.setEscrowKey(escrowKey); - vm.expectRevert(LOCK_STATE_NOT_VALID); + vm.expectRevert(FastLaneErrorsEvents.InvalidLockState.selector); mockAtlas.transferUserERC20(WETH_ADDRESS, solverOneEOA, 10e18, userEOA, mockDAppControl, uint16(0), escrowKey.lockState); // HandlingPayments @@ -76,7 +74,7 @@ contract Permit69Test is BaseTest { 1 << (mockAtlas.getExecutionPhaseOffset() + uint16(ExecutionPhase.HandlingPayments)) ); mockAtlas.setEscrowKey(escrowKey); - vm.expectRevert(LOCK_STATE_NOT_VALID); + vm.expectRevert(FastLaneErrorsEvents.InvalidLockState.selector); mockAtlas.transferUserERC20(WETH_ADDRESS, solverOneEOA, 10e18, userEOA, mockDAppControl, uint16(0), escrowKey.lockState); // Releasing @@ -84,7 +82,7 @@ contract Permit69Test is BaseTest { 1 << (mockAtlas.getExecutionPhaseOffset() + uint16(ExecutionPhase.Releasing)) ); mockAtlas.setEscrowKey(escrowKey); - vm.expectRevert(LOCK_STATE_NOT_VALID); + vm.expectRevert(FastLaneErrorsEvents.InvalidLockState.selector); mockAtlas.transferUserERC20(WETH_ADDRESS, solverOneEOA, 10e18, userEOA, mockDAppControl, uint16(0), escrowKey.lockState); vm.stopPrank(); @@ -110,7 +108,7 @@ contract Permit69Test is BaseTest { function testTransferDAppERC20RevertsIsCallerNotExecutionEnv() public { vm.prank(solverOneEOA); - vm.expectRevert(CALLER_IS_NOT_EXECUTION_ENV); + vm.expectRevert(FastLaneErrorsEvents.EnvironmentMismatch.selector); mockAtlas.transferDAppERC20(WETH_ADDRESS, solverOneEOA, 10e18, userEOA, mockDAppControl, uint16(0), escrowKey.lockState); } @@ -123,7 +121,7 @@ contract Permit69Test is BaseTest { 1 << (mockAtlas.getExecutionPhaseOffset() + uint16(ExecutionPhase.Uninitialized)) ); mockAtlas.setEscrowKey(escrowKey); - vm.expectRevert(LOCK_STATE_NOT_VALID); + vm.expectRevert(FastLaneErrorsEvents.InvalidLockState.selector); mockAtlas.transferDAppERC20(WETH_ADDRESS, solverOneEOA, 10e18, userEOA, mockDAppControl, uint16(0), escrowKey.lockState); // UserOperation @@ -131,7 +129,7 @@ contract Permit69Test is BaseTest { 1 << (mockAtlas.getExecutionPhaseOffset() + uint16(ExecutionPhase.UserOperation)) ); mockAtlas.setEscrowKey(escrowKey); - vm.expectRevert(LOCK_STATE_NOT_VALID); + vm.expectRevert(FastLaneErrorsEvents.InvalidLockState.selector); mockAtlas.transferDAppERC20(WETH_ADDRESS, solverOneEOA, 10e18, userEOA, mockDAppControl, uint16(0), escrowKey.lockState); // SolverOperations @@ -139,7 +137,7 @@ contract Permit69Test is BaseTest { 1 << (mockAtlas.getExecutionPhaseOffset() + uint16(ExecutionPhase.SolverOperations)) ); mockAtlas.setEscrowKey(escrowKey); - vm.expectRevert(LOCK_STATE_NOT_VALID); + vm.expectRevert(FastLaneErrorsEvents.InvalidLockState.selector); mockAtlas.transferDAppERC20(WETH_ADDRESS, solverOneEOA, 10e18, userEOA, mockDAppControl, uint16(0), escrowKey.lockState); // Releasing @@ -147,7 +145,7 @@ contract Permit69Test is BaseTest { 1 << (mockAtlas.getExecutionPhaseOffset() + uint16(ExecutionPhase.Releasing)) ); mockAtlas.setEscrowKey(escrowKey); - vm.expectRevert(LOCK_STATE_NOT_VALID); + vm.expectRevert(FastLaneErrorsEvents.InvalidLockState.selector); mockAtlas.transferDAppERC20(WETH_ADDRESS, solverOneEOA, 10e18, userEOA, mockDAppControl, uint16(0), escrowKey.lockState); vm.stopPrank(); @@ -262,15 +260,15 @@ contract MockAtlasForPermit69Tests is Permit69 { address internal _environment; // Public functions to expose the internal constants for testing - function getExecutionPhaseOffset() public view returns (uint16) { + function getExecutionPhaseOffset() public pure returns (uint16) { return EXECUTION_PHASE_OFFSET; } - function getSafeUserTransfer() public view returns (uint16) { + function getSafeUserTransfer() public pure returns (uint16) { return SAFE_USER_TRANSFER; } - function getSafeDAppTransfer() public view returns (uint16) { + function getSafeDAppTransfer() public pure returns (uint16) { return SAFE_DAPP_TRANSFER; } @@ -285,12 +283,12 @@ contract MockAtlasForPermit69Tests is Permit69 { // Overriding the virtual functions in Permit69 function _verifyCallerIsExecutionEnv( - address user, - address controller, - uint32 callConfig - ) internal override { + address, + address, + uint32 + ) internal view override { if(msg.sender != _environment) { - revert("ERR-T001 EnvironmentMismatch"); + revert FastLaneErrorsEvents.EnvironmentMismatch(); } } From 78e394baa9a388180a29fa839e903b935a916035 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Tue, 7 Nov 2023 16:40:41 +0200 Subject: [PATCH 29/38] Consolidate Verification interface fns --- src/contracts/atlas/Atlas.sol | 3 +-- src/contracts/interfaces/IAtlasVerification.sol | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/contracts/atlas/Atlas.sol b/src/contracts/atlas/Atlas.sol index 17b4b2f3..0eb6309e 100644 --- a/src/contracts/atlas/Atlas.sol +++ b/src/contracts/atlas/Atlas.sol @@ -5,7 +5,6 @@ import {IExecutionEnvironment} from "../interfaces/IExecutionEnvironment.sol"; import {IDAppControl} from "../interfaces/IDAppControl.sol"; import {IAtlasFactory} from "../interfaces/IAtlasFactory.sol"; import {IAtlasVerification} from "../interfaces/IAtlasVerification.sol"; -import {IDAppIntegration} from "../interfaces/IDAppIntegration.sol"; import {Escrow} from "./Escrow.sol"; @@ -39,7 +38,7 @@ contract Atlas is Escrow { function createExecutionEnvironment(address dAppControl) external returns (address executionEnvironment) { executionEnvironment = IAtlasFactory(FACTORY).createExecutionEnvironment(msg.sender, dAppControl); - IDAppIntegration(VERIFICATION).initializeNonce(msg.sender); + IAtlasVerification(VERIFICATION).initializeNonce(msg.sender); } function metacall( // <- Entrypoint Function diff --git a/src/contracts/interfaces/IAtlasVerification.sol b/src/contracts/interfaces/IAtlasVerification.sol index 8cc4dbc2..231c7470 100644 --- a/src/contracts/interfaces/IAtlasVerification.sol +++ b/src/contracts/interfaces/IAtlasVerification.sol @@ -29,4 +29,6 @@ interface IAtlasVerification { function removeSignatory(address controller, address signatory) external; function integrateDApp(address dAppControl) external; function disableDApp(address dAppControl) external; + + function initializeNonce(address account) external; } \ No newline at end of file From 54918f2f89de1391faaa2e39dcc1eb39967ba27a Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Tue, 7 Nov 2023 18:52:49 +0200 Subject: [PATCH 30/38] Start splitting out GasAccLib --- src/contracts/atlas/GasAccounting.sol | 2 +- src/contracts/atlas/GasAccountingLib.sol | 33 ++++++++++++++++++++++++ src/contracts/atlas/SafetyLocks.sol | 2 +- src/contracts/libraries/GasParties.sol | 2 +- 4 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 src/contracts/atlas/GasAccountingLib.sol diff --git a/src/contracts/atlas/GasAccounting.sol b/src/contracts/atlas/GasAccounting.sol index cad2da89..5f0c5799 100644 --- a/src/contracts/atlas/GasAccounting.sol +++ b/src/contracts/atlas/GasAccounting.sol @@ -8,7 +8,7 @@ import "../types/LockTypes.sol"; import {EscrowBits} from "../libraries/EscrowBits.sol"; -import {PartyMath, LEDGER_LENGTH} from "../libraries/GasParties.sol"; +import {PartyMath} from "../libraries/GasParties.sol"; import "forge-std/Test.sol"; diff --git a/src/contracts/atlas/GasAccountingLib.sol b/src/contracts/atlas/GasAccountingLib.sol new file mode 100644 index 00000000..ba9e567b --- /dev/null +++ b/src/contracts/atlas/GasAccountingLib.sol @@ -0,0 +1,33 @@ +//SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.16; + +import {PartyMath} from "../libraries/GasParties.sol"; +import {Storage} from "./Storage.sol"; +import {FastLaneErrorsEvents} from "../types/Emissions.sol"; + +contract GasAccountingLib is Storage, FastLaneErrorsEvents { + constructor( + uint256 _escrowDuration, + address _factory, + address _verification, + address _simulator + ) Storage(_escrowDuration, _factory, _verification, _simulator) {} + + + + + + + + + + + + + + + // Not needed in GasAccLib but set to keep in sync with Atlas + function _computeDomainSeparator() internal virtual override view returns (bytes32) { + return bytes32(0); + } +} \ No newline at end of file diff --git a/src/contracts/atlas/SafetyLocks.sol b/src/contracts/atlas/SafetyLocks.sol index 86275622..9a85dacd 100644 --- a/src/contracts/atlas/SafetyLocks.sol +++ b/src/contracts/atlas/SafetyLocks.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.16; import {SafetyBits} from "../libraries/SafetyBits.sol"; import {CallBits} from "../libraries/CallBits.sol"; -import {PartyMath, LEDGER_LENGTH} from "../libraries/GasParties.sol"; +import {PartyMath} from "../libraries/GasParties.sol"; import "../types/SolverCallTypes.sol"; import "../types/UserCallTypes.sol"; diff --git a/src/contracts/libraries/GasParties.sol b/src/contracts/libraries/GasParties.sol index ae5ad706..dd7f4279 100644 --- a/src/contracts/libraries/GasParties.sol +++ b/src/contracts/libraries/GasParties.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.16; import {Party, Ledger} from "../types/EscrowTypes.sol"; import {Lock, BaseLock, ExecutionPhase} from "../types/LockTypes.sol"; -uint256 constant LEDGER_LENGTH = 5; // uint256(type(Party).max); // 5 +uint256 constant LEDGER_LENGTH = 5; // type(Party).max = 5 library PartyMath { From 7558496d0ad97ac699f8b2151c5dd02896e23131 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Tue, 7 Nov 2023 19:32:11 +0200 Subject: [PATCH 31/38] GasAccLib integrated with Atlas and tests - needs to be delegatecalled --- script/deploy-atlas.s.sol | 39 +- src/contracts/atlas/AtlETH.sol | 3 +- src/contracts/atlas/Atlas.sol | 3 +- src/contracts/atlas/Escrow.sol | 3 +- src/contracts/atlas/GasAccounting.sol | 5 +- src/contracts/atlas/GasAccountingLib.sol | 918 ++++++++++++++++++++++- src/contracts/atlas/SafetyLocks.sol | 3 +- src/contracts/atlas/Storage.sol | 3 + src/contracts/common/Permit69.sol | 3 +- test/Permit69.t.sol | 5 +- test/base/BaseTest.t.sol | 13 + 11 files changed, 985 insertions(+), 13 deletions(-) diff --git a/script/deploy-atlas.s.sol b/script/deploy-atlas.s.sol index 39c284b7..d789ece5 100644 --- a/script/deploy-atlas.s.sol +++ b/script/deploy-atlas.s.sol @@ -9,6 +9,7 @@ import {DeployBaseScript} from "script/base/deploy-base.s.sol"; import {Atlas} from "src/contracts/atlas/Atlas.sol"; import {AtlasFactory} from "src/contracts/atlas/AtlasFactory.sol"; import {AtlasVerification} from "src/contracts/atlas/AtlasVerification.sol"; +import {GasAccountingLib} from "src/contracts/atlas/GasAccountingLib.sol"; import {SwapIntentController} from "src/contracts/examples/intents-example/SwapIntent.sol"; import {TxBuilder} from "src/contracts/helpers/TxBuilder.sol"; import {Simulator} from "src/contracts/helpers/Simulator.sol"; @@ -18,6 +19,7 @@ contract DeployAtlasScript is DeployBaseScript { Atlas public atlas; AtlasFactory public atlasFactory; AtlasVerification public atlasVerification; + GasAccountingLib public gasAccountingLib; Simulator public simulator; function run() external { @@ -34,6 +36,10 @@ contract DeployAtlasScript is DeployBaseScript { deployer, vm.getNonce(deployer) + 2 ); + address expectedGasAccountingLibAddr = computeCreateAddress( + deployer, + vm.getNonce(deployer) + 3 + ); console.log("Deployer address: \t\t\t\t", deployer); @@ -44,10 +50,17 @@ contract DeployAtlasScript is DeployBaseScript { _escrowDuration: 64, _factory: expectedAtlasFactoryAddr, _verification: expectedAtlasVerificationAddr, + _gasAccLib: expectedGasAccountingLibAddr, _simulator: address(simulator) }); atlasFactory = new AtlasFactory(address(atlas)); atlasVerification = new AtlasVerification(address(atlas)); + gasAccountingLib = new GasAccountingLib({ + _escrowDuration: 64, + _factory: expectedAtlasFactoryAddr, + _verification: expectedAtlasVerificationAddr, + _simulator: address(simulator) + }); vm.stopBroadcast(); @@ -64,6 +77,7 @@ contract DeployAtlasAndSwapIntentDAppControlScript is DeployBaseScript { Atlas public atlas; AtlasFactory public atlasFactory; AtlasVerification public atlasVerification; + GasAccountingLib public gasAccountingLib; Simulator public simulator; SwapIntentController public swapIntentControl; @@ -81,21 +95,31 @@ contract DeployAtlasAndSwapIntentDAppControlScript is DeployBaseScript { deployer, vm.getNonce(deployer) + 2 ); + address expectedGasAccountingLibAddr = computeCreateAddress( + deployer, + vm.getNonce(deployer) + 3 + ); console.log("Deployer address: \t\t\t\t", deployer); vm.startBroadcast(deployerPrivateKey); - // Deploy the Atlas contract simulator = new Simulator(); atlas = new Atlas({ _escrowDuration: 64, _factory: expectedAtlasFactoryAddr, _verification: expectedAtlasVerificationAddr, + _gasAccLib: expectedGasAccountingLibAddr, _simulator: address(simulator) }); atlasFactory = new AtlasFactory(address(atlas)); atlasVerification = new AtlasVerification(address(atlas)); + gasAccountingLib = new GasAccountingLib({ + _escrowDuration: 64, + _factory: expectedAtlasFactoryAddr, + _verification: expectedAtlasVerificationAddr, + _simulator: address(simulator) + }); // Deploy the SwapIntent DAppControl contract swapIntentControl = new SwapIntentController(address(atlas)); @@ -121,6 +145,7 @@ contract DeployAtlasAndSwapIntentDAppControlAndTxBuilderScript is DeployBaseScri Atlas public atlas; AtlasFactory public atlasFactory; AtlasVerification public atlasVerification; + GasAccountingLib public gasAccountingLib; Simulator public simulator; SwapIntentController public swapIntentControl; TxBuilder public txBuilder; @@ -139,21 +164,31 @@ contract DeployAtlasAndSwapIntentDAppControlAndTxBuilderScript is DeployBaseScri deployer, vm.getNonce(deployer) + 2 ); + address expectedGasAccountingLibAddr = computeCreateAddress( + deployer, + vm.getNonce(deployer) + 3 + ); console.log("Deployer address: \t\t\t\t", deployer); vm.startBroadcast(deployerPrivateKey); - // Deploy the Atlas contract simulator = new Simulator(); atlas = new Atlas({ _escrowDuration: 64, _factory: expectedAtlasFactoryAddr, _verification: expectedAtlasVerificationAddr, + _gasAccLib: expectedGasAccountingLibAddr, _simulator: address(simulator) }); atlasFactory = new AtlasFactory(address(atlas)); atlasVerification = new AtlasVerification(address(atlas)); + gasAccountingLib = new GasAccountingLib({ + _escrowDuration: 64, + _factory: expectedAtlasFactoryAddr, + _verification: expectedAtlasVerificationAddr, + _simulator: address(simulator) + }); // Deploy the SwapIntent DAppControl contract swapIntentControl = new SwapIntentController(address(atlas)); diff --git a/src/contracts/atlas/AtlETH.sol b/src/contracts/atlas/AtlETH.sol index 0acc476f..1ff01518 100644 --- a/src/contracts/atlas/AtlETH.sol +++ b/src/contracts/atlas/AtlETH.sol @@ -29,8 +29,9 @@ abstract contract AtlETH is Permit69 { uint256 _escrowDuration, address _factory, address _verification, + address _gasAccLib, address _simulator - ) Permit69(_escrowDuration, _factory, _verification, _simulator) {} + ) Permit69(_escrowDuration, _factory, _verification, _gasAccLib, _simulator) {} /*////////////////////////////////////////////////////////////// ATLETH diff --git a/src/contracts/atlas/Atlas.sol b/src/contracts/atlas/Atlas.sol index 0eb6309e..20be3aaf 100644 --- a/src/contracts/atlas/Atlas.sol +++ b/src/contracts/atlas/Atlas.sol @@ -33,8 +33,9 @@ contract Atlas is Escrow { uint256 _escrowDuration, address _factory, address _verification, + address _gasAccLib, address _simulator - ) Escrow(_escrowDuration, _factory, _verification, _simulator) {} + ) Escrow(_escrowDuration, _factory, _verification, _gasAccLib, _simulator) {} function createExecutionEnvironment(address dAppControl) external returns (address executionEnvironment) { executionEnvironment = IAtlasFactory(FACTORY).createExecutionEnvironment(msg.sender, dAppControl); diff --git a/src/contracts/atlas/Escrow.sol b/src/contracts/atlas/Escrow.sol index 73c01310..749962cb 100644 --- a/src/contracts/atlas/Escrow.sol +++ b/src/contracts/atlas/Escrow.sol @@ -33,8 +33,9 @@ abstract contract Escrow is AtlETH { uint256 _escrowDuration, address _factory, address _verification, + address _gasAccLib, address _simulator - ) AtlETH(_escrowDuration, _factory, _verification, _simulator) {} + ) AtlETH(_escrowDuration, _factory, _verification, _gasAccLib, _simulator) {} /////////////////////////////////////////////////// /// EXTERNAL FUNCTIONS FOR BUNDLER INTERACTION /// diff --git a/src/contracts/atlas/GasAccounting.sol b/src/contracts/atlas/GasAccounting.sol index 5f0c5799..5373b7a7 100644 --- a/src/contracts/atlas/GasAccounting.sol +++ b/src/contracts/atlas/GasAccounting.sol @@ -10,7 +10,7 @@ import "../types/LockTypes.sol"; import {EscrowBits} from "../libraries/EscrowBits.sol"; import {PartyMath} from "../libraries/GasParties.sol"; -import "forge-std/Test.sol"; +import "forge-std/Test.sol"; //TODO remove abstract contract GasAccounting is SafetyLocks { using PartyMath for Party; @@ -21,8 +21,9 @@ abstract contract GasAccounting is SafetyLocks { uint256 _escrowDuration, address _factory, address _verification, + address _gasAccLib, address _simulator - ) SafetyLocks(_escrowDuration, _factory, _verification, _simulator) {} + ) SafetyLocks(_escrowDuration, _factory, _verification, _gasAccLib, _simulator) {} // NOTE: donations are simply deposits that have a different msg.sender than receiving party function _deposit(Party party, uint256 amt) internal returns (uint256 balanceOwed) { diff --git a/src/contracts/atlas/GasAccountingLib.sol b/src/contracts/atlas/GasAccountingLib.sol index ba9e567b..774ef01b 100644 --- a/src/contracts/atlas/GasAccountingLib.sol +++ b/src/contracts/atlas/GasAccountingLib.sol @@ -2,31 +2,945 @@ pragma solidity ^0.8.16; import {PartyMath} from "../libraries/GasParties.sol"; +import {EscrowBits} from "../libraries/EscrowBits.sol"; import {Storage} from "./Storage.sol"; import {FastLaneErrorsEvents} from "../types/Emissions.sol"; +import "../types/EscrowTypes.sol"; +import "../types/LockTypes.sol"; + +import "forge-std/Test.sol"; //TODO remove + contract GasAccountingLib is Storage, FastLaneErrorsEvents { + using PartyMath for Party; + using PartyMath for uint256; + using PartyMath for Ledger[LEDGER_LENGTH]; + constructor( uint256 _escrowDuration, address _factory, address _verification, address _simulator - ) Storage(_escrowDuration, _factory, _verification, _simulator) {} + ) Storage(_escrowDuration, _factory, _verification, address(this), _simulator) {} + + + // NOTE: donations are simply deposits that have a different msg.sender than receiving party + function _deposit(Party party, uint256 amt) internal returns (uint256 balanceOwed) { + + (Ledger memory partyLedger, uint256 partyIndex) = _getLedger(party); + + if (partyLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(1); + + int64 depositAmount = int64(uint64(amt / tx.gasprice)); + + partyLedger.balance += depositAmount; + + balanceOwed = partyLedger.balance < 0 ? uint256(uint64(-1 * partyLedger.balance)) : 0; + + ledgers[partyIndex] = partyLedger; + } + + + function _borrow(Party party, uint256 amt) internal { + // Note that for Solver borrows, the repayment check happens *inside* the try/catch. + + (Ledger memory partyLedger, uint256 partyIndex) = _getLedger(party); + + if(partyLedger.status >= LedgerStatus.Borrowing) revert LedgerFinalized(2); + + int64 borrowAmount = int64(uint64(amt / tx.gasprice))+1; + partyLedger.balance -= borrowAmount; + partyLedger.status = LedgerStatus.Borrowing; + + ledgers[partyIndex] = partyLedger; + } + + function _tradeCorrection(Party party, uint256 amt) internal { + // Note that for Solver borrows, the repayment check happens *inside* the try/catch. + // This function is to mark off a solver borrow from a failed tx + + (Ledger memory partyLedger, uint256 partyIndex) = _getLedger(party); + + if(partyLedger.status != LedgerStatus.Borrowing) revert LedgerFinalized(3); + + int64 borrowAmount = int64(uint64(amt / tx.gasprice))+1; + partyLedger.balance += borrowAmount; + partyLedger.status = LedgerStatus.Active; + + ledgers[partyIndex] = partyLedger; + } + + function _use(Party party, address partyAddress, uint256 amt) internal { + + (Ledger memory partyLedger, uint256 partyIndex) = _getLedger(party); + + if(partyLedger.status >= LedgerStatus.Balancing) revert LedgerBalancing(1); + + int64 amount = int64(uint64(amt / tx.gasprice))+1; + + if (partyLedger.requested > 0) { + if (amount > partyLedger.requested) { + amount -= partyLedger.requested; + partyLedger.requested = 0; + } else { + partyLedger.requested -= amount; + ledgers[partyIndex] = partyLedger; + return; + } + } + + if (partyLedger.contributed > 0) { + if (amount > partyLedger.contributed) { + amount -= partyLedger.contributed; + partyLedger.contributed = 0; + } else { + partyLedger.contributed -= amount; + ledgers[partyIndex] = partyLedger; + return; + } + } + + // Avoid the storage read for as long as possible + if (partyLedger.balance > 0) { + if (amount > partyLedger.balance) { + amount -= partyLedger.balance; + partyLedger.balance = 0; + } else { + partyLedger.balance -= amount; + ledgers[partyIndex] = partyLedger; + return; + } + } + + amt = uint256(uint64(amount+1)) * tx.gasprice; + uint256 balance = uint256(_escrowAccountData[partyAddress].balance); + + if (balance > amt) { + partyLedger.balance -= amount; + ledgers[partyIndex] = partyLedger; + return; + } + + revert InsufficientFunds(); + } + + function _requestFrom(Party donor, Party recipient, uint256 amt) internal { + // TODO: different parties will be ineligible to request funds from once their phase is over. + // We need to add a phase check to verify this. + + (Ledger memory donorLedger, uint256 donorIndex) = _getLedger(donor); + if(donorLedger.status >= LedgerStatus.Balancing) revert LedgerBalancing(2); + + (Ledger memory recipientLedger, uint256 recipientIndex) = _getLedger(recipient); + if(recipientLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(4); + + int64 amount = int64(uint64(amt / tx.gasprice)); + + donorLedger.contributed -= amount; + recipientLedger.requested -= amount; + + ledgers[donorIndex] = donorLedger; + ledgers[recipientIndex] = recipientLedger; + } + + function _contributeTo(Party donor, Party recipient, uint256 amt) internal { + + (Ledger memory donorLedger, uint256 donorIndex) = _getLedger(donor); + if(donorLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(5); + + (Ledger memory recipientLedger, uint256 recipientIndex) = _getLedger(recipient); + if(recipientLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(6); + + int64 amount = int64(uint64(amt / tx.gasprice)); + + donorLedger.balance -= amount; + donorLedger.contributed += amount; + recipientLedger.requested += amount; + + ledgers[donorIndex] = donorLedger; + ledgers[recipientIndex] = recipientLedger; + } + + // Returns true if Solver status is Finalized and the caller (Execution Environment) is in surplus + function validateBalances() external view returns (bool valid) { + valid = ledgers[uint256(Party.Solver)].status == LedgerStatus.Finalized && _isInSurplus(msg.sender); + } + + function _isInSurplus(address environment) internal view returns (bool) { + Lock memory mLock = lock; + if (mLock.activeEnvironment != environment) return false; + + int64 totalBalanceDelta; + int64 totalRequests; + int64 totalContributions; + + uint256 activeParties = uint256(mLock.activeParties); + for (uint256 i; i < LEDGER_LENGTH;) { + // If party has not been touched, skip it + if (activeParties & 1<<(i+1) == 0){ + unchecked{++i;} + continue; + } + + Ledger memory partyLedger = ledgers[i]; + if (uint256(partyLedger.proxy) != i) { + unchecked{++i;} + continue; + } + + totalBalanceDelta += partyLedger.balance; + totalRequests += partyLedger.requested; + totalContributions += partyLedger.contributed; + + unchecked{++i;} + } + + int64 atlasBalanceDelta = int64(uint64((address(this).balance) / tx.gasprice)) - int64(mLock.startingBalance); + + // If atlas balance is lower than expected, return false + if (atlasBalanceDelta < totalRequests + totalContributions + totalBalanceDelta) return false; + + // If the requests have not yet been met, return false + if (totalRequests + totalContributions < 0) return false; + + // Otherwise return true + return true; + } + + function _balance(uint256 accruedGasRebate, address user, address dapp, address winningSolver, address bundler) internal { + + Lock memory mLock = lock; + uint256 activeParties = uint256(mLock.activeParties); + + int64 totalRequests; + int64 totalContributions; + int64 totalBalanceDelta; + + Ledger[LEDGER_LENGTH] memory parties; + + (parties, activeParties, totalRequests, totalContributions, totalBalanceDelta) = _loadLedgers(activeParties); + + (parties, totalRequests, totalContributions, totalBalanceDelta) = _allocateGasRebate( + parties, mLock.startingBalance, accruedGasRebate, totalRequests, totalContributions, totalBalanceDelta); + + console.log(""); + console.log("* * * * * "); + console.log("INITIAL:"); + _consolePrint(parties, activeParties); + console.log("_______"); + console.log("* * * * * "); + console.log(""); + + // First, remove overfilled requests (refunding to general pool) + if (totalRequests > 0) { + (parties, totalRequests, totalContributions) = _removeSurplusRequests( + parties, activeParties, totalRequests, totalContributions); + } + + // Next, balance each party's surplus contributions against their own deficit requests + (parties, totalRequests, totalContributions) = _balanceAgainstSelf( + parties, activeParties, totalRequests, totalContributions); + + // Then allocate surplus contributions back to the correct parties + if (totalRequests + totalContributions > 0) { + (parties, totalRequests, totalContributions) = _allocateSurplusContributions( + parties, activeParties, totalRequests, totalContributions); + } + + console.log("* * * * * "); + console.log("FINAL:"); + _consolePrint(parties, activeParties); + console.log("_______"); + console.log("* * * * * "); + + // Finally, assign the balance deltas to the parties + _assignBalanceDeltas(parties, activeParties, user, dapp, winningSolver, bundler); + } + + function _loadLedgers(uint256 activeParties) + internal + view + returns (Ledger[LEDGER_LENGTH] memory, uint256, int64, int64, int64) + { + Ledger[LEDGER_LENGTH] memory parties; + + int64 totalRequests; + int64 totalContributions; + int64 totalBalanceDelta; + + for (uint256 i; i < LEDGER_LENGTH;) { + // If party has not been touched, skip it + if (activeParties.isInactive(i)) { + unchecked{++i;} + continue; + } + + Ledger memory partyLedger = ledgers[i]; + + if(partyLedger.contributed < 0) revert NoUnfilledRequests(); + + // Only tally totals from non-proxies + if (uint256(partyLedger.proxy) == i) { + + totalBalanceDelta += partyLedger.balance; + totalRequests += partyLedger.requested; + totalContributions += partyLedger.contributed; + + // Mark inactive if proxy + } else { + activeParties = activeParties.markInactive(Party(i)); + } + + parties[i] = partyLedger; + + unchecked{++i;} + } + + return (parties, activeParties, totalRequests, totalContributions, totalBalanceDelta); + } + + function _allocateGasRebate( + Ledger[LEDGER_LENGTH] memory parties, uint64 startingGasBal, + uint256 accruedGasRebate, int64 totalRequests, int64 totalContributions, int64 totalBalanceDelta) + internal + view + returns (Ledger[LEDGER_LENGTH] memory, int64, int64, int64) + { + int64 gasRemainder = int64(uint64(gasleft() + 20_000)); + + // Reduce the bundler's gas request by the unused gas + (, uint256 i) = parties._getLedgerFromMemory(Party.Bundler); + + int64 gasRebate = int64(uint64(accruedGasRebate)); + + parties[i].requested += gasRemainder; + parties[i].balance += gasRebate; + + totalRequests += gasRemainder; + totalContributions -= gasRemainder; + totalBalanceDelta += gasRebate; + + if(totalRequests + totalContributions < 0) revert MissingFunds(1); + + // TODO: Adjust to accomodate the direction of rounding errors. + int64 atlasBalanceDelta = int64(uint64(address(this).balance / tx.gasprice)) - int64(startingGasBal); + + { + console.log(""); + console.log("--"); + _logInt64("gasRemainder :", gasRemainder); + console.log("gasRebate : +", accruedGasRebate); + console.log("-"); + _logInt64("observedDelta:", atlasBalanceDelta); + _logInt64("actualDelta :", totalBalanceDelta); + console.log("-"); + _logInt64("surplus :", totalRequests + totalContributions); + _logInt64("totalRequests:", totalRequests); + _logInt64("contributions:", totalContributions); + console.log("--"); + } + + if (atlasBalanceDelta < totalBalanceDelta + totalContributions + totalRequests) { + revert MissingFunds(2); + } + + return (parties, totalRequests, totalContributions, totalBalanceDelta); + } + + function _balanceAgainstSelf( + Ledger[LEDGER_LENGTH] memory parties, uint256 activeParties, int64 totalRequests, int64 totalContributions) + internal + pure + returns (Ledger[LEDGER_LENGTH] memory, int64, int64) + { + // NOTE: + // ORDER is: + // 1: FirstToAct (DApp) + // ... + // Last: LastToAct (builder) + + uint256 i = LEDGER_LENGTH; + + do { + --i; + + // If party has not been touched, skip it + if (activeParties.isInactive(i)) { + continue; + } + + Ledger memory partyLedger = parties[i]; + + // CASE: Some Requests still in Deficit + if (partyLedger.requested < 0 && partyLedger.contributed > 0) { + + // CASE: Contributions > Requests + if (partyLedger.contributed + partyLedger.requested > 0) { + totalRequests -= partyLedger.requested; // subtracting a negative + totalContributions += partyLedger.requested; // adding a negative + + partyLedger.contributed += partyLedger.requested; // adding a negative + partyLedger.requested = 0; + + // CASE: Requests >= Contributions + } else { + totalRequests += partyLedger.contributed; // adding a positive + totalContributions -= partyLedger.contributed; // subtracting a positive + + partyLedger.requested += partyLedger.contributed; // adding a positive + partyLedger.contributed = 0; + } + + parties[i] = partyLedger; + } + + } while (i != 0); + + return (parties, totalRequests, totalContributions); + } + + function _removeSurplusRequests( + Ledger[LEDGER_LENGTH] memory parties, uint256 activeParties, int64 totalRequests, int64 totalContributions) + internal + pure + returns (Ledger[LEDGER_LENGTH] memory, int64, int64) + { + // NOTE: A check to verify totalRequests > 0 will happen prior to calling this + + // NOTE: + // ORDER is: + // 1: LastToAct (builder) + // ... + // Last: FirstToAct (DApp) + + for (uint256 i; i 0; ) { + // If party has not been touched, skip it + if (activeParties.isInactive(i)) { + unchecked{++i;} + continue; + } + + Ledger memory partyLedger = parties[i]; + + if (partyLedger.requested > 0) { + if (totalRequests > partyLedger.requested) { + totalRequests -= partyLedger.requested; + totalContributions += partyLedger.requested; + partyLedger.requested = 0; + + } else { + partyLedger.requested -= totalRequests; + totalContributions += totalRequests; + totalRequests = 0; + } + + parties[i] = partyLedger; + } + unchecked{++i;} + } + + return (parties, totalRequests, totalContributions); + } + + function _allocateSurplusContributions( + Ledger[LEDGER_LENGTH] memory parties, uint256 activeParties, int64 totalRequests, int64 totalContributions) + internal + pure + returns (Ledger[LEDGER_LENGTH] memory, int64, int64) + { + // NOTE: A check to verify totalRequests + totalContributions > 0 will happen prior to calling this + + // NOTE: + // ORDER is: + // 1: FirstToAct (DApp) + // ... + // Last: LastToAct (builder) + + int64 netBalance = totalRequests + totalContributions; + + uint256 i = LEDGER_LENGTH; + + do { + --i; + + // If party has not been touched, skip it + if (activeParties.isInactive(i)) { + continue; + } + + Ledger memory partyLedger = parties[i]; + + if (partyLedger.contributed > 0) { + if (netBalance > partyLedger.contributed) { + totalContributions -= partyLedger.contributed; + partyLedger.balance += partyLedger.contributed; + partyLedger.contributed = 0; + + } else { + partyLedger.contributed -= netBalance; + partyLedger.balance += netBalance; + totalContributions -= netBalance; + } + + parties[i] = partyLedger; + + netBalance = totalRequests + totalContributions; + } + + } while (i != 0 && netBalance > 0); + + return (parties, totalRequests, totalContributions); + } + + function _assignBalanceDeltas( + Ledger[LEDGER_LENGTH] memory parties, + uint256 activeParties, address user, address dapp, address winningSolver, address bundler) + internal + { + for (uint256 i=0; i < LEDGER_LENGTH;) { + // If party has not been touched, skip it + if (activeParties.isInactive(i)) { + unchecked{++i;} + continue; + } + + Ledger memory partyLedger = parties[i]; + + address partyAddress = _partyAddress(i, user, dapp, winningSolver, bundler); + EscrowAccountData memory escrowData = _escrowAccountData[partyAddress]; + + + console.log("-"); + console.log("Starting Bal:", escrowData.balance); + + + bool requiresUpdate; + if (partyLedger.balance < 0) { + escrowData.balance -= (uint128(uint64(partyLedger.balance * -1)) * uint128(tx.gasprice)); + requiresUpdate = true; + + } else if (partyLedger.balance > 0) { + escrowData.balance += (uint128(uint64(partyLedger.balance)) * uint128(tx.gasprice)); + requiresUpdate = true; + } + + if (i == uint256(Party.Solver)) { + ++escrowData.nonce; + requiresUpdate = true; + } + + // Track lastAccessed for all parties, although only check against it for solver parties + if (requiresUpdate) { + escrowData.lastAccessed = uint64(block.number); + _escrowAccountData[partyAddress] = escrowData; + } + + console.log("Ending Bal :", escrowData.balance); + console.log("-"); + + + unchecked{++i;} + } + } + + function _consolePrint(Ledger[LEDGER_LENGTH] memory parties, uint256 activeParties) internal view { + for (uint256 i=0; i < LEDGER_LENGTH; i++) { + if (activeParties.isInactive(i)) { + console.log(""); + console.log("Party:", _partyName(i)); + console.log("confirmed - inactive"); + console.log("-"); + continue; + } + + Ledger memory partyLedger = parties[i]; + + if (partyLedger.status == LedgerStatus.Proxy) { + console.log(""); + console.log("Party:", _partyName(i)); + console.log("confirmed - proxy"); + console.log("-"); + continue; + } + + console.log(""); + console.log("Party:", _partyName(i)); + console.log("confirmed - ACTIVE"); + console.log("-"); + _logInt64("netBalance :", partyLedger.balance); + _logInt64("requested :", partyLedger.requested); + _logInt64("contributed :", partyLedger.contributed); + } + } + + // TODO: Unroll this - just doing it for now to improve readability + function _partyAddress(uint256 index, address user, address dapp, address winningSolver, address bundler) internal view returns (address) { + Party party = Party(index); + if (party == Party.DApp) return dapp; + if (party == Party.User) return user; + if (party == Party.Solver) return winningSolver; + if (party == Party.Bundler) return bundler; // <3 + if (party == Party.Builder) return block.coinbase; + return address(this); + } + + + // for testing purposes + function _partyName(uint256 index) internal pure returns (string memory) { + Party party = Party(index); + if (party == Party.DApp) return "dApp"; + if (party == Party.User) return "user"; + if (party == Party.Solver) return "solver"; + if (party == Party.Bundler) return "bundler"; + if (party == Party.Builder) return "builder"; + return "unknown"; + } + + function _logInt64(string memory pretext, int64 i) internal view { + if (i < 0) console.log(string.concat(pretext, " -"), uint64(-1 * i)); + else console.log(string.concat(pretext, " +"), uint64(i)); + } + + + function _validParty(address environment, Party party) internal returns (bool valid) { + Lock memory mLock = lock; + if (mLock.activeEnvironment != environment) { + return false; + } + + uint256 activeParties = uint256(mLock.activeParties); + + if (activeParties.isInactive(party)) { + activeParties = activeParties.markActive(party); + lock.activeParties = uint16(activeParties); + } + return true; + } + + function _validParties(address environment, Party partyOne, Party partyTwo) internal returns (bool valid) { + Lock memory mLock = lock; + if (mLock.activeEnvironment != environment) { + return false; + } + + uint256 parties = partyOne.toBit() | partyTwo.toBit(); + uint256 activeParties = uint256(mLock.activeParties); + + if (activeParties & parties != parties) { + activeParties |= parties; + lock.activeParties = uint16(activeParties); + } + return true; + } + + function _getLedger(Party party) internal view returns (Ledger memory partyLedger, uint256 index) { + uint256 partyIndex; + + do { + partyIndex = uint256(party); + partyLedger = ledgers[partyIndex]; + party = partyLedger.proxy; + index = uint256(party); + + } while (partyIndex != index); + + if (partyLedger.status == LedgerStatus.Inactive) partyLedger.status = LedgerStatus.Active; + } + + function _checkSolverProxy(address solverFrom, address bundler) internal returns (bool validSolver) { + // Note that the Solver can't be the User or the DApp - those combinations are blocked in the ExecutionEnvironment. + + if (solverFrom == block.coinbase) { + uint256 builderIndex = uint256(Party.Builder); + Ledger memory partyLedger = ledgers[builderIndex]; + + // CASE: Invalid combination (solver = coinbase = user | dapp) + if (uint256(partyLedger.proxy) > uint256(Party.Solver)) { + return false; + } + + // CASE: ledger is finalized or balancing + if (uint256(partyLedger.status) > uint256(LedgerStatus.Borrowing)) { + return false; + } + + // CASE: proxy is solver or builder + // Pass, and check builder proxy next + + // CASE: no proxy yet, so make one + if (uint256(partyLedger.proxy) == builderIndex) { + uint256 activeParties = _getActiveParties(); + if (activeParties.isInactive(Party.Builder)) { + _saveActiveParties(activeParties.markActive(Party.Builder)); + } + + partyLedger.status = LedgerStatus.Proxy; + partyLedger.proxy = Party.Solver; + // Note: don't overwrite the stored values - we may need to undo the proxy if solver fails + ledgers[builderIndex] = partyLedger; + + // Solver inherits the requests and contributions of their alter ego + uint256 solverIndex = uint256(Party.Solver); + Ledger memory sLedger = ledgers[solverIndex]; + + sLedger.balance += partyLedger.balance; + sLedger.contributed += partyLedger.contributed; + sLedger.requested += partyLedger.requested; + + ledgers[solverIndex] = sLedger; + } + } + + + if (solverFrom == bundler) { + uint256 bundlerIndex = uint256(Party.Bundler); + Ledger memory partyLedger = ledgers[bundlerIndex]; + + // CASE: Invalid combination (solver = bundler = user | dapp) + if (uint256(partyLedger.proxy) > uint256(Party.Solver)) { + return false; + } + + // CASE: ledger is finalized or balancing + if (uint256(partyLedger.status) > uint256(LedgerStatus.Borrowing)) { + return false; + } + + // CASE: proxy is solver or builder + // Pass, and check builder proxy next + + // CASE: no proxy + if (uint256(partyLedger.proxy) == bundlerIndex) { + // Bundler is always active, so no need to mark. + + partyLedger.status = LedgerStatus.Proxy; + partyLedger.proxy = Party.Solver; + // Note: don't overwrite the stored values - we may need to undo the proxy if solver fails + ledgers[bundlerIndex] = partyLedger; + + // Solver inherits the requests and contributions of their alter ego + uint256 solverIndex = uint256(Party.Solver); + Ledger memory sLedger = ledgers[solverIndex]; + + sLedger.balance += partyLedger.balance; + sLedger.contributed += partyLedger.contributed; + sLedger.requested += partyLedger.requested; + + ledgers[solverIndex] = sLedger; + } + } + + return true; + } + + function _updateSolverProxy(address solverFrom, address bundler, bool solverSuccessful) internal { + // Note that the Solver can't be the User or the DApp - those combinations are blocked in the ExecutionEnvironment. + + if (solverFrom == block.coinbase && block.coinbase != bundler) { + + uint256 builderIndex = uint256(Party.Builder); + uint256 solverIndex = uint256(Party.Solver); + + // Solver inherited the requests and contributions of their alter ego + Ledger memory partyLedger = ledgers[builderIndex]; + Ledger memory sLedger = ledgers[solverIndex]; + + if (solverSuccessful) { + // CASE: Delete the balances on the older ledger + // TODO: Pretty sure we can skip this since it gets ignored and deleted later + partyLedger.balance = 0; + partyLedger.contributed = 0; + partyLedger.requested = 0; + + ledgers[builderIndex] = partyLedger; // Proxy status stays + + } else { + // CASE: Undo the balance adjustments for the next solver + sLedger.balance -= partyLedger.balance; + sLedger.contributed -= partyLedger.contributed; + sLedger.requested -= partyLedger.requested; + + ledgers[solverIndex] = sLedger; + + partyLedger.proxy = Party.Builder; + partyLedger.status = LedgerStatus.Active; + + ledgers[builderIndex] = partyLedger; + } + } + + if (solverFrom == bundler) { + + uint256 bundlerIndex = uint256(Party.Bundler); + uint256 solverIndex = uint256(Party.Solver); + + // Solver inherited the requests and contributions of their alter ego + Ledger memory partyLedger = ledgers[bundlerIndex]; + Ledger memory sLedger = ledgers[solverIndex]; + + if (solverSuccessful) { + // CASE: Delete the balances on the older ledger + // TODO: Pretty sure we can skip this since it gets ignored and deleted later + partyLedger.balance = 0; + partyLedger.contributed = 0; + partyLedger.requested = 0; + + ledgers[bundlerIndex] = partyLedger; // Proxy status stays + + } else { + // CASE: Undo the balance adjustments for the next solver + sLedger.balance -= partyLedger.balance; + sLedger.contributed -= partyLedger.contributed; + sLedger.requested -= partyLedger.requested; + + ledgers[solverIndex] = sLedger; + + partyLedger.proxy = Party.Bundler; + partyLedger.status = LedgerStatus.Active; + + ledgers[bundlerIndex] = partyLedger; + } + } + } + + function contribute(Party recipient) external payable { + if(!_validParty(msg.sender, recipient)) revert InvalidEnvironment(); + + int64 amount = int64(uint64((msg.value) / tx.gasprice)); + + (Ledger memory partyLedger, uint256 pIndex) = _getLedger(recipient); + + if(partyLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(7); + + if (partyLedger.status == LedgerStatus.Inactive) partyLedger.status = LedgerStatus.Active; + + if (partyLedger.requested < 0) { + // CASE: still in deficit + if (partyLedger.requested + amount < 0) { + partyLedger.requested += amount; + amount = 0; + + // CASE: surplus + } else { + amount += partyLedger.requested; + partyLedger.requested = 0; + } + } + + if (amount != 0) partyLedger.contributed += amount; + + ledgers[pIndex] = partyLedger; + } + + function deposit(Party party) external payable { + if(!_validParty(msg.sender, party)) revert InvalidEnvironment(); + + int64 amount = int64(uint64((msg.value) / tx.gasprice)); + + uint256 pIndex = uint256(party); + Ledger memory partyLedger = ledgers[pIndex]; + + if(partyLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(8); + + if (partyLedger.status == LedgerStatus.Inactive) partyLedger.status = LedgerStatus.Active; + + partyLedger.balance += amount; + + ledgers[pIndex] = partyLedger; + } + + // NOTE: DAPPs can gain malicious access to these funcs if they want to, but attacks beyond + // the approved amounts will only lead to a revert. + // Bundlers must make sure the DApp hasn't maliciously upgraded their contract to avoid wasting gas. + function contributeTo(Party donor, Party recipient, uint256 amt) external { + if(!_validParties(msg.sender, donor, recipient)) revert InvalidEnvironment(); + _contributeTo(donor, recipient, amt); + } + + function requestFrom(Party donor, Party recipient, uint256 amt) external { + if(!_validParties(msg.sender, donor, recipient)) revert InvalidEnvironment(); + _requestFrom(donor, recipient, amt); + } + + function finalize(Party party, address partyAddress) external returns (bool) { + if(!_validParty(msg.sender, party)) revert InvalidEnvironment(); + if(party == Party.Solver) revert SolverMustReconcile(); + + uint256 pIndex = uint256(party); + Ledger memory partyLedger = ledgers[pIndex]; + + if (partyLedger.status == LedgerStatus.Finalized) return false; + + if (partyLedger.contributed + partyLedger.requested < 0) return false; + + uint256 grossBalance = uint256(_escrowAccountData[partyAddress].balance); + + if (int64(uint64(grossBalance / tx.gasprice)) + partyLedger.balance - 1 < 0) return false; + + partyLedger.status = LedgerStatus.Finalized; + ledgers[pIndex] = partyLedger; + + return true; + } + function reconcile(address environment, address searcherFrom, uint256 maxApprovedGasSpend) external payable returns (bool) { + // NOTE: approvedAmount is the amount of the solver's atlETH that the solver is allowing + // to be used to cover what they owe. This will be subtracted later - tx will revert here if there isn't enough. + if (!_validParty(environment, Party.Solver)) { + return false; + } + uint256 partyIndex = uint256(Party.Solver); + Ledger memory partyLedger = ledgers[partyIndex]; + if (partyLedger.status == LedgerStatus.Finalized) { + return false; + } + if (msg.value != 0) { + int64 amount = int64(uint64((msg.value) / tx.gasprice)); + partyLedger.balance += amount; + } + if (maxApprovedGasSpend != 0) { + uint256 solverSurplusBalance = uint256(_escrowAccountData[searcherFrom].balance) - (EscrowBits.SOLVER_GAS_LIMIT * tx.gasprice + 1); + maxApprovedGasSpend = maxApprovedGasSpend > solverSurplusBalance ? solverSurplusBalance : maxApprovedGasSpend; + int64 gasAllowance = int64(uint64(maxApprovedGasSpend / tx.gasprice)); + if (partyLedger.balance < 0) { + if (gasAllowance < partyLedger.balance) { + return false; + } + gasAllowance += partyLedger.balance; // note that .balance is a negative number so this is a subtraction + } + partyLedger.contributed += gasAllowance; // note that surplus .contributed is refunded to the party + partyLedger.balance -= gasAllowance; + } + if (partyLedger.contributed < 0) { + return false; + } + + partyLedger.status = LedgerStatus.Finalized; // no additional requests can be made to this party + ledgers[partyIndex] = partyLedger; + return true; + } + // Requirements ported from SafetyLocks: + function _getActiveParties() internal view returns (uint256 activeParties) { + Lock memory mLock = lock; + activeParties = uint256(mLock.activeParties); + } + function _saveActiveParties(uint256 activeParties) internal { + lock.activeParties = uint16(activeParties); + } - // Not needed in GasAccLib but set to keep in sync with Atlas + // NOTE: Not used except to set an immutable in Storage.sol function _computeDomainSeparator() internal virtual override view returns (bytes32) { return bytes32(0); } diff --git a/src/contracts/atlas/SafetyLocks.sol b/src/contracts/atlas/SafetyLocks.sol index 9a85dacd..0e43e251 100644 --- a/src/contracts/atlas/SafetyLocks.sol +++ b/src/contracts/atlas/SafetyLocks.sol @@ -27,8 +27,9 @@ abstract contract SafetyLocks is Storage, FastLaneErrorsEvents { uint256 _escrowDuration, address _factory, address _verification, + address _gasAccLib, address _simulator - ) Storage(_escrowDuration, _factory, _verification, _simulator) {} + ) Storage(_escrowDuration, _factory, _verification, _gasAccLib, _simulator) {} function _initializeEscrowLock(UserOperation calldata userOp, address executionEnvironment, address bundler, uint256 gasLimit) onlyWhenUnlocked internal { diff --git a/src/contracts/atlas/Storage.sol b/src/contracts/atlas/Storage.sol index b8a2ffdc..23a1d6ce 100644 --- a/src/contracts/atlas/Storage.sol +++ b/src/contracts/atlas/Storage.sol @@ -13,6 +13,7 @@ contract Storage { uint256 public immutable ESCROW_DURATION; address public immutable FACTORY; address public immutable VERIFICATION; + address public immutable GAS_ACC_LIB; address public immutable SIMULATOR; // AtlETH ERC-20 constants @@ -43,11 +44,13 @@ contract Storage { uint256 _escrowDuration, address _factory, address _verification, + address _gasAccLib, address _simulator ) { ESCROW_DURATION = _escrowDuration; FACTORY = _factory; VERIFICATION = _verification; + GAS_ACC_LIB = _gasAccLib; SIMULATOR = _simulator; INITIAL_CHAIN_ID = block.chainid; INITIAL_DOMAIN_SEPARATOR = _computeDomainSeparator(); diff --git a/src/contracts/common/Permit69.sol b/src/contracts/common/Permit69.sol index 63849500..78f4e0f9 100644 --- a/src/contracts/common/Permit69.sol +++ b/src/contracts/common/Permit69.sol @@ -28,8 +28,9 @@ abstract contract Permit69 is GasAccounting { uint256 _escrowDuration, address _factory, address _verification, + address _gasAccLib, address _simulator - ) GasAccounting(_escrowDuration, _factory, _verification, _simulator) {} + ) GasAccounting(_escrowDuration, _factory, _verification, _gasAccLib, _simulator) {} // Virtual Functions defined by other Atlas modules function _verifyCallerIsExecutionEnv( diff --git a/test/Permit69.t.sol b/test/Permit69.t.sol index aea4e7c8..e78da608 100644 --- a/test/Permit69.t.sol +++ b/test/Permit69.t.sol @@ -42,7 +42,7 @@ contract Permit69Test is BaseTest { callDepth: 0 }); - mockAtlas = new MockAtlasForPermit69Tests(10,address(0),address(0),address(0)); + mockAtlas = new MockAtlasForPermit69Tests(10,address(0),address(0), address(0), address(0)); mockAtlas.setEscrowKey(escrowKey); mockAtlas.setEnvironment(mockExecutionEnvAddress); @@ -251,8 +251,9 @@ contract MockAtlasForPermit69Tests is Permit69 { uint256 _escrowDuration, address _factory, address _verification, + address _gasAccLib, address _simulator - ) Permit69(_escrowDuration, _factory, _verification, _simulator) {} + ) Permit69(_escrowDuration, _factory, _verification, _gasAccLib, _simulator) {} // Declared in SafetyLocks.sol in the canonical Atlas system // The only property relevant to testing Permit69 is _escrowKey.lockState (bitwise uint16) diff --git a/test/base/BaseTest.t.sol b/test/base/BaseTest.t.sol index 49213671..a6e092d5 100644 --- a/test/base/BaseTest.t.sol +++ b/test/base/BaseTest.t.sol @@ -8,6 +8,7 @@ import {IDAppIntegration} from "src/contracts/interfaces/IDAppIntegration.sol"; import {Atlas} from "src/contracts/atlas/Atlas.sol"; import {AtlasFactory} from "src/contracts/atlas/AtlasFactory.sol"; import {AtlasVerification} from "src/contracts/atlas/AtlasVerification.sol"; +import {GasAccountingLib} from "src/contracts/atlas/GasAccountingLib.sol"; import {Sorter} from "src/contracts/helpers/Sorter.sol"; import {Simulator} from "src/contracts/helpers/Simulator.sol"; @@ -40,6 +41,7 @@ contract BaseTest is Test, TestConstants { Atlas public atlas; AtlasFactory public atlasFactory; AtlasVerification public atlasVerification; + GasAccountingLib public gasAccountingLib; Simulator public simulator; Sorter public sorter; @@ -80,15 +82,26 @@ contract BaseTest is Test, TestConstants { payee, vm.getNonce(payee) + 2 ); + address expectedGasAccountingLibAddr = computeCreateAddress( + payee, + vm.getNonce(payee) + 3 + ); atlas = new Atlas({ _escrowDuration: 64, _factory: expectedAtlasFactoryAddr, _verification: expectedAtlasVerificationAddr, + _gasAccLib: expectedGasAccountingLibAddr, _simulator: address(simulator) }); atlasFactory = new AtlasFactory(address(atlas)); atlasVerification = new AtlasVerification(address(atlas)); + gasAccountingLib = new GasAccountingLib({ + _escrowDuration: 64, + _factory: expectedAtlasFactoryAddr, + _verification: expectedAtlasVerificationAddr, + _simulator: address(simulator) + }); simulator.setAtlas(address(atlas)); From 641adff7e76b475a74cd1542fb654bc499f51009 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Wed, 8 Nov 2023 16:17:57 +0200 Subject: [PATCH 32/38] Refactor GasAcc logic to separate delegatecalled contract - in progress --- src/contracts/atlas/GasAccounting.sol | 339 +++-------------------- src/contracts/atlas/GasAccountingLib.sol | 198 ++++++------- 2 files changed, 141 insertions(+), 396 deletions(-) diff --git a/src/contracts/atlas/GasAccounting.sol b/src/contracts/atlas/GasAccounting.sol index 5373b7a7..a2d7aa70 100644 --- a/src/contracts/atlas/GasAccounting.sol +++ b/src/contracts/atlas/GasAccounting.sol @@ -2,11 +2,11 @@ pragma solidity ^0.8.16; import {SafetyLocks} from "../atlas/SafetyLocks.sol"; +import {GasAccountingLib} from "./GasAccountingLib.sol"; import "../types/EscrowTypes.sol"; import "../types/LockTypes.sol"; - import {EscrowBits} from "../libraries/EscrowBits.sol"; import {PartyMath} from "../libraries/GasParties.sol"; @@ -25,37 +25,55 @@ abstract contract GasAccounting is SafetyLocks { address _simulator ) SafetyLocks(_escrowDuration, _factory, _verification, _gasAccLib, _simulator) {} - // NOTE: donations are simply deposits that have a different msg.sender than receiving party - function _deposit(Party party, uint256 amt) internal returns (uint256 balanceOwed) { + // --------------------------------------- + // EXTERNAL FUNCTIONS + // --------------------------------------- - (Ledger memory partyLedger, uint256 partyIndex) = _getLedger(party); + function deposit(Party party) external payable { + GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.deposit.selector, party)); + } - if (partyLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(1); + function contribute(Party recipient) external payable { + GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.contribute.selector, recipient)); + } - int64 depositAmount = int64(uint64(amt / tx.gasprice)); + function contributeTo(Party donor, Party recipient, uint256 amt) external payable { + GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.contributeTo.selector, donor, recipient, amt)); + } - partyLedger.balance += depositAmount; - - balanceOwed = partyLedger.balance < 0 ? uint256(uint64(-1 * partyLedger.balance)) : 0; + function requestFrom(Party donor, Party recipient, uint256 amt) external payable { + GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.requestFrom.selector, donor, recipient, amt)); + } - ledgers[partyIndex] = partyLedger; + function finalize(Party party, address partyAddress) external payable { + GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.finalize.selector, party, partyAddress)); } + function reconcile(address environment, address searcherFrom, uint256 maxApprovedGasSpend) external payable returns (bool) { + (bool success, bytes memory data) = GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.reconcile.selector, environment, searcherFrom, maxApprovedGasSpend)); + return abi.decode(data, (bool)); + } - function _borrow(Party party, uint256 amt) internal { - // Note that for Solver borrows, the repayment check happens *inside* the try/catch. - - (Ledger memory partyLedger, uint256 partyIndex) = _getLedger(party); + // --------------------------------------- + // INTERNAL FUNCTIONS + // --------------------------------------- - if(partyLedger.status >= LedgerStatus.Borrowing) revert LedgerFinalized(2); - - int64 borrowAmount = int64(uint64(amt / tx.gasprice))+1; - partyLedger.balance -= borrowAmount; - partyLedger.status = LedgerStatus.Borrowing; - - ledgers[partyIndex] = partyLedger; + function _updateSolverProxy(address solverFrom, address bundler, bool solverSuccessful) internal { + GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.updateSolverProxy.selector, solverFrom, bundler, solverSuccessful)); } + function _checkSolverProxy(address solverFrom, address bundler) internal returns (bool validSolver) { + (bool success, bytes memory data) = GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.checkSolverProxy.selector, solverFrom, bundler)); + return abi.decode(data, (bool)); + } + + function _borrow(Party party, uint256 amt) internal { + GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.borrow.selector, party, amt)); + } + + // STILL TO BE REFACTORED: + + function _tradeCorrection(Party party, uint256 amt) internal { // Note that for Solver borrows, the repayment check happens *inside* the try/catch. // This function is to mark off a solver borrow from a failed tx @@ -648,284 +666,5 @@ abstract contract GasAccounting is SafetyLocks { if (partyLedger.status == LedgerStatus.Inactive) partyLedger.status = LedgerStatus.Active; } - function _checkSolverProxy(address solverFrom, address bundler) internal returns (bool validSolver) { - // Note that the Solver can't be the User or the DApp - those combinations are blocked in the ExecutionEnvironment. - - if (solverFrom == block.coinbase) { - uint256 builderIndex = uint256(Party.Builder); - Ledger memory partyLedger = ledgers[builderIndex]; - - // CASE: Invalid combination (solver = coinbase = user | dapp) - if (uint256(partyLedger.proxy) > uint256(Party.Solver)) { - return false; - } - - // CASE: ledger is finalized or balancing - if (uint256(partyLedger.status) > uint256(LedgerStatus.Borrowing)) { - return false; - } - - // CASE: proxy is solver or builder - // Pass, and check builder proxy next - - // CASE: no proxy yet, so make one - if (uint256(partyLedger.proxy) == builderIndex) { - uint256 activeParties = _getActiveParties(); - if (activeParties.isInactive(Party.Builder)) { - _saveActiveParties(activeParties.markActive(Party.Builder)); - } - - partyLedger.status = LedgerStatus.Proxy; - partyLedger.proxy = Party.Solver; - // Note: don't overwrite the stored values - we may need to undo the proxy if solver fails - ledgers[builderIndex] = partyLedger; - - // Solver inherits the requests and contributions of their alter ego - uint256 solverIndex = uint256(Party.Solver); - Ledger memory sLedger = ledgers[solverIndex]; - - sLedger.balance += partyLedger.balance; - sLedger.contributed += partyLedger.contributed; - sLedger.requested += partyLedger.requested; - - ledgers[solverIndex] = sLedger; - } - } - - - if (solverFrom == bundler) { - uint256 bundlerIndex = uint256(Party.Bundler); - Ledger memory partyLedger = ledgers[bundlerIndex]; - - // CASE: Invalid combination (solver = bundler = user | dapp) - if (uint256(partyLedger.proxy) > uint256(Party.Solver)) { - return false; - } - - // CASE: ledger is finalized or balancing - if (uint256(partyLedger.status) > uint256(LedgerStatus.Borrowing)) { - return false; - } - - // CASE: proxy is solver or builder - // Pass, and check builder proxy next - - // CASE: no proxy - if (uint256(partyLedger.proxy) == bundlerIndex) { - // Bundler is always active, so no need to mark. - - partyLedger.status = LedgerStatus.Proxy; - partyLedger.proxy = Party.Solver; - // Note: don't overwrite the stored values - we may need to undo the proxy if solver fails - ledgers[bundlerIndex] = partyLedger; - - // Solver inherits the requests and contributions of their alter ego - uint256 solverIndex = uint256(Party.Solver); - Ledger memory sLedger = ledgers[solverIndex]; - - sLedger.balance += partyLedger.balance; - sLedger.contributed += partyLedger.contributed; - sLedger.requested += partyLedger.requested; - - ledgers[solverIndex] = sLedger; - } - } - return true; - } - - function _updateSolverProxy(address solverFrom, address bundler, bool solverSuccessful) internal { - // Note that the Solver can't be the User or the DApp - those combinations are blocked in the ExecutionEnvironment. - - if (solverFrom == block.coinbase && block.coinbase != bundler) { - - uint256 builderIndex = uint256(Party.Builder); - uint256 solverIndex = uint256(Party.Solver); - - // Solver inherited the requests and contributions of their alter ego - Ledger memory partyLedger = ledgers[builderIndex]; - Ledger memory sLedger = ledgers[solverIndex]; - - if (solverSuccessful) { - // CASE: Delete the balances on the older ledger - // TODO: Pretty sure we can skip this since it gets ignored and deleted later - partyLedger.balance = 0; - partyLedger.contributed = 0; - partyLedger.requested = 0; - - ledgers[builderIndex] = partyLedger; // Proxy status stays - - } else { - // CASE: Undo the balance adjustments for the next solver - sLedger.balance -= partyLedger.balance; - sLedger.contributed -= partyLedger.contributed; - sLedger.requested -= partyLedger.requested; - - ledgers[solverIndex] = sLedger; - - partyLedger.proxy = Party.Builder; - partyLedger.status = LedgerStatus.Active; - - ledgers[builderIndex] = partyLedger; - } - } - - if (solverFrom == bundler) { - - uint256 bundlerIndex = uint256(Party.Bundler); - uint256 solverIndex = uint256(Party.Solver); - - // Solver inherited the requests and contributions of their alter ego - Ledger memory partyLedger = ledgers[bundlerIndex]; - Ledger memory sLedger = ledgers[solverIndex]; - - if (solverSuccessful) { - // CASE: Delete the balances on the older ledger - // TODO: Pretty sure we can skip this since it gets ignored and deleted later - partyLedger.balance = 0; - partyLedger.contributed = 0; - partyLedger.requested = 0; - - ledgers[bundlerIndex] = partyLedger; // Proxy status stays - - } else { - // CASE: Undo the balance adjustments for the next solver - sLedger.balance -= partyLedger.balance; - sLedger.contributed -= partyLedger.contributed; - sLedger.requested -= partyLedger.requested; - - ledgers[solverIndex] = sLedger; - - partyLedger.proxy = Party.Bundler; - partyLedger.status = LedgerStatus.Active; - - ledgers[bundlerIndex] = partyLedger; - } - } - } - - function contribute(Party recipient) external payable { - if(!_validParty(msg.sender, recipient)) revert InvalidEnvironment(); - - int64 amount = int64(uint64((msg.value) / tx.gasprice)); - - (Ledger memory partyLedger, uint256 pIndex) = _getLedger(recipient); - - if(partyLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(7); - - if (partyLedger.status == LedgerStatus.Inactive) partyLedger.status = LedgerStatus.Active; - - if (partyLedger.requested < 0) { - // CASE: still in deficit - if (partyLedger.requested + amount < 0) { - partyLedger.requested += amount; - amount = 0; - - // CASE: surplus - } else { - amount += partyLedger.requested; - partyLedger.requested = 0; - } - } - - if (amount != 0) partyLedger.contributed += amount; - - ledgers[pIndex] = partyLedger; - } - - function deposit(Party party) external payable { - if(!_validParty(msg.sender, party)) revert InvalidEnvironment(); - - int64 amount = int64(uint64((msg.value) / tx.gasprice)); - - uint256 pIndex = uint256(party); - Ledger memory partyLedger = ledgers[pIndex]; - - if(partyLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(8); - - if (partyLedger.status == LedgerStatus.Inactive) partyLedger.status = LedgerStatus.Active; - - partyLedger.balance += amount; - - ledgers[pIndex] = partyLedger; - } - - // NOTE: DAPPs can gain malicious access to these funcs if they want to, but attacks beyond - // the approved amounts will only lead to a revert. - // Bundlers must make sure the DApp hasn't maliciously upgraded their contract to avoid wasting gas. - function contributeTo(Party donor, Party recipient, uint256 amt) external { - if(!_validParties(msg.sender, donor, recipient)) revert InvalidEnvironment(); - _contributeTo(donor, recipient, amt); - } - - function requestFrom(Party donor, Party recipient, uint256 amt) external { - if(!_validParties(msg.sender, donor, recipient)) revert InvalidEnvironment(); - _requestFrom(donor, recipient, amt); - } - - function finalize(Party party, address partyAddress) external returns (bool) { - if(!_validParty(msg.sender, party)) revert InvalidEnvironment(); - if(party == Party.Solver) revert SolverMustReconcile(); - - uint256 pIndex = uint256(party); - Ledger memory partyLedger = ledgers[pIndex]; - - if (partyLedger.status == LedgerStatus.Finalized) return false; - - if (partyLedger.contributed + partyLedger.requested < 0) return false; - - uint256 grossBalance = uint256(_escrowAccountData[partyAddress].balance); - - if (int64(uint64(grossBalance / tx.gasprice)) + partyLedger.balance - 1 < 0) return false; - - partyLedger.status = LedgerStatus.Finalized; - ledgers[pIndex] = partyLedger; - - return true; - } - - function reconcile(address environment, address searcherFrom, uint256 maxApprovedGasSpend) external payable returns (bool) { - // NOTE: approvedAmount is the amount of the solver's atlETH that the solver is allowing - // to be used to cover what they owe. This will be subtracted later - tx will revert here if there isn't enough. - if (!_validParty(environment, Party.Solver)) { - return false; - } - - uint256 partyIndex = uint256(Party.Solver); - - Ledger memory partyLedger = ledgers[partyIndex]; - if (partyLedger.status == LedgerStatus.Finalized) { - return false; - } - - if (msg.value != 0) { - int64 amount = int64(uint64((msg.value) / tx.gasprice)); - partyLedger.balance += amount; - } - - if (maxApprovedGasSpend != 0) { - uint256 solverSurplusBalance = uint256(_escrowAccountData[searcherFrom].balance) - (EscrowBits.SOLVER_GAS_LIMIT * tx.gasprice + 1); - maxApprovedGasSpend = maxApprovedGasSpend > solverSurplusBalance ? solverSurplusBalance : maxApprovedGasSpend; - - int64 gasAllowance = int64(uint64(maxApprovedGasSpend / tx.gasprice)); - - if (partyLedger.balance < 0) { - if (gasAllowance < partyLedger.balance) { - return false; - } - gasAllowance += partyLedger.balance; // note that .balance is a negative number so this is a subtraction - } - - partyLedger.contributed += gasAllowance; // note that surplus .contributed is refunded to the party - partyLedger.balance -= gasAllowance; - } - - if (partyLedger.contributed < 0) { - return false; - } - - partyLedger.status = LedgerStatus.Finalized; // no additional requests can be made to this party - ledgers[partyIndex] = partyLedger; - return true; - } } \ No newline at end of file diff --git a/src/contracts/atlas/GasAccountingLib.sol b/src/contracts/atlas/GasAccountingLib.sol index 774ef01b..a0ee938c 100644 --- a/src/contracts/atlas/GasAccountingLib.sol +++ b/src/contracts/atlas/GasAccountingLib.sol @@ -11,6 +11,8 @@ import "../types/LockTypes.sol"; import "forge-std/Test.sol"; //TODO remove +// TODO check for address(this) or other assumptions from when inside Atlas inheritance + contract GasAccountingLib is Storage, FastLaneErrorsEvents { using PartyMath for Party; using PartyMath for uint256; @@ -23,6 +25,22 @@ contract GasAccountingLib is Storage, FastLaneErrorsEvents { address _simulator ) Storage(_escrowDuration, _factory, _verification, address(this), _simulator) {} + function deposit(Party party) external payable { + if(!_validParty(msg.sender, party)) revert InvalidEnvironment(); + + int64 amount = int64(uint64((msg.value) / tx.gasprice)); + + uint256 pIndex = uint256(party); + Ledger memory partyLedger = ledgers[pIndex]; + + if(partyLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(8); + + if (partyLedger.status == LedgerStatus.Inactive) partyLedger.status = LedgerStatus.Active; + + partyLedger.balance += amount; + + ledgers[pIndex] = partyLedger; + } // NOTE: donations are simply deposits that have a different msg.sender than receiving party function _deposit(Party party, uint256 amt) internal returns (uint256 balanceOwed) { @@ -40,8 +58,87 @@ contract GasAccountingLib is Storage, FastLaneErrorsEvents { ledgers[partyIndex] = partyLedger; } + // NOTE: DAPPs can gain malicious access to these funcs if they want to, but attacks beyond + // the approved amounts will only lead to a revert. + // Bundlers must make sure the DApp hasn't maliciously upgraded their contract to avoid wasting gas. + function contributeTo(Party donor, Party recipient, uint256 amt) external { + if(!_validParties(msg.sender, donor, recipient)) revert InvalidEnvironment(); + _contributeTo(donor, recipient, amt); + } + + function _contributeTo(Party donor, Party recipient, uint256 amt) internal { + + (Ledger memory donorLedger, uint256 donorIndex) = _getLedger(donor); + if(donorLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(5); + + (Ledger memory recipientLedger, uint256 recipientIndex) = _getLedger(recipient); + if(recipientLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(6); + + int64 amount = int64(uint64(amt / tx.gasprice)); + + donorLedger.balance -= amount; + donorLedger.contributed += amount; + recipientLedger.requested += amount; + + ledgers[donorIndex] = donorLedger; + ledgers[recipientIndex] = recipientLedger; + } + + + function contribute(Party recipient) external payable { + if(!_validParty(msg.sender, recipient)) revert InvalidEnvironment(); + + int64 amount = int64(uint64((msg.value) / tx.gasprice)); + + (Ledger memory partyLedger, uint256 pIndex) = _getLedger(recipient); + + if(partyLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(7); + + if (partyLedger.status == LedgerStatus.Inactive) partyLedger.status = LedgerStatus.Active; + + if (partyLedger.requested < 0) { + // CASE: still in deficit + if (partyLedger.requested + amount < 0) { + partyLedger.requested += amount; + amount = 0; + + // CASE: surplus + } else { + amount += partyLedger.requested; + partyLedger.requested = 0; + } + } + + if (amount != 0) partyLedger.contributed += amount; + + ledgers[pIndex] = partyLedger; + } + + function requestFrom(Party donor, Party recipient, uint256 amt) external { + if(!_validParties(msg.sender, donor, recipient)) revert InvalidEnvironment(); + _requestFrom(donor, recipient, amt); + } + + function _requestFrom(Party donor, Party recipient, uint256 amt) internal { + // TODO: different parties will be ineligible to request funds from once their phase is over. + // We need to add a phase check to verify this. + + (Ledger memory donorLedger, uint256 donorIndex) = _getLedger(donor); + if(donorLedger.status >= LedgerStatus.Balancing) revert LedgerBalancing(2); + + (Ledger memory recipientLedger, uint256 recipientIndex) = _getLedger(recipient); + if(recipientLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(4); + + int64 amount = int64(uint64(amt / tx.gasprice)); + + donorLedger.contributed -= amount; + recipientLedger.requested -= amount; + + ledgers[donorIndex] = donorLedger; + ledgers[recipientIndex] = recipientLedger; + } - function _borrow(Party party, uint256 amt) internal { + function borrow(Party party, uint256 amt) external { // Note that for Solver borrows, the repayment check happens *inside* the try/catch. (Ledger memory partyLedger, uint256 partyIndex) = _getLedger(party); @@ -55,6 +152,7 @@ contract GasAccountingLib is Storage, FastLaneErrorsEvents { ledgers[partyIndex] = partyLedger; } + function _tradeCorrection(Party party, uint256 amt) internal { // Note that for Solver borrows, the repayment check happens *inside* the try/catch. // This function is to mark off a solver borrow from a failed tx @@ -124,42 +222,9 @@ contract GasAccountingLib is Storage, FastLaneErrorsEvents { revert InsufficientFunds(); } - function _requestFrom(Party donor, Party recipient, uint256 amt) internal { - // TODO: different parties will be ineligible to request funds from once their phase is over. - // We need to add a phase check to verify this. - - (Ledger memory donorLedger, uint256 donorIndex) = _getLedger(donor); - if(donorLedger.status >= LedgerStatus.Balancing) revert LedgerBalancing(2); - - (Ledger memory recipientLedger, uint256 recipientIndex) = _getLedger(recipient); - if(recipientLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(4); - - int64 amount = int64(uint64(amt / tx.gasprice)); - - donorLedger.contributed -= amount; - recipientLedger.requested -= amount; - - ledgers[donorIndex] = donorLedger; - ledgers[recipientIndex] = recipientLedger; - } - - function _contributeTo(Party donor, Party recipient, uint256 amt) internal { - - (Ledger memory donorLedger, uint256 donorIndex) = _getLedger(donor); - if(donorLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(5); - - (Ledger memory recipientLedger, uint256 recipientIndex) = _getLedger(recipient); - if(recipientLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(6); - - int64 amount = int64(uint64(amt / tx.gasprice)); - donorLedger.balance -= amount; - donorLedger.contributed += amount; - recipientLedger.requested += amount; - ledgers[donorIndex] = donorLedger; - ledgers[recipientIndex] = recipientLedger; - } + // Returns true if Solver status is Finalized and the caller (Execution Environment) is in surplus function validateBalances() external view returns (bool valid) { @@ -647,7 +712,7 @@ contract GasAccountingLib is Storage, FastLaneErrorsEvents { if (partyLedger.status == LedgerStatus.Inactive) partyLedger.status = LedgerStatus.Active; } - function _checkSolverProxy(address solverFrom, address bundler) internal returns (bool validSolver) { + function checkSolverProxy(address solverFrom, address bundler) external returns (bool validSolver) { // Note that the Solver can't be the User or the DApp - those combinations are blocked in the ExecutionEnvironment. if (solverFrom == block.coinbase) { @@ -733,7 +798,7 @@ contract GasAccountingLib is Storage, FastLaneErrorsEvents { return true; } - function _updateSolverProxy(address solverFrom, address bundler, bool solverSuccessful) internal { + function updateSolverProxy(address solverFrom, address bundler, bool solverSuccessful) external { // Note that the Solver can't be the User or the DApp - those combinations are blocked in the ExecutionEnvironment. if (solverFrom == block.coinbase && block.coinbase != bundler) { @@ -803,65 +868,6 @@ contract GasAccountingLib is Storage, FastLaneErrorsEvents { } } - function contribute(Party recipient) external payable { - if(!_validParty(msg.sender, recipient)) revert InvalidEnvironment(); - - int64 amount = int64(uint64((msg.value) / tx.gasprice)); - - (Ledger memory partyLedger, uint256 pIndex) = _getLedger(recipient); - - if(partyLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(7); - - if (partyLedger.status == LedgerStatus.Inactive) partyLedger.status = LedgerStatus.Active; - - if (partyLedger.requested < 0) { - // CASE: still in deficit - if (partyLedger.requested + amount < 0) { - partyLedger.requested += amount; - amount = 0; - - // CASE: surplus - } else { - amount += partyLedger.requested; - partyLedger.requested = 0; - } - } - - if (amount != 0) partyLedger.contributed += amount; - - ledgers[pIndex] = partyLedger; - } - - function deposit(Party party) external payable { - if(!_validParty(msg.sender, party)) revert InvalidEnvironment(); - - int64 amount = int64(uint64((msg.value) / tx.gasprice)); - - uint256 pIndex = uint256(party); - Ledger memory partyLedger = ledgers[pIndex]; - - if(partyLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(8); - - if (partyLedger.status == LedgerStatus.Inactive) partyLedger.status = LedgerStatus.Active; - - partyLedger.balance += amount; - - ledgers[pIndex] = partyLedger; - } - - // NOTE: DAPPs can gain malicious access to these funcs if they want to, but attacks beyond - // the approved amounts will only lead to a revert. - // Bundlers must make sure the DApp hasn't maliciously upgraded their contract to avoid wasting gas. - function contributeTo(Party donor, Party recipient, uint256 amt) external { - if(!_validParties(msg.sender, donor, recipient)) revert InvalidEnvironment(); - _contributeTo(donor, recipient, amt); - } - - function requestFrom(Party donor, Party recipient, uint256 amt) external { - if(!_validParties(msg.sender, donor, recipient)) revert InvalidEnvironment(); - _requestFrom(donor, recipient, amt); - } - function finalize(Party party, address partyAddress) external returns (bool) { if(!_validParty(msg.sender, party)) revert InvalidEnvironment(); if(party == Party.Solver) revert SolverMustReconcile(); From 0654d49e47695ed7dcf838ab36c01093db381104 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Wed, 8 Nov 2023 17:36:06 +0200 Subject: [PATCH 33/38] GasAccLib fully separated - 1kb left to cut --- script/deploy-atlas.s.sol | 9 +- src/contracts/atlas/GasAccounting.sol | 604 +--------------------- src/contracts/atlas/GasAccountingLib.sol | 625 ++++++++++++----------- test/base/BaseTest.t.sol | 3 +- 4 files changed, 343 insertions(+), 898 deletions(-) diff --git a/script/deploy-atlas.s.sol b/script/deploy-atlas.s.sol index d789ece5..ae9d0f53 100644 --- a/script/deploy-atlas.s.sol +++ b/script/deploy-atlas.s.sol @@ -59,7 +59,8 @@ contract DeployAtlasScript is DeployBaseScript { _escrowDuration: 64, _factory: expectedAtlasFactoryAddr, _verification: expectedAtlasVerificationAddr, - _simulator: address(simulator) + _simulator: address(simulator), + _atlas: address(atlas) }); vm.stopBroadcast(); @@ -118,7 +119,8 @@ contract DeployAtlasAndSwapIntentDAppControlScript is DeployBaseScript { _escrowDuration: 64, _factory: expectedAtlasFactoryAddr, _verification: expectedAtlasVerificationAddr, - _simulator: address(simulator) + _simulator: address(simulator), + _atlas: address(atlas) }); // Deploy the SwapIntent DAppControl contract @@ -187,7 +189,8 @@ contract DeployAtlasAndSwapIntentDAppControlAndTxBuilderScript is DeployBaseScri _escrowDuration: 64, _factory: expectedAtlasFactoryAddr, _verification: expectedAtlasVerificationAddr, - _simulator: address(simulator) + _simulator: address(simulator), + _atlas: address(atlas) }); // Deploy the SwapIntent DAppControl contract diff --git a/src/contracts/atlas/GasAccounting.sol b/src/contracts/atlas/GasAccounting.sol index a2d7aa70..045a9c46 100644 --- a/src/contracts/atlas/GasAccounting.sol +++ b/src/contracts/atlas/GasAccounting.sol @@ -29,6 +29,12 @@ abstract contract GasAccounting is SafetyLocks { // EXTERNAL FUNCTIONS // --------------------------------------- + // Returns true if Solver status is Finalized and the caller (Execution Environment) is in surplus + // NOTE: This was a view function until logic got moved to delegatecalled contract - still should only be view + function validateBalances() external returns (bool valid) { + valid = ledgers[uint256(Party.Solver)].status == LedgerStatus.Finalized && _isInSurplus(msg.sender); + } + function deposit(Party party) external payable { GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.deposit.selector, party)); } @@ -37,15 +43,15 @@ abstract contract GasAccounting is SafetyLocks { GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.contribute.selector, recipient)); } - function contributeTo(Party donor, Party recipient, uint256 amt) external payable { - GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.contributeTo.selector, donor, recipient, amt)); + function contributeTo(Party donor, Party recipient, uint256 amt) external { + GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.contributeTo.selector, msg.sender, donor, recipient, amt)); } - function requestFrom(Party donor, Party recipient, uint256 amt) external payable { - GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.requestFrom.selector, donor, recipient, amt)); + function requestFrom(Party donor, Party recipient, uint256 amt) external { + GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.requestFrom.selector, msg.sender, donor, recipient, amt)); } - function finalize(Party party, address partyAddress) external payable { + function finalize(Party party, address partyAddress) external { GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.finalize.selector, party, partyAddress)); } @@ -71,600 +77,34 @@ abstract contract GasAccounting is SafetyLocks { GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.borrow.selector, party, amt)); } - // STILL TO BE REFACTORED: - - function _tradeCorrection(Party party, uint256 amt) internal { - // Note that for Solver borrows, the repayment check happens *inside* the try/catch. - // This function is to mark off a solver borrow from a failed tx - - (Ledger memory partyLedger, uint256 partyIndex) = _getLedger(party); - - if(partyLedger.status != LedgerStatus.Borrowing) revert LedgerFinalized(3); - - int64 borrowAmount = int64(uint64(amt / tx.gasprice))+1; - partyLedger.balance += borrowAmount; - partyLedger.status = LedgerStatus.Active; - - ledgers[partyIndex] = partyLedger; + GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.tradeCorrection.selector, party, amt)); } function _use(Party party, address partyAddress, uint256 amt) internal { - - (Ledger memory partyLedger, uint256 partyIndex) = _getLedger(party); - - if(partyLedger.status >= LedgerStatus.Balancing) revert LedgerBalancing(1); - - int64 amount = int64(uint64(amt / tx.gasprice))+1; - - if (partyLedger.requested > 0) { - if (amount > partyLedger.requested) { - amount -= partyLedger.requested; - partyLedger.requested = 0; - } else { - partyLedger.requested -= amount; - ledgers[partyIndex] = partyLedger; - return; - } - } - - if (partyLedger.contributed > 0) { - if (amount > partyLedger.contributed) { - amount -= partyLedger.contributed; - partyLedger.contributed = 0; - } else { - partyLedger.contributed -= amount; - ledgers[partyIndex] = partyLedger; - return; - } - } - - // Avoid the storage read for as long as possible - if (partyLedger.balance > 0) { - if (amount > partyLedger.balance) { - amount -= partyLedger.balance; - partyLedger.balance = 0; - } else { - partyLedger.balance -= amount; - ledgers[partyIndex] = partyLedger; - return; - } - } - - amt = uint256(uint64(amount+1)) * tx.gasprice; - uint256 balance = uint256(_escrowAccountData[partyAddress].balance); - - if (balance > amt) { - partyLedger.balance -= amount; - ledgers[partyIndex] = partyLedger; - return; - } - - revert InsufficientFunds(); + GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.use.selector, party, partyAddress, amt)); } function _requestFrom(Party donor, Party recipient, uint256 amt) internal { - // TODO: different parties will be ineligible to request funds from once their phase is over. - // We need to add a phase check to verify this. - - (Ledger memory donorLedger, uint256 donorIndex) = _getLedger(donor); - if(donorLedger.status >= LedgerStatus.Balancing) revert LedgerBalancing(2); - - (Ledger memory recipientLedger, uint256 recipientIndex) = _getLedger(recipient); - if(recipientLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(4); - - int64 amount = int64(uint64(amt / tx.gasprice)); - - donorLedger.contributed -= amount; - recipientLedger.requested -= amount; - - ledgers[donorIndex] = donorLedger; - ledgers[recipientIndex] = recipientLedger; + GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.requestFrom.selector, address(this), donor, recipient, amt)); } function _contributeTo(Party donor, Party recipient, uint256 amt) internal { - - (Ledger memory donorLedger, uint256 donorIndex) = _getLedger(donor); - if(donorLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(5); - - (Ledger memory recipientLedger, uint256 recipientIndex) = _getLedger(recipient); - if(recipientLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(6); - - int64 amount = int64(uint64(amt / tx.gasprice)); - - donorLedger.balance -= amount; - donorLedger.contributed += amount; - recipientLedger.requested += amount; - - ledgers[donorIndex] = donorLedger; - ledgers[recipientIndex] = recipientLedger; - } - - // Returns true if Solver status is Finalized and the caller (Execution Environment) is in surplus - function validateBalances() external view returns (bool valid) { - valid = ledgers[uint256(Party.Solver)].status == LedgerStatus.Finalized && _isInSurplus(msg.sender); + GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.contributeTo.selector, address(this), donor, recipient, amt)); } - function _isInSurplus(address environment) internal view returns (bool) { - Lock memory mLock = lock; - if (mLock.activeEnvironment != environment) return false; - - int64 totalBalanceDelta; - int64 totalRequests; - int64 totalContributions; - - uint256 activeParties = uint256(mLock.activeParties); - for (uint256 i; i < LEDGER_LENGTH;) { - // If party has not been touched, skip it - if (activeParties & 1<<(i+1) == 0){ - unchecked{++i;} - continue; - } - - Ledger memory partyLedger = ledgers[i]; - if (uint256(partyLedger.proxy) != i) { - unchecked{++i;} - continue; - } - - totalBalanceDelta += partyLedger.balance; - totalRequests += partyLedger.requested; - totalContributions += partyLedger.contributed; - - unchecked{++i;} - } - - int64 atlasBalanceDelta = int64(uint64((address(this).balance) / tx.gasprice)) - int64(mLock.startingBalance); - - // If atlas balance is lower than expected, return false - if (atlasBalanceDelta < totalRequests + totalContributions + totalBalanceDelta) return false; - - // If the requests have not yet been met, return false - if (totalRequests + totalContributions < 0) return false; - - // Otherwise return true - return true; + function _isInSurplus(address environment) internal returns (bool) { + (bool success, bytes memory data) = GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.isInSurplus.selector, environment)); + return abi.decode(data, (bool)); } function _balance(uint256 accruedGasRebate, address user, address dapp, address winningSolver, address bundler) internal { - - Lock memory mLock = lock; - uint256 activeParties = uint256(mLock.activeParties); - - int64 totalRequests; - int64 totalContributions; - int64 totalBalanceDelta; - - Ledger[LEDGER_LENGTH] memory parties; - - (parties, activeParties, totalRequests, totalContributions, totalBalanceDelta) = _loadLedgers(activeParties); - - (parties, totalRequests, totalContributions, totalBalanceDelta) = _allocateGasRebate( - parties, mLock.startingBalance, accruedGasRebate, totalRequests, totalContributions, totalBalanceDelta); - - console.log(""); - console.log("* * * * * "); - console.log("INITIAL:"); - _consolePrint(parties, activeParties); - console.log("_______"); - console.log("* * * * * "); - console.log(""); - - // First, remove overfilled requests (refunding to general pool) - if (totalRequests > 0) { - (parties, totalRequests, totalContributions) = _removeSurplusRequests( - parties, activeParties, totalRequests, totalContributions); - } - - // Next, balance each party's surplus contributions against their own deficit requests - (parties, totalRequests, totalContributions) = _balanceAgainstSelf( - parties, activeParties, totalRequests, totalContributions); - - // Then allocate surplus contributions back to the correct parties - if (totalRequests + totalContributions > 0) { - (parties, totalRequests, totalContributions) = _allocateSurplusContributions( - parties, activeParties, totalRequests, totalContributions); - } - - console.log("* * * * * "); - console.log("FINAL:"); - _consolePrint(parties, activeParties); - console.log("_______"); - console.log("* * * * * "); - - // Finally, assign the balance deltas to the parties - _assignBalanceDeltas(parties, activeParties, user, dapp, winningSolver, bundler); + GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.balance.selector, accruedGasRebate, user, dapp, winningSolver, bundler)); } - function _loadLedgers(uint256 activeParties) - internal - view - returns (Ledger[LEDGER_LENGTH] memory, uint256, int64, int64, int64) - { - Ledger[LEDGER_LENGTH] memory parties; - - int64 totalRequests; - int64 totalContributions; - int64 totalBalanceDelta; - - for (uint256 i; i < LEDGER_LENGTH;) { - // If party has not been touched, skip it - if (activeParties.isInactive(i)) { - unchecked{++i;} - continue; - } - - Ledger memory partyLedger = ledgers[i]; - - if(partyLedger.contributed < 0) revert NoUnfilledRequests(); - - // Only tally totals from non-proxies - if (uint256(partyLedger.proxy) == i) { - - totalBalanceDelta += partyLedger.balance; - totalRequests += partyLedger.requested; - totalContributions += partyLedger.contributed; - - // Mark inactive if proxy - } else { - activeParties = activeParties.markInactive(Party(i)); - } - - parties[i] = partyLedger; - - unchecked{++i;} - } - - return (parties, activeParties, totalRequests, totalContributions, totalBalanceDelta); - } - - function _allocateGasRebate( - Ledger[LEDGER_LENGTH] memory parties, uint64 startingGasBal, - uint256 accruedGasRebate, int64 totalRequests, int64 totalContributions, int64 totalBalanceDelta) - internal - view - returns (Ledger[LEDGER_LENGTH] memory, int64, int64, int64) - { - int64 gasRemainder = int64(uint64(gasleft() + 20_000)); - - // Reduce the bundler's gas request by the unused gas - (, uint256 i) = parties._getLedgerFromMemory(Party.Bundler); - - int64 gasRebate = int64(uint64(accruedGasRebate)); - - parties[i].requested += gasRemainder; - parties[i].balance += gasRebate; - - totalRequests += gasRemainder; - totalContributions -= gasRemainder; - totalBalanceDelta += gasRebate; - - if(totalRequests + totalContributions < 0) revert MissingFunds(1); - - // TODO: Adjust to accomodate the direction of rounding errors. - int64 atlasBalanceDelta = int64(uint64(address(this).balance / tx.gasprice)) - int64(startingGasBal); - - { - console.log(""); - console.log("--"); - _logInt64("gasRemainder :", gasRemainder); - console.log("gasRebate : +", accruedGasRebate); - console.log("-"); - _logInt64("observedDelta:", atlasBalanceDelta); - _logInt64("actualDelta :", totalBalanceDelta); - console.log("-"); - _logInt64("surplus :", totalRequests + totalContributions); - _logInt64("totalRequests:", totalRequests); - _logInt64("contributions:", totalContributions); - console.log("--"); - } - - if (atlasBalanceDelta < totalBalanceDelta + totalContributions + totalRequests) { - revert MissingFunds(2); - } - - return (parties, totalRequests, totalContributions, totalBalanceDelta); - } - - function _balanceAgainstSelf( - Ledger[LEDGER_LENGTH] memory parties, uint256 activeParties, int64 totalRequests, int64 totalContributions) - internal - pure - returns (Ledger[LEDGER_LENGTH] memory, int64, int64) - { - // NOTE: - // ORDER is: - // 1: FirstToAct (DApp) - // ... - // Last: LastToAct (builder) - - uint256 i = LEDGER_LENGTH; - - do { - --i; - - // If party has not been touched, skip it - if (activeParties.isInactive(i)) { - continue; - } - - Ledger memory partyLedger = parties[i]; - - // CASE: Some Requests still in Deficit - if (partyLedger.requested < 0 && partyLedger.contributed > 0) { - - // CASE: Contributions > Requests - if (partyLedger.contributed + partyLedger.requested > 0) { - totalRequests -= partyLedger.requested; // subtracting a negative - totalContributions += partyLedger.requested; // adding a negative - - partyLedger.contributed += partyLedger.requested; // adding a negative - partyLedger.requested = 0; - - // CASE: Requests >= Contributions - } else { - totalRequests += partyLedger.contributed; // adding a positive - totalContributions -= partyLedger.contributed; // subtracting a positive - - partyLedger.requested += partyLedger.contributed; // adding a positive - partyLedger.contributed = 0; - } - - parties[i] = partyLedger; - } - - } while (i != 0); - - return (parties, totalRequests, totalContributions); - } - - function _removeSurplusRequests( - Ledger[LEDGER_LENGTH] memory parties, uint256 activeParties, int64 totalRequests, int64 totalContributions) - internal - pure - returns (Ledger[LEDGER_LENGTH] memory, int64, int64) - { - // NOTE: A check to verify totalRequests > 0 will happen prior to calling this - - // NOTE: - // ORDER is: - // 1: LastToAct (builder) - // ... - // Last: FirstToAct (DApp) - - for (uint256 i; i 0; ) { - // If party has not been touched, skip it - if (activeParties.isInactive(i)) { - unchecked{++i;} - continue; - } - - Ledger memory partyLedger = parties[i]; - - if (partyLedger.requested > 0) { - if (totalRequests > partyLedger.requested) { - totalRequests -= partyLedger.requested; - totalContributions += partyLedger.requested; - partyLedger.requested = 0; - - } else { - partyLedger.requested -= totalRequests; - totalContributions += totalRequests; - totalRequests = 0; - } - - parties[i] = partyLedger; - } - unchecked{++i;} - } - - return (parties, totalRequests, totalContributions); - } - - function _allocateSurplusContributions( - Ledger[LEDGER_LENGTH] memory parties, uint256 activeParties, int64 totalRequests, int64 totalContributions) - internal - pure - returns (Ledger[LEDGER_LENGTH] memory, int64, int64) - { - // NOTE: A check to verify totalRequests + totalContributions > 0 will happen prior to calling this - - // NOTE: - // ORDER is: - // 1: FirstToAct (DApp) - // ... - // Last: LastToAct (builder) - - int64 netBalance = totalRequests + totalContributions; - - uint256 i = LEDGER_LENGTH; - - do { - --i; - - // If party has not been touched, skip it - if (activeParties.isInactive(i)) { - continue; - } - - Ledger memory partyLedger = parties[i]; - - if (partyLedger.contributed > 0) { - if (netBalance > partyLedger.contributed) { - totalContributions -= partyLedger.contributed; - partyLedger.balance += partyLedger.contributed; - partyLedger.contributed = 0; - - } else { - partyLedger.contributed -= netBalance; - partyLedger.balance += netBalance; - totalContributions -= netBalance; - } - - parties[i] = partyLedger; - - netBalance = totalRequests + totalContributions; - } - - } while (i != 0 && netBalance > 0); - - return (parties, totalRequests, totalContributions); - } - - function _assignBalanceDeltas( - Ledger[LEDGER_LENGTH] memory parties, - uint256 activeParties, address user, address dapp, address winningSolver, address bundler) - internal - { - for (uint256 i=0; i < LEDGER_LENGTH;) { - // If party has not been touched, skip it - if (activeParties.isInactive(i)) { - unchecked{++i;} - continue; - } - - Ledger memory partyLedger = parties[i]; - - address partyAddress = _partyAddress(i, user, dapp, winningSolver, bundler); - EscrowAccountData memory escrowData = _escrowAccountData[partyAddress]; - - - console.log("-"); - console.log("Starting Bal:", escrowData.balance); - - - bool requiresUpdate; - if (partyLedger.balance < 0) { - escrowData.balance -= (uint128(uint64(partyLedger.balance * -1)) * uint128(tx.gasprice)); - requiresUpdate = true; - - } else if (partyLedger.balance > 0) { - escrowData.balance += (uint128(uint64(partyLedger.balance)) * uint128(tx.gasprice)); - requiresUpdate = true; - } - - if (i == uint256(Party.Solver)) { - ++escrowData.nonce; - requiresUpdate = true; - } - - // Track lastAccessed for all parties, although only check against it for solver parties - if (requiresUpdate) { - escrowData.lastAccessed = uint64(block.number); - _escrowAccountData[partyAddress] = escrowData; - } - - console.log("Ending Bal :", escrowData.balance); - console.log("-"); - - - unchecked{++i;} - } - } - - function _consolePrint(Ledger[LEDGER_LENGTH] memory parties, uint256 activeParties) internal view { - for (uint256 i=0; i < LEDGER_LENGTH; i++) { - if (activeParties.isInactive(i)) { - console.log(""); - console.log("Party:", _partyName(i)); - console.log("confirmed - inactive"); - console.log("-"); - continue; - } - - Ledger memory partyLedger = parties[i]; - - if (partyLedger.status == LedgerStatus.Proxy) { - console.log(""); - console.log("Party:", _partyName(i)); - console.log("confirmed - proxy"); - console.log("-"); - continue; - } - - console.log(""); - console.log("Party:", _partyName(i)); - console.log("confirmed - ACTIVE"); - console.log("-"); - _logInt64("netBalance :", partyLedger.balance); - _logInt64("requested :", partyLedger.requested); - _logInt64("contributed :", partyLedger.contributed); - } - } - - // TODO: Unroll this - just doing it for now to improve readability - function _partyAddress(uint256 index, address user, address dapp, address winningSolver, address bundler) internal view returns (address) { - Party party = Party(index); - if (party == Party.DApp) return dapp; - if (party == Party.User) return user; - if (party == Party.Solver) return winningSolver; - if (party == Party.Bundler) return bundler; // <3 - if (party == Party.Builder) return block.coinbase; - return address(this); - } - - - // for testing purposes - function _partyName(uint256 index) internal pure returns (string memory) { - Party party = Party(index); - if (party == Party.DApp) return "dApp"; - if (party == Party.User) return "user"; - if (party == Party.Solver) return "solver"; - if (party == Party.Bundler) return "bundler"; - if (party == Party.Builder) return "builder"; - return "unknown"; - } - - function _logInt64(string memory pretext, int64 i) internal view { - if (i < 0) console.log(string.concat(pretext, " -"), uint64(-1 * i)); - else console.log(string.concat(pretext, " +"), uint64(i)); - } - - - function _validParty(address environment, Party party) internal returns (bool valid) { - Lock memory mLock = lock; - if (mLock.activeEnvironment != environment) { - return false; - } - - uint256 activeParties = uint256(mLock.activeParties); - - if (activeParties.isInactive(party)) { - activeParties = activeParties.markActive(party); - lock.activeParties = uint16(activeParties); - } - return true; - } - - function _validParties(address environment, Party partyOne, Party partyTwo) internal returns (bool valid) { - Lock memory mLock = lock; - if (mLock.activeEnvironment != environment) { - return false; - } - - uint256 parties = partyOne.toBit() | partyTwo.toBit(); - uint256 activeParties = uint256(mLock.activeParties); - - if (activeParties & parties != parties) { - activeParties |= parties; - lock.activeParties = uint16(activeParties); - } - return true; - } - - function _getLedger(Party party) internal view returns (Ledger memory partyLedger, uint256 index) { - uint256 partyIndex; - - do { - partyIndex = uint256(party); - partyLedger = ledgers[partyIndex]; - party = partyLedger.proxy; - index = uint256(party); - - } while (partyIndex != index); - - if (partyLedger.status == LedgerStatus.Inactive) partyLedger.status = LedgerStatus.Active; + function _validParties(address environment, Party partyOne, Party partyTwo) internal returns (bool) { + (bool success, bytes memory data) = GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.validParties.selector, environment, partyOne, partyTwo)); + return abi.decode(data, (bool)); } - } \ No newline at end of file diff --git a/src/contracts/atlas/GasAccountingLib.sol b/src/contracts/atlas/GasAccountingLib.sol index a0ee938c..ac938e83 100644 --- a/src/contracts/atlas/GasAccountingLib.sol +++ b/src/contracts/atlas/GasAccountingLib.sol @@ -18,12 +18,21 @@ contract GasAccountingLib is Storage, FastLaneErrorsEvents { using PartyMath for uint256; using PartyMath for Ledger[LEDGER_LENGTH]; + address public immutable ATLAS; + constructor( uint256 _escrowDuration, address _factory, address _verification, - address _simulator - ) Storage(_escrowDuration, _factory, _verification, address(this), _simulator) {} + address _simulator, + address _atlas + ) Storage(_escrowDuration, _factory, _verification, address(this), _simulator) { + ATLAS = _atlas; + } + + // --------------------------------------- + // EXTERNAL FUNCTIONS + // --------------------------------------- function deposit(Party party) external payable { if(!_validParty(msg.sender, party)) revert InvalidEnvironment(); @@ -42,47 +51,17 @@ contract GasAccountingLib is Storage, FastLaneErrorsEvents { ledgers[pIndex] = partyLedger; } - // NOTE: donations are simply deposits that have a different msg.sender than receiving party - function _deposit(Party party, uint256 amt) internal returns (uint256 balanceOwed) { - - (Ledger memory partyLedger, uint256 partyIndex) = _getLedger(party); - - if (partyLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(1); - - int64 depositAmount = int64(uint64(amt / tx.gasprice)); - - partyLedger.balance += depositAmount; - - balanceOwed = partyLedger.balance < 0 ? uint256(uint64(-1 * partyLedger.balance)) : 0; - - ledgers[partyIndex] = partyLedger; - } - // NOTE: DAPPs can gain malicious access to these funcs if they want to, but attacks beyond // the approved amounts will only lead to a revert. - // Bundlers must make sure the DApp hasn't maliciously upgraded their contract to avoid wasting gas. - function contributeTo(Party donor, Party recipient, uint256 amt) external { - if(!_validParties(msg.sender, donor, recipient)) revert InvalidEnvironment(); + // Bundlers must make sure the DApp hasn't maliciously upgraded their contract to avoid wasting gas. + + // callingEnv should be either internal Atlas call (from Permit69) or from an ExecEnv via Atlas to this contract + function contributeTo(address callingEnv, Party donor, Party recipient, uint256 amt) external { + if(!_validParties(callingEnv, donor, recipient)) revert InvalidEnvironment(); _contributeTo(donor, recipient, amt); } - function _contributeTo(Party donor, Party recipient, uint256 amt) internal { - - (Ledger memory donorLedger, uint256 donorIndex) = _getLedger(donor); - if(donorLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(5); - - (Ledger memory recipientLedger, uint256 recipientIndex) = _getLedger(recipient); - if(recipientLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(6); - - int64 amount = int64(uint64(amt / tx.gasprice)); - - donorLedger.balance -= amount; - donorLedger.contributed += amount; - recipientLedger.requested += amount; - - ledgers[donorIndex] = donorLedger; - ledgers[recipientIndex] = recipientLedger; - } + function contribute(Party recipient) external payable { @@ -114,30 +93,12 @@ contract GasAccountingLib is Storage, FastLaneErrorsEvents { ledgers[pIndex] = partyLedger; } - function requestFrom(Party donor, Party recipient, uint256 amt) external { - if(!_validParties(msg.sender, donor, recipient)) revert InvalidEnvironment(); + // callingEnv should be either internal Atlas call (from Permit69) or from an ExecEnv via Atlas to this contract + function requestFrom(address callingEnv, Party donor, Party recipient, uint256 amt) external { + if(!_validParties(callingEnv, donor, recipient)) revert InvalidEnvironment(); _requestFrom(donor, recipient, amt); } - function _requestFrom(Party donor, Party recipient, uint256 amt) internal { - // TODO: different parties will be ineligible to request funds from once their phase is over. - // We need to add a phase check to verify this. - - (Ledger memory donorLedger, uint256 donorIndex) = _getLedger(donor); - if(donorLedger.status >= LedgerStatus.Balancing) revert LedgerBalancing(2); - - (Ledger memory recipientLedger, uint256 recipientIndex) = _getLedger(recipient); - if(recipientLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(4); - - int64 amount = int64(uint64(amt / tx.gasprice)); - - donorLedger.contributed -= amount; - recipientLedger.requested -= amount; - - ledgers[donorIndex] = donorLedger; - ledgers[recipientIndex] = recipientLedger; - } - function borrow(Party party, uint256 amt) external { // Note that for Solver borrows, the repayment check happens *inside* the try/catch. @@ -153,7 +114,7 @@ contract GasAccountingLib is Storage, FastLaneErrorsEvents { } - function _tradeCorrection(Party party, uint256 amt) internal { + function tradeCorrection(Party party, uint256 amt) external { // Note that for Solver borrows, the repayment check happens *inside* the try/catch. // This function is to mark off a solver borrow from a failed tx @@ -168,7 +129,7 @@ contract GasAccountingLib is Storage, FastLaneErrorsEvents { ledgers[partyIndex] = partyLedger; } - function _use(Party party, address partyAddress, uint256 amt) internal { + function use(Party party, address partyAddress, uint256 amt) external { (Ledger memory partyLedger, uint256 partyIndex) = _getLedger(party); @@ -223,15 +184,7 @@ contract GasAccountingLib is Storage, FastLaneErrorsEvents { } - - - - // Returns true if Solver status is Finalized and the caller (Execution Environment) is in surplus - function validateBalances() external view returns (bool valid) { - valid = ledgers[uint256(Party.Solver)].status == LedgerStatus.Finalized && _isInSurplus(msg.sender); - } - - function _isInSurplus(address environment) internal view returns (bool) { + function isInSurplus(address environment) external view returns (bool) { Lock memory mLock = lock; if (mLock.activeEnvironment != environment) return false; @@ -260,7 +213,7 @@ contract GasAccountingLib is Storage, FastLaneErrorsEvents { unchecked{++i;} } - int64 atlasBalanceDelta = int64(uint64((address(this).balance) / tx.gasprice)) - int64(mLock.startingBalance); + int64 atlasBalanceDelta = int64(uint64((ATLAS.balance) / tx.gasprice)) - int64(mLock.startingBalance); // If atlas balance is lower than expected, return false if (atlasBalanceDelta < totalRequests + totalContributions + totalBalanceDelta) return false; @@ -272,7 +225,7 @@ contract GasAccountingLib is Storage, FastLaneErrorsEvents { return true; } - function _balance(uint256 accruedGasRebate, address user, address dapp, address winningSolver, address bundler) internal { + function balance(uint256 accruedGasRebate, address user, address dapp, address winningSolver, address bundler) external { Lock memory mLock = lock; uint256 activeParties = uint256(mLock.activeParties); @@ -302,24 +255,291 @@ contract GasAccountingLib is Storage, FastLaneErrorsEvents { parties, activeParties, totalRequests, totalContributions); } - // Next, balance each party's surplus contributions against their own deficit requests - (parties, totalRequests, totalContributions) = _balanceAgainstSelf( - parties, activeParties, totalRequests, totalContributions); + // Next, balance each party's surplus contributions against their own deficit requests + (parties, totalRequests, totalContributions) = _balanceAgainstSelf( + parties, activeParties, totalRequests, totalContributions); + + // Then allocate surplus contributions back to the correct parties + if (totalRequests + totalContributions > 0) { + (parties, totalRequests, totalContributions) = _allocateSurplusContributions( + parties, activeParties, totalRequests, totalContributions); + } + + console.log("* * * * * "); + console.log("FINAL:"); + _consolePrint(parties, activeParties); + console.log("_______"); + console.log("* * * * * "); + + // Finally, assign the balance deltas to the parties + _assignBalanceDeltas(parties, activeParties, user, dapp, winningSolver, bundler); + } + + function validParties(address environment, Party partyOne, Party partyTwo) external returns (bool valid) { + return _validParties(environment, partyOne, partyTwo); + } + + function checkSolverProxy(address solverFrom, address bundler) external returns (bool validSolver) { + // Note that the Solver can't be the User or the DApp - those combinations are blocked in the ExecutionEnvironment. + + if (solverFrom == block.coinbase) { + uint256 builderIndex = uint256(Party.Builder); + Ledger memory partyLedger = ledgers[builderIndex]; + + // CASE: Invalid combination (solver = coinbase = user | dapp) + if (uint256(partyLedger.proxy) > uint256(Party.Solver)) { + return false; + } + + // CASE: ledger is finalized or balancing + if (uint256(partyLedger.status) > uint256(LedgerStatus.Borrowing)) { + return false; + } + + // CASE: proxy is solver or builder + // Pass, and check builder proxy next + + // CASE: no proxy yet, so make one + if (uint256(partyLedger.proxy) == builderIndex) { + uint256 activeParties = _getActiveParties(); + if (activeParties.isInactive(Party.Builder)) { + _saveActiveParties(activeParties.markActive(Party.Builder)); + } + + partyLedger.status = LedgerStatus.Proxy; + partyLedger.proxy = Party.Solver; + // Note: don't overwrite the stored values - we may need to undo the proxy if solver fails + ledgers[builderIndex] = partyLedger; + + // Solver inherits the requests and contributions of their alter ego + uint256 solverIndex = uint256(Party.Solver); + Ledger memory sLedger = ledgers[solverIndex]; + + sLedger.balance += partyLedger.balance; + sLedger.contributed += partyLedger.contributed; + sLedger.requested += partyLedger.requested; + + ledgers[solverIndex] = sLedger; + } + } + + + if (solverFrom == bundler) { + uint256 bundlerIndex = uint256(Party.Bundler); + Ledger memory partyLedger = ledgers[bundlerIndex]; + + // CASE: Invalid combination (solver = bundler = user | dapp) + if (uint256(partyLedger.proxy) > uint256(Party.Solver)) { + return false; + } + + // CASE: ledger is finalized or balancing + if (uint256(partyLedger.status) > uint256(LedgerStatus.Borrowing)) { + return false; + } + + // CASE: proxy is solver or builder + // Pass, and check builder proxy next + + // CASE: no proxy + if (uint256(partyLedger.proxy) == bundlerIndex) { + // Bundler is always active, so no need to mark. + + partyLedger.status = LedgerStatus.Proxy; + partyLedger.proxy = Party.Solver; + // Note: don't overwrite the stored values - we may need to undo the proxy if solver fails + ledgers[bundlerIndex] = partyLedger; + + // Solver inherits the requests and contributions of their alter ego + uint256 solverIndex = uint256(Party.Solver); + Ledger memory sLedger = ledgers[solverIndex]; + + sLedger.balance += partyLedger.balance; + sLedger.contributed += partyLedger.contributed; + sLedger.requested += partyLedger.requested; + + ledgers[solverIndex] = sLedger; + } + } + + return true; + } + + function updateSolverProxy(address solverFrom, address bundler, bool solverSuccessful) external { + // Note that the Solver can't be the User or the DApp - those combinations are blocked in the ExecutionEnvironment. + + if (solverFrom == block.coinbase && block.coinbase != bundler) { + + uint256 builderIndex = uint256(Party.Builder); + uint256 solverIndex = uint256(Party.Solver); + + // Solver inherited the requests and contributions of their alter ego + Ledger memory partyLedger = ledgers[builderIndex]; + Ledger memory sLedger = ledgers[solverIndex]; + + if (solverSuccessful) { + // CASE: Delete the balances on the older ledger + // TODO: Pretty sure we can skip this since it gets ignored and deleted later + partyLedger.balance = 0; + partyLedger.contributed = 0; + partyLedger.requested = 0; + + ledgers[builderIndex] = partyLedger; // Proxy status stays + + } else { + // CASE: Undo the balance adjustments for the next solver + sLedger.balance -= partyLedger.balance; + sLedger.contributed -= partyLedger.contributed; + sLedger.requested -= partyLedger.requested; + + ledgers[solverIndex] = sLedger; + + partyLedger.proxy = Party.Builder; + partyLedger.status = LedgerStatus.Active; + + ledgers[builderIndex] = partyLedger; + } + } + + if (solverFrom == bundler) { + + uint256 bundlerIndex = uint256(Party.Bundler); + uint256 solverIndex = uint256(Party.Solver); + + // Solver inherited the requests and contributions of their alter ego + Ledger memory partyLedger = ledgers[bundlerIndex]; + Ledger memory sLedger = ledgers[solverIndex]; + + if (solverSuccessful) { + // CASE: Delete the balances on the older ledger + // TODO: Pretty sure we can skip this since it gets ignored and deleted later + partyLedger.balance = 0; + partyLedger.contributed = 0; + partyLedger.requested = 0; + + ledgers[bundlerIndex] = partyLedger; // Proxy status stays + + } else { + // CASE: Undo the balance adjustments for the next solver + sLedger.balance -= partyLedger.balance; + sLedger.contributed -= partyLedger.contributed; + sLedger.requested -= partyLedger.requested; + + ledgers[solverIndex] = sLedger; + + partyLedger.proxy = Party.Bundler; + partyLedger.status = LedgerStatus.Active; + + ledgers[bundlerIndex] = partyLedger; + } + } + } + + function finalize(Party party, address partyAddress) external returns (bool) { + if(!_validParty(msg.sender, party)) revert InvalidEnvironment(); + if(party == Party.Solver) revert SolverMustReconcile(); + + uint256 pIndex = uint256(party); + Ledger memory partyLedger = ledgers[pIndex]; + + if (partyLedger.status == LedgerStatus.Finalized) return false; + + if (partyLedger.contributed + partyLedger.requested < 0) return false; + + uint256 grossBalance = uint256(_escrowAccountData[partyAddress].balance); + + if (int64(uint64(grossBalance / tx.gasprice)) + partyLedger.balance - 1 < 0) return false; + + partyLedger.status = LedgerStatus.Finalized; + ledgers[pIndex] = partyLedger; + + return true; + } + + function reconcile(address environment, address searcherFrom, uint256 maxApprovedGasSpend) external payable returns (bool) { + // NOTE: approvedAmount is the amount of the solver's atlETH that the solver is allowing + // to be used to cover what they owe. This will be subtracted later - tx will revert here if there isn't enough. + if (!_validParty(environment, Party.Solver)) { + return false; + } + + uint256 partyIndex = uint256(Party.Solver); + + Ledger memory partyLedger = ledgers[partyIndex]; + if (partyLedger.status == LedgerStatus.Finalized) { + return false; + } + + if (msg.value != 0) { + int64 amount = int64(uint64((msg.value) / tx.gasprice)); + partyLedger.balance += amount; + } + + if (maxApprovedGasSpend != 0) { + uint256 solverSurplusBalance = uint256(_escrowAccountData[searcherFrom].balance) - (EscrowBits.SOLVER_GAS_LIMIT * tx.gasprice + 1); + maxApprovedGasSpend = maxApprovedGasSpend > solverSurplusBalance ? solverSurplusBalance : maxApprovedGasSpend; + + int64 gasAllowance = int64(uint64(maxApprovedGasSpend / tx.gasprice)); + + if (partyLedger.balance < 0) { + if (gasAllowance < partyLedger.balance) { + return false; + } + gasAllowance += partyLedger.balance; // note that .balance is a negative number so this is a subtraction + } + + partyLedger.contributed += gasAllowance; // note that surplus .contributed is refunded to the party + partyLedger.balance -= gasAllowance; + } + + if (partyLedger.contributed < 0) { + return false; + } + + partyLedger.status = LedgerStatus.Finalized; // no additional requests can be made to this party + ledgers[partyIndex] = partyLedger; + return true; + } + + // --------------------------------------- + // INTERNAL HELPERS + // --------------------------------------- + + function _contributeTo(Party donor, Party recipient, uint256 amt) internal { + + (Ledger memory donorLedger, uint256 donorIndex) = _getLedger(donor); + if(donorLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(5); + + (Ledger memory recipientLedger, uint256 recipientIndex) = _getLedger(recipient); + if(recipientLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(6); + + int64 amount = int64(uint64(amt / tx.gasprice)); + + donorLedger.balance -= amount; + donorLedger.contributed += amount; + recipientLedger.requested += amount; + + ledgers[donorIndex] = donorLedger; + ledgers[recipientIndex] = recipientLedger; + } + + function _requestFrom(Party donor, Party recipient, uint256 amt) internal { + // TODO: different parties will be ineligible to request funds from once their phase is over. + // We need to add a phase check to verify this. - // Then allocate surplus contributions back to the correct parties - if (totalRequests + totalContributions > 0) { - (parties, totalRequests, totalContributions) = _allocateSurplusContributions( - parties, activeParties, totalRequests, totalContributions); - } + (Ledger memory donorLedger, uint256 donorIndex) = _getLedger(donor); + if(donorLedger.status >= LedgerStatus.Balancing) revert LedgerBalancing(2); - console.log("* * * * * "); - console.log("FINAL:"); - _consolePrint(parties, activeParties); - console.log("_______"); - console.log("* * * * * "); + (Ledger memory recipientLedger, uint256 recipientIndex) = _getLedger(recipient); + if(recipientLedger.status == LedgerStatus.Finalized) revert LedgerFinalized(4); - // Finally, assign the balance deltas to the parties - _assignBalanceDeltas(parties, activeParties, user, dapp, winningSolver, bundler); + int64 amount = int64(uint64(amt / tx.gasprice)); + + donorLedger.contributed -= amount; + recipientLedger.requested -= amount; + + ledgers[donorIndex] = donorLedger; + ledgers[recipientIndex] = recipientLedger; } function _loadLedgers(uint256 activeParties) @@ -682,6 +902,8 @@ contract GasAccountingLib is Storage, FastLaneErrorsEvents { return true; } + + function _validParties(address environment, Party partyOne, Party partyTwo) internal returns (bool valid) { Lock memory mLock = lock; if (mLock.activeEnvironment != environment) { @@ -711,231 +933,10 @@ contract GasAccountingLib is Storage, FastLaneErrorsEvents { if (partyLedger.status == LedgerStatus.Inactive) partyLedger.status = LedgerStatus.Active; } - - function checkSolverProxy(address solverFrom, address bundler) external returns (bool validSolver) { - // Note that the Solver can't be the User or the DApp - those combinations are blocked in the ExecutionEnvironment. - - if (solverFrom == block.coinbase) { - uint256 builderIndex = uint256(Party.Builder); - Ledger memory partyLedger = ledgers[builderIndex]; - - // CASE: Invalid combination (solver = coinbase = user | dapp) - if (uint256(partyLedger.proxy) > uint256(Party.Solver)) { - return false; - } - - // CASE: ledger is finalized or balancing - if (uint256(partyLedger.status) > uint256(LedgerStatus.Borrowing)) { - return false; - } - - // CASE: proxy is solver or builder - // Pass, and check builder proxy next - - // CASE: no proxy yet, so make one - if (uint256(partyLedger.proxy) == builderIndex) { - uint256 activeParties = _getActiveParties(); - if (activeParties.isInactive(Party.Builder)) { - _saveActiveParties(activeParties.markActive(Party.Builder)); - } - - partyLedger.status = LedgerStatus.Proxy; - partyLedger.proxy = Party.Solver; - // Note: don't overwrite the stored values - we may need to undo the proxy if solver fails - ledgers[builderIndex] = partyLedger; - - // Solver inherits the requests and contributions of their alter ego - uint256 solverIndex = uint256(Party.Solver); - Ledger memory sLedger = ledgers[solverIndex]; - - sLedger.balance += partyLedger.balance; - sLedger.contributed += partyLedger.contributed; - sLedger.requested += partyLedger.requested; - - ledgers[solverIndex] = sLedger; - } - } - - - if (solverFrom == bundler) { - uint256 bundlerIndex = uint256(Party.Bundler); - Ledger memory partyLedger = ledgers[bundlerIndex]; - - // CASE: Invalid combination (solver = bundler = user | dapp) - if (uint256(partyLedger.proxy) > uint256(Party.Solver)) { - return false; - } - - // CASE: ledger is finalized or balancing - if (uint256(partyLedger.status) > uint256(LedgerStatus.Borrowing)) { - return false; - } - - // CASE: proxy is solver or builder - // Pass, and check builder proxy next - - // CASE: no proxy - if (uint256(partyLedger.proxy) == bundlerIndex) { - // Bundler is always active, so no need to mark. - - partyLedger.status = LedgerStatus.Proxy; - partyLedger.proxy = Party.Solver; - // Note: don't overwrite the stored values - we may need to undo the proxy if solver fails - ledgers[bundlerIndex] = partyLedger; - - // Solver inherits the requests and contributions of their alter ego - uint256 solverIndex = uint256(Party.Solver); - Ledger memory sLedger = ledgers[solverIndex]; - - sLedger.balance += partyLedger.balance; - sLedger.contributed += partyLedger.contributed; - sLedger.requested += partyLedger.requested; - - ledgers[solverIndex] = sLedger; - } - } - - return true; - } - - function updateSolverProxy(address solverFrom, address bundler, bool solverSuccessful) external { - // Note that the Solver can't be the User or the DApp - those combinations are blocked in the ExecutionEnvironment. - - if (solverFrom == block.coinbase && block.coinbase != bundler) { - - uint256 builderIndex = uint256(Party.Builder); - uint256 solverIndex = uint256(Party.Solver); - - // Solver inherited the requests and contributions of their alter ego - Ledger memory partyLedger = ledgers[builderIndex]; - Ledger memory sLedger = ledgers[solverIndex]; - - if (solverSuccessful) { - // CASE: Delete the balances on the older ledger - // TODO: Pretty sure we can skip this since it gets ignored and deleted later - partyLedger.balance = 0; - partyLedger.contributed = 0; - partyLedger.requested = 0; - - ledgers[builderIndex] = partyLedger; // Proxy status stays - - } else { - // CASE: Undo the balance adjustments for the next solver - sLedger.balance -= partyLedger.balance; - sLedger.contributed -= partyLedger.contributed; - sLedger.requested -= partyLedger.requested; - - ledgers[solverIndex] = sLedger; - - partyLedger.proxy = Party.Builder; - partyLedger.status = LedgerStatus.Active; - - ledgers[builderIndex] = partyLedger; - } - } - - if (solverFrom == bundler) { - - uint256 bundlerIndex = uint256(Party.Bundler); - uint256 solverIndex = uint256(Party.Solver); - - // Solver inherited the requests and contributions of their alter ego - Ledger memory partyLedger = ledgers[bundlerIndex]; - Ledger memory sLedger = ledgers[solverIndex]; - - if (solverSuccessful) { - // CASE: Delete the balances on the older ledger - // TODO: Pretty sure we can skip this since it gets ignored and deleted later - partyLedger.balance = 0; - partyLedger.contributed = 0; - partyLedger.requested = 0; - - ledgers[bundlerIndex] = partyLedger; // Proxy status stays - - } else { - // CASE: Undo the balance adjustments for the next solver - sLedger.balance -= partyLedger.balance; - sLedger.contributed -= partyLedger.contributed; - sLedger.requested -= partyLedger.requested; - - ledgers[solverIndex] = sLedger; - - partyLedger.proxy = Party.Bundler; - partyLedger.status = LedgerStatus.Active; - - ledgers[bundlerIndex] = partyLedger; - } - } - } - - function finalize(Party party, address partyAddress) external returns (bool) { - if(!_validParty(msg.sender, party)) revert InvalidEnvironment(); - if(party == Party.Solver) revert SolverMustReconcile(); - - uint256 pIndex = uint256(party); - Ledger memory partyLedger = ledgers[pIndex]; - - if (partyLedger.status == LedgerStatus.Finalized) return false; - - if (partyLedger.contributed + partyLedger.requested < 0) return false; - - uint256 grossBalance = uint256(_escrowAccountData[partyAddress].balance); - - if (int64(uint64(grossBalance / tx.gasprice)) + partyLedger.balance - 1 < 0) return false; - - partyLedger.status = LedgerStatus.Finalized; - ledgers[pIndex] = partyLedger; - - return true; - } - - function reconcile(address environment, address searcherFrom, uint256 maxApprovedGasSpend) external payable returns (bool) { - // NOTE: approvedAmount is the amount of the solver's atlETH that the solver is allowing - // to be used to cover what they owe. This will be subtracted later - tx will revert here if there isn't enough. - if (!_validParty(environment, Party.Solver)) { - return false; - } - - uint256 partyIndex = uint256(Party.Solver); - - Ledger memory partyLedger = ledgers[partyIndex]; - if (partyLedger.status == LedgerStatus.Finalized) { - return false; - } - - if (msg.value != 0) { - int64 amount = int64(uint64((msg.value) / tx.gasprice)); - partyLedger.balance += amount; - } - - if (maxApprovedGasSpend != 0) { - uint256 solverSurplusBalance = uint256(_escrowAccountData[searcherFrom].balance) - (EscrowBits.SOLVER_GAS_LIMIT * tx.gasprice + 1); - maxApprovedGasSpend = maxApprovedGasSpend > solverSurplusBalance ? solverSurplusBalance : maxApprovedGasSpend; - - int64 gasAllowance = int64(uint64(maxApprovedGasSpend / tx.gasprice)); - - if (partyLedger.balance < 0) { - if (gasAllowance < partyLedger.balance) { - return false; - } - gasAllowance += partyLedger.balance; // note that .balance is a negative number so this is a subtraction - } - - partyLedger.contributed += gasAllowance; // note that surplus .contributed is refunded to the party - partyLedger.balance -= gasAllowance; - } - - if (partyLedger.contributed < 0) { - return false; - } - - partyLedger.status = LedgerStatus.Finalized; // no additional requests can be made to this party - ledgers[partyIndex] = partyLedger; - return true; - } - - - // Requirements ported from SafetyLocks: + + // --------------------------------------- + // MISC DEPENDENCIES + // --------------------------------------- function _getActiveParties() internal view returns (uint256 activeParties) { Lock memory mLock = lock; diff --git a/test/base/BaseTest.t.sol b/test/base/BaseTest.t.sol index a6e092d5..7122c841 100644 --- a/test/base/BaseTest.t.sol +++ b/test/base/BaseTest.t.sol @@ -100,7 +100,8 @@ contract BaseTest is Test, TestConstants { _escrowDuration: 64, _factory: expectedAtlasFactoryAddr, _verification: expectedAtlasVerificationAddr, - _simulator: address(simulator) + _simulator: address(simulator), + _atlas: address(atlas) }); simulator.setAtlas(address(atlas)); From 35151ffcf41e74b911ed9898c2ff32e046ba2e37 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Wed, 8 Nov 2023 17:55:21 +0200 Subject: [PATCH 34/38] Delegatecall safety checks --- src/contracts/atlas/GasAccounting.sol | 40 +++++++++++++++++++-------- src/contracts/types/Emissions.sol | 1 + 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/contracts/atlas/GasAccounting.sol b/src/contracts/atlas/GasAccounting.sol index 045a9c46..74b0f7de 100644 --- a/src/contracts/atlas/GasAccounting.sol +++ b/src/contracts/atlas/GasAccounting.sol @@ -36,27 +36,33 @@ abstract contract GasAccounting is SafetyLocks { } function deposit(Party party) external payable { - GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.deposit.selector, party)); + (bool success,) = GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.deposit.selector, party)); + if(!success) revert GasAccountingLibError(); } function contribute(Party recipient) external payable { - GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.contribute.selector, recipient)); + (bool success,) = GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.contribute.selector, recipient)); + if(!success) revert GasAccountingLibError(); } function contributeTo(Party donor, Party recipient, uint256 amt) external { - GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.contributeTo.selector, msg.sender, donor, recipient, amt)); + (bool success,) = GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.contributeTo.selector, msg.sender, donor, recipient, amt)); + if(!success) revert GasAccountingLibError(); } function requestFrom(Party donor, Party recipient, uint256 amt) external { - GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.requestFrom.selector, msg.sender, donor, recipient, amt)); + (bool success,) = GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.requestFrom.selector, msg.sender, donor, recipient, amt)); + if(!success) revert GasAccountingLibError(); } function finalize(Party party, address partyAddress) external { - GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.finalize.selector, party, partyAddress)); + (bool success,) = GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.finalize.selector, party, partyAddress)); + if(!success) revert GasAccountingLibError(); } function reconcile(address environment, address searcherFrom, uint256 maxApprovedGasSpend) external payable returns (bool) { (bool success, bytes memory data) = GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.reconcile.selector, environment, searcherFrom, maxApprovedGasSpend)); + if(!success) revert GasAccountingLibError(); return abi.decode(data, (bool)); } @@ -65,45 +71,55 @@ abstract contract GasAccounting is SafetyLocks { // --------------------------------------- function _updateSolverProxy(address solverFrom, address bundler, bool solverSuccessful) internal { - GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.updateSolverProxy.selector, solverFrom, bundler, solverSuccessful)); + (bool success,) = GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.updateSolverProxy.selector, solverFrom, bundler, solverSuccessful)); + if(!success) revert GasAccountingLibError(); } function _checkSolverProxy(address solverFrom, address bundler) internal returns (bool validSolver) { (bool success, bytes memory data) = GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.checkSolverProxy.selector, solverFrom, bundler)); + if(!success) revert GasAccountingLibError(); return abi.decode(data, (bool)); } function _borrow(Party party, uint256 amt) internal { - GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.borrow.selector, party, amt)); + (bool success,) = GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.borrow.selector, party, amt)); + if(!success) revert GasAccountingLibError(); } function _tradeCorrection(Party party, uint256 amt) internal { - GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.tradeCorrection.selector, party, amt)); + (bool success,) = GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.tradeCorrection.selector, party, amt)); + if(!success) revert GasAccountingLibError(); } function _use(Party party, address partyAddress, uint256 amt) internal { - GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.use.selector, party, partyAddress, amt)); + (bool success,) = GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.use.selector, party, partyAddress, amt)); + if(!success) revert GasAccountingLibError(); } function _requestFrom(Party donor, Party recipient, uint256 amt) internal { - GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.requestFrom.selector, address(this), donor, recipient, amt)); + (bool success,) = GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.requestFrom.selector, address(this), donor, recipient, amt)); + if(!success) revert GasAccountingLibError(); } function _contributeTo(Party donor, Party recipient, uint256 amt) internal { - GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.contributeTo.selector, address(this), donor, recipient, amt)); + (bool success,) = GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.contributeTo.selector, address(this), donor, recipient, amt)); + if(!success) revert GasAccountingLibError(); } function _isInSurplus(address environment) internal returns (bool) { (bool success, bytes memory data) = GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.isInSurplus.selector, environment)); + if(!success) revert GasAccountingLibError(); return abi.decode(data, (bool)); } function _balance(uint256 accruedGasRebate, address user, address dapp, address winningSolver, address bundler) internal { - GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.balance.selector, accruedGasRebate, user, dapp, winningSolver, bundler)); + (bool success,) = GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.balance.selector, accruedGasRebate, user, dapp, winningSolver, bundler)); + if(!success) revert GasAccountingLibError(); } function _validParties(address environment, Party partyOne, Party partyTwo) internal returns (bool) { (bool success, bytes memory data) = GAS_ACC_LIB.delegatecall(abi.encodeWithSelector(GasAccountingLib.validParties.selector, environment, partyOne, partyTwo)); + if(!success) revert GasAccountingLibError(); return abi.decode(data, (bool)); } diff --git a/src/contracts/types/Emissions.sol b/src/contracts/types/Emissions.sol index 78ab9b98..0f9d2e8b 100644 --- a/src/contracts/types/Emissions.sol +++ b/src/contracts/types/Emissions.sol @@ -97,6 +97,7 @@ contract FastLaneErrorsEvents { error InsufficientFunds(); error NoUnfilledRequests(); error SolverMustReconcile(); + error GasAccountingLibError(); // NEW - SafetyLocks error NotInitialized(); From 5248b0165a800c76122a731c3217883b32896141 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Wed, 8 Nov 2023 18:16:44 +0200 Subject: [PATCH 35/38] Replace Atlas modifiers with internal functions --- src/contracts/atlas/AtlETH.sol | 36 ++++++++++++++++++----------- src/contracts/atlas/SafetyLocks.sol | 6 ++--- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/contracts/atlas/AtlETH.sol b/src/contracts/atlas/AtlETH.sol index 1ff01518..bfe99900 100644 --- a/src/contracts/atlas/AtlETH.sol +++ b/src/contracts/atlas/AtlETH.sol @@ -7,7 +7,6 @@ import "../types/EscrowTypes.sol"; import {Permit69} from "../common/Permit69.sol"; // TODO split out events and errors to share with AtlasEscrow -// TODO all modifiers should be internal fns for contract size savings /// @notice Modified Solmate ERC20 with some Atlas-specific modifications. /// @author FastLane Labs @@ -37,15 +36,7 @@ abstract contract AtlETH is Permit69 { ATLETH //////////////////////////////////////////////////////////////*/ - // Custom checks for atlETH transfer functions. - // Interactions (transfers, withdrawals) are allowed only after the owner last interaction - // with Atlas was at least `escrowDuration` blocks ago. - modifier tokenTransferChecks(address account) { - if(block.number <= _escrowAccountData[account].lastAccessed + ESCROW_DURATION) { - revert EscrowLockActive(); - } - _; - } + function balanceOf(address account) public view returns (uint256) { return _escrowAccountData[account].balance; @@ -60,12 +51,16 @@ abstract contract AtlETH is Permit69 { } // Deposit ETH and get atlETH in return. - function deposit() external payable onlyWhenUnlocked { + function deposit() external payable { + _checkIfUnlocked(); _mint(msg.sender, msg.value); } // Redeem atlETH for ETH. - function withdraw(uint256 amount) external onlyWhenUnlocked tokenTransferChecks(msg.sender) { + function withdraw(uint256 amount) external { + _checkIfUnlocked(); + _checkTransfersAllowed(msg.sender); + if (_escrowAccountData[msg.sender].balance < amount) revert InsufficientBalance(); _burn(msg.sender, amount); SafeTransferLib.safeTransferETH(msg.sender, amount); @@ -81,7 +76,9 @@ abstract contract AtlETH is Permit69 { return true; } - function transfer(address to, uint256 amount) public tokenTransferChecks(msg.sender) returns (bool) { + function transfer(address to, uint256 amount) public returns (bool) { + _checkTransfersAllowed(msg.sender); + _escrowAccountData[msg.sender].balance -= uint128(amount); // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. @@ -92,7 +89,9 @@ abstract contract AtlETH is Permit69 { return true; } - function transferFrom(address from, address to, uint256 amount) public tokenTransferChecks(from) returns (bool) { + function transferFrom(address from, address to, uint256 amount) public returns (bool) { + _checkTransfersAllowed(from); + uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; _escrowAccountData[from].balance -= uint128(amount); @@ -157,6 +156,15 @@ abstract contract AtlETH is Permit69 { ); } + // Custom checks for atlETH transfer functions. + // Interactions (transfers, withdrawals) are allowed only after the owner last interaction + // with Atlas was at least `escrowDuration` blocks ago. + function _checkTransfersAllowed(address account) internal view { + if(block.number <= _escrowAccountData[account].lastAccessed + ESCROW_DURATION) { + revert EscrowLockActive(); + } + } + /*////////////////////////////////////////////////////////////// INTERNAL MINT/BURN LOGIC //////////////////////////////////////////////////////////////*/ diff --git a/src/contracts/atlas/SafetyLocks.sol b/src/contracts/atlas/SafetyLocks.sol index 0e43e251..e8a74d29 100644 --- a/src/contracts/atlas/SafetyLocks.sol +++ b/src/contracts/atlas/SafetyLocks.sol @@ -31,7 +31,8 @@ abstract contract SafetyLocks is Storage, FastLaneErrorsEvents { address _simulator ) Storage(_escrowDuration, _factory, _verification, _gasAccLib, _simulator) {} - function _initializeEscrowLock(UserOperation calldata userOp, address executionEnvironment, address bundler, uint256 gasLimit) onlyWhenUnlocked internal { + function _initializeEscrowLock(UserOperation calldata userOp, address executionEnvironment, address bundler, uint256 gasLimit) internal { + _checkIfUnlocked(); uint256 activeParties; activeParties = activeParties.markActive(Party.Bundler); @@ -149,9 +150,8 @@ abstract contract SafetyLocks is Storage, FastLaneErrorsEvents { lock.activeParties = uint16(activeParties); } - modifier onlyWhenUnlocked() { + function _checkIfUnlocked() internal view { if(lock.activeEnvironment != UNLOCKED) revert AlreadyInitialized(); - _; } function activeEnvironment() external view returns (address) { From 85aa8808eb0298a0d7235a65208f29b12aae8e8a Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Wed, 8 Nov 2023 18:42:42 +0200 Subject: [PATCH 36/38] Small fixes, set optimizer to 20 runs --- foundry.toml | 3 +- src/contracts/atlas/Escrow.sol | 125 -------------------------- src/contracts/atlas/GasAccounting.sol | 7 +- src/contracts/types/EscrowTypes.sol | 4 - test/SwapIntent.t.sol | 4 +- 5 files changed, 5 insertions(+), 138 deletions(-) diff --git a/foundry.toml b/foundry.toml index 30f751e2..a80f9c54 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,11 +1,12 @@ [profile.default] bytecode_hash = "none" -optimizer_runs = 200 +optimizer_runs = 20 timeout = 30000 block_gas_limit = 300000000 gas_limit = 3000000000 gas_price = 1500000000 solc_version = "0.8.18" +evm_version = 'paris' fs_permissions = [{ access = "read-write", path = "./"}] \ No newline at end of file diff --git a/src/contracts/atlas/Escrow.sol b/src/contracts/atlas/Escrow.sol index 749962cb..3b19b3e0 100644 --- a/src/contracts/atlas/Escrow.sol +++ b/src/contracts/atlas/Escrow.sol @@ -216,107 +216,6 @@ abstract contract Escrow is AtlETH { } } - // function _verify(SolverOperation calldata solverOp, uint256 gasWaterMark, bool auctionAlreadyComplete) - // internal - // view - // returns (uint256 result, uint256 gasLimit, EscrowAccountData memory solverEscrow) - // { - // // verify solver's signature - // if (_verifySignature(solverOp)) { - // // verify the solver has correct usercalldata and the solver escrow checks - // (result, gasLimit, solverEscrow) = _verifySolverOperation(solverOp); - // } else { - // (result, gasLimit) = (1 << uint256(SolverOutcome.InvalidSignature), 0); - // // solverEscrow returns null - // } - - // result = _solverOpPreCheck(result, gasWaterMark, tx.gasprice, solverOp.maxFeePerGas, auctionAlreadyComplete); - // } - - // function _getSolverHash(SolverOperation calldata solverOp) internal pure returns (bytes32 solverHash) { - // return keccak256( - // abi.encode( - // SOLVER_TYPE_HASH, - // solverOp.from, - // solverOp.to, - // solverOp.value, - // solverOp.gas, - // solverOp.maxFeePerGas, - // solverOp.nonce, - // solverOp.deadline, - // solverOp.solver, - // solverOp.control, - // solverOp.userOpHash, - // solverOp.bidToken, - // solverOp.bidAmount, - // keccak256(solverOp.data) - // ) - // ); - // } - - // TODO take out commented fns after porting - - // function getSolverPayload(SolverOperation calldata solverOp) public view returns (bytes32 payload) { - // payload = _hashTypedDataV4(_getSolverHash(solverOp)); - // } - - // function _verifySignature(SolverOperation calldata solverOp) internal view returns (bool) { - // address signer = _hashTypedDataV4(_getSolverHash(solverOp)).recover(solverOp.signature); - // return signer == solverOp.from; - // } - - // function _verifySolverOperation(SolverOperation calldata solverOp) - // internal - // view - // returns (uint256 result, uint256 gasLimit, EscrowAccountData memory solverEscrow) - // { - // solverEscrow = _escrowAccountData[solverOp.from]; - - // // TODO big unchecked block - audit/review carefully - // unchecked { - // if (solverOp.to != address(this)) { - // result |= 1 << uint256(SolverOutcome.InvalidTo); - // } - - // if (solverOp.nonce <= uint256(solverEscrow.nonce)) { - // result |= 1 << uint256(SolverOutcome.InvalidNonceUnder); - // } else if (solverOp.nonce > uint256(solverEscrow.nonce) + 1) { - // result |= 1 << uint256(SolverOutcome.InvalidNonceOver); - - // // TODO: reconsider the jump up for gapped nonces? Intent is to mitigate dmg - // // potential inflicted by a hostile solver/builder. - // solverEscrow.nonce = uint32(solverOp.nonce); - // } else { - // ++solverEscrow.nonce; - // } - - // if (solverEscrow.lastAccessed >= uint64(block.number)) { - // result |= 1 << uint256(SolverOutcome.PerBlockLimit); - // } else { - // solverEscrow.lastAccessed = uint64(block.number); - // } - - // gasLimit = (100) * (solverOp.gas < EscrowBits.SOLVER_GAS_LIMIT ? solverOp.gas : EscrowBits.SOLVER_GAS_LIMIT) - // / (100 + EscrowBits.SOLVER_GAS_BUFFER) + EscrowBits.FASTLANE_GAS_BUFFER; - - // uint256 gasCost = (tx.gasprice * gasLimit) + (solverOp.data.length * CALLDATA_LENGTH_PREMIUM * tx.gasprice); - - // // see if solver's escrow can afford tx gascost - // if (gasCost > _escrowAccountData[solverOp.from].balance) { - // // charge solver for calldata so that we can avoid vampire attacks from solver onto user - // result |= 1 << uint256(SolverOutcome.InsufficientEscrow); - // } - - // // Verify that we can lend the solver their tx value - // if (solverOp.value > address(this).balance - (gasLimit * tx.gasprice)) { - // result |= 1 << uint256(SolverOutcome.CallValueTooHigh); - // } - - // // subtract out the gas buffer since the solver's metaTx won't use it - // gasLimit -= EscrowBits.FASTLANE_GAS_BUFFER; - // } - // } - // Returns a SolverOutcome enum value function _solverOpWrapper( uint256 gasLimit, @@ -372,28 +271,4 @@ abstract contract Escrow is AtlETH { fallback() external payable { revert(); // no untracked balance transfers plz. (not that this fully stops it) } - - // BITWISE STUFF - // function _solverOpPreCheck( - // uint256 result, - // uint256 gasWaterMark, - // uint256 txGasPrice, - // uint256 maxFeePerGas, - // bool auctionAlreadyComplete - // ) internal pure returns (uint256) { - // if (auctionAlreadyComplete) { - // result |= 1 << uint256(SolverOutcome.LostAuction); - // } - - // if (gasWaterMark < EscrowBits.VALIDATION_GAS_LIMIT + EscrowBits.SOLVER_GAS_LIMIT) { - // // Make sure to leave enough gas for dApp validation calls - // result |= 1 << uint256(SolverOutcome.UserOutOfGas); - // } - - // if (txGasPrice > maxFeePerGas) { - // result |= 1 << uint256(SolverOutcome.GasPriceOverCap); - // } - - // return result; - // } } diff --git a/src/contracts/atlas/GasAccounting.sol b/src/contracts/atlas/GasAccounting.sol index 74b0f7de..a82ab348 100644 --- a/src/contracts/atlas/GasAccounting.sol +++ b/src/contracts/atlas/GasAccounting.sol @@ -7,16 +7,11 @@ import {GasAccountingLib} from "./GasAccountingLib.sol"; import "../types/EscrowTypes.sol"; import "../types/LockTypes.sol"; -import {EscrowBits} from "../libraries/EscrowBits.sol"; -import {PartyMath} from "../libraries/GasParties.sol"; +// import {EscrowBits} from "../libraries/EscrowBits.sol"; import "forge-std/Test.sol"; //TODO remove abstract contract GasAccounting is SafetyLocks { - using PartyMath for Party; - using PartyMath for uint256; - using PartyMath for Ledger[LEDGER_LENGTH]; - constructor( uint256 _escrowDuration, address _factory, diff --git a/src/contracts/types/EscrowTypes.sol b/src/contracts/types/EscrowTypes.sol index 50b8121a..5a619b03 100644 --- a/src/contracts/types/EscrowTypes.sol +++ b/src/contracts/types/EscrowTypes.sol @@ -3,10 +3,6 @@ pragma solidity ^0.8.16; uint256 constant CALLDATA_LENGTH_PREMIUM = 32; // 16 (default) * 2 -struct AccountingData { - mapping(address borrower => uint256 amount) ethBorrowed; // TODO worth allowing ERC20s to be borrowed? -} - struct EscrowAccountData { uint128 balance; uint32 nonce; // EOA nonce diff --git a/test/SwapIntent.t.sol b/test/SwapIntent.t.sol index 051ca961..8a82a17a 100644 --- a/test/SwapIntent.t.sol +++ b/test/SwapIntent.t.sol @@ -194,11 +194,11 @@ contract SwapIntentTest is BaseTest { vm.startPrank(userEOA); - // assertFalse(simulator.simUserOperation(userOp), "metasimUserOperationcall tested true a"); + assertFalse(simulator.simUserOperation(userOp), "metasimUserOperationcall tested true a"); WETH.approve(address(atlas), swapIntent.amountUserSells); - // assertTrue(simulator.simUserOperation(userOp), "metasimUserOperationcall tested false c"); + assertTrue(simulator.simUserOperation(userOp), "metasimUserOperationcall tested false c"); atlas.metacall({ userOp: userOp, From 01d50dbb6b5385183311237feb2f255792616d95 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Wed, 8 Nov 2023 19:45:14 +0200 Subject: [PATCH 37/38] Split out some logic to SafetyLocksLib - Atlas should be deployable now --- script/deploy-atlas.s.sol | 46 +++++++ src/contracts/atlas/AtlETH.sol | 11 +- src/contracts/atlas/Atlas.sol | 3 +- src/contracts/atlas/Escrow.sol | 3 +- src/contracts/atlas/GasAccounting.sol | 3 +- src/contracts/atlas/GasAccountingLib.sol | 4 +- src/contracts/atlas/SafetyLocks.sol | 155 ++++++++++++----------- src/contracts/atlas/SafetyLocksLib.sol | 121 ++++++++++++++++++ src/contracts/atlas/Storage.sol | 3 + src/contracts/common/Permit69.sol | 3 +- src/contracts/types/Emissions.sol | 1 + test/Permit69.t.sol | 5 +- test/base/BaseTest.t.sol | 17 +++ 13 files changed, 289 insertions(+), 86 deletions(-) create mode 100644 src/contracts/atlas/SafetyLocksLib.sol diff --git a/script/deploy-atlas.s.sol b/script/deploy-atlas.s.sol index ae9d0f53..c2bc5a90 100644 --- a/script/deploy-atlas.s.sol +++ b/script/deploy-atlas.s.sol @@ -10,6 +10,7 @@ import {Atlas} from "src/contracts/atlas/Atlas.sol"; import {AtlasFactory} from "src/contracts/atlas/AtlasFactory.sol"; import {AtlasVerification} from "src/contracts/atlas/AtlasVerification.sol"; import {GasAccountingLib} from "src/contracts/atlas/GasAccountingLib.sol"; +import {SafetyLocksLib} from "src/contracts/atlas/SafetyLocksLib.sol"; import {SwapIntentController} from "src/contracts/examples/intents-example/SwapIntent.sol"; import {TxBuilder} from "src/contracts/helpers/TxBuilder.sol"; import {Simulator} from "src/contracts/helpers/Simulator.sol"; @@ -20,6 +21,7 @@ contract DeployAtlasScript is DeployBaseScript { AtlasFactory public atlasFactory; AtlasVerification public atlasVerification; GasAccountingLib public gasAccountingLib; + SafetyLocksLib public safetyLocksLib; Simulator public simulator; function run() external { @@ -40,6 +42,10 @@ contract DeployAtlasScript is DeployBaseScript { deployer, vm.getNonce(deployer) + 3 ); + address expectedSafetyLocksLibAddr = computeCreateAddress( + deployer, + vm.getNonce(deployer) + 4 + ); console.log("Deployer address: \t\t\t\t", deployer); @@ -51,6 +57,7 @@ contract DeployAtlasScript is DeployBaseScript { _factory: expectedAtlasFactoryAddr, _verification: expectedAtlasVerificationAddr, _gasAccLib: expectedGasAccountingLibAddr, + _safetyLocksLib: expectedSafetyLocksLibAddr, _simulator: address(simulator) }); atlasFactory = new AtlasFactory(address(atlas)); @@ -59,6 +66,15 @@ contract DeployAtlasScript is DeployBaseScript { _escrowDuration: 64, _factory: expectedAtlasFactoryAddr, _verification: expectedAtlasVerificationAddr, + _safetyLocksLib: expectedSafetyLocksLibAddr, + _simulator: address(simulator), + _atlas: address(atlas) + }); + safetyLocksLib = new SafetyLocksLib({ + _escrowDuration: 64, + _factory: expectedAtlasFactoryAddr, + _verification: expectedAtlasVerificationAddr, + _gasAccLib: expectedGasAccountingLibAddr, _simulator: address(simulator), _atlas: address(atlas) }); @@ -79,6 +95,7 @@ contract DeployAtlasAndSwapIntentDAppControlScript is DeployBaseScript { AtlasFactory public atlasFactory; AtlasVerification public atlasVerification; GasAccountingLib public gasAccountingLib; + SafetyLocksLib public safetyLocksLib; Simulator public simulator; SwapIntentController public swapIntentControl; @@ -100,6 +117,10 @@ contract DeployAtlasAndSwapIntentDAppControlScript is DeployBaseScript { deployer, vm.getNonce(deployer) + 3 ); + address expectedSafetyLocksLibAddr = computeCreateAddress( + deployer, + vm.getNonce(deployer) + 4 + ); console.log("Deployer address: \t\t\t\t", deployer); @@ -111,6 +132,7 @@ contract DeployAtlasAndSwapIntentDAppControlScript is DeployBaseScript { _factory: expectedAtlasFactoryAddr, _verification: expectedAtlasVerificationAddr, _gasAccLib: expectedGasAccountingLibAddr, + _safetyLocksLib: expectedSafetyLocksLibAddr, _simulator: address(simulator) }); atlasFactory = new AtlasFactory(address(atlas)); @@ -119,6 +141,15 @@ contract DeployAtlasAndSwapIntentDAppControlScript is DeployBaseScript { _escrowDuration: 64, _factory: expectedAtlasFactoryAddr, _verification: expectedAtlasVerificationAddr, + _safetyLocksLib: expectedSafetyLocksLibAddr, + _simulator: address(simulator), + _atlas: address(atlas) + }); + safetyLocksLib = new SafetyLocksLib({ + _escrowDuration: 64, + _factory: expectedAtlasFactoryAddr, + _verification: expectedAtlasVerificationAddr, + _gasAccLib: expectedGasAccountingLibAddr, _simulator: address(simulator), _atlas: address(atlas) }); @@ -148,6 +179,7 @@ contract DeployAtlasAndSwapIntentDAppControlAndTxBuilderScript is DeployBaseScri AtlasFactory public atlasFactory; AtlasVerification public atlasVerification; GasAccountingLib public gasAccountingLib; + SafetyLocksLib public safetyLocksLib; Simulator public simulator; SwapIntentController public swapIntentControl; TxBuilder public txBuilder; @@ -170,6 +202,10 @@ contract DeployAtlasAndSwapIntentDAppControlAndTxBuilderScript is DeployBaseScri deployer, vm.getNonce(deployer) + 3 ); + address expectedSafetyLocksLibAddr = computeCreateAddress( + deployer, + vm.getNonce(deployer) + 4 + ); console.log("Deployer address: \t\t\t\t", deployer); @@ -181,6 +217,7 @@ contract DeployAtlasAndSwapIntentDAppControlAndTxBuilderScript is DeployBaseScri _factory: expectedAtlasFactoryAddr, _verification: expectedAtlasVerificationAddr, _gasAccLib: expectedGasAccountingLibAddr, + _safetyLocksLib: expectedSafetyLocksLibAddr, _simulator: address(simulator) }); atlasFactory = new AtlasFactory(address(atlas)); @@ -189,6 +226,15 @@ contract DeployAtlasAndSwapIntentDAppControlAndTxBuilderScript is DeployBaseScri _escrowDuration: 64, _factory: expectedAtlasFactoryAddr, _verification: expectedAtlasVerificationAddr, + _safetyLocksLib: expectedSafetyLocksLibAddr, + _simulator: address(simulator), + _atlas: address(atlas) + }); + safetyLocksLib = new SafetyLocksLib({ + _escrowDuration: 64, + _factory: expectedAtlasFactoryAddr, + _verification: expectedAtlasVerificationAddr, + _gasAccLib: expectedGasAccountingLibAddr, _simulator: address(simulator), _atlas: address(atlas) }); diff --git a/src/contracts/atlas/AtlETH.sol b/src/contracts/atlas/AtlETH.sol index bfe99900..b53f8819 100644 --- a/src/contracts/atlas/AtlETH.sol +++ b/src/contracts/atlas/AtlETH.sol @@ -29,8 +29,9 @@ abstract contract AtlETH is Permit69 { address _factory, address _verification, address _gasAccLib, + address _safetyLocksLib, address _simulator - ) Permit69(_escrowDuration, _factory, _verification, _gasAccLib, _simulator) {} + ) Permit69(_escrowDuration, _factory, _verification, _gasAccLib, _safetyLocksLib, _simulator) {} /*////////////////////////////////////////////////////////////// ATLETH @@ -42,12 +43,12 @@ abstract contract AtlETH is Permit69 { return _escrowAccountData[account].balance; } - function nextAccountNonce(address account) external view returns (uint256 nextNonce) { - nextNonce = uint256(_escrowAccountData[account].nonce) + 1; + function nextAccountNonce(address account) external view returns (uint256) { + return uint256(_escrowAccountData[account].nonce) + 1; } - function accountLastActiveBlock(address account) external view returns (uint256 lastBlock) { - lastBlock = uint256(_escrowAccountData[account].lastAccessed); + function accountLastActiveBlock(address account) external view returns (uint256) { + return uint256(_escrowAccountData[account].lastAccessed); } // Deposit ETH and get atlETH in return. diff --git a/src/contracts/atlas/Atlas.sol b/src/contracts/atlas/Atlas.sol index 20be3aaf..dd072f98 100644 --- a/src/contracts/atlas/Atlas.sol +++ b/src/contracts/atlas/Atlas.sol @@ -34,8 +34,9 @@ contract Atlas is Escrow { address _factory, address _verification, address _gasAccLib, + address _safetyLocksLib, address _simulator - ) Escrow(_escrowDuration, _factory, _verification, _gasAccLib, _simulator) {} + ) Escrow(_escrowDuration, _factory, _verification, _gasAccLib, _safetyLocksLib, _simulator) {} function createExecutionEnvironment(address dAppControl) external returns (address executionEnvironment) { executionEnvironment = IAtlasFactory(FACTORY).createExecutionEnvironment(msg.sender, dAppControl); diff --git a/src/contracts/atlas/Escrow.sol b/src/contracts/atlas/Escrow.sol index 3b19b3e0..a1119a3f 100644 --- a/src/contracts/atlas/Escrow.sol +++ b/src/contracts/atlas/Escrow.sol @@ -34,8 +34,9 @@ abstract contract Escrow is AtlETH { address _factory, address _verification, address _gasAccLib, + address _safetyLocksLib, address _simulator - ) AtlETH(_escrowDuration, _factory, _verification, _gasAccLib, _simulator) {} + ) AtlETH(_escrowDuration, _factory, _verification, _gasAccLib, _safetyLocksLib, _simulator) {} /////////////////////////////////////////////////// /// EXTERNAL FUNCTIONS FOR BUNDLER INTERACTION /// diff --git a/src/contracts/atlas/GasAccounting.sol b/src/contracts/atlas/GasAccounting.sol index a82ab348..7ef6e2d7 100644 --- a/src/contracts/atlas/GasAccounting.sol +++ b/src/contracts/atlas/GasAccounting.sol @@ -17,8 +17,9 @@ abstract contract GasAccounting is SafetyLocks { address _factory, address _verification, address _gasAccLib, + address _safetyLocksLib, address _simulator - ) SafetyLocks(_escrowDuration, _factory, _verification, _gasAccLib, _simulator) {} + ) SafetyLocks(_escrowDuration, _factory, _verification, _gasAccLib, _safetyLocksLib, _simulator) {} // --------------------------------------- // EXTERNAL FUNCTIONS diff --git a/src/contracts/atlas/GasAccountingLib.sol b/src/contracts/atlas/GasAccountingLib.sol index ac938e83..8b7cf30b 100644 --- a/src/contracts/atlas/GasAccountingLib.sol +++ b/src/contracts/atlas/GasAccountingLib.sol @@ -24,9 +24,11 @@ contract GasAccountingLib is Storage, FastLaneErrorsEvents { uint256 _escrowDuration, address _factory, address _verification, + // address _gasAccLib, + address _safetyLocksLib, address _simulator, address _atlas - ) Storage(_escrowDuration, _factory, _verification, address(this), _simulator) { + ) Storage(_escrowDuration, _factory, _verification, address(this), _safetyLocksLib, _simulator) { ATLAS = _atlas; } diff --git a/src/contracts/atlas/SafetyLocks.sol b/src/contracts/atlas/SafetyLocks.sol index e8a74d29..9d50a6b6 100644 --- a/src/contracts/atlas/SafetyLocks.sol +++ b/src/contracts/atlas/SafetyLocks.sol @@ -14,6 +14,7 @@ import "../types/LockTypes.sol"; import {Storage} from "./Storage.sol"; import {FastLaneErrorsEvents} from "../types/Emissions.sol"; +import {SafetyLocksLib} from "./SafetyLocksLib.sol"; import "forge-std/Test.sol"; @@ -28,85 +29,91 @@ abstract contract SafetyLocks is Storage, FastLaneErrorsEvents { address _factory, address _verification, address _gasAccLib, + address _safetyLocksLib, address _simulator - ) Storage(_escrowDuration, _factory, _verification, _gasAccLib, _simulator) {} + ) Storage(_escrowDuration, _factory, _verification, _gasAccLib, _safetyLocksLib, _simulator) {} function _initializeEscrowLock(UserOperation calldata userOp, address executionEnvironment, address bundler, uint256 gasLimit) internal { - _checkIfUnlocked(); - - uint256 activeParties; - activeParties = activeParties.markActive(Party.Bundler); - activeParties = activeParties.markActive(Party.Solver); - - uint256 bundlerIndex = uint256(Party.Bundler); - - // Check for proxies - // NOTE: Order is important here so that we can loop through these later without having to go backwards to find final proxy - // Builder proxy - if (block.coinbase == bundler) { - activeParties = activeParties.markActive(Party.Builder); - ledgers[uint256(Party.Builder)] = Ledger({ - balance: 0, - contributed: 0, - requested: 0, - status: LedgerStatus.Proxy, - proxy: Party.Bundler - }); - - } else if (block.coinbase == userOp.from) { - activeParties = activeParties.markActive(Party.Builder); - ledgers[uint256(Party.Builder)] = Ledger({ - balance: 0, - contributed: 0, - requested: 0, - status: LedgerStatus.Proxy, - proxy: Party.User - }); - } - - // Bundler proxy - if (bundler == userOp.from) { - // Bundler already marked active - ledgers[uint256(Party.Bundler)] = Ledger({ - balance: 0, - contributed: 0, - requested: 0, - status: LedgerStatus.Proxy, - proxy: Party.User - }); - bundlerIndex = uint256(Party.User); - } - - // Initialize Ledger - int64 iGasLimit = int64(uint64(gasLimit)); - - if (msg.value != 0) { - int64 bundlerDeposit = int64(uint64(msg.value / tx.gasprice)); - ledgers[bundlerIndex] = Ledger({ - balance: 0, - contributed: bundlerDeposit, - requested: 0 - bundlerDeposit - iGasLimit, - status: LedgerStatus.Active, - proxy: Party(bundlerIndex) - }); - } else { - ledgers[bundlerIndex] = Ledger({ - balance: 0, - contributed: 0, - requested: 0 - iGasLimit, - status: LedgerStatus.Active, - proxy: Party(bundlerIndex) - }); - } - - // Initialize the Lock - lock = Lock({ - activeEnvironment: executionEnvironment, - activeParties: uint16(activeParties), - startingBalance: uint64((address(this).balance - msg.value) / tx.gasprice) - }); + (bool success,) = SAFETY_LOCKS_LIB.delegatecall(abi.encodeWithSelector(SafetyLocksLib.initializeEscrowLock.selector, userOp, executionEnvironment, bundler, gasLimit)); + if(!success) revert SafetyLocksLibError(); } + // function _initializeEscrowLock(UserOperation calldata userOp, address executionEnvironment, address bundler, uint256 gasLimit) internal { + // _checkIfUnlocked(); + + // uint256 activeParties; + // activeParties = activeParties.markActive(Party.Bundler); + // activeParties = activeParties.markActive(Party.Solver); + + // uint256 bundlerIndex = uint256(Party.Bundler); + + // // Check for proxies + // // NOTE: Order is important here so that we can loop through these later without having to go backwards to find final proxy + // // Builder proxy + // if (block.coinbase == bundler) { + // activeParties = activeParties.markActive(Party.Builder); + // ledgers[uint256(Party.Builder)] = Ledger({ + // balance: 0, + // contributed: 0, + // requested: 0, + // status: LedgerStatus.Proxy, + // proxy: Party.Bundler + // }); + + // } else if (block.coinbase == userOp.from) { + // activeParties = activeParties.markActive(Party.Builder); + // ledgers[uint256(Party.Builder)] = Ledger({ + // balance: 0, + // contributed: 0, + // requested: 0, + // status: LedgerStatus.Proxy, + // proxy: Party.User + // }); + // } + + // // Bundler proxy + // if (bundler == userOp.from) { + // // Bundler already marked active + // ledgers[uint256(Party.Bundler)] = Ledger({ + // balance: 0, + // contributed: 0, + // requested: 0, + // status: LedgerStatus.Proxy, + // proxy: Party.User + // }); + // bundlerIndex = uint256(Party.User); + // } + + // // Initialize Ledger + // int64 iGasLimit = int64(uint64(gasLimit)); + + // if (msg.value != 0) { + // int64 bundlerDeposit = int64(uint64(msg.value / tx.gasprice)); + // ledgers[bundlerIndex] = Ledger({ + // balance: 0, + // contributed: bundlerDeposit, + // requested: 0 - bundlerDeposit - iGasLimit, + // status: LedgerStatus.Active, + // proxy: Party(bundlerIndex) + // }); + // } else { + // ledgers[bundlerIndex] = Ledger({ + // balance: 0, + // contributed: 0, + // requested: 0 - iGasLimit, + // status: LedgerStatus.Active, + // proxy: Party(bundlerIndex) + // }); + // } + + // // Initialize the Lock + // lock = Lock({ + // activeEnvironment: executionEnvironment, + // activeParties: uint16(activeParties), + // startingBalance: uint64((address(this).balance - msg.value) / tx.gasprice) + // }); + // } + function _buildEscrowLock( DAppConfig calldata dConfig, address executionEnvironment, diff --git a/src/contracts/atlas/SafetyLocksLib.sol b/src/contracts/atlas/SafetyLocksLib.sol new file mode 100644 index 00000000..79708363 --- /dev/null +++ b/src/contracts/atlas/SafetyLocksLib.sol @@ -0,0 +1,121 @@ +//SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.16; + +import {Storage} from "./Storage.sol"; +import {FastLaneErrorsEvents} from "../types/Emissions.sol"; + +import {SafetyBits} from "../libraries/SafetyBits.sol"; +import {CallBits} from "../libraries/CallBits.sol"; +import {PartyMath} from "../libraries/GasParties.sol"; + +import "../types/UserCallTypes.sol"; +import "../types/LockTypes.sol"; +import "../types/EscrowTypes.sol"; + + +// TODO check for address(this) or other assumptions from when inside Atlas inheritance + +contract SafetyLocksLib is Storage, FastLaneErrorsEvents { + using SafetyBits for EscrowKey; + using CallBits for uint32; + using PartyMath for Party; + using PartyMath for uint256; + + address public immutable ATLAS; + + constructor( + uint256 _escrowDuration, + address _factory, + address _verification, + address _gasAccLib, + // address _safetyLocksLib, + address _simulator, + address _atlas + ) Storage(_escrowDuration, _factory, _verification, _gasAccLib, address(this), _simulator) { + ATLAS = _atlas; + } + + + + function initializeEscrowLock(UserOperation calldata userOp, address executionEnvironment, address bundler, uint256 gasLimit) external payable { + _checkIfUnlocked(); + + uint256 activeParties; + activeParties = activeParties.markActive(Party.Bundler); + activeParties = activeParties.markActive(Party.Solver); + + uint256 bundlerIndex = uint256(Party.Bundler); + + // Check for proxies + // NOTE: Order is important here so that we can loop through these later without having to go backwards to find final proxy + // Builder proxy + if (block.coinbase == bundler) { + activeParties = activeParties.markActive(Party.Builder); + ledgers[uint256(Party.Builder)] = Ledger({ + balance: 0, + contributed: 0, + requested: 0, + status: LedgerStatus.Proxy, + proxy: Party.Bundler + }); + + } else if (block.coinbase == userOp.from) { + activeParties = activeParties.markActive(Party.Builder); + ledgers[uint256(Party.Builder)] = Ledger({ + balance: 0, + contributed: 0, + requested: 0, + status: LedgerStatus.Proxy, + proxy: Party.User + }); + } + + // Bundler proxy + if (bundler == userOp.from) { + // Bundler already marked active + ledgers[uint256(Party.Bundler)] = Ledger({ + balance: 0, + contributed: 0, + requested: 0, + status: LedgerStatus.Proxy, + proxy: Party.User + }); + bundlerIndex = uint256(Party.User); + } + + // Initialize Ledger + int64 iGasLimit = int64(uint64(gasLimit)); + + if (msg.value != 0) { + int64 bundlerDeposit = int64(uint64(msg.value / tx.gasprice)); + ledgers[bundlerIndex] = Ledger({ + balance: 0, + contributed: bundlerDeposit, + requested: 0 - bundlerDeposit - iGasLimit, + status: LedgerStatus.Active, + proxy: Party(bundlerIndex) + }); + } else { + ledgers[bundlerIndex] = Ledger({ + balance: 0, + contributed: 0, + requested: 0 - iGasLimit, + status: LedgerStatus.Active, + proxy: Party(bundlerIndex) + }); + } + + // Initialize the Lock + lock = Lock({ + activeEnvironment: executionEnvironment, + activeParties: uint16(activeParties), + startingBalance: uint64((address(this).balance - msg.value) / tx.gasprice) + }); + } + + + function _checkIfUnlocked() internal view { + if(lock.activeEnvironment != UNLOCKED) revert AlreadyInitialized(); + } + +} \ No newline at end of file diff --git a/src/contracts/atlas/Storage.sol b/src/contracts/atlas/Storage.sol index 23a1d6ce..82e9afa2 100644 --- a/src/contracts/atlas/Storage.sol +++ b/src/contracts/atlas/Storage.sol @@ -14,6 +14,7 @@ contract Storage { address public immutable FACTORY; address public immutable VERIFICATION; address public immutable GAS_ACC_LIB; + address public immutable SAFETY_LOCKS_LIB; address public immutable SIMULATOR; // AtlETH ERC-20 constants @@ -45,12 +46,14 @@ contract Storage { address _factory, address _verification, address _gasAccLib, + address _safetyLocksLib, address _simulator ) { ESCROW_DURATION = _escrowDuration; FACTORY = _factory; VERIFICATION = _verification; GAS_ACC_LIB = _gasAccLib; + SAFETY_LOCKS_LIB = _safetyLocksLib; SIMULATOR = _simulator; INITIAL_CHAIN_ID = block.chainid; INITIAL_DOMAIN_SEPARATOR = _computeDomainSeparator(); diff --git a/src/contracts/common/Permit69.sol b/src/contracts/common/Permit69.sol index 78f4e0f9..f2eb7b2a 100644 --- a/src/contracts/common/Permit69.sol +++ b/src/contracts/common/Permit69.sol @@ -29,8 +29,9 @@ abstract contract Permit69 is GasAccounting { address _factory, address _verification, address _gasAccLib, + address _safetyLocksLib, address _simulator - ) GasAccounting(_escrowDuration, _factory, _verification, _gasAccLib, _simulator) {} + ) GasAccounting(_escrowDuration, _factory, _verification, _gasAccLib, _safetyLocksLib, _simulator) {} // Virtual Functions defined by other Atlas modules function _verifyCallerIsExecutionEnv( diff --git a/src/contracts/types/Emissions.sol b/src/contracts/types/Emissions.sol index 0f9d2e8b..434287cd 100644 --- a/src/contracts/types/Emissions.sol +++ b/src/contracts/types/Emissions.sol @@ -102,6 +102,7 @@ contract FastLaneErrorsEvents { // NEW - SafetyLocks error NotInitialized(); error AlreadyInitialized(); + error SafetyLocksLibError(); diff --git a/test/Permit69.t.sol b/test/Permit69.t.sol index e78da608..5410059c 100644 --- a/test/Permit69.t.sol +++ b/test/Permit69.t.sol @@ -42,7 +42,7 @@ contract Permit69Test is BaseTest { callDepth: 0 }); - mockAtlas = new MockAtlasForPermit69Tests(10,address(0),address(0), address(0), address(0)); + mockAtlas = new MockAtlasForPermit69Tests(10,address(0),address(0), address(0), address(0), address(0)); mockAtlas.setEscrowKey(escrowKey); mockAtlas.setEnvironment(mockExecutionEnvAddress); @@ -252,8 +252,9 @@ contract MockAtlasForPermit69Tests is Permit69 { address _factory, address _verification, address _gasAccLib, + address _safetyLocksLib, address _simulator - ) Permit69(_escrowDuration, _factory, _verification, _gasAccLib, _simulator) {} + ) Permit69(_escrowDuration, _factory, _verification, _gasAccLib, _safetyLocksLib, _simulator) {} // Declared in SafetyLocks.sol in the canonical Atlas system // The only property relevant to testing Permit69 is _escrowKey.lockState (bitwise uint16) diff --git a/test/base/BaseTest.t.sol b/test/base/BaseTest.t.sol index 7122c841..9ca42a59 100644 --- a/test/base/BaseTest.t.sol +++ b/test/base/BaseTest.t.sol @@ -9,6 +9,7 @@ import {Atlas} from "src/contracts/atlas/Atlas.sol"; import {AtlasFactory} from "src/contracts/atlas/AtlasFactory.sol"; import {AtlasVerification} from "src/contracts/atlas/AtlasVerification.sol"; import {GasAccountingLib} from "src/contracts/atlas/GasAccountingLib.sol"; +import {SafetyLocksLib} from "src/contracts/atlas/SafetyLocksLib.sol"; import {Sorter} from "src/contracts/helpers/Sorter.sol"; import {Simulator} from "src/contracts/helpers/Simulator.sol"; @@ -42,6 +43,7 @@ contract BaseTest is Test, TestConstants { AtlasFactory public atlasFactory; AtlasVerification public atlasVerification; GasAccountingLib public gasAccountingLib; + SafetyLocksLib public safetyLocksLib; Simulator public simulator; Sorter public sorter; @@ -86,12 +88,18 @@ contract BaseTest is Test, TestConstants { payee, vm.getNonce(payee) + 3 ); + address expectedSafetyLocksLibAddr = computeCreateAddress( + payee, + vm.getNonce(payee) + 4 + ); + atlas = new Atlas({ _escrowDuration: 64, _factory: expectedAtlasFactoryAddr, _verification: expectedAtlasVerificationAddr, _gasAccLib: expectedGasAccountingLibAddr, + _safetyLocksLib: expectedSafetyLocksLibAddr, _simulator: address(simulator) }); atlasFactory = new AtlasFactory(address(atlas)); @@ -100,6 +108,15 @@ contract BaseTest is Test, TestConstants { _escrowDuration: 64, _factory: expectedAtlasFactoryAddr, _verification: expectedAtlasVerificationAddr, + _safetyLocksLib: expectedSafetyLocksLibAddr, + _simulator: address(simulator), + _atlas: address(atlas) + }); + safetyLocksLib = new SafetyLocksLib({ + _escrowDuration: 64, + _factory: expectedAtlasFactoryAddr, + _verification: expectedAtlasVerificationAddr, + _gasAccLib: expectedGasAccountingLibAddr, _simulator: address(simulator), _atlas: address(atlas) }); From 1ee2ceecd4e445511e34c0e23038736d89945c63 Mon Sep 17 00:00:00 2001 From: Ben Sparks <52714090+BenSparksCode@users.noreply.github.com> Date: Thu, 16 Nov 2023 16:34:59 +0200 Subject: [PATCH 38/38] Remove commented code --- src/contracts/atlas/SafetyLocks.sol | 76 ----------------------------- 1 file changed, 76 deletions(-) diff --git a/src/contracts/atlas/SafetyLocks.sol b/src/contracts/atlas/SafetyLocks.sol index 9d50a6b6..399e29ac 100644 --- a/src/contracts/atlas/SafetyLocks.sol +++ b/src/contracts/atlas/SafetyLocks.sol @@ -38,82 +38,6 @@ abstract contract SafetyLocks is Storage, FastLaneErrorsEvents { if(!success) revert SafetyLocksLibError(); } - // function _initializeEscrowLock(UserOperation calldata userOp, address executionEnvironment, address bundler, uint256 gasLimit) internal { - // _checkIfUnlocked(); - - // uint256 activeParties; - // activeParties = activeParties.markActive(Party.Bundler); - // activeParties = activeParties.markActive(Party.Solver); - - // uint256 bundlerIndex = uint256(Party.Bundler); - - // // Check for proxies - // // NOTE: Order is important here so that we can loop through these later without having to go backwards to find final proxy - // // Builder proxy - // if (block.coinbase == bundler) { - // activeParties = activeParties.markActive(Party.Builder); - // ledgers[uint256(Party.Builder)] = Ledger({ - // balance: 0, - // contributed: 0, - // requested: 0, - // status: LedgerStatus.Proxy, - // proxy: Party.Bundler - // }); - - // } else if (block.coinbase == userOp.from) { - // activeParties = activeParties.markActive(Party.Builder); - // ledgers[uint256(Party.Builder)] = Ledger({ - // balance: 0, - // contributed: 0, - // requested: 0, - // status: LedgerStatus.Proxy, - // proxy: Party.User - // }); - // } - - // // Bundler proxy - // if (bundler == userOp.from) { - // // Bundler already marked active - // ledgers[uint256(Party.Bundler)] = Ledger({ - // balance: 0, - // contributed: 0, - // requested: 0, - // status: LedgerStatus.Proxy, - // proxy: Party.User - // }); - // bundlerIndex = uint256(Party.User); - // } - - // // Initialize Ledger - // int64 iGasLimit = int64(uint64(gasLimit)); - - // if (msg.value != 0) { - // int64 bundlerDeposit = int64(uint64(msg.value / tx.gasprice)); - // ledgers[bundlerIndex] = Ledger({ - // balance: 0, - // contributed: bundlerDeposit, - // requested: 0 - bundlerDeposit - iGasLimit, - // status: LedgerStatus.Active, - // proxy: Party(bundlerIndex) - // }); - // } else { - // ledgers[bundlerIndex] = Ledger({ - // balance: 0, - // contributed: 0, - // requested: 0 - iGasLimit, - // status: LedgerStatus.Active, - // proxy: Party(bundlerIndex) - // }); - // } - - // // Initialize the Lock - // lock = Lock({ - // activeEnvironment: executionEnvironment, - // activeParties: uint16(activeParties), - // startingBalance: uint64((address(this).balance - msg.value) / tx.gasprice) - // }); - // } - function _buildEscrowLock( DAppConfig calldata dConfig, address executionEnvironment,