Skip to content

Commit

Permalink
Merge branch 'main' into ci-cd-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
BenSparksCode committed Nov 17, 2023
2 parents d410066 + 54f6b97 commit 83ce943
Show file tree
Hide file tree
Showing 13 changed files with 113 additions and 38 deletions.
Binary file added AtlasFlow.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 26 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,24 @@ Atlas is infrastructure-agnostic; each DApp may choose how the DApp-designated b
3. **BloXroute**: When Atlas is launched, BloXroute's BDN will support the aggregation of User and Solver Operations for rapid bundling.
4. **SUAVE**: Once live, Operations can be sent to the SUAVE network, bundled into a transaction by the SUAVE Atlas implementation, and then made available for use by builders.

### Auctioneer Overview

Each DApp may choose a party to act as a trusted auctioneer. **It is strongly recommended that the DApp select the auction beneficiary act as the auctioneer.** The beneficiary can always trust themselves and this prevents adding new, trusted parties. We expect most -but not all- DApps to select the User as the auctioneer and to handle the auctioneer duties without User input through the frontend, which the User already trusts explicitly.

The auctioneer is tasked with signing a **DAppOperation** that includes a **CallChainHash**. This hash guarantees that the bundler cannot tamper with the execution order of the **SolverOperation**s. Any party can easily generate this hash by making a view call to the *getCallChainHash(SolverOperations[])* function. Note that infrastructure networks with programmable guarantees such as SUAVE will not require this as it can be handled trustlessly in-network.

***Auctioneer Example***:
1. User connects to a DApp frontend and receives a session key from a FastLane x DApp backend.
2. User signs their UserOperation, which is propagated over the bloXroute BDN to solvers.
3. The frontend receives SolverOperations via the BDN.
4. After a set period of time, the frontend calls the *getCallChainHash()* view function via the User's RPC.
5. The frontend then uses the session key from step 1 to sign the **DAppOperation**, which includes the **CallChainHash**.
6. The frontend then propagates the DAppOperation over the BDN to bundlers.
7. Any bundler who tampers with the order of the SolverOperations will cause their transaction to revert, thereby blocking any gas reimbursement from Atlas.

Note that input from the User is only required for step 2; all other steps have no impact on UX.


### Atlas Transaction Structure

![AtlasTransaction](./AtlasTransactionOverview.jpg)
Expand Down Expand Up @@ -50,6 +68,10 @@ The DAppControl contract has the option to define functions that execute at the

*These functions are executed by the Execution Environment via "delegatecall."

### Atlas Frontend / Infrastructure Flow

![AtlasFlow](./AtlasFlow.jpeg)

### Advantages:
- Atlas Solvers have first access to any value created by the User Operation. This exclusive access supercedes that of any wallets, RPCs, relays, builders, validators, and sequencers.

Expand All @@ -59,16 +81,16 @@ The DAppControl contract has the option to define functions that execute at the

- Due to the unique nature of the Execution Environment - a smart account that Atlas creates to facilitate a trustless environment for Users, Solvers, and DApps - Users have an extra layer of protection against allowance-based exploits.

- DApp Governance has the option to subsidize a User's gas cost. Note that unlike traditional Account Abstraction protocols, Atlas empowers DApp Governance to subsidize the User's gas costs *conditionally* based on the *result* of the User's (or Solver's) execution.
- DApp Governance has the option to subsidize a User's gas cost. Note that unlike traditional Account Abstraction protocols, Atlas empowers DApp Governance to subsidize the User's gas costs *conditionally* based on the *result* of the User's (or Solver's) execution. We expect that most DApps will require Solvers to subsidize all gas costs not attributed to other Solvers.

- By putting control of any User-created value in the hands of each DApp's Governance team, and by retaining the MEV before any RPCs or private relays see the transaction, Atlas has the potential to nullify the value of private orderflow, thereby acting as a counterforce to one of the strongest centralization risks in the Ethereum ecosystem.

### Disadvantages:

- Just as in the early days of Ethereum, Solvers do not benefit from "free reverts." If a Solver Operation fails, then the Solver still must pay their gas cost to the User.
- Just as in the early days of Ethereum, Solvers do not benefit from "free reverts." If a Solver Operation fails, then the Solver still must pay their gas cost to the Bundler.

- Atlas represents a less efficient use of block space than traditional, infrastructure-based MEV capture systems. This arises due to the checks and verifications that allow Atlas to function without relying on privacy guarantees from centralized, third-party infrastructure or off-chain agreements with permissioned builders.
- Atlas represents a less efficient use of block space than traditional, infrastructure-based MEV capture systems. This arises due to the checks and verifications that allow Atlas to function without relying on privacy guarantees from centralized, third-party infrastructure or off-chain agreements with permissioned builders. Note that this extra usage of gas will typically be handled by Solvers, and that if no Solver is willing to pay for the increased gas cost then the User can simply do a non-Atlas transaction. In other words, the extra gas cost will only be incurred when its cost is less than its benefit.

### Notes:

Note that the Bundler's backend may want to use a reputation system for solver bids in order to not take up too much space in the block. The further down the the solverOps[], the higher the reputation requirement for inclusion by the backend. This isnt necessarily required - it's not an economic issue - it's just that it's important to be a good member of the ecosystem and not waste too much precious blockspace by filling it with probabalistic solver txs that have a low success rate but a high profit-to-cost ratio.
Note that the auctioneer (typically the frontend) may want to use a reputation system for solver bids in order to not take up too much space in the block. The further down the the solverOps[], the higher the reputation requirement for inclusion by the backend. This isnt necessarily required - it's not an economic issue - it's just that it's important to be a good member of the ecosystem and not waste too much precious blockspace by filling it with probabalistic solver txs that have a low success rate but a high profit-to-cost ratio.
10 changes: 10 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,14 @@
tab_width = 4
wrap_comments = true

[fmt]
bracket_spacing = true
int_types = "long"
line_length = 120
multiline_func_header = "all"
number_underscore = "thousands"
quote_style = "double"
tab_width = 4
wrap_comments = true

fs_permissions = [{ access = "read-write", path = "./"}]
4 changes: 4 additions & 0 deletions script/base/deploy-base.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { TxBuilder } from "src/contracts/helpers/TxBuilder.sol";
import { Simulator } from "src/contracts/helpers/Simulator.sol";
import { SimpleRFQSolver } from "test/SwapIntent.t.sol";

import { Utilities } from "src/contracts/helpers/Utilities.sol";

contract DeployBaseScript is Script {
using stdJson for string;

Expand All @@ -33,6 +35,8 @@ contract DeployBaseScript is Script {
TxBuilder public txBuilder;
SimpleRFQSolver public rfqSolver;

Utilities public u;

function _getDeployChain() internal view returns (string memory) {
// OPTIONS: LOCAL, SEPOLIA, MAINNET
string memory deployChain = vm.envString("DEPLOY_TO");
Expand Down
6 changes: 5 additions & 1 deletion script/deploy-solver.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@ contract DeploySimpleRFQSolverScript is DeployBaseScript {
uint256 deployerPrivateKey = vm.envUint("SOLVER1_PRIVATE_KEY");
address deployer = vm.addr(deployerPrivateKey);
address atlasAddress = _getAddressFromDeploymentsJson("ATLAS");
address wethAddress = u.getUsefulContractAddress(vm.envString("DEPLOY_TO"), "WETH");

console.log("Deployer address: \t\t\t\t", deployer);
console.log("Using Atlas address: \t\t\t\t", atlasAddress);

vm.startBroadcast(deployerPrivateKey);

rfqSolver = new SimpleRFQSolver(atlasAddress);
rfqSolver = new SimpleRFQSolver({
weth: wethAddress,
atlas: atlasAddress
});

vm.stopBroadcast();

Expand Down
23 changes: 23 additions & 0 deletions src/contracts/helpers/Utilities.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import "forge-std/Script.sol";
import "forge-std/Test.sol";
import "forge-std/StdJson.sol";

contract Utilities is Script {
using stdJson for string;

function getUsefulContractAddress(string memory chain, string memory key) public view returns (address) {
string memory root = vm.projectRoot();
string memory path = string.concat(root, "/useful-addresses.json");
string memory json = vm.readFile(path);
string memory fullKey = string.concat(".", chain, ".", key);

address res = json.readAddress(fullKey);
if (res == address(0x0000000000000000000000000000000000000020)) {
revert(string.concat(fullKey, " not found in useful-addresses.json"));
}
return res;
}
}
5 changes: 3 additions & 2 deletions src/contracts/solver/SolverBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ interface IWETH9 {
}

contract SolverBase is Test {
address public constant WETH_ADDRESS = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
address public immutable WETH_ADDRESS;

// TODO consider making these accessible (internal) for solvers which may want to use them
address private immutable _owner;
address private immutable _escrow;

constructor(address atlasEscrow, address owner) {
constructor(address weth, address atlasEscrow, address owner) {
WETH_ADDRESS = weth;
_owner = owner;
_escrow = atlasEscrow;
}
Expand Down
2 changes: 1 addition & 1 deletion src/contracts/solver/src/TestSolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ import { SolverBase } from "../SolverBase.sol";
import { BlindBackrun } from "./BlindBackrun/BlindBackrun.sol";

contract Solver is SolverBase, BlindBackrun {
constructor(address atlasEscrow, address owner) SolverBase(atlasEscrow, owner) { }
constructor(address weth, address atlasEscrow, address owner) SolverBase(weth, atlasEscrow, owner) { }
}
8 changes: 4 additions & 4 deletions test/Accounting.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ contract AccountingTest is BaseTest {
function testSolverBorrowRepaySuccessfully() public {
// Solver deploys the RFQ solver contract (defined at bottom of this file)
vm.startPrank(solverOneEOA);
HonestRFQSolver honestSolver = new HonestRFQSolver(address(atlas));
HonestRFQSolver honestSolver = new HonestRFQSolver(WETH_ADDRESS, address(atlas));
vm.stopPrank();

SolverOperation[] memory solverOps = _setupBorrowRepayTestUsingBasicSwapIntent(address(honestSolver));
Expand All @@ -90,7 +90,7 @@ contract AccountingTest is BaseTest {
// Solver deploys the RFQ solver contract (defined at bottom of this file)
vm.startPrank(solverOneEOA);
// TODO make evil solver
HonestRFQSolver evilSolver = new HonestRFQSolver(address(atlas));
HonestRFQSolver evilSolver = new HonestRFQSolver(WETH_ADDRESS, address(atlas));
// atlas.deposit{value: gasCostCoverAmount}(solverOneEOA);
vm.stopPrank();

Expand Down Expand Up @@ -219,7 +219,7 @@ contract AccountingTest is BaseTest {
contract HonestRFQSolver is SolverBase {
address public immutable ATLAS;

constructor(address atlas) SolverBase(atlas, msg.sender) {
constructor(address weth, address atlas) SolverBase(weth, atlas, msg.sender) {
ATLAS = atlas;
}

Expand Down Expand Up @@ -250,7 +250,7 @@ contract HonestRFQSolver is SolverBase {
contract EvilRFQSolver is HonestRFQSolver {
address deployer;

constructor(address atlas) HonestRFQSolver(atlas) {
constructor(address weth, address atlas) HonestRFQSolver(weth, atlas) {
deployer = msg.sender;
}

Expand Down
25 changes: 4 additions & 21 deletions test/SwapIntent.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,6 @@ import { DAppOperation, DAppConfig } from "../src/contracts/types/DAppApprovalTy
import { SwapIntentController, SwapIntent, Condition } from "../src/contracts/examples/intents-example/SwapIntent.sol";
import { SolverBase } from "../src/contracts/solver/SolverBase.sol";

// QUESTIONS:

// Refactor Ideas:
// 1. Lots of bitwise operations explicitly coded in contracts - could be a helper lib thats more readable
// 2. helper is currently a V2Helper and shared from BaseTest. Should only be in Uni V2 related tests
// 3. Need a more generic helper for BaseTest
// 4. Gonna be lots of StackTooDeep errors. Maybe need a way to elegantly deal with that in BaseTest
// 5. Change atlasSolverCall structure in SolverBase - maybe virtual fn to be overridden, which hooks for checks
// 6. Maybe emit error msg or some other better UX for error if !valid in metacall()

// Doc Ideas:
// 1. Step by step instructions for building a metacall transaction (for internal testing, and integrating dApps)

// To Understand Better:
// 1. The lock system (and look for any gas optimizations / ways to reduce lock actions)

interface IUniV2Router02 {
function swapExactTokensForTokens(
uint256 amountIn,
Expand Down Expand Up @@ -84,7 +68,6 @@ contract SwapIntentTest is BaseTest {

function testAtlasSwapIntentWithBasicRFQ() public {
// Swap 10 WETH for 20 DAI

UserCondition userCondition = new UserCondition();

Condition[] memory conditions = new Condition[](2);
Expand All @@ -109,7 +92,7 @@ contract SwapIntentTest is BaseTest {

// Solver deploys the RFQ solver contract (defined at bottom of this file)
vm.startPrank(solverOneEOA);
SimpleRFQSolver rfqSolver = new SimpleRFQSolver(address(atlas));
SimpleRFQSolver rfqSolver = new SimpleRFQSolver(WETH_ADDRESS, address(atlas));
atlas.deposit{ value: 1e18 }();
vm.stopPrank();

Expand Down Expand Up @@ -230,7 +213,7 @@ contract SwapIntentTest is BaseTest {

// Solver deploys the RFQ solver contract (defined at bottom of this file)
vm.startPrank(solverOneEOA);
UniswapIntentSolver uniswapSolver = new UniswapIntentSolver(address(atlas));
UniswapIntentSolver uniswapSolver = new UniswapIntentSolver(WETH_ADDRESS, address(atlas));
deal(WETH_ADDRESS, address(uniswapSolver), 1e18); // 1 WETH to solver to pay bid
atlas.deposit{ value: 1e18 }();
vm.stopPrank();
Expand Down Expand Up @@ -340,7 +323,7 @@ contract SwapIntentTest is BaseTest {
// This solver magically has the tokens needed to fulfil the user's swap.
// This might involve an offchain RFQ system
contract SimpleRFQSolver is SolverBase {
constructor(address atlas) SolverBase(atlas, msg.sender) { }
constructor(address weth, address atlas) SolverBase(weth, atlas, msg.sender) { }

function fulfillRFQ(SwapIntent calldata swapIntent, address executionEnvironment) public {
require(
Expand Down Expand Up @@ -368,7 +351,7 @@ contract SimpleRFQSolver is SolverBase {
contract UniswapIntentSolver is SolverBase {
IUniV2Router02 router = IUniV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);

constructor(address atlas) SolverBase(atlas, msg.sender) { }
constructor(address weth, address atlas) SolverBase(weth, atlas, msg.sender) { }

function fulfillWithSwap(SwapIntent calldata swapIntent, address executionEnvironment) public onlySelf {
// Checks recieved expected tokens from Atlas on behalf of user to swap
Expand Down
9 changes: 7 additions & 2 deletions test/base/BaseTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { TestConstants } from "./TestConstants.sol";

import { V2Helper } from "../V2Helper.sol";

import { Utilities } from "src/contracts/helpers/Utilities.sol";

contract BaseTest is Test, TestConstants {
address public me = address(this);

Expand Down Expand Up @@ -57,6 +59,8 @@ contract BaseTest is Test, TestConstants {

V2Helper public helper;

Utilities public u;

// Fork stuff
ChainVars public chain = mainnet;
uint256 public forkNetwork;
Expand Down Expand Up @@ -126,7 +130,7 @@ contract BaseTest is Test, TestConstants {

vm.startPrank(solverOneEOA);

solverOne = new Solver(escrow, solverOneEOA);
solverOne = new Solver(WETH_ADDRESS, escrow, solverOneEOA);
atlas.deposit{ value: 1e18 }();

deal(TOKEN_ZERO, address(solverOne), 10e24);
Expand All @@ -136,7 +140,7 @@ contract BaseTest is Test, TestConstants {

vm.startPrank(solverTwoEOA);

solverTwo = new Solver(escrow, solverTwoEOA);
solverTwo = new Solver(WETH_ADDRESS, escrow, solverTwoEOA);
atlas.deposit{ value: 1e18 }();

vm.stopPrank();
Expand All @@ -145,6 +149,7 @@ contract BaseTest is Test, TestConstants {
deal(TOKEN_ONE, address(solverTwo), 10e24);

helper = new V2Helper(address(control), address(atlas), address(atlasVerification));
u = new Utilities();

deal(TOKEN_ZERO, address(atlas), 1);
deal(TOKEN_ONE, address(atlas), 1);
Expand Down
13 changes: 10 additions & 3 deletions test/base/TestConstants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,22 @@ import { IUniswapV2Pair } from "../../src/contracts/examples/v2-example/interfac
contract TestConstants {
uint256 public constant BLOCK_START = 17_441_786;

// MAINNET
ChainVars public mainnet = ChainVars({ rpcUrlKey: "MAINNET_RPC_URL", forkBlock: BLOCK_START });

// Structs
struct ChainVars {
string rpcUrlKey;
uint256 forkBlock;
address weth;
address dai;
}

// MAINNET
ChainVars public mainnet = ChainVars({
rpcUrlKey: "MAINNET_RPC_URL",
forkBlock: BLOCK_START,
weth: address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2),
dai: address(0x6B175474E89094C44Da98b954EedeAC495271d0F)
});

// Constants
address public constant FXS_ADDRESS = address(0x3432B6A60D23Ca0dFCa7761B7ab56459D9C964D0);
address public constant WETH_ADDRESS = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
Expand Down
16 changes: 16 additions & 0 deletions useful-addresses.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"MAINNET": {
"WETH": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"DAI": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"USDC": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"UNI": "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984",
"UNISWAP_V2_ROUTER": "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"
},
"SEPOLIA": {
"WETH": "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14",
"DAI": "",
"USDC": "",
"UNI": "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984",
"UNISWAP_V2_ROUTER": "0x8f1dD60dBDb493DD940a44985AB43FB9901dcd2e"
}
}

0 comments on commit 83ce943

Please sign in to comment.