Skip to content

Commit

Permalink
Adding refunder e2e (#776)
Browse files Browse the repository at this point in the history
Add an e2e test for the refunder service to refund non-settled ethflow orders.
Related to issue: #626
  • Loading branch information
josojo authored Nov 16, 2022
1 parent b384a43 commit e5dacdf
Show file tree
Hide file tree
Showing 11 changed files with 262 additions and 30 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/contracts/artifacts/CoWSwapEthFlow.json

Large diffs are not rendered by default.

19 changes: 9 additions & 10 deletions crates/contracts/src/bin/vendor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ fn run() -> Result<()> {
"balancer-labs/balancer-v2-monorepo/ad1442113b26ec22081c2047e2ec95355a7f12ba/\
pkg/deployments/tasks/20210624-stable-pool/abi/StablePoolFactory.json",
)?
.github(
"CoWSwapEthFlow",
"cowprotocol/ethflowcontract/hardhatversion/build/artifacts/src/CoWSwapEthFlow.sol/CoWSwapEthFlow.json"
)?
.npm(
"ERC20Mintable",
"@openzeppelin/[email protected]/build/contracts/ERC20Mintable.json",
Expand Down Expand Up @@ -102,6 +106,11 @@ fn run() -> Result<()> {
"balancer-labs/balancer-v2-monorepo/7a643349a5ef4511234b19a33e3f18d30770cb66/\
pkg/deployments/tasks/20210721-liquidity-bootstrapping-pool/abi/LiquidityBootstrappingPool.json",
)?
.github(
"CoWSwapOnchainOrders",
"cowprotocol/ethflowcontract/v0.0.0-rc.1\
-artifacts/artifacts/CoWSwapOnchainOrders.sol/CoWSwapOnchainOrders.json"
)?
.github(
"BalancerV2LiquidityBootstrappingPoolFactory",
"balancer-labs/balancer-v2-monorepo/7a643349a5ef4511234b19a33e3f18d30770cb66/\
Expand All @@ -122,16 +131,6 @@ fn run() -> Result<()> {
"balancer-labs/balancer-v2-monorepo/903d34e491a5e9c5d59dabf512c7addf1ccf9bbd/\
pkg/deployments/tasks/20220609-stable-pool-v2/abi/StablePoolFactory.json",
)?
.github(
"CoWSwapOnchainOrders",
"cowprotocol/ethflowcontract/v0.0.0-rc.1\
-artifacts/artifacts/CoWSwapOnchainOrders.sol/CoWSwapOnchainOrders.json"
)?
.github(
"CoWSwapEthFlow",
"cowprotocol/ethflowcontract/v0.0.0-rc.1\
-artifacts/artifacts/CoWSwapEthFlow.sol/CoWSwapEthFlow.json"
)?
.npm(
"ERC20",
"@openzeppelin/[email protected]/build/contracts/ERC20.json",
Expand Down
2 changes: 2 additions & 0 deletions crates/e2e/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ secp256k1 = { workspace = true }
serde_json = { workspace = true }
shared = { path = "../shared" }
solver = { path = "../solver" }
refunder = { path = "../refunder" }
tokio = { workspace = true, features = ["macros"] }
tracing = { workspace = true }
web3 = { workspace = true }
async-trait = { workspace = true }
sqlx = { workspace = true }
8 changes: 6 additions & 2 deletions crates/e2e/tests/e2e/deploy.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use anyhow::{Context, Result};
use contracts::{
BalancerV2Authorizer, BalancerV2Vault, GPv2AllowListAuthentication, GPv2Settlement,
UniswapV2Factory, UniswapV2Router02, WETH9,
BalancerV2Authorizer, BalancerV2Vault, CoWSwapEthFlow, GPv2AllowListAuthentication,
GPv2Settlement, UniswapV2Factory, UniswapV2Router02, WETH9,
};
use ethcontract::{Address, U256};
use model::DomainSeparator;
Expand All @@ -15,6 +15,7 @@ pub struct Contracts {
pub weth: WETH9,
pub allowance: Address,
pub domain_separator: DomainSeparator,
pub ethflow: CoWSwapEthFlow,
}

pub async fn deploy(web3: &Web3) -> Result<Contracts> {
Expand Down Expand Up @@ -95,6 +96,8 @@ pub async fn deploy(web3: &Web3) -> Result<Contracts> {
.0,
);

let ethflow = deploy!(CoWSwapEthFlow(gp_settlement.address(), weth.address(),));

Ok(Contracts {
balancer_vault,
gp_settlement,
Expand All @@ -103,5 +106,6 @@ pub async fn deploy(web3: &Web3) -> Result<Contracts> {
weth,
allowance,
domain_separator,
ethflow,
})
}
1 change: 1 addition & 0 deletions crates/e2e/tests/e2e/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod eth_integration;
mod limit_orders;
mod onchain_settlement;
mod order_cancellation;
mod refunder;
mod settlement_without_onchain_liquidity;
mod smart_contract_orders;
mod vault_balances;
192 changes: 192 additions & 0 deletions crates/e2e/tests/e2e/refunder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
use crate::services::{deploy_mintable_token, to_wei, OrderbookServices, API_HOST};
use ethcontract::{Account, Address, Bytes, H256, U256};
use model::{
order::{OrderBuilder, OrderKind},
quote::{OrderQuoteRequest, OrderQuoteResponse, OrderQuoteSide, QuoteSigningScheme, Validity},
signature::hashed_eip712_message,
DomainSeparator,
};
use refunder::{
ethflow_order::EthflowOrder,
refund_service::{RefundService, INVALIDATED_OWNER},
};
use shared::{ethrpc::Web3, http_client::HttpClientFactory, maintenance::Maintaining};
use sqlx::PgPool;

const QUOTING_ENDPOINT: &str = "/api/v1/quote/";

#[tokio::test]
#[ignore]
async fn local_node_refunder_tx() {
crate::local_node::test(refunder_tx).await;
}

async fn refunder_tx(web3: Web3) {
shared::tracing::initialize_for_tests("warn,orderbook=debug,solver=debug,autopilot=debug");
shared::exit_process_on_panic::set_panic_hook();
let contracts = crate::deploy::deploy(&web3).await.expect("deploy");

let accounts: Vec<Address> = web3.eth().accounts().await.expect("get accounts failed");
let setup_account = Account::Local(accounts[0], None);
let user = Account::Local(accounts[1], None);
let refunder_account = Account::Local(accounts[2], None);

// Create & Mint tokens to trade
let token = deploy_mintable_token(&web3).await;
tx!(
setup_account,
token.mint(setup_account.address(), to_wei(100_000))
);
tx_value!(setup_account, to_wei(100_000), contracts.weth.deposit());

// Create and fund Uniswap pool for price estimation
tx!(
setup_account,
contracts
.uniswap_factory
.create_pair(token.address(), contracts.weth.address())
);
tx!(
setup_account,
token.approve(contracts.uniswap_router.address(), to_wei(100_000))
);
tx!(
setup_account,
contracts
.weth
.approve(contracts.uniswap_router.address(), to_wei(100_000))
);
tx!(
setup_account,
contracts.uniswap_router.add_liquidity(
token.address(),
contracts.weth.address(),
to_wei(100_000),
to_wei(100_000),
0_u64.into(),
0_u64.into(),
setup_account.address(),
U256::max_value(),
)
);

let services = OrderbookServices::new(&web3, &contracts, true).await;
let http_factory = HttpClientFactory::default();
let client = http_factory.create();

// Get quote id for order placement
let buy_token = token.address();
let receiver = Some(setup_account.address());
let sell_amount = U256::from("3000000000000000");
let buy_amount = U256::from("1");
// A valid_to in the past is chosen, such that the refunder can refund it immediately
let valid_to = chrono::offset::Utc::now().timestamp() as u32 - 1;
let quote = OrderQuoteRequest {
from: contracts.ethflow.address(),
sell_token: contracts.weth.address(),
buy_token,
receiver,
validity: Validity::For(u32::MAX),
signing_scheme: QuoteSigningScheme::Eip1271 {
onchain_order: true,
verification_gas_limit: 0,
},
side: OrderQuoteSide::Sell {
sell_amount: model::quote::SellAmount::AfterFee { value: sell_amount },
},
..Default::default()
};
let quoting = client
.post(&format!("{API_HOST}{QUOTING_ENDPOINT}"))
.json(&quote)
.send()
.await
.unwrap();
assert_eq!(quoting.status(), 200);
let quote_response = quoting.json::<OrderQuoteResponse>().await.unwrap();
let quote_id: i64 = quote_response.id.unwrap();
let fee_amount = quote_response.quote.fee_amount;

// Creating ethflow order
let ethflow_order = EthflowOrder {
buy_token,
receiver: receiver.unwrap(),
sell_amount,
buy_amount,
app_data: Bytes([0u8; 32]),
fee_amount,
valid_to,
partially_fillable: false,
quote_id,
};

// Each ethflow user order has an order that is representing
// it as EIP1271 order with a different owner and valid_to
let technical_order = OrderBuilder::default()
.with_kind(OrderKind::Sell)
.with_sell_token(contracts.weth.address())
.with_sell_amount(sell_amount)
.with_fee_amount(fee_amount)
.with_receiver(receiver)
.with_buy_token(token.address())
.with_buy_amount(buy_amount)
.with_valid_to(u32::MAX)
.with_eip1271(
contracts.ethflow.address(),
contracts.ethflow.address().0.to_vec(),
)
.build();
let domain_separator = DomainSeparator(
contracts
.gp_settlement
.domain_separator()
.call()
.await
.expect("Couldn't query domain separator")
.0,
);
let order_hash = H256(hashed_eip712_message(
&domain_separator,
&technical_order.data.hash_struct(),
));

// Mine ethflow order
tx_value!(
user,
sell_amount + fee_amount,
contracts.ethflow.create_order(ethflow_order.encode())
);

// Run autopilot indexing loop
services.maintenance.run_maintenance().await.unwrap();

// Create the refund service and execute the refund tx
let pg_pool = PgPool::connect_lazy("postgresql://").expect("failed to create database");
let mut refunder = RefundService::new(
pg_pool,
web3,
contracts.ethflow.clone(),
-3i64, // Needs to be negative, as valid to was chosen to be in the past
10u64,
refunder_account,
);

let order_status = contracts
.ethflow
.orders(Bytes(order_hash.0))
.call()
.await
.expect("Couldn't fetch native token balance");
assert_ne!(order_status.0, INVALIDATED_OWNER);

refunder.try_to_refund_all_eligble_orders().await.unwrap();

// Observe that the order got invalidated
let order_status = contracts
.ethflow
.orders(Bytes(order_hash.0))
.call()
.await
.expect("Couldn't fetch native token balance");
assert_eq!(order_status.0, INVALIDATED_OWNER);
}
38 changes: 33 additions & 5 deletions crates/e2e/tests/e2e/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ use crate::deploy::Contracts;
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use autopilot::{
event_updater::GPv2SettlementContract, limit_order_quoter::LimitOrderQuoter,
database::onchain_order_events::{
ethflow_events::EthFlowOnchainOrderParser, OnchainOrderParser,
},
event_updater::{CoWSwapOnchainOrdersContract, GPv2SettlementContract},
limit_order_quoter::LimitOrderQuoter,
solvable_orders::SolvableOrdersCache,
};
use contracts::{ERC20Mintable, GnosisSafe, GnosisSafeCompatibilityFallbackHandler, WETH9};
use contracts::{
CoWSwapOnchainOrders, ERC20Mintable, GnosisSafe, GnosisSafeCompatibilityFallbackHandler, WETH9,
};
use database::quotes::QuoteId;
use ethcontract::{Bytes, H160, H256, U256};
use model::quote::QuoteSigningScheme;
use model::{quote::QuoteSigningScheme, DomainSeparator};
use orderbook::{database::Postgres, orderbook::Orderbook};
use reqwest::{Client, StatusCode};
use shared::{
Expand Down Expand Up @@ -191,7 +197,7 @@ impl OrderbookServices {
.await
.unwrap();
database::clear_DANGER(&api_db.pool).await.unwrap();
let event_updater = Arc::new(autopilot::event_updater::EventUpdater::new(
let gpv2_event_updater = Arc::new(autopilot::event_updater::EventUpdater::new(
GPv2SettlementContract::new(contracts.gp_settlement.clone()),
autopilot_db.clone(),
contracts.gp_settlement.clone().raw_instance().web3(),
Expand Down Expand Up @@ -286,14 +292,36 @@ impl OrderbookServices {
)
.with_limit_orders(enable_limit_orders),
);
let custom_ethflow_order_parser = EthFlowOnchainOrderParser {};
let chain_id = web3.eth().chain_id().await.unwrap();
let onchain_order_event_parser = OnchainOrderParser::new(
autopilot_db.clone(),
quoter.clone(),
Box::new(custom_ethflow_order_parser),
DomainSeparator::new(chain_id.as_u64(), contracts.gp_settlement.address()),
contracts.gp_settlement.address(),
HashSet::new(),
);
let cow_onchain_order_contract =
CoWSwapOnchainOrders::at(web3, contracts.ethflow.address());
let ethflow_event_updater = Arc::new(autopilot::event_updater::EventUpdater::new(
CoWSwapOnchainOrdersContract::new(cow_onchain_order_contract),
onchain_order_event_parser,
contracts.ethflow.clone().raw_instance().web3(),
None,
));
let orderbook = Arc::new(Orderbook::new(
contracts.domain_separator,
contracts.gp_settlement.address(),
api_db.as_ref().clone(),
order_validator.clone(),
));
let maintenance = ServiceMaintenance {
maintainers: vec![Arc::new(autopilot_db.clone()), event_updater],
maintainers: vec![
Arc::new(autopilot_db.clone()),
ethflow_event_updater,
gpv2_event_updater,
],
};
let quotes = Arc::new(QuoteHandler::new(order_validator, quoter.clone()));
orderbook::serve_api(
Expand Down
5 changes: 5 additions & 0 deletions crates/model/src/order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ impl OrderBuilder {
self
}

pub fn with_receiver(mut self, receiver: Option<H160>) -> Self {
self.0.data.receiver = receiver;
self
}

pub fn with_fee_amount(mut self, fee_amount: U256) -> Self {
self.0.data.fee_amount = fee_amount;
self
Expand Down
Loading

0 comments on commit e5dacdf

Please sign in to comment.