diff --git a/subgraph/tests/entities.rs b/subgraph/tests/entities.rs index 4035b4f0b..2ee40aae0 100644 --- a/subgraph/tests/entities.rs +++ b/subgraph/tests/entities.rs @@ -2230,6 +2230,211 @@ async fn token_vault_entity_take_order_test() -> anyhow::Result<()> { Ok(()) } +#[tokio::main] +#[test] +async fn bounty_entity_clear_test() -> anyhow::Result<()> { + let alice = get_wallet(0); + let bob = get_wallet(1); + let bounty_bot = get_wallet(2); + + let orderbook = get_orderbook().await?; + + // Deploy ExpressionDeployerNP for the config + let expression_deployer = get_expression_deployer().await?; + + // Deploy ERC20 token contract (A) + let token_a = deploy_erc20_mock(None).await?; + // Deploy ERC20 token contract (B) + let token_b = deploy_erc20_mock(None).await?; + + // Generate vault ids for each account (Input and Output) + let vault_id = generate_random_u256(); + + // Order Alice Configuration + let order_alice = generate_order_config( + &expression_deployer, + &token_a, + Some(vault_id), + &token_b, + Some(vault_id), + ) + .await; + + // Order Bob Configuration + let order_bob = generate_order_config( + &expression_deployer, + &token_b, + Some(vault_id), + &token_a, + Some(vault_id), + ) + .await; + + // Add order alice with Alice connected to the OB + let add_order_alice = orderbook.connect(&alice).await.add_order(order_alice); + let tx = add_order_alice.send().await?; + let add_order_alice_data = get_add_order_event(orderbook, &tx).await?; + + // Add order bob with Bob connected to the OB + let add_order_bob = orderbook.connect(&bob).await.add_order(order_bob); + let tx = add_order_bob.send().await?; + let add_order_bob_data = get_add_order_event(orderbook, &tx).await?; + + // Make deposit of corresponded output token + let decimal_a = token_a.decimals().call().await?; + let amount_alice = get_amount_tokens(8, decimal_a); + + let decimal_b = token_b.decimals().call().await?; + let amount_bob = get_amount_tokens(6, decimal_b); + + // Alice has token_b as output + mint_tokens(&amount_alice, &alice.address(), &token_b).await?; + + // Approve Alice token_b using to OB + approve_tokens( + // &amount_alice, + &amount_alice, + &orderbook.address(), + &token_b.connect(&alice).await, + ) + .await?; + + // Deposit using Alice + let deposit_func = + orderbook + .connect(&alice) + .await + .deposit(token_b.address(), vault_id, amount_alice); + let _ = deposit_func.send().await?.await?; + + // Bob has token_a as output + mint_tokens(&amount_bob, &bob.address(), &token_a).await?; + + // Approve Bob token_a using to OB + approve_tokens( + &amount_bob, + &orderbook.address(), + &token_a.connect(&bob).await, + ) + .await?; + + // Deposit using Bob + let deposit_func = + orderbook + .connect(&bob) + .await + .deposit(token_a.address(), vault_id, amount_bob); + let _ = deposit_func.send().await?.await?; + + // BOUNTY BOT CLEARS THE ORDER + // Clear configuration + let order_alice = &add_order_alice_data.order; + let order_bob = &add_order_bob_data.order; + + let a_signed_context: Vec = Vec::new(); + let b_signed_context: Vec = Vec::new(); + + let clear_config_1 = generate_clear_config(&vault_id, &vault_id); + let clear_1 = ClearCall { + alice: order_alice.to_owned(), + bob: order_bob.to_owned(), + clear_config: clear_config_1, + alice_signed_context: a_signed_context.clone(), + bob_signed_context: b_signed_context.clone(), + }; + + let clear_config_2 = generate_clear_config(&vault_id, &vault_id); + let clear_2 = ClearCall { + alice: order_bob.to_owned(), + bob: order_alice.to_owned(), + clear_config: clear_config_2, + alice_signed_context: b_signed_context, + bob_signed_context: a_signed_context, + }; + + let clear_configs = vec![clear_1, clear_2]; + + let multi_clear_bytes = generate_multi_clear(&clear_configs); + + let multicall_func = orderbook + .connect(&bounty_bot) + .await + .multicall(multi_clear_bytes); + + let tx_multicall = multicall_func.send().await?; + + // Tx hash that hold all the logs + let clears_tx_hash = tx_multicall.tx_hash(); + + let clear_events = get_clear_events(&orderbook, &clears_tx_hash).await?; + let after_clear_events = get_after_clear_events(&orderbook, &clears_tx_hash).await?; + + let block_data = get_block_data(&clears_tx_hash).await?; + + // It should emit the same amount of events both parts + assert_eq!(clear_events.len(), after_clear_events.len()); + + // Bounty Vault Entity ID + let bounty_vault = format!("{}-{:?}", vault_id, bounty_bot.address()); + + // Wait for Subgraph sync + wait().await?; + + for (index, clear) in clear_events.iter().enumerate() { + let after_clear = after_clear_events.get(index).unwrap(); + + let bounty_entity_id = format!("{:?}-{}", clears_tx_hash, index); + let order_clear_id = bounty_entity_id.clone(); + + let clear_state_change = &after_clear.clear_state_change; + + // In these tests, generally only one token is added in the Order, so we pick the "first" in the array + let alice_token_output: &Address = &clear.alice.valid_outputs.first().unwrap().token; + let bob_token_output: &Address = &clear.bob.valid_outputs.first().unwrap().token; + + // Bounty Amount from A (alice) + let bounty_amount_a = clear_state_change + .alice_output + .saturating_sub(clear_state_change.bob_input); + + let bounty_amount_a_display = + display_number(bounty_amount_a, get_decimals(*alice_token_output).await?); + + // Bounty Amount from B (bpb) + let bounty_amount_b = clear_state_change + .bob_output + .saturating_sub(clear_state_change.alice_input); + + let bounty_amount_b_display = + display_number(bounty_amount_b, get_decimals(*bob_token_output).await?); + + let resp = Query::bounty(&bounty_entity_id).await?; + + assert_eq!(resp.clearer, bounty_bot.address()); + assert_eq!(resp.order_clear, order_clear_id); + + assert_eq!(resp.bounty_vault_a, bounty_vault); + assert_eq!(resp.bounty_vault_b, bounty_vault); + + assert_eq!(resp.bounty_token_a, *alice_token_output); + assert_eq!(resp.bounty_token_b, *bob_token_output); + + assert_eq!(resp.bounty_amount_a, Some(bounty_amount_a)); + assert_eq!(resp.bounty_amount_a_display, Some(bounty_amount_a_display)); + + assert_eq!(resp.bounty_amount_b, Some(bounty_amount_b)); + assert_eq!(resp.bounty_amount_b_display, Some(bounty_amount_b_display)); + + assert_eq!(resp.transaction, clears_tx_hash); + assert_eq!(resp.emitter, bounty_bot.address()); + assert_eq!(resp.timestamp, block_data.timestamp); + + // assert_eq!(resp.bounty_amount_b, Some(bounty_amount_b)); + // assert_eq!(resp.bounty_token_b, token_b.address()); + } + Ok(()) +} + #[test] fn util_cbor_meta_test() -> anyhow::Result<()> { // Read meta from root repository (output from nix command) and convert to Bytes diff --git a/subgraph/tests/subgraph/query/bounty/bounty.graphql b/subgraph/tests/subgraph/query/bounty/bounty.graphql new file mode 100644 index 000000000..5f45a9c10 --- /dev/null +++ b/subgraph/tests/subgraph/query/bounty/bounty.graphql @@ -0,0 +1,34 @@ +query Bounty($id: String) { + bounty(id: $id) { + id + clearer { + id + } + orderClear { + id + } + bountyVaultA { + id + } + bountyVaultB { + id + } + bountyTokenA { + id + } + bountyTokenB { + id + } + bountyAmountA + bountyAmountADisplay + bountyAmountB + bountyAmountBDisplay + transaction { + id + } + emitter { + id + } + timestamp + } +} diff --git a/subgraph/tests/subgraph/query/bounty/mod.rs b/subgraph/tests/subgraph/query/bounty/mod.rs new file mode 100644 index 000000000..53c16042e --- /dev/null +++ b/subgraph/tests/subgraph/query/bounty/mod.rs @@ -0,0 +1,104 @@ +use self::bounty::ResponseData; +use super::SG_URL; +use crate::utils::{bytes_to_h256, hex_string_to_bytes, mn_mpz_to_u256}; +use anyhow::{anyhow, Result}; +use ethers::types::{Address, Bytes, TxHash, U256}; +use graphql_client::{GraphQLQuery, Response}; +use rust_bigint::BigInt; +use serde::{Deserialize, Serialize}; + +// use bigdecimal::BigDecimal; +type BigDecimal = String; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "tests/subgraph/query/schema.json", + query_path = "tests/subgraph/query/bounty/bounty.graphql", + response_derives = "Debug, Serialize, Deserialize" +)] +#[derive(Serialize, Deserialize, Debug)] +pub struct Bounty; + +#[derive(Serialize, Deserialize, Debug)] +pub struct BountyResponse { + pub id: String, + pub clearer: Address, + pub order_clear: String, + pub bounty_vault_a: String, + pub bounty_vault_b: String, + pub bounty_token_a: Address, + pub bounty_token_b: Address, + pub bounty_amount_a: Option, + pub bounty_amount_a_display: Option, + pub bounty_amount_b: Option, + pub bounty_amount_b_display: Option, + pub transaction: TxHash, + pub emitter: Address, + pub timestamp: U256, +} + +impl BountyResponse { + pub fn from(response: ResponseData) -> BountyResponse { + let data = response.bounty.unwrap(); + + let clearer = Address::from_slice(&data.clearer.id); + + let bounty_token_a = + Address::from_slice(&hex_string_to_bytes(&data.bounty_token_a.id).unwrap()); + let bounty_token_b = + Address::from_slice(&hex_string_to_bytes(&data.bounty_token_b.id).unwrap()); + + let bounty_amount_a = match data.bounty_amount_a { + Some(value) => Some(mn_mpz_to_u256(&value)), + None => None, + }; + let bounty_amount_b = match data.bounty_amount_b { + Some(value) => Some(mn_mpz_to_u256(&value)), + None => None, + }; + + let emitter = Address::from_slice(&data.emitter.id); + let transaction = bytes_to_h256(&hex_string_to_bytes(&data.transaction.id).unwrap()); + + BountyResponse { + id: data.id, + clearer, + order_clear: data.order_clear.id, + bounty_vault_a: data.bounty_vault_a.id, + bounty_vault_b: data.bounty_vault_b.id, + bounty_token_a, + bounty_token_b, + bounty_amount_a, + bounty_amount_a_display: data.bounty_amount_a_display, + bounty_amount_b, + bounty_amount_b_display: data.bounty_amount_b_display, + transaction, + emitter, + timestamp: mn_mpz_to_u256(&data.timestamp), + } + } +} + +pub async fn get_bounty(id: &String) -> Result { + let variables = bounty::Variables { + id: id.to_string().into(), + }; + + let request_body = Bounty::build_query(variables); + let client = reqwest::Client::new(); + let res = client + .post((*SG_URL).clone()) + .json(&request_body) + .send() + .await?; + + let response_body: Response = res.json().await?; + + match response_body.data { + Some(data) => { + let response = BountyResponse::from(data); + Ok(response) + } + None => Err(anyhow!("Failed to get query")), + } +} diff --git a/subgraph/tests/subgraph/query/mod.rs b/subgraph/tests/subgraph/query/mod.rs index 96489d4cd..3c0a34bec 100644 --- a/subgraph/tests/subgraph/query/mod.rs +++ b/subgraph/tests/subgraph/query/mod.rs @@ -1,3 +1,4 @@ +pub(crate) mod bounty; pub(crate) mod content_meta_v1; pub(crate) mod erc20; pub(crate) mod io; @@ -5,16 +6,17 @@ pub(crate) mod order; pub(crate) mod order_clear; pub(crate) mod orderbook; pub(crate) mod rain_meta_v1; +pub(crate) mod token_vault; pub(crate) mod vault; pub(crate) mod vault_deposit; pub(crate) mod vault_withdraw; -pub(crate) mod token_vault; use anyhow::Result; use ethers::types::{Address, Bytes}; use once_cell::sync::Lazy; use reqwest::Url; +use bounty::{get_bounty, BountyResponse}; use content_meta_v1::{get_content_meta_v1, ContentMetaV1Response}; use erc20::{get_erc20, ERC20Response}; use io::{get_i_o, IOResponse}; @@ -22,10 +24,10 @@ use order::{get_order, OrderResponse}; use order_clear::{get_order_clear, OrderClearResponse}; use orderbook::{get_orderbook_query, OrderBookResponse}; use rain_meta_v1::{get_rain_meta_v1, RainMetaV1Response}; +use token_vault::{get_token_vault, TokenVaultResponse}; use vault::{get_vault, VaultResponse}; use vault_deposit::{get_vault_deposit, VaultDepositResponse}; use vault_withdraw::{get_vault_withdraw, VaultWithdrawResponse}; -use token_vault::{get_token_vault, TokenVaultResponse}; pub static SG_URL: Lazy = Lazy::new(|| Url::parse("http://localhost:8000/subgraphs/name/test/test").unwrap()); @@ -76,4 +78,8 @@ impl Query { pub async fn token_vault(id: &String) -> Result { get_token_vault(id).await } + + pub async fn bounty(id: &String) -> Result { + get_bounty(id).await + } }