Skip to content

Commit

Permalink
added a Intent-based Swap DEX thingy to illustrate the versatility of…
Browse files Browse the repository at this point in the history
… Atlas. Searchers optimistically receive the users sell token and can optimistically borrow eth from the Atlas escrow, and can arbitrarily execute their own data to fulfill the intent. Also added a donateToBundler function in escrow to streamline the gas accounting / payback process
  • Loading branch information
thogard785 committed Aug 9, 2023
1 parent 4a90abf commit 09cc51b
Show file tree
Hide file tree
Showing 16 changed files with 345 additions and 49 deletions.
38 changes: 19 additions & 19 deletions src/contracts/atlas/Atlas.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,20 @@ contract Atlas is Test, Factory {
// and that the signatures are valid.
bool valid = _verifyProtocol(userCall.metaTx.to, protocolCall, verification) && _verifyUser(protocolCall, userCall);

// Get the execution environment
address environment = _getExecutionEnvironmentCustom(userCall.metaTx.from, verification.proof.controlCodeHash, protocolCall.to, protocolCall.callConfig);

// Check that the value of the tx is greater than or equal to the value specified
// NOTE: a msg.value *higher* than user value could be used by the staging call.
// There is a further check in the handler before the usercall to verify.
if(msg.value < userCall.metaTx.value) { valid = false; }
if(searcherCalls.length >= type(uint8).max - 1) { valid = false; }
if(block.number > userCall.metaTx.deadline || block.number > verification.proof.deadline) { valid = false; }
if(tx.gasprice > userCall.metaTx.maxFeePerGas) { valid = false; }
if (msg.value < userCall.metaTx.value) { valid = false; }
//if (msg.sender != tx.origin) { valid = false; }
if (searcherCalls.length >= type(uint8).max - 1) { valid = false; }
if (block.number > userCall.metaTx.deadline || block.number > verification.proof.deadline) { valid = false; }
if (tx.gasprice > userCall.metaTx.maxFeePerGas) { valid = false; }
if (environment.codehash == bytes32(0)) { valid = false; }
if (!protocolCall.callConfig.allowsZeroSearchers() || protocolCall.callConfig.needsSearcherFullfillment()) {
if (searcherCalls.length == 0) { valid = false;}
if (searcherCalls.length == 0) { valid = false; }
}


// Get the execution environment
address environment = _getExecutionEnvironmentCustom(userCall.metaTx.from, verification.proof.controlCodeHash, protocolCall.to, protocolCall.callConfig);
valid = valid && environment.codehash != bytes32(0);
// TODO: More checks

// Gracefully return if not valid. This allows signature data to be stored, which helps prevent
// replay attacks.
Expand All @@ -64,7 +63,7 @@ contract Atlas is Test, Factory {
try this.execute{value: msg.value}(protocolCall, userCall.metaTx, searcherCalls, environment, verification.proof.callChainHash)
returns (uint256 accruedGasRebate) {
// Gas Refund to sender only if execution is successful
_executeGasRefund(msg.sender, accruedGasRebate);
_executeGasRefund(gasMarker, accruedGasRebate, userCall.metaTx.from);

} catch {
// TODO: This portion needs more nuanced logic
Expand Down Expand Up @@ -120,18 +119,19 @@ contract Atlas is Test, Factory {
bytes memory userReturnData = _executeUserCall(userCall, environment);

uint256 i;
bool auctionAlreadyWon;
bool auctionWon;

for (; i < searcherCalls.length;) {

proof = proof.next(searcherCalls[i].metaTx.from, searcherCalls[i].metaTx.data);

// Only execute searcher meta tx if userCallHash matches
if (userCallHash == searcherCalls[i].metaTx.userCallHash) {
auctionAlreadyWon = auctionAlreadyWon
|| _searcherExecutionIteration(
protocolCall, searcherCalls[i], stagingReturnData, auctionAlreadyWon, environment
);
if (!auctionWon && _searcherExecutionIteration(
protocolCall, searcherCalls[i], stagingReturnData, auctionWon, environment
)) {
auctionWon = true;
}
}

unchecked {
Expand All @@ -140,7 +140,7 @@ contract Atlas is Test, Factory {
}

// If no searcher was successful, manually transition the lock
if (!auctionAlreadyWon) {
if (!auctionWon) {
if (protocolCall.callConfig.needsSearcherFullfillment()) {
revert("ERR-F08 UserNotFulfilled");
}
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 @@ -12,6 +12,7 @@ string constant ALTERED_USER_HASH = "AlteredUserCalldataHash";
string constant INVALID_SEARCHER_HASH = "InvalidSearcherCalldataHash";
string constant HASH_CHAIN_BROKEN = "CalldataHashChainMismatch";
string constant INTENT_UNFULFILLED = "IntentUnfulfilled";
string constant SEARCHER_STAGING_FAILED = "SearcherStagingFailed";

contract FastLaneErrorsEvents {
// NOTE: nonce is the executed nonce
Expand Down Expand Up @@ -39,6 +40,7 @@ contract FastLaneErrorsEvents {
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));
bytes32 internal constant _SEARCHER_STAGING_FAILED = keccak256(abi.encodePacked(SEARCHER_STAGING_FAILED));

// string constant SEARCHER_ETHER_BID_UNPAID = "SearcherMsgValueNotRepaid";
// bytes32 constant _SEARCHER_ETHER_BID_UNPAID = keccak256(abi.encodePacked(SEARCHER_ETHER_BID_UNPAID));
Expand Down
40 changes: 29 additions & 11 deletions src/contracts/atlas/Escrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ contract Escrow is ProtocolVerifier, SafetyLocks, SearcherWrapper {
using ECDSA for bytes32;
using EscrowBits for uint256;

uint256 constant public BUNDLER_PREMIUM = 110; // the amount over cost that bundlers get paid
uint256 constant public BUNDLER_BASE = 100;

uint32 public immutable escrowDuration;

// NOTE: these storage vars / maps should only be accessible by *signed* searcher transactions
Expand Down Expand Up @@ -61,6 +64,19 @@ contract Escrow is ProtocolVerifier, SafetyLocks, SearcherWrapper {
nextNonce = uint256(_escrowData[searcherMetaTxSigner].nonce) + 1;
}

///////////////////////////////////////////////////
/// EXTERNAL FUNCTIONS FOR BUNDLER INTERACTION ///
///////////////////////////////////////////////////

function donateToBundler() external payable {
// NOTE: All donations in excess of 10% greater than cost are forwarded
// to the user.
require(_escrowKey.lockState != 0, "ERR-E079 DonateRequiresLock");

uint256 gasRebate = msg.value / tx.gasprice;
_escrowKey.gasRefund += uint32(gasRebate);
}

///////////////////////////////////////////////////
/// INTERNAL FUNCTIONS ///
///////////////////////////////////////////////////
Expand Down Expand Up @@ -183,18 +199,20 @@ contract Escrow is ProtocolVerifier, SafetyLocks, SearcherWrapper {
accruedGasRebate = uint256(_escrowKey.gasRefund);
}

function _executeGasRefund(address gasPayor, uint256 accruedGasRebate) internal {
uint256 gasRebate = accruedGasRebate * tx.gasprice;

/*
emit UserTxResult(
userCallFrom,
0,
gasRebate
);
*/
function _executeGasRefund(uint256 gasMarker, uint256 accruedGasRebate, address user) internal {
uint256 gasFeeSpent = (gasMarker + 21_000 - gasleft()) * tx.gasprice;
uint256 gasFeeCredit = accruedGasRebate * tx.gasprice;

SafeTransferLib.safeTransferETH(gasPayor, gasRebate);
if (gasFeeCredit * BUNDLER_BASE > gasFeeSpent * BUNDLER_PREMIUM) {
uint256 gasFeeRebate = gasFeeSpent * BUNDLER_PREMIUM / BUNDLER_BASE;
gasFeeCredit -= gasFeeRebate;
SafeTransferLib.safeTransferETH(msg.sender, gasFeeRebate);
SafeTransferLib.safeTransferETH(user, gasFeeCredit);
// TODO: Consider tipping validator / builder here to incentivize a non-adversarial environment?

} else {
SafeTransferLib.safeTransferETH(msg.sender, gasFeeCredit);
}
}

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

contract ExecutionEnvironment is Test {
Expand Down Expand Up @@ -147,11 +148,8 @@ contract ExecutionEnvironment is Test {
// address(this) = ExecutionEnvironment
require(msg.sender == atlas, "ERR-CE00 InvalidSenderStaging");

bytes memory data =
abi.encodeWithSelector(IProtocolControl.verificationCall.selector, stagingReturnData, userReturnData);

data = abi.encodePacked(
data,
bytes memory data = abi.encodePacked(
abi.encodeWithSelector(IProtocolControl.verificationCall.selector, stagingReturnData, userReturnData),
_user(),
_control(),
_config(),
Expand Down Expand Up @@ -198,9 +196,22 @@ contract ExecutionEnvironment is Test {

// Verify that the ProtocolControl contract matches the searcher's expectations
require(searcherCall.metaTx.controlCodeHash == _controlCodeHash(), ALTERED_USER_HASH);
bool success;

// Handle any searcher staging, if necessary
if (_config().needsSearcherStaging()) {
bytes memory data;
(success, data) = _control().delegatecall(
abi.encodeWithSelector(IProtocolControl.searcherStagingCall.selector, stagingReturnData, searcherCall.metaTx.to)
);
require(success, SEARCHER_STAGING_FAILED);

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

// Execute the searcher call.
(bool success,) = ISearcherContract(searcherCall.metaTx.to).metaFlashCall{
(success,) = ISearcherContract(searcherCall.metaTx.to).metaFlashCall{
gas: gasLimit,
value: searcherCall.metaTx.value
}(searcherCall.metaTx.from, searcherCall.metaTx.data, searcherCall.bids);
Expand All @@ -213,7 +224,7 @@ contract ExecutionEnvironment is Test {
if (_config().needsSearcherFullfillment()) {
bytes memory data;
(success, data) = _control().delegatecall(
abi.encodeWithSelector(IProtocolControl.fulfillmentCall.selector, stagingReturnData)
abi.encodeWithSelector(IProtocolControl.fulfillmentCall.selector, stagingReturnData, searcherCall.metaTx.to)
);
require(success, INTENT_UNFULFILLED);

Expand Down
2 changes: 2 additions & 0 deletions src/contracts/atlas/SearcherWrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ contract SearcherWrapper is FastLaneErrorsEvents {
return (SearcherOutcome.InvalidUserHash, 0);
} else if (errorSwitch == _HASH_CHAIN_BROKEN) {
return (SearcherOutcome.InvalidSequencing, 0);
} else if (errorSwitch == _SEARCHER_STAGING_FAILED) {
return (SearcherOutcome.SearcherStagingFailed, 0);
} else {
return (SearcherOutcome.EVMError, 0);
}
Expand Down
Loading

0 comments on commit 09cc51b

Please sign in to comment.