diff --git a/src/infra/dex/okx/mod.rs b/src/infra/dex/okx/mod.rs index 51f0d58..b8c7393 100644 --- a/src/infra/dex/okx/mod.rs +++ b/src/infra/dex/okx/mod.rs @@ -111,6 +111,8 @@ impl Okx { fn handle_api_error(code: i64, message: &str) -> Result<(), Error> { Err(match code { 0 => return Ok(()), + 82000 => Error::NotFound, // Insufficient liquidity + 82104 => Error::NotFound, // Token not supported 50011 => Error::RateLimited, _ => Error::Api { code, diff --git a/src/tests/okx/api_calls.rs b/src/tests/okx/api_calls.rs index 7f1dbe4..207737a 100644 --- a/src/tests/okx/api_calls.rs +++ b/src/tests/okx/api_calls.rs @@ -122,3 +122,43 @@ async fn swap_api_error() { crate::infra::dex::okx::Error::Api { .. } )); } + +#[ignore] +#[tokio::test] +// To run this test set following environment variables accordingly to your OKX +// setup: OKX_PROJECT_ID, OKX_API_KEY, OKX_SECRET_KEY, OKX_PASSPHRASE +async fn swap_sell_insufficient_liquidity() { + let okx_config = okx_dex::Config { + endpoint: reqwest::Url::parse("https://www.okx.com/api/v5/dex/aggregator/swap").unwrap(), + chain_id: crate::domain::eth::ChainId::Mainnet, + okx_credentials: okx_dex::OkxCredentialsConfig { + project_id: env::var("OKX_PROJECT_ID").unwrap(), + api_key: env::var("OKX_API_KEY").unwrap(), + api_secret_key: env::var("OKX_SECRET_KEY").unwrap(), + api_passphrase: env::var("OKX_PASSPHRASE").unwrap(), + }, + block_stream: None, + }; + + let order = Order { + sell: TokenAddress::from(H160::from_slice( + &hex::decode("C8CD2BE653759aed7B0996315821AAe71e1FEAdF").unwrap(), + )), + buy: TokenAddress::from(H160::from_slice( + &hex::decode("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(), + )), + side: crate::domain::order::Side::Sell, + amount: Amount::new(U256::from_dec_str("10000000000000").unwrap()), + owner: H160::from_slice(&hex::decode("6f9ffea7370310cd0f890dfde5e0e061059dcfb8").unwrap()), + }; + + let slippage = Slippage::one_percent(); + + let okx = crate::infra::dex::okx::Okx::try_new(okx_config).unwrap(); + let swap_response = okx.swap(&order, &slippage).await; + + assert!(matches!( + swap_response.unwrap_err(), + crate::infra::dex::okx::Error::NotFound + )); +} diff --git a/src/tests/okx/mod.rs b/src/tests/okx/mod.rs index 463c98f..8995aa9 100644 --- a/src/tests/okx/mod.rs +++ b/src/tests/okx/mod.rs @@ -2,6 +2,7 @@ use {crate::tests, std::net::SocketAddr}; mod api_calls; mod market_order; +mod not_found; /// Creates a temporary file containing the config of the given solver. pub fn config(solver_addr: &SocketAddr) -> tests::Config { diff --git a/src/tests/okx/not_found.rs b/src/tests/okx/not_found.rs new file mode 100644 index 0000000..17f129a --- /dev/null +++ b/src/tests/okx/not_found.rs @@ -0,0 +1,77 @@ +//! This test ensures that the OKX solver properly handles cases where no swap +//! was found for the specified order. + +use { + crate::tests::{self, mock}, + serde_json::json, +}; + +#[tokio::test] +async fn sell() { + let api = mock::http::setup(vec![mock::http::Expectation::Get { + path: mock::http::Path::exact( + "?chainId=1&amount=1000000000000000000&\ + fromTokenAddress=0xc8cd2be653759aed7b0996315821aae71e1feadf&\ + toTokenAddress=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2&slippage=0.01&\ + userWalletAddress=0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a", + ), + res: json!({"code":"82000","data":[],"msg":"Insufficient liquidity."}), + }]) + .await; + + let engine = tests::SolverEngine::new("okx", super::config(&api.address)).await; + + let solution = engine + .solve(json!({ + "id": "1", + "tokens": { + "0xC8CD2BE653759aed7B0996315821AAe71e1FEAdF": { + "decimals": 18, + "symbol": "TETH", + "referencePrice": "4327903683155778", + "availableBalance": "1583034704488033979459", + "trusted": true, + }, + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { + "decimals": 18, + "symbol": "WETH", + "referencePrice": "1000000000000000000", + "availableBalance": "482725140468789680", + "trusted": true, + }, + }, + "orders": [ + { + "uid": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ + 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ + 2a2a2a2a", + "sellToken": "0xC8CD2BE653759aed7B0996315821AAe71e1FEAdF", + "buyToken": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "sellAmount": "1000000000000000000", + "buyAmount": "200000000000000000000", + "fullSellAmount": "1000000000000000000", + "fullBuyAmount": "200000000000000000000", + "kind": "sell", + "partiallyFillable": false, + "class": "market", + "sellTokenSource": "erc20", + "buyTokenDestination": "erc20", + "preInteractions": [], + "postInteractions": [], + "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", + "validTo": 0, + "appData": "0x0000000000000000000000000000000000000000000000000000000000000000", + "signingScheme": "presign", + "signature": "0x", + } + ], + "liquidity": [], + "effectiveGasPrice": "15000000000", + "deadline": "2106-01-01T00:00:00.000Z", + "surplusCapturingJitOrderOwners": [] + })) + .await + .unwrap(); + + assert_eq!(solution, json!({ "solutions": [] }),); +}