Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Separate Account for Prefunding Trader #2

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/contracts/artifacts/Solver.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions crates/contracts/artifacts/Spardose.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"abi":[{"inputs":[{"internalType":"address","name":"trader","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ensureBalance","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b506103ee806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c56cca8314610030575b600080fd5b61004361003e3660046102fa565b610045565b005b6040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8481166004830152600091908416906370a0823190602401602060405180830381865afa1580156100b5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100d99190610336565b90508181106100e85750505050565b60006100f4828461034f565b905061011773ffffffffffffffffffffffffffffffffffffffff851686836101ae565b6101a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f5f746f6b656e0000000000000000000000000000000000000000000000000000606482015260840160405180910390fd5b5050505050565b6040805173ffffffffffffffffffffffffffffffffffffffff8481166024830152604480830185905283518084039091018152606490920183526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb00000000000000000000000000000000000000000000000000000000179052915160009260609190871690610247908490610389565b6000604051808303816000865af19150503d8060008114610284576040519150601f19603f3d011682016040523d82523d6000602084013e610289565b606091505b5090935090508280156102a057506102a0816102aa565b9695505050505050565b60008151600014806102cb5750818060200190518101906102cb91906103b8565b92915050565b803573ffffffffffffffffffffffffffffffffffffffff811681146102f557600080fd5b919050565b60008060006060848603121561030f57600080fd5b610318846102d1565b9250610326602085016102d1565b9150604084013590509250925092565b60006020828403121561034857600080fd5b5051919050565b818103818111156102cb577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000825160005b818110156103aa5760208186018101518583015201610390565b506000920191825250919050565b6000602082840312156103ca57600080fd5b815180151581146103da57600080fd5b939250505056fea164736f6c6343000811000a","deployedBytecode":"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c56cca8314610030575b600080fd5b61004361003e3660046102fa565b610045565b005b6040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8481166004830152600091908416906370a0823190602401602060405180830381865afa1580156100b5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100d99190610336565b90508181106100e85750505050565b60006100f4828461034f565b905061011773ffffffffffffffffffffffffffffffffffffffff851686836101ae565b6101a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f5f746f6b656e0000000000000000000000000000000000000000000000000000606482015260840160405180910390fd5b5050505050565b6040805173ffffffffffffffffffffffffffffffffffffffff8481166024830152604480830185905283518084039091018152606490920183526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb00000000000000000000000000000000000000000000000000000000179052915160009260609190871690610247908490610389565b6000604051808303816000865af19150503d8060008114610284576040519150601f19603f3d011682016040523d82523d6000602084013e610289565b606091505b5090935090508280156102a057506102a0816102aa565b9695505050505050565b60008151600014806102cb5750818060200190518101906102cb91906103b8565b92915050565b803573ffffffffffffffffffffffffffffffffffffffff811681146102f557600080fd5b919050565b60008060006060848603121561030f57600080fd5b610318846102d1565b9250610326602085016102d1565b9150604084013590509250925092565b60006020828403121561034857600080fd5b5051919050565b818103818111156102cb577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000825160005b818110156103aa5760208186018101518583015201610390565b506000920191825250919050565b6000602082840312156103ca57600080fd5b815180151581146103da57600080fd5b939250505056fea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}}
3 changes: 2 additions & 1 deletion crates/contracts/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -990,8 +990,9 @@ fn main() {
generate_contract("CowAmmUniswapV2PriceOracle");

// Support contracts used for trade and token simulations.
generate_contract("Trader");
generate_contract("Solver");
generate_contract("Spardose");
generate_contract("Trader");

// Support contracts used for various order simulations.
generate_contract("Balances");
Expand Down
1 change: 1 addition & 0 deletions crates/contracts/solidity/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ CONTRACTS := \
Signatures.sol \
SimulateCode.sol \
Solver.sol \
Spardose.sol \
Swapper.sol \
Trader.sol
ARTIFACTS := $(patsubst %.sol,$(ARTIFACTDIR)/%.json,$(CONTRACTS))
Expand Down
36 changes: 17 additions & 19 deletions crates/contracts/solidity/Solver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Interaction, Trade, ISettlement } from "./interfaces/ISettlement.sol";
import { Caller } from "./libraries/Caller.sol";
import { Math } from "./libraries/Math.sol";
import { SafeERC20 } from "./libraries/SafeERC20.sol";
import { Spardose } from "./Spardose.sol";
import { Trader } from "./Trader.sol";

/// @title A contract for impersonating a solver. This contract
Expand All @@ -16,7 +17,11 @@ import { Trader } from "./Trader.sol";
contract Solver {
using Caller for *;
using Math for *;
using SafeERC20 for *;

struct Mock {
bool enabled;
address spardose;
}

uint256 private _simulationOverhead;
uint256[] private _queriedBalances;
Expand All @@ -35,10 +40,10 @@ contract Solver {
/// @param tokens - list of tokens used in the trade
/// @param receiver - address receiving the bought tokens
/// @param settlementCall - the calldata of the `settle()` call
/// @param mockPreconditions - controls whether things like ETH wrapping
/// or setting allowance should be done on behalf of the
/// user to support quote verification even if the user didn't
/// wrap their ETH or set the necessary allowances yet.
/// @param mock - mocking configuration for the simulation; this controls
/// whether things like ETH wrapping, setting allowance and
/// pre-funding should be done on behalf of the user to support
/// quote verification for users who aren't ready to swap.
///
/// @return gasUsed - gas used for the `settle()` call
/// @return queriedBalances - list of balances stored during the simulation
Expand All @@ -51,16 +56,16 @@ contract Solver {
address[] calldata tokens,
address payable receiver,
bytes calldata settlementCall,
bool mockPreconditions
Mock memory mock
) external returns (
uint256 gasUsed,
uint256[] memory queriedBalances
) {
require(msg.sender == address(this), "only simulation logic is allowed to call 'swap' function");

// Prepare the trade in the context of the trader so we are allowed
// to set approvals and things like that.
if (mockPreconditions) {
if (mock.enabled) {
// Prepare the trade in the context of the trader so we are allowed
// to set approvals and things like that.
Trader(trader)
.prepareSwap(
settlementContract,
Expand All @@ -69,16 +74,9 @@ contract Solver {
nativeToken
);

uint256 traderSellBalance = IERC20(sellToken).balanceOf(trader);
if (traderSellBalance < sellAmount) {
// The solver potentially has access to some additional funds
// that were mocked using state overrides. Attemp to transfer
// them to the trader so they have sufficient balance for the
// settlement.
if (!IERC20(sellToken).trySafeTransfer(trader, sellAmount)) {
revert("trader does not have enough sell_token");
}
}
// Ensure that the user has sufficient sell token balance for the
// swap using balance overrides.
Spardose(mock.spardose).ensureBalance(trader, sellToken, sellAmount);
}

// Warm the storage for sending ETH to smart contract addresses.
Expand Down
34 changes: 34 additions & 0 deletions crates/contracts/solidity/Spardose.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import { IERC20 } from "./interfaces/IERC20.sol";
import { SafeERC20 } from "./libraries/SafeERC20.sol";

/// @title A piggy bank contract (Spardose is piggy bank in German)
/// @notice This contract account is used for pre-funding traders with tokens
/// for quote simulations. A separate contract is used (instead of overriding
/// the balance of the solver or trader directly) in order to interfere as
/// little as possible with the settlement.
contract Spardose {
using SafeERC20 for *;

/// @dev Ensures that the trader has at least `amount` tokens. If not, it
/// will transfer the difference to the trader.
///
/// @param trader - the address of the trader
/// @param token - the token to ensure a balance for
/// @param amount - the amount of `token` that the `trader` must hold.
function ensureBalance(address trader, address token, uint256 amount) external {
uint256 traderBalance = IERC20(token).balanceOf(trader);
if (traderBalance >= amount) {
// Nothing to do.
return;
}

uint256 difference = amount - traderBalance;
require(
IERC20(token).trySafeTransfer(trader, difference),
"trader does not have enough sell_token"
nlordell marked this conversation as resolved.
Show resolved Hide resolved
);
}
}
5 changes: 3 additions & 2 deletions crates/contracts/solidity/libraries/SafeERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ library SafeERC20 {

function trySafeTransfer(IERC20 self, address target, uint256 amount) internal returns (bool success) {
bytes memory cdata = abi.encodeCall(self.transfer, (target, amount));
bytes memory rdata = address(self).doCall(cdata);
return check(rdata);
bytes memory rdata;
(success, rdata) = address(self).call(cdata);
return success && check(rdata);
}

function safeApprove(IERC20 self, address target, uint256 amount) internal {
Expand Down
1 change: 1 addition & 0 deletions crates/contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ pub mod support {
Signatures;
SimulateCode;
Solver;
Spardose;
Swapper;
Trader;
}
Expand Down
51 changes: 48 additions & 3 deletions crates/e2e/tests/e2e/quote_verification.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use {
bigdecimal::{BigDecimal, Zero},
e2e::setup::*,
ethcontract::H160,
e2e::{setup::*, tx},
ethcontract::{H160, U256},
ethrpc::Web3,
model::{
order::{BuyTokenDestination, OrderKind, SellTokenSource},
Expand All @@ -19,6 +19,12 @@ use {
std::{str::FromStr, sync::Arc},
};

#[tokio::test]
#[ignore]
async fn local_node_standard_verified_quote() {
run_test(standard_verified_quote).await;
}

#[tokio::test]
#[ignore]
async fn forked_node_bypass_verification_for_rfq_quotes() {
Expand All @@ -43,6 +49,45 @@ async fn local_node_verified_quote_with_simulated_balance() {
run_test(verified_quote_with_simulated_balance).await;
}

/// Verified quotes work as expected.
async fn standard_verified_quote(web3: Web3) {
tracing::info!("Setting up chain state.");
let mut onchain = OnchainComponents::deploy(web3).await;

let [solver] = onchain.make_solvers(to_wei(10)).await;
let [trader] = onchain.make_accounts(to_wei(1)).await;
let [token] = onchain
.deploy_tokens_with_weth_uni_v2_pools(to_wei(1_000), to_wei(1_000))
.await;

token.mint(trader.address(), to_wei(1)).await;
tx!(
trader.account(),
token.approve(onchain.contracts().allowance, to_wei(1))
);

tracing::info!("Starting services.");
let services = Services::new(&onchain).await;
services.start_protocol(solver).await;

// quote where the trader has no balances or approval set.
let response = services
.submit_quote(&OrderQuoteRequest {
from: trader.address(),
sell_token: token.address(),
buy_token: onchain.contracts().weth.address(),
side: OrderQuoteSide::Sell {
sell_amount: SellAmount::BeforeFee {
value: to_wei(1).try_into().unwrap(),
},
},
..Default::default()
})
.await
.unwrap();
assert!(response.verified);
}

/// The block number from which we will fetch state for the forked tests.
const FORK_BLOCK_MAINNET: u64 = 19796077;

Expand Down Expand Up @@ -240,7 +285,7 @@ async fn verified_quote_with_simulated_balance(web3: Web3) {
)],
..Default::default()
},
solver.clone(),
solver,
)
.await;

Expand Down
38 changes: 26 additions & 12 deletions crates/shared/src/price_estimation/trade_verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use {
contracts::{
deployed_bytecode,
dummy_contract,
support::{AnyoneAuthenticator, Solver, Trader},
support::{AnyoneAuthenticator, Solver, Spardose, Trader},
GPv2Settlement,
WETH9,
},
Expand Down Expand Up @@ -73,6 +73,7 @@ pub struct TradeVerifier {

impl TradeVerifier {
const DEFAULT_GAS: u64 = 8_000_000;
const SPARDOSE: H160 = addr!("0000000000000000000000000000000000020000");
const TRADER_IMPL: H160 = addr!("0000000000000000000000000000000000010000");

pub async fn new(
Expand Down Expand Up @@ -165,6 +166,11 @@ impl TradeVerifier {
OrderKind::Buy => *out_amount,
};

// Only enable additional mocking (approvals, native token wrapping,
// balance overrides) if the user did not provide pre-interactions. If
// the user did provide pre-interactions, it's reasonable to assume that
// they will set up all the necessary details of the trade.
let mock_enabled = verification.pre_interactions.is_empty();
let simulation = solver
.methods()
.swap(
Expand All @@ -176,11 +182,7 @@ impl TradeVerifier {
tokens.clone(),
verification.receiver,
Bytes(settlement.data.unwrap().0),
// only if the user did not provide pre-interactions is it safe
// to set up the trade's pre-conditions on behalf of the user.
// if the user provided pre-interactions it's reasonable to assume
// that they will set up all the necessary details for the trade.
verification.pre_interactions.is_empty(),
(mock_enabled, Self::SPARDOSE),
)
.tx;

Expand Down Expand Up @@ -321,11 +323,17 @@ impl TradeVerifier {
);
}

// Set up mocked solver.
let mut solver_override = StateOverride {
code: Some(deployed_bytecode!(Solver)),
..Default::default()
};
// Setup the funding contract override. Regardless of whether or not the
// contract has funds, it needs to exist in order to not revert
// simulations (Solidity reverts on attempts to call addresses without
// any code).
overrides.insert(
Self::SPARDOSE,
StateOverride {
code: Some(deployed_bytecode!(Spardose)),
..Default::default()
},
);

// Provide mocked balances if possible to the solver to allow it to
nlordell marked this conversation as resolved.
Show resolved Hide resolved
// give some balances to the trader in order to verify trades even for
Expand All @@ -339,7 +347,7 @@ impl TradeVerifier {
self.balance_overrides
.state_override(&BalanceOverrideRequest {
token: query.sell_token,
holder: trade.solver(),
holder: Self::SPARDOSE,
amount: match query.kind {
OrderKind::Sell => query.in_amount.get(),
OrderKind::Buy => trade.out_amount(
Expand All @@ -355,6 +363,12 @@ impl TradeVerifier {
overrides.insert(query.sell_token, solver_balance_override);
}

// Set up mocked solver.
let mut solver_override = StateOverride {
code: Some(deployed_bytecode!(Solver)),
..Default::default()
};

// If the trade requires a special tx.origin we also need to fake the
// authenticator and tx origin balance.
if trade
Expand Down