Skip to content

Commit

Permalink
added support for searcher fulfillment of user intents. updated call …
Browse files Browse the repository at this point in the history
…config with additional config settings and removed the unused ones. Updated ExecutionEnvironment to handle intent execution / verification
  • Loading branch information
thogard785 committed Aug 8, 2023
1 parent 28abbbe commit 2619c93
Show file tree
Hide file tree
Showing 15 changed files with 256 additions and 171 deletions.
5 changes: 3 additions & 2 deletions src/contracts/atlas/Atlas.sol
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ contract Atlas is Test, Factory {
if (userCallHash == searcherCalls[i].metaTx.userCallHash) {
auctionAlreadyWon = auctionAlreadyWon
|| _searcherExecutionIteration(
protocolCall, searcherCalls[i], auctionAlreadyWon, environment
protocolCall, searcherCalls[i], stagingReturnData, auctionAlreadyWon, environment
);
}

Expand All @@ -125,10 +125,11 @@ contract Atlas is Test, Factory {
function _searcherExecutionIteration(
ProtocolCall calldata protocolCall,
SearcherCall calldata searcherCall,
bytes memory stagingReturnData,
bool auctionAlreadyWon,
address environment
) internal returns (bool) {
if (_executeSearcherCall(searcherCall, auctionAlreadyWon, environment)) {
if (_executeSearcherCall(searcherCall, stagingReturnData, auctionAlreadyWon, environment)) {
if (!auctionAlreadyWon) {
auctionAlreadyWon = true;
_executePayments(protocolCall, searcherCall.bids, environment);
Expand Down
2 changes: 2 additions & 0 deletions src/contracts/atlas/Emissions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ string constant SEARCHER_EVM_ERROR = "SearcherEVMError";
string constant ALTERED_USER_HASH = "AlteredUserCalldataHash";
string constant INVALID_SEARCHER_HASH = "InvalidSearcherCalldataHash";
string constant HASH_CHAIN_BROKEN = "CalldataHashChainMismatch";
string constant INTENT_UNFULFILLED = "IntentUnfulfilled";

contract FastLaneErrorsEvents {
// NOTE: nonce is the executed nonce
Expand All @@ -37,6 +38,7 @@ contract FastLaneErrorsEvents {
bytes32 internal constant _ALTERED_USER_HASH = keccak256(abi.encodePacked(ALTERED_USER_HASH));
bytes32 internal constant _INVALID_SEARCHER_HASH = keccak256(abi.encodePacked(INVALID_SEARCHER_HASH));
bytes32 internal constant _HASH_CHAIN_BROKEN = keccak256(abi.encodePacked(HASH_CHAIN_BROKEN));
bytes32 internal constant _INTENT_UNFULFILLED = keccak256(abi.encodePacked(INTENT_UNFULFILLED));

// string constant SEARCHER_ETHER_BID_UNPAID = "SearcherMsgValueNotRepaid";
// bytes32 constant _SEARCHER_ETHER_BID_UNPAID = keccak256(abi.encodePacked(SEARCHER_ETHER_BID_UNPAID));
Expand Down
12 changes: 7 additions & 5 deletions src/contracts/atlas/Escrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {CallVerification} from "../libraries/CallVerification.sol";

contract Escrow is ProtocolVerifier, SafetyLocks, SearcherWrapper {
using ECDSA for bytes32;
using EscrowBits for uint256;

uint32 public immutable escrowDuration;

Expand Down Expand Up @@ -81,6 +82,7 @@ contract Escrow is ProtocolVerifier, SafetyLocks, SearcherWrapper {

function _executeSearcherCall(
SearcherCall calldata searcherCall,
bytes memory stagingReturnData,
bool isAuctionAlreadyComplete,
address environment
) internal returns (bool) {
Expand All @@ -95,22 +97,22 @@ contract Escrow is ProtocolVerifier, SafetyLocks, SearcherWrapper {
uint256 escrowSurplus;

// If there are no errors, attempt to execute
if (EscrowBits.canExecute(result)) {
if (result.canExecute()) {
// Open the searcher lock
_openSearcherLock(searcherCall.metaTx.to, environment);

// Execute the searcher call
(outcome, escrowSurplus) = _searcherCallWrapper(searcherCall, gasLimit, environment);
(outcome, escrowSurplus) = _searcherCallWrapper(gasLimit, environment, searcherCall, stagingReturnData);

unchecked {
searcherEscrow.total += uint128(escrowSurplus);
}

result |= 1 << uint256(outcome);

if (EscrowBits.executedWithError(result)) {
if (result.executedWithError()) {
result |= 1 << uint256(SearcherOutcome.ExecutionCompleted);
} else if (EscrowBits.executionSuccessful(result)) {
} else if (result.executionSuccessful()) {
// first successful searcher call that paid what it bid
isAuctionAlreadyComplete = true; // cannot be reached if bool is already true
result |= 1 << uint256(SearcherOutcome.ExecutionCompleted);
Expand All @@ -119,7 +121,7 @@ contract Escrow is ProtocolVerifier, SafetyLocks, SearcherWrapper {
uint256 gasRebate; // TODO: can reuse gasWaterMark here for gas efficiency if it really matters

// Update the searcher's escrow balances
if (EscrowBits.updateEscrow(result)) {
if (result.updateEscrow()) {
gasRebate = _update(searcherCall.metaTx, searcherEscrow, gasWaterMark, result);
}

Expand Down
19 changes: 17 additions & 2 deletions src/contracts/atlas/ExecutionEnvironment.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import {
SEARCHER_CALL_REVERTED,
SEARCHER_MSG_VALUE_UNPAID,
SEARCHER_FAILED_CALLBACK,
SEARCHER_BID_UNPAID
SEARCHER_BID_UNPAID,
INTENT_UNFULFILLED
} from "./Emissions.sol";

contract ExecutionEnvironment is Test {
Expand Down Expand Up @@ -166,7 +167,8 @@ contract ExecutionEnvironment is Test {
function searcherMetaTryCatch(
uint256 gasLimit,
uint256 escrowBalance,
SearcherCall calldata searcherCall
SearcherCall calldata searcherCall,
bytes calldata stagingReturnData
) external payable {
// msg.sender = atlas
// address(this) = ExecutionEnvironment
Expand Down Expand Up @@ -207,6 +209,19 @@ contract ExecutionEnvironment is Test {
require(success, SEARCHER_CALL_REVERTED);
require(ISafetyLocks(atlas).confirmSafetyCallback(), SEARCHER_FAILED_CALLBACK);

// If this was a user intent, handle and verify fulfillment
if (_config().needsSearcherFullfillment()) {
bytes memory data;
(success, data) = _control().delegatecall(
abi.encodeWithSelector(IProtocolControl.fulfillmentCall.selector, stagingReturnData)
);
require(success, INTENT_UNFULFILLED);

success = abi.decode(data, (bool));
require(success, INTENT_UNFULFILLED);
}


// Verify that the searcher paid what they bid
bool etherIsBidToken;
i = 0;
Expand Down
13 changes: 8 additions & 5 deletions src/contracts/atlas/SearcherWrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,20 @@ import {SearcherOutcome} from "../types/EscrowTypes.sol";

contract SearcherWrapper is FastLaneErrorsEvents {
function _searcherCallWrapper(
SearcherCall calldata searcherCall,
uint256 gasLimit,
address environment
address environment,
SearcherCall calldata searcherCall,
bytes memory stagingReturnData
) internal returns (SearcherOutcome, uint256) {
// address(this) = Escrow
// msg.sender = ExecutionEnvironment
// address(this) = Atlas/Escrow
// msg.sender = tx.origin

// Get current Ether balance
uint256 currentBalance = address(this).balance;

// Call the execution environment
try IExecutionEnvironment(environment).searcherMetaTryCatch{value: searcherCall.metaTx.value}(
gasLimit, currentBalance, searcherCall
gasLimit, currentBalance, searcherCall, stagingReturnData
) {
return (SearcherOutcome.Success, address(this).balance - currentBalance);
} catch Error(string memory err) {
Expand All @@ -36,6 +37,8 @@ contract SearcherWrapper is FastLaneErrorsEvents {
return (SearcherOutcome.BidNotPaid, 0);
} else if (errorSwitch == _SEARCHER_MSG_VALUE_UNPAID) {
return (SearcherOutcome.CallValueTooHigh, 0);
} else if (errorSwitch == _INTENT_UNFULFILLED) {
return (SearcherOutcome.IntentUnfulfilled, 0);
} else if (errorSwitch == _SEARCHER_CALL_REVERTED) {
return (SearcherOutcome.CallReverted, 0);
} else if (errorSwitch == _SEARCHER_FAILED_CALLBACK) {
Expand Down
3 changes: 2 additions & 1 deletion src/contracts/interfaces/IExecutionEnvironment.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ interface IExecutionEnvironment {
function searcherMetaTryCatch(
uint256 gasLimit,
uint256 escrowBalance,
SearcherCall calldata searcherCall
SearcherCall calldata searcherCall,
bytes calldata stagingReturnData
) external payable;

function allocateRewards(
Expand Down
8 changes: 5 additions & 3 deletions src/contracts/interfaces/IProtocolControl.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ interface IProtocolControl {

function allocatingCall(bytes calldata data) external;

function verificationCall(bytes calldata data) external returns (bool);
function fulfillmentCall(bytes calldata data) external returns (bytes memory);

function getProtocolCall() external view returns (ProtocolCall memory protocolCall);
function verificationCall(bytes calldata data) external returns (bytes memory);

function getCallConfig() external view returns (bool, bool, bool, bool, bool, bool, bool, bool, bool);
function getProtocolCall() external view returns (ProtocolCall memory protocolCall);

function getCallConfig() external view returns (bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool);

function getPayeeData(bytes calldata data) external returns (PayeeData[] memory);

function getBidFormat(bytes calldata data) external returns (BidData[] memory);
Expand Down
117 changes: 61 additions & 56 deletions src/contracts/libraries/CallBits.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,94 +8,91 @@ import "../types/CallTypes.sol";
library CallBits {
uint16 internal constant _ONE = uint16(1);

/*
TODO: Figure out why labeling these constants and referencing the constant uses more gas
than computing directly. Implied uint256 conversion?
uint16 constant internal _NEEDS_STAGING = _ONE << uint16(CallConfig.CallStaging);
uint16 constant internal _NEEDS_DELEGATE_STAGING = _ONE << uint16(CallConfig.DelegateStaging);
uint16 constant internal _NEEDS_LOCAL_USER = _ONE << uint16(CallConfig.LocalUser);
uint16 constant internal _NEEDS_DELEGATE_USER = _ONE << uint16(CallConfig.DelegateUser);
uint16 constant internal _NEEDS_DELEGATE_ALLOCATING = _ONE << uint16(CallConfig.DelegateAllocating);
uint16 constant internal _NEEDS_VERIFICATION = _ONE << uint16(CallConfig.CallVerification);
uint16 constant internal _NEEDS_DELEGATE_VERIFICATION = _ONE << uint16(CallConfig.DelegateVerification);
uint16 constant internal _NEEDS_SEQUENCED_NONCES = _ONE << uint16(CallConfig.Sequenced);
uint16 constant internal _ALLOWS_RECYCLED_STORAGE = _ONE << uint16(CallConfig.RecycledStorage);
*/

function buildCallConfig(address protocolControl) internal view returns (uint16 callConfig) {
(
bool sequenced,
bool requireStaging,
bool delegateStaging,
bool localUser,
bool delegateUser,
bool delegateAllocating,
bool searcherFulfillment,
bool requireVerification,
bool delegateVerification,
bool recycledStorage
bool zeroSearchers,
bool reuseUserOp,
bool userBundler,
bool protocolBundler,
bool unknownBundler
) = IProtocolControl(protocolControl).getCallConfig();

// WTB tuple unpacking :*(
callConfig = encodeCallConfig(
sequenced,
requireStaging,
delegateStaging,
localUser,
delegateUser,
delegateAllocating,
requireVerification,
delegateVerification,
recycledStorage
sequenced,
requireStaging,
localUser,
delegateUser,
searcherFulfillment,
requireVerification,
zeroSearchers,
reuseUserOp,
userBundler,
protocolBundler,
unknownBundler
);
}

function encodeCallConfig(
bool sequenced,
bool requireStaging,
bool delegateStaging,
bool localUser,
bool delegateUser,
bool delegateAllocating,
bool searcherFulfillment,
bool requireVerification,
bool delegateVerification,
bool recycledStorage
bool zeroSearchers,
bool reuseUserOp,
bool userBundler,
bool protocolBundler,
bool unknownBundler
) internal pure returns (uint16 callConfig) {
if (sequenced) {
callConfig ^= _ONE << uint16(CallConfig.Sequenced);
}

if (requireStaging) {
callConfig ^= _ONE << uint16(CallConfig.CallStaging);
if (delegateStaging) {
callConfig ^= _ONE << uint16(CallConfig.DelegateStaging);
}
}
if (localUser) {
callConfig ^= _ONE << uint16(CallConfig.LocalUser);
}
if (delegateUser) {
callConfig ^= _ONE << uint16(CallConfig.DelegateUser);
}
if (delegateAllocating) {
callConfig ^= _ONE << uint16(CallConfig.DelegateAllocating);
if (searcherFulfillment) {
callConfig ^= _ONE << uint16(CallConfig.SearcherFulfillment);
}
if (requireVerification) {
callConfig ^= _ONE << uint16(CallConfig.CallVerification);
if (delegateVerification) {
callConfig ^= _ONE << uint16(CallConfig.DelegateVerification);
}
}
if (recycledStorage) {
callConfig ^= _ONE << uint16(CallConfig.RecycledStorage);
if (zeroSearchers) {
callConfig ^= _ONE << uint16(CallConfig.ZeroSearchers);
}
if (reuseUserOp) {
callConfig ^= _ONE << uint16(CallConfig.ReuseUserOp);
}
if (userBundler) {
callConfig ^= _ONE << uint16(CallConfig.UserBundler);
}
if (protocolBundler) {
callConfig ^= _ONE << uint16(CallConfig.ProtocolBundler);
}
if (unknownBundler) {
callConfig ^= _ONE << uint16(CallConfig.UnknownBundler);
}
}

function needsStagingCall(uint16 callConfig) internal pure returns (bool needsStaging) {
needsStaging = (callConfig & 1 << uint16(CallConfig.CallStaging) != 0);
function needsSequencedNonces(uint16 callConfig) internal pure returns (bool sequenced) {
sequenced = (callConfig & 1 << uint16(CallConfig.Sequenced) != 0);
}

function needsDelegateStaging(uint16 callConfig) internal pure returns (bool delegateStaging) {
delegateStaging = (callConfig & 1 << uint16(CallConfig.DelegateStaging) != 0);
function needsStagingCall(uint16 callConfig) internal pure returns (bool needsStaging) {
needsStaging = (callConfig & 1 << uint16(CallConfig.CallStaging) != 0);
}

function needsLocalUser(uint16 callConfig) internal pure returns (bool localUser) {
Expand All @@ -106,23 +103,31 @@ library CallBits {
delegateUser = (callConfig & 1 << uint16(CallConfig.DelegateUser) != 0);
}

function needsDelegateAllocating(uint16 callConfig) internal pure returns (bool delegateAllocating) {
delegateAllocating = (callConfig & 1 << uint16(CallConfig.DelegateAllocating) != 0);
}

function needsDelegateVerification(uint16 callConfig) internal pure returns (bool delegateVerification) {
delegateVerification = (callConfig & 1 << uint16(CallConfig.DelegateVerification) != 0);
function needsSearcherFullfillment(uint16 callConfig) internal pure returns (bool searcherFulfillment) {
searcherFulfillment = (callConfig & 1 << uint16(CallConfig.SearcherFulfillment) != 0);
}

function needsVerificationCall(uint16 callConfig) internal pure returns (bool needsVerification) {
needsVerification = (callConfig & 1 << uint16(CallConfig.CallVerification) != 0);
}

function allowsRecycledStorage(uint16 callConfig) internal pure returns (bool recycledStorage) {
recycledStorage = (callConfig & 1 << uint16(CallConfig.RecycledStorage) != 0);
function allowsZeroSearchers(uint16 callConfig) internal pure returns (bool zeroSearchers) {
zeroSearchers = (callConfig & 1 << uint16(CallConfig.ZeroSearchers) != 0);
}

function needsSequencedNonces(uint16 callConfig) internal pure returns (bool sequenced) {
sequenced = (callConfig & 1 << uint16(CallConfig.Sequenced) != 0);
function allowsReuseUserOps(uint16 callConfig) internal pure returns (bool reuseUserOp) {
reuseUserOp = (callConfig & 1 << uint16(CallConfig.ReuseUserOp) != 0);
}

function allowsUserBundler(uint16 callConfig) internal pure returns (bool userBundler) {
userBundler = (callConfig & 1 << uint16(CallConfig.UserBundler) != 0);
}

function allowsProtocolBundler(uint16 callConfig) internal pure returns (bool protocolBundler) {
protocolBundler = (callConfig & 1 << uint16(CallConfig.ProtocolBundler) != 0);
}

function allowsUnknownBundler(uint16 callConfig) internal pure returns (bool unknownBundler) {
unknownBundler = (callConfig & 1 << uint16(CallConfig.UnknownBundler) != 0);
}
}
11 changes: 6 additions & 5 deletions src/contracts/libraries/EscrowBits.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ library EscrowBits {
uint256 internal constant _EXECUTION_REFUND = (
1 << uint256(SearcherOutcome.CallReverted) | 1 << uint256(SearcherOutcome.BidNotPaid)
| 1 << uint256(SearcherOutcome.CallValueTooHigh) | 1 << uint256(SearcherOutcome.UnknownError)
| 1 << uint256(SearcherOutcome.CallbackFailed) | 1 << uint256(SearcherOutcome.EVMError)
| 1 << uint256(SearcherOutcome.Success)
| 1 << uint256(SearcherOutcome.CallbackFailed) | 1 << uint256(SearcherOutcome.IntentUnfulfilled)
| 1 << uint256(SearcherOutcome.EVMError) | 1 << uint256(SearcherOutcome.Success)
);

uint256 internal constant _NO_NONCE_UPDATE = (
Expand All @@ -34,9 +34,10 @@ library EscrowBits {
);

uint256 internal constant _EXECUTED_WITH_ERROR = (
1 << uint256(SearcherOutcome.BidNotPaid) | 1 << uint256(SearcherOutcome.CallReverted)
| 1 << uint256(SearcherOutcome.BidNotPaid) | 1 << uint256(SearcherOutcome.CallValueTooHigh)
| 1 << uint256(SearcherOutcome.CallbackFailed)
1 << uint256(SearcherOutcome.CallReverted) | 1 << uint256(SearcherOutcome.BidNotPaid)
| 1 << uint256(SearcherOutcome.CallValueTooHigh) | 1 << uint256(SearcherOutcome.UnknownError)
| 1 << uint256(SearcherOutcome.CallbackFailed) | 1 << uint256(SearcherOutcome.IntentUnfulfilled)
| 1 << uint256(SearcherOutcome.EVMError)
);

uint256 internal constant _EXECUTED_SUCCESSFULLY = (1 << uint256(SearcherOutcome.Success));
Expand Down
Loading

0 comments on commit 2619c93

Please sign in to comment.