Skip to content

Commit 1fde2e9

Browse files
committed
Init Orderly integration
1 parent a2b93f6 commit 1fde2e9

18 files changed

+2828
-2
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ logs/*
3030

3131
# Stash your local test environment variables here
3232
env/local-test.env
33+
env/.env*
3334

3435
# See scripts/pyodide/README.md
3536
*.whl

deps/web3-ethereum-defi

Submodule web3-ethereum-defi updated 149 files

tests/orderly/conftest.py

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
2+
"""Arbitrum Sepolia fork based tests for Orderly.
3+
4+
Tests are based on the Orderly vault deployment on Arbitrum Sepolia testnet.
5+
"""
6+
import os
7+
8+
import pytest
9+
from eth_account import Account
10+
from eth_typing import HexAddress
11+
from web3 import Web3
12+
13+
from eth_defi.hotwallet import HotWallet
14+
from eth_defi.provider.anvil import AnvilLaunch, fork_network_anvil
15+
from eth_defi.provider.multi_provider import create_multi_provider_web3
16+
from eth_defi.token import TokenDetails, fetch_erc20_details
17+
from eth_defi.trace import assert_transaction_success_with_explanation
18+
from eth_defi.vault.base import VaultSpec
19+
20+
from tradeexecutor.ethereum.ethereum_protocol_adapters import EthereumPairConfigurator
21+
from tradeexecutor.ethereum.orderly.orderly_execution import OrderlyExecution
22+
from tradeexecutor.ethereum.orderly.orderly_vault import OrderlyVault
23+
from tradeexecutor.ethereum.orderly.tx import OrderlyTransactionBuilder
24+
from tradeexecutor.state.identifier import TradingPairIdentifier, AssetIdentifier, TradingPairKind
25+
from tradeexecutor.strategy.generic.generic_pricing_model import GenericPricing
26+
from tradeexecutor.ethereum.orderly.orderly_routing import OrderlyRouting
27+
from tradeexecutor.strategy.reverse_universe import create_universe_from_trading_pair_identifiers
28+
from tradeexecutor.strategy.trading_strategy_universe import TradingStrategyUniverse, translate_token
29+
from tradingstrategy.chain import ChainId
30+
from tradingstrategy.exchange import ExchangeUniverse, Exchange, ExchangeType
31+
from tradingstrategy.pair import PandasPairUniverse
32+
from tradingstrategy.timebucket import TimeBucket
33+
from tradingstrategy.universe import Universe
34+
35+
36+
JSON_RPC_ARBITRUM_SEPOLIA = os.environ.get("JSON_RPC_ARBITRUM_SEPOLIA")
37+
HOT_WALLET_PRIVATE_KEY = os.environ.get("HOT_WALLET_PRIVATE_KEY")
38+
39+
CI = os.environ.get("CI", None) is not None
40+
41+
pytestmark = pytest.mark.skipif(not JSON_RPC_ARBITRUM_SEPOLIA, reason="No JSON_RPC_ARBITRUM_SEPOLIA environment variable")
42+
43+
44+
@pytest.fixture()
45+
def large_usdc_holder() -> HexAddress:
46+
"""A known address with large USDC balance on Arbitrum Sepolia.
47+
48+
This needs to be updated if the fork block changes.
49+
"""
50+
# You may need to find an appropriate holder for the test
51+
return "0x0000000000000000000000000000000000000000" # TODO: Find actual USDC holder on Arbitrum Sepolia
52+
53+
54+
@pytest.fixture()
55+
def anvil_arbitrum_sepolia_fork(request, large_usdc_holder) -> AnvilLaunch:
56+
"""Create a testable fork of live Arbitrum Sepolia chain.
57+
58+
:return: JSON-RPC URL for Web3
59+
"""
60+
assert JSON_RPC_ARBITRUM_SEPOLIA, "JSON_RPC_ARBITRUM_SEPOLIA not set"
61+
launch = fork_network_anvil(
62+
JSON_RPC_ARBITRUM_SEPOLIA,
63+
unlocked_addresses=[large_usdc_holder] if large_usdc_holder != "0x0000000000000000000000000000000000000000" else [],
64+
fork_block_number=178687280, # Can be updated to a more recent block
65+
)
66+
try:
67+
yield launch
68+
finally:
69+
# Wind down Anvil process after the test is complete
70+
launch.close()
71+
72+
73+
@pytest.fixture()
74+
def web3(anvil_arbitrum_sepolia_fork) -> Web3:
75+
"""Create a web3 connector.
76+
77+
- By default use Anvil forked Arbitrum Sepolia
78+
79+
- Enable Tenderly testnet with `JSON_RPC_TENDERLY` to debug
80+
otherwise impossible to debug transactions
81+
"""
82+
83+
tenderly_fork_rpc = os.environ.get("JSON_RPC_TENDERLY", None)
84+
85+
if tenderly_fork_rpc:
86+
web3 = create_multi_provider_web3(tenderly_fork_rpc)
87+
else:
88+
web3 = create_multi_provider_web3(
89+
anvil_arbitrum_sepolia_fork.json_rpc_url,
90+
default_http_timeout=(3, 250.0), # multicall slow, so allow improved timeout
91+
)
92+
assert web3.eth.chain_id == 421614 # Arbitrum Sepolia chain ID
93+
return web3
94+
95+
96+
@pytest.fixture(scope='module')
97+
def usdc() -> AssetIdentifier:
98+
"""USDC on Arbitrum Sepolia"""
99+
return AssetIdentifier(
100+
chain_id=421614,
101+
address="0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d",
102+
decimals=6,
103+
token_symbol="USDC",
104+
)
105+
106+
107+
@pytest.fixture(scope='module')
108+
def weth() -> AssetIdentifier:
109+
"""WETH on Arbitrum Sepolia"""
110+
return AssetIdentifier(
111+
chain_id=421614,
112+
address="0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9",
113+
decimals=18,
114+
token_symbol="WETH",
115+
)
116+
117+
118+
@pytest.fixture()
119+
def usdc_token(web3, usdc) -> TokenDetails:
120+
"""USDC token details"""
121+
return fetch_erc20_details(
122+
web3,
123+
usdc.address,
124+
chain_id=usdc.chain_id,
125+
)
126+
127+
128+
@pytest.fixture()
129+
def weth_token(web3, weth) -> TokenDetails:
130+
"""WETH token details"""
131+
return fetch_erc20_details(
132+
web3,
133+
weth.address,
134+
chain_id=weth.chain_id,
135+
)
136+
137+
138+
@pytest.fixture()
139+
def hot_wallet(web3, usdc_token, large_usdc_holder) -> HotWallet:
140+
"""A test account with USDC balance."""
141+
142+
if HOT_WALLET_PRIVATE_KEY:
143+
# Use provided private key
144+
hw = HotWallet(Account.from_key(HOT_WALLET_PRIVATE_KEY))
145+
else:
146+
# Create a test wallet
147+
hw = HotWallet.create_for_testing(
148+
web3,
149+
test_account_n=1,
150+
eth_amount=10
151+
)
152+
153+
hw.sync_nonce(web3)
154+
155+
# Give hot wallet some native token for gas
156+
web3.eth.send_transaction(
157+
{
158+
"from": web3.eth.accounts[9],
159+
"to": hw.address,
160+
"value": 1 * 10**18,
161+
}
162+
)
163+
164+
# Top up with USDC if we have a holder
165+
if large_usdc_holder != "0x0000000000000000000000000000000000000000":
166+
tx_hash = usdc_token.contract.functions.transfer(hw.address, 1000 * 10**6).transact({"from": large_usdc_holder, "gas": 100_000})
167+
assert_transaction_success_with_explanation(web3, tx_hash)
168+
169+
return hw
170+
171+
172+
@pytest.fixture()
173+
def orderly_vault_address() -> HexAddress:
174+
"""Orderly vault address on Arbitrum Sepolia.
175+
176+
This is the deployed Orderly vault contract address on Arbitrum Sepolia testnet.
177+
"""
178+
return "0x0EaC556c0C2321BA25b9DC01e4e3c95aD5CDCd2f"
179+
180+
181+
@pytest.fixture()
182+
def broker_id() -> str:
183+
"""Orderly broker ID for testing"""
184+
return "woofi_pro"
185+
186+
187+
@pytest.fixture()
188+
def orderly_account_id() -> HexAddress:
189+
"""Orderly account ID for testing"""
190+
# This is a test account ID - should be replaced with actual test account
191+
return "0xca47e3fb4339d0e30c639bb30cf8c2d18cbb8687a27bc39249287232f86f8d00"
192+
193+
194+
@pytest.fixture()
195+
def orderly_vault(web3, orderly_vault_address, broker_id) -> OrderlyVault:
196+
"""Orderly vault instance"""
197+
return OrderlyVault(
198+
web3=web3,
199+
spec=VaultSpec(
200+
chain_id=421614,
201+
vault_address=orderly_vault_address,),
202+
broker_id=broker_id,
203+
)
204+
205+
206+
@pytest.fixture()
207+
def asset_manager(web3) -> HotWallet:
208+
"""Account that we use for Orderly trades"""
209+
hot_wallet = HotWallet.create_for_testing(web3, eth_amount=1)
210+
return hot_wallet
211+
212+
213+
@pytest.fixture()
214+
def orderly_tx_builder(
215+
orderly_vault: OrderlyVault,
216+
asset_manager: HotWallet,
217+
broker_id: str,
218+
orderly_account_id: str,
219+
) -> OrderlyTransactionBuilder:
220+
"""Orderly transaction builder instance"""
221+
return OrderlyTransactionBuilder(
222+
vault=orderly_vault,
223+
hot_wallet=asset_manager,
224+
broker_id=broker_id,
225+
orderly_account_id=orderly_account_id,
226+
)
227+
228+
229+
@pytest.fixture()
230+
def orderly_execution_model(
231+
web3: Web3,
232+
orderly_vault: OrderlyVault,
233+
orderly_tx_builder: OrderlyTransactionBuilder,
234+
broker_id: str,
235+
orderly_account_id: str,
236+
) -> OrderlyExecution:
237+
"""Set OrderlyExecution in Arbitrum Sepolia fork testing mode."""
238+
239+
execution_model = OrderlyExecution(
240+
vault=orderly_vault,
241+
broker_id=broker_id,
242+
orderly_account_id=orderly_account_id,
243+
tx_builder=orderly_tx_builder,
244+
mainnet_fork=True,
245+
confirmation_block_count=0,
246+
)
247+
return execution_model
248+
249+
250+
@pytest.fixture()
251+
def orderly_routing_model(orderly_execution_model, orderly_strategy_universe) -> OrderlyRouting:
252+
"""Create routing model for Orderly"""
253+
return orderly_execution_model.create_default_routing_model(orderly_strategy_universe)
254+
255+
256+
@pytest.fixture()
257+
def orderly_pricing_model(
258+
web3,
259+
orderly_strategy_universe,
260+
) -> GenericPricing:
261+
"""Create pricing model for Orderly"""
262+
pair_configurator = EthereumPairConfigurator(
263+
web3,
264+
orderly_strategy_universe,
265+
)
266+
267+
weth_usdc = orderly_strategy_universe.get_pair_by_human_description(
268+
(ChainId.arbitrum_sepolia, "uniswap-v3", "WETH", "USDC", 0.0005),
269+
)
270+
271+
weth = weth_usdc.base
272+
273+
return GenericPricing(
274+
pair_configurator,
275+
exchange_rate_pairs={
276+
weth: weth_usdc,
277+
}
278+
)
279+
280+
281+
@pytest.fixture(scope='module')
282+
def orderly_pair_universe(
283+
usdc,
284+
weth,
285+
) -> PandasPairUniverse:
286+
"""Define pair universe for testing on Arbitrum Sepolia.
287+
288+
This is a simplified universe with WETH-USDC pair for testing.
289+
"""
290+
291+
exchange_universe = ExchangeUniverse(
292+
exchanges={
293+
1: Exchange(
294+
chain_id=ChainId(421614), # Arbitrum Sepolia
295+
chain_slug="arbitrum-sepolia",
296+
exchange_id=1,
297+
exchange_slug="uniswap-v3",
298+
address="0x0000000000000000000000000000000000000000", # TODO: Add actual Uniswap v3 factory on Arbitrum Sepolia
299+
exchange_type=ExchangeType.uniswap_v3,
300+
pair_count=1,
301+
),
302+
}
303+
)
304+
305+
# Create a test trading pair
306+
# Note: These addresses need to be updated with actual pool addresses on Arbitrum Sepolia
307+
weth_usdc_uniswap_v3 = TradingPairIdentifier(
308+
base=weth,
309+
quote=usdc,
310+
pool_address="0x0000000000000000000000000000000000000000", # TODO: Add actual pool address
311+
exchange_address="0x0000000000000000000000000000000000000000", # TODO: Add actual factory address
312+
fee=0.0005,
313+
)
314+
315+
universe = create_universe_from_trading_pair_identifiers(
316+
[weth_usdc_uniswap_v3],
317+
exchange_universe=exchange_universe,
318+
)
319+
return universe
320+
321+
322+
@pytest.fixture(scope='module')
323+
def orderly_strategy_universe(
324+
orderly_pair_universe,
325+
) -> TradingStrategyUniverse:
326+
"""Trading strategy universe for Orderly testing"""
327+
328+
universe = Universe(
329+
chains={ChainId.arbitrum_sepolia},
330+
time_bucket=TimeBucket.not_applicable,
331+
exchange_universe=orderly_pair_universe.exchange_universe,
332+
pairs=orderly_pair_universe,
333+
)
334+
335+
usdc = orderly_pair_universe.get_token("0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d")
336+
return TradingStrategyUniverse(
337+
data_universe=universe,
338+
reserve_assets={translate_token(usdc)},
339+
)
340+
341+
342+
@pytest.fixture
343+
def vault_pair(weth, usdc, orderly_vault_address) -> TradingPairIdentifier:
344+
return TradingPairIdentifier(
345+
base=weth, # Vault shares, not used
346+
quote=usdc, # USDC denomination
347+
pool_address=orderly_vault_address,
348+
exchange_address="0x0000000000000000000000000000000000000000",
349+
fee=0.0,
350+
kind=TradingPairKind.vault,
351+
)

0 commit comments

Comments
 (0)