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

SQS-347 | integration, synthetic monitoring tests for active orders #504

Merged
merged 2 commits into from
Sep 10, 2024
Merged
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 locust/locustfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
from token_prices import TokenPrices
from in_given_out import ExactAmountOutQuote
from out_given_in import ExactAmountInQuote
from orderbook_active_orders import OrderbookActiveOrders
from passthrough_portfolio_balances import PassthroughPortfolioBalances
from passthrough_orderbook_active_orders import PassthroughOrderbookActiveOrders
deividaspetraitis marked this conversation as resolved.
Show resolved Hide resolved
18 changes: 0 additions & 18 deletions locust/orderbook_active_orders.py

This file was deleted.

10 changes: 10 additions & 0 deletions locust/passthrough_orderbook_active_orders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from locust import HttpUser, task
deividaspetraitis marked this conversation as resolved.
Show resolved Hide resolved

# burner address for the integration tests
addr = "osmo1jgz4xmaw9yk9pjxd4h8c2zs0r0vmgyn88s8t6l"
p0mvn marked this conversation as resolved.
Show resolved Hide resolved


class PassthroughOrderbookActiveOrders(HttpUser):
@task
def passthrough_orderbook_active_orders(self):
self.client.get(f"/passthrough/active-orders?userOsmoAddress={addr}")
111 changes: 111 additions & 0 deletions tests/active_orderbook_orders_response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from decimal import Decimal
from typing import List
import time
from datetime import datetime


class LimitOrderAsset:
def __init__(self, symbol: str):
self.symbol = symbol

def validate(self):
# Ensure symbol is not empty
assert self.symbol, "Symbol must not be empty"

class LimitOrder:
def __init__(self, tick_id: int, order_id: int, order_direction: str, owner: str, quantity: str, etas: str,
claim_bounty: str, placed_quantity: str, placed_at: int, price: str, percentClaimed: str,
totalFilled: str, percentFilled: str, orderbookAddress: str, status: str, output: str,
quote_asset: dict, base_asset: dict):
self.tick_id = int(tick_id)
self.order_id = int(order_id)
self.order_direction = order_direction
self.owner = owner
self.quantity = Decimal(quantity)
self.etas = Decimal(etas)
self.claim_bounty = Decimal(claim_bounty)
self.placed_quantity = Decimal(placed_quantity)
self.placed_at = int(placed_at)
self.price = Decimal(price)
self.percent_claimed = Decimal(percentClaimed) # Changed variable name
self.total_filled = Decimal(totalFilled)
self.percent_filled = Decimal(percentFilled)
self.orderbook_address = orderbookAddress
self.status = status
self.output = Decimal(output)
self.quote_asset = LimitOrderAsset(**quote_asset)
self.base_asset = LimitOrderAsset(**base_asset)

def validate(self, owner_address=None):
# Check if order_id is non-negative
assert self.order_id >= 0, f"Order ID {self.order_id} cannot be negative"

# Check if order_direction is either "bid" or "ask"
assert self.order_direction in ['bid', 'ask'], f"Order direction {self.order_direction} must be 'bid' or 'ask'"

# Validate owner address (Osmosis address format)
assert self.owner == owner_address, f"Owner address {self.owner} is invalid"

# Check if quantity is non-negative
assert self.quantity > 0, f"Quantity {self.quantity} cannot be negative"

# Check if claim_bounty is non-negative
assert self.claim_bounty > 0, f"Claim bounty {self.claim_bounty} cannot be negative"

# Validate placed_quantity is non-negative
assert self.placed_quantity > 0, f"Placed quantity {self.placed_quantity} cannot be negative"

# Validate placed_at is a valid Unix timestamp
assert 0 <= self.placed_at <= int(time.time()), f"Placed_at timestamp {self.placed_at} is invalid"

# Check if price is positive
assert self.price > 0, f"Price {self.price} must be positive"

# Check if percent_claimed is between 0 and 100
assert 0 <= self.percent_claimed <= 100, f"Percent claimed {self.percent_claimed} must be between 0 and 100"

# Check if total_filled is non-negative
assert self.total_filled >= 0, f"Total filled {self.total_filled} cannot be negative"

# Check if percent_filled is between 0 and 100
assert 0 <= self.percent_filled <= 100, f"Percent filled {self.percent_filled} must be between 0 and 100"

# Ensure status is not empty
assert self.status, "Status must not be empty"

# Ensure orderbook_address is not empty
assert self.orderbook_address, "Orderbook address must not be empty"

# Check if output is non-negative
assert self.output >= 0, f"Output {self.output} cannot be negative"

# Validate quote_asset
self.quote_asset.validate()

# Validate base_asset
self.base_asset.validate()

@staticmethod
def _is_valid_unix_timestamp(timestamp):
try:
datetime.utcfromtimestamp(int(timestamp))
return True
except (ValueError, OverflowError):
return False


class OrderbookActiveOrdersResponse:
def __init__(self, orders: List[dict], is_best_effort: bool):
self.orders = [LimitOrder(**order) for order in orders]
self.is_best_effort = is_best_effort

def validate(self, owner_address):
# Validate each order
order_ids = set()
for order in self.orders:
order.validate(owner_address)

# Ensure order_id is unique
if order.order_id in order_ids:
raise ValueError(f"Duplicate order_id found: {order.order_id}")
order_ids.add(order.order_id)
p0mvn marked this conversation as resolved.
Show resolved Hide resolved
14 changes: 14 additions & 0 deletions tests/sqs_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
CANONICAL_ORDERBOOKS_URL = "/pools/canonical-orderbooks"

PASSTHROUGH_PORTFOLIO_ASSETS = "/passthrough/portfolio-assets/"
PASSTHROUGH_ACTIVE_ORDERBOOK_ORDERS = "/passthrough/active-orders"
p0mvn marked this conversation as resolved.
Show resolved Hide resolved

CONFIG_URL = "/config"

Expand Down Expand Up @@ -240,6 +241,19 @@ def get_canonical_orderbooks(self):

return response.json()


def get_active_orderbook_orders(self, address):
"""
Fetches active orderbook orders from the specified endpoint and address and returns them.
"""

response = requests.get(self.url + f"{PASSTHROUGH_ACTIVE_ORDERBOOK_ORDERS}?userOsmoAddress={address}", headers=self.headers)

if response.status_code != 200:
raise Exception(f"Error fetching active orderbook orders: {response.text}")

return response.json()

def get_portfolio_assets(self, address):
"""
Fetches the portfolio assets from the specified endpoint and address and returns them.
Expand Down
54 changes: 41 additions & 13 deletions tests/test_passthrough.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,63 @@
import time
import conftest
import pytest

from sqs_service import *
import util
from active_orderbook_orders_response import OrderbookActiveOrdersResponse
from conftest import SERVICE_MAP
from e2e_math import *
from decimal import *
p0mvn marked this conversation as resolved.
Show resolved Hide resolved

# Arbitrary choice based on performance at the time of test writing
EXPECTED_LATENCY_UPPER_BOUND_MS = 150
p0mvn marked this conversation as resolved.
Show resolved Hide resolved

user_balances_assets_category_name = "user-balances"
unstaking_assets_category_name = "unstaking"
staked_assets_category_name = "staked"
inLocks_assets_category_name = "in-locks"
pooled_assets_category_name = "pooled"
unclaimed_rewards_assets_category_name = "unclaimed-rewards"
total_assets_category_name = "total-assets"
user_balances_assets_category_name = "user-balances"
unstaking_assets_category_name = "unstaking"
staked_assets_category_name = "staked"
inLocks_assets_category_name = "in-locks"
pooled_assets_category_name = "pooled"
unclaimed_rewards_assets_category_name = "unclaimed-rewards"
total_assets_category_name = "total-assets"

# Test suite for the /passthrough endpoint

# Note: this is for convinience to skip long-running tests in development
# locally.
# @pytest.mark.skip(reason="This test is currently disabled")
class TestPassthrough:


class TestPassthrough:
def test_poortfolio_assets(self, environment_url):
run_test_portfolio_assets(environment_url)

def test_active_orderbook_orders(self, environment_url):
run_test_active_orderbook_orders(environment_url)


def run_test_active_orderbook_orders(environment_url):
sqs_service = SERVICE_MAP[environment_url]

# list of burner addresses for the integration tests
addresses = [
"osmo1jgz4xmaw9yk9pjxd4h8c2zs0r0vmgyn88s8t6l",
]
p0mvn marked this conversation as resolved.
Show resolved Hide resolved

for address in addresses:

start_time = time.time()
response = sqs_service.get_active_orderbook_orders(address)
elapsed_time_ms = (time.time() - start_time) * 1000

assert EXPECTED_LATENCY_UPPER_BOUND_MS > elapsed_time_ms, f"Error: latency {elapsed_time_ms} exceeded {EXPECTED_LATENCY_UPPER_BOUND_MS} ms"

resp = OrderbookActiveOrdersResponse(**response)

resp.validate(address)
deividaspetraitis marked this conversation as resolved.
Show resolved Hide resolved


def run_test_portfolio_assets(environment_url):
sqs_service = SERVICE_MAP[environment_url]

# Arbitrary addresses
addresses = [
"osmo1044qatzg4a0wm63jchrfdnn2u8nwdgxxt6e524",
Expand Down Expand Up @@ -64,6 +93,7 @@ def run_test_portfolio_assets(environment_url):
total_assets = categories.get(total_assets_category_name)
validate_category(total_assets, True)


def validate_category(category, should_have_breakdown=False):
assert category is not None

Expand All @@ -80,9 +110,7 @@ def validate_category(category, should_have_breakdown=False):

account_coins_result = category.get('account_coins_result')
assert account_coins_result is not None

for coin_result in account_coins_result:
assert coin_result.get('coin') is not None
assert coin_result.get('cap_value') is not None


6 changes: 5 additions & 1 deletion tests/test_synthetic_geo.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from util import *

from test_pools import run_pool_liquidity_cap_test, run_canonical_orderbook_test, run_pool_filters_test
from test_passthrough import run_test_portfolio_assets
from test_passthrough import run_test_portfolio_assets, run_test_active_orderbook_orders
from test_candidate_routes import run_candidate_routes_test
from test_router_quote_out_given_in import TestExactAmountInQuote
from test_router_quote_in_given_out import TestExactAmountOutQuote
Expand Down Expand Up @@ -41,6 +41,10 @@ def test_synth_pools_filters(self, environment_url):
def test_synth_passthrough_portfolio_assets(self, environment_url):
run_test_portfolio_assets(environment_url)

# /passthrough/active-orders endpoint
def test_synth_passthrough_active_orders(self, environment_url):
run_test_active_orderbook_orders(environment_url)

# /router/routes endpoint
def test_synth_candidate_routes(self, environment_url):
tokens_to_pair = [constants.USDC, constants.UOSMO]
Expand Down
Loading