diff --git a/integration-tests/config.json b/integration-tests/config.json index 08b152027..387ba06b9 100644 --- a/integration-tests/config.json +++ b/integration-tests/config.json @@ -17,8 +17,17 @@ }, "Networks": [ { - "ChainID": 31337, - "RPCURL": "http://anvil:8545" - } + "ChainID": 31337, + "ChainName": "Anvil", + "DefaultRPCURL": "http://anvil:8545", + "RPCURL": "http://anvil:8545", + "ShortName": "eth", + "NativeCurrencyName": "Ether", + "NativeCurrencySymbol": "ETH", + "NativeCurrencyDecimals": 18, + "IsTest": false, + "Layer": 1, + "Enabled": true + } ] } diff --git a/integration-tests/conftest.py b/integration-tests/conftest.py index ec58edf3d..27b4a7546 100644 --- a/integration-tests/conftest.py +++ b/integration-tests/conftest.py @@ -21,6 +21,12 @@ def pytest_addoption(parser): help="", default="ws://0.0.0.0:8354", ) + parser.addoption( + "--anvil_url", + action="store", + help="", + default="http://0.0.0.0:8545", + ) parser.addoption( "--password", action="store", @@ -35,11 +41,11 @@ class Account(): private_key: str user_1 = Account( - address="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + address="0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", private_key="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", ) user_2 = Account( - address="0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + address="0x70997970c51812dc3a010c7d01b50e0d17dc79c8", private_key="0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", ) diff --git a/integration-tests/docker-compose.anvil.yml b/integration-tests/docker-compose.anvil.yml index 61dc6a158..141d8eda1 100644 --- a/integration-tests/docker-compose.anvil.yml +++ b/integration-tests/docker-compose.anvil.yml @@ -3,7 +3,7 @@ services: image: ghcr.io/foundry-rs/foundry:latest platform: linux/amd64 command: - - anvil --host 0.0.0.0 + - anvil --host 0.0.0.0 --block-time 2 deploy-sntv2: platform: linux/amd64 diff --git a/integration-tests/docker-compose.test.status-go.yml b/integration-tests/docker-compose.test.status-go.yml index 5b0b9b466..ae2b01bfd 100644 --- a/integration-tests/docker-compose.test.status-go.yml +++ b/integration-tests/docker-compose.test.status-go.yml @@ -16,6 +16,9 @@ services: "--password", "Strong12345", "--dir", "/tmp/status-go-data", # Keep in sync with `config.json/DataDir` value. Later this arg will not be needed. ] + ports: + - 3333:3333 + # - 8354:8354 # use for local debbuging only healthcheck: test: ["CMD-SHELL", "curl -X POST --data '{\"jsonrpc\":\"2.0\",\"method\":\"net_version\",\"params\":[],\"id\":1}' -H 'Content-Type: application/json' http://0.0.0.0:3333 || exit 1"] interval: 5s @@ -72,6 +75,8 @@ services: "-m", "wallet", "--rpc_url=http://status-go:3333", "--rpc_url_2=http://status-go-no-funds:3333", + "--anvil_url=http://anvil:8545", + "--ws_url=ws://status-go:8354", "--junitxml=/tests-rpc/reports/report.xml", ] volumes: diff --git a/integration-tests/tests/test_cases.py b/integration-tests/tests/test_cases.py index 81ebae27b..338e935cd 100644 --- a/integration-tests/tests/test_cases.py +++ b/integration-tests/tests/test_cases.py @@ -3,6 +3,7 @@ import websocket import threading import logging import jsonschema +import time import requests from conftest import option, user_1, user_2 @@ -16,9 +17,11 @@ class RpcTestCase: try: return response.json()[key] except json.JSONDecodeError: - raise AssertionError(f"Invalid JSON in response: {response.content}") + raise AssertionError( + f"Invalid JSON in response: {response.content}") except KeyError: - raise AssertionError(f"Key '{key}' not found in the JSON response.") + raise AssertionError( + f"Key '{key}' not found in the JSON response: {response.content}") def verify_is_valid_json_rpc_response(self, response, _id=None): assert response.status_code == 200 @@ -40,7 +43,6 @@ class RpcTestCase: assert response.content self._try_except_JSONDecodeError_KeyError(response, "error") - def rpc_request(self, method, params=[], _id=None, client=None, url=None): client = client if client else requests.Session() url = url if url else option.rpc_url @@ -60,28 +62,27 @@ class RpcTestCase: schema=json.load(schema)) - class TransactionTestCase(RpcTestCase): - def wallet_create_multi_transaction(self, **kwargs): method = "wallet_createMultiTransaction" transferTx_data = { - "data": "", - "from": user_1.address, - "gas": "0x5BBF", - "input": "", - "maxFeePerGas": "0xbcc0f04fd", - "maxPriorityFeePerGas": "0xbcc0f04fd", - "to": user_2.address, - "type": "0x02", - "value": "0x5af3107a4000", - } + "data": "", + "from": user_1.address, + "gas": "0x5BBF", + "input": "", + "maxFeePerGas": "0xbcc0f04fd", + "maxPriorityFeePerGas": "0xbcc0f04fd", + "to": user_2.address, + "type": "0x02", + "value": "0x5af3107a4000", + } for key, new_value in kwargs.items(): if key in transferTx_data: transferTx_data[key] = new_value else: - print(f"Warning: The key '{key}' does not exist in the transferTx parameters and will be ignored.") + print( + f"Warning: The key '{key}' does not exist in the transferTx parameters and will be ignored.") params = [ { "fromAddress": user_1.address, @@ -116,10 +117,23 @@ class TransactionTestCase(RpcTestCase): class SignalTestCase(RpcTestCase): - received_signals = [] + await_signals = [] + received_signals = {} - def _on_message(self, ws, signal): - self.received_signals.append(signal) + def on_message(self, ws, signal): + signal = json.loads(signal) + if signal.get("type") in self.await_signals: + self.received_signals[signal["type"]] = signal + + def wait_for_signal(self, signal_type, timeout=10): + start_time = time.time() + while signal_type not in self.received_signals: + time_passed = time.time() - start_time + if time_passed >= timeout: + raise TimeoutError( + f"Signal {signal_type} is not received in {timeout} seconds") + time.sleep(0.5) + return self.received_signals[signal_type] def _on_error(self, ws, error): logging.info(f"Error: {error}") @@ -134,7 +148,7 @@ class SignalTestCase(RpcTestCase): self.url = f"{option.ws_url}/signals" ws = websocket.WebSocketApp(self.url, - on_message=self._on_message, + on_message=self.on_message, on_error=self._on_error, on_close=self._on_close) diff --git a/integration-tests/tests/test_router.py b/integration-tests/tests/test_router.py new file mode 100644 index 000000000..55a3ab43d --- /dev/null +++ b/integration-tests/tests/test_router.py @@ -0,0 +1,120 @@ +import pytest +import time +import uuid +from conftest import user_1, user_2, option +from test_cases import SignalTestCase + + +@pytest.mark.rpc +@pytest.mark.transaction +@pytest.mark.wallet +class TestTransactionFromRoute(SignalTestCase): + + await_signals = [ + "wallet.suggested.routes", + "wallet.router.sign-transactions", + "wallet.router.sending-transactions-started", + "wallet.transaction.status-changed", + "wallet.router.transactions-sent" + ] + + def test_tx_from_route(self): + + _uuid = str(uuid.uuid4()) + amount_in = "0xde0b6b3a7640000" + + method = "wallet_getSuggestedRoutesAsync" + params = [ + { + "uuid": _uuid, + "sendType": 0, + "addrFrom": user_1.address, + "addrTo": user_2.address, + "amountIn": amount_in, + "amountOut": "0x0", + "tokenID": "ETH", + "tokenIDIsOwnerToken": False, + "toTokenID": "", + "disabledFromChainIDs": [10, 42161], + "disabledToChainIDs": [10, 42161], + "gasFeeMode": 1, + "fromLockedAmount": {} + } + ] + response = self.rpc_request(method, params) + self.verify_is_valid_json_rpc_response(response) + + routes = self.wait_for_signal("wallet.suggested.routes") + assert routes['event']['Uuid'] == _uuid + + method = "wallet_buildTransactionsFromRoute" + params = [ + { + "uuid": _uuid, + "slippagePercentage": 0 + } + ] + response = self.rpc_request(method, params) + self.verify_is_valid_json_rpc_response(response) + + self.wait_for_signal("wallet.router.sign-transactions") + + assert self.received_signals[ + 'wallet.router.sign-transactions']['event']['signingDetails']['signOnKeycard'] == False + transaction_hashes = self.received_signals[ + 'wallet.router.sign-transactions']['event']['signingDetails']['hashes'] + + assert transaction_hashes, "Transaction hashes are empty!" + + tx_signatures = {} + + for hash in transaction_hashes: + + method = "wallet_signMessage" + params = [ + hash, + user_1.address, + option.password + ] + + response = self.rpc_request(method, params) + self.verify_is_valid_json_rpc_response(response) + + if response.json()["result"].startswith("0x"): + tx_signature = response.json()["result"][2:] + + signature = { + "r": tx_signature[:64], + "s": tx_signature[64:128], + "v": tx_signature[128:] + } + + tx_signatures[hash] = signature + + method = "wallet_sendRouterTransactionsWithSignatures" + params = [ + { + "uuid": _uuid, + "Signatures": tx_signatures + } + ] + response = self.rpc_request(method, params) + self.verify_is_valid_json_rpc_response(response) + + tx_status = self.wait_for_signal("wallet.transaction.status-changed") + + + assert tx_status["event"]["chainId"] == 31337 + assert tx_status["event"]["status"] == "Success" + tx_hash = tx_status["event"]["hash"] + + method = "eth_getTransactionByHash" + params = [tx_hash] + + response = self.rpc_request(method, params, url=option.anvil_url) + self.verify_is_valid_json_rpc_response(response) + tx_details = response.json()["result"] + + assert tx_details["value"] == amount_in + assert tx_details["to"] == user_2.address + assert tx_details["from"] == user_1.address diff --git a/integration-tests/tests/test_wallet_rpc.py b/integration-tests/tests/test_wallet_rpc.py index 7d0f4d23e..b212bec32 100644 --- a/integration-tests/tests/test_wallet_rpc.py +++ b/integration-tests/tests/test_wallet_rpc.py @@ -84,8 +84,7 @@ class TestRpc(RpcTestCase): ("wallet_getEthereumChains", []), ("wallet_getTokenList", []), ("wallet_getCryptoOnRamps", []), - ("wallet_getCachedCurrencyFormats", []), - ("wallet_fetchAllCurrencyFormats", []) + ("wallet_getCachedCurrencyFormats", []) ], ) def test_(self, method, params): diff --git a/services/wallet/common/const.go b/services/wallet/common/const.go index 3f60d44fa..f96cbf1c9 100644 --- a/services/wallet/common/const.go +++ b/services/wallet/common/const.go @@ -30,6 +30,7 @@ const ( ArbitrumSepolia uint64 = 421614 BinanceChainID uint64 = 56 // obsolete? BinanceTestChainID uint64 = 97 // obsolete? + AnvilMainnet uint64 = 31337 ) var ( diff --git a/services/wallet/router/router_updates.go b/services/wallet/router/router_updates.go index 217e2ef36..f35b03eff 100644 --- a/services/wallet/router/router_updates.go +++ b/services/wallet/router/router_updates.go @@ -11,11 +11,13 @@ import ( ) var ( - newBlockCheckIntervalMainnet = 3 * time.Second - newBlockCheckIntervalOptimism = 1 * time.Second - newBlockCheckIntervalArbitrum = 200 * time.Millisecond + newBlockCheckIntervalMainnet = 3 * time.Second + newBlockCheckIntervalOptimism = 1 * time.Second + newBlockCheckIntervalArbitrum = 200 * time.Millisecond + newBlockCheckIntervalAnvilMainnet = 2 * time.Second - feeRecalculationTimeout = 5 * time.Minute + feeRecalculationTimeout = 5 * time.Minute + feeRecalculationAnvilTimeout = 5 * time.Second ) type fetchingLastBlock struct { @@ -42,7 +44,11 @@ func (r *Router) subscribeForUdates(chainID uint64) error { } r.clientsForUpdatesPerChains.Store(chainID, flb) - r.startTimeoutForUpdates(flb.closeCh) + timeout := feeRecalculationTimeout + if chainID == walletCommon.AnvilMainnet { + timeout = feeRecalculationAnvilTimeout + } + r.startTimeoutForUpdates(flb.closeCh, timeout) var ticker *time.Ticker switch chainID { @@ -55,6 +61,8 @@ func (r *Router) subscribeForUdates(chainID uint64) error { case walletCommon.ArbitrumMainnet, walletCommon.ArbitrumSepolia: ticker = time.NewTicker(newBlockCheckIntervalArbitrum) + case walletCommon.AnvilMainnet: + ticker = time.NewTicker(newBlockCheckIntervalAnvilMainnet) } ctx, cancelCtx := context.WithCancel(context.Background()) @@ -123,8 +131,8 @@ func (r *Router) subscribeForUdates(chainID uint64) error { return nil } -func (r *Router) startTimeoutForUpdates(closeCh chan struct{}) { - dedlineTicker := time.NewTicker(feeRecalculationTimeout) +func (r *Router) startTimeoutForUpdates(closeCh chan struct{}, timeout time.Duration) { + dedlineTicker := time.NewTicker(timeout) go func() { defer gocommon.LogOnPanic() for {