diff --git a/.github/workflows/dockerize.yml b/.github/workflows/dockerize.yml new file mode 100644 index 000000000..74c6340ae --- /dev/null +++ b/.github/workflows/dockerize.yml @@ -0,0 +1,32 @@ +name: Update docker image + +on: + push: + workflow_dispatch: +env: + IMAGE_NAME: neonlabsorg/uniswap-v4 +jobs: + dockerize: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - run: | + git submodule update --init --remote --recursive + - name: Define tag + id: define-env + run: | + if [[ "${{ github.ref_name }}" == 'main' ]]; then + tag='latest' + else + tag='${{ github.ref_name }}' + fi + echo "tag=${tag}" + echo "tag=${tag}" >> $GITHUB_OUTPUT + - name: Build image + run: | + docker build -t $IMAGE_NAME:${{ steps.define-env.outputs.tag }} . + - name: Push image + run: | + docker login -u ${{ secrets.DOCKER_USERNAME }} -p "${{ secrets.DOCKER_PASSWORD }}" + echo "Push image $IMAGE_NAME:${{ steps.define-env.outputs.tag }} to Docker registry" + docker push --all-tags $IMAGE_NAME \ No newline at end of file diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml deleted file mode 100644 index c773069bd..000000000 --- a/.github/workflows/semgrep.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Semgrep -on: - workflow_dispatch: {} - pull_request: {} - push: - branches: - - main - schedule: - # random HH:MM to avoid a load spike on GitHub Actions at 00:00 - - cron: '35 11 * * *' -jobs: - semgrep: - name: semgrep/ci - runs-on: ubuntu-20.04 - env: - SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} - container: - image: returntocorp/semgrep - if: (github.actor != 'dependabot[bot]') - steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - - run: semgrep ci diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 73de4cdbe..000000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Test - -on: - push: - branches: - - main - pull_request: - -jobs: - run-tests: - name: Forge Tests - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly - - - name: Run tests - run: forge test --isolate -vvv - env: - FOUNDRY_PROFILE: ci - FORGE_SNAPSHOT_CHECK: true diff --git a/.gitmodules b/.gitmodules index 9d6618d5b..195bcb01d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ -[submodule "lib/v4-core"] - path = lib/v4-core - url = https://github.com/Uniswap/v4-core [submodule "lib/permit2"] path = lib/permit2 url = https://github.com/Uniswap/permit2 +[submodule "lib/v4-core"] + path = lib/v4-core + url = https://github.com/neonlabsorg/uniswap-v4-core diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..59f3eab1e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM python:3.10 + +COPY . /app + +WORKDIR /app + +RUN pip install --upgrade pip +RUN pip install --no-cache-dir -r requirements.txt diff --git a/conftest.py b/conftest.py new file mode 100644 index 000000000..4b1620af4 --- /dev/null +++ b/conftest.py @@ -0,0 +1,104 @@ +import os + +import pytest + +from utils import EthAccounts, Faucet, NeonChainWeb3Client + + +@pytest.fixture(scope="class") +def web3_client(): + print(f"Proxy IP: {os.environ.get('PROXY_IP', '')}") + return NeonChainWeb3Client(f"http://{os.environ.get('PROXY_IP', '')}:9090/solana") + + +@pytest.fixture(scope="class") +def faucet(web3_client) -> Faucet: + return Faucet(f"http://{os.environ.get('PROXY_IP', '')}:3333", web3_client) + + +@pytest.fixture(scope="class") +def accounts(web3_client, faucet): + return EthAccounts(web3_client, faucet) + + +@pytest.fixture(scope="function") +def pool_manager(accounts, web3_client): + contract, _ = web3_client.deploy_and_get_contract( + contract="lib/v4-core/src/PoolManager.sol", + version="0.8.26", + contract_name="PoolManager", + account=accounts[0], + import_remapping={ + "solmate/": f"{os.getcwd()}/lib/permit2/lib/solmate/", + }, + ) + print(f"Pool manager address: {contract.address}") + yield contract + + +@pytest.fixture(scope="function") +def v4_router(accounts, pool_manager, web3_client): + contract, _ = web3_client.deploy_and_get_contract( + contract="test/mocks/MockV4Router.sol", + version="0.8.26", + contract_name="MockV4Router", + account=accounts[0], + constructor_args=[pool_manager.address], + import_remapping={ + "solmate/": f"{os.getcwd()}/lib/permit2/lib/solmate/", + }, + ) + print(f"V4 Router address: {contract.address}") + yield contract, pool_manager + + +@pytest.fixture(scope="function") +def position_manager(accounts, pool_manager, web3_client): + contract, _ = web3_client.deploy_and_get_contract( + contract="lib/v4-core/src/test/PoolModifyLiquidityTest.sol", + version="0.8.26", + contract_name="PoolModifyLiquidityTest", + account=accounts[0], + constructor_args=[pool_manager.address], + ) + print(f"Position manager address: {contract.address}") + yield contract + + +v4_router_test_contracts = [ + "V4Router.t.sol", + "V4Router2.t.sol", + "V4Router3.t.sol", + "V4Router4.t.sol", + "V4Router5.t.sol", + "V4Router6.t.sol", + "V4Router7.t.sol", + "V4Router8.t.sol", + "V4Router9.t.sol", +] + + +@pytest.fixture(scope="function") +def v4_router_test(request, accounts, v4_router, position_manager, web3_client): + v4, pool_manager = v4_router + deployed_contracts = dict() + + contract_name = "" + if request.node.get_closest_marker("contract_filename"): + contract_name = request.node.get_closest_marker("contract_filename").kwargs.get("name", "") + + for name in v4_router_test_contracts if contract_name == "" else [contract_name]: + contract, _ = web3_client.deploy_and_get_contract( + contract=f"test/router/{name}", + version="0.8.26", + contract_name="V4RouterTest", + account=accounts[0], + constructor_args=[v4.address, pool_manager.address, position_manager.address], + import_remapping={ + "forge-std": f"{os.getcwd()}/lib/v4-core/lib/forge-std/src", + "solmate/": f"{os.getcwd()}/lib/permit2/lib/solmate/", + "permit2/": f"{os.getcwd()}/lib/permit2/", + }, + ) + deployed_contracts[name] = contract + yield deployed_contracts diff --git a/lib/v4-core b/lib/v4-core index 18b223cab..aeb078d0e 160000 --- a/lib/v4-core +++ b/lib/v4-core @@ -1 +1 @@ -Subproject commit 18b223cab19dc778d9d287a82d29fee3e99162b0 +Subproject commit aeb078d0ee2b9d15869e5e76abf434fa24f1cccf diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..54152cd26 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,62 @@ +aiohappyeyeballs==2.4.0 +aiohttp==3.10.5 +aiosignal==1.3.1 +annotated-types==0.7.0 +anyio==4.4.0 +async-timeout==4.0.3 +attrs==24.2.0 +base58==2.1.1 +bitarray==2.9.2 +black==24.8.0 +certifi==2024.8.30 +charset-normalizer==3.3.2 +ckzg==2.0.1 +click==8.1.7 +construct==2.10.68 +construct-typing==0.5.6 +cytoolz==0.12.3 +eth-account==0.13.3 +eth-hash==0.7.0 +eth-keyfile==0.8.1 +eth-keys==0.5.1 +eth-rlp==2.1.0 +eth-typing==5.0.0 +eth-utils==5.0.0 +eth_abi==5.1.0 +exceptiongroup==1.2.2 +frozenlist==1.4.1 +h11==0.14.0 +hexbytes==1.2.1 +httpcore==1.0.5 +httpx==0.27.2 +idna==3.8 +iniconfig==2.0.0 +isort==5.13.2 +jsonalias==0.1.1 +multidict==6.1.0 +mypy-extensions==1.0.0 +packaging==23.2 +parsimonious==0.10.0 +pathspec==0.12.1 +platformdirs==4.3.6 +pluggy==1.5.0 +py-solc-x==2.0.3 +pycryptodome==3.20.0 +pydantic==2.9.1 +pydantic_core==2.23.3 +pytest==8.3.3 +pyunormalize==15.1.0 +regex==2024.7.24 +requests==2.32.3 +rlp==4.0.1 +sniffio==1.3.1 +solana==0.34.3 +solders==0.21.0 +tomli==2.0.1 +toolz==0.12.1 +types-requests==2.32.0.20240907 +typing_extensions==4.12.2 +urllib3==2.2.2 +web3==7.2.0 +websockets==11.0.3 +yarl==1.11.1 diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 52c019232..baa0ba4cb 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -1,15 +1,15 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.26; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; -import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; -import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; -import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; -import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; -import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; -import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; +import {IPoolManager} from "../lib/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "../lib/v4-core/src/types/PoolKey.sol"; +import {PoolIdLibrary} from "../lib/v4-core/src/types/PoolId.sol"; +import {Currency} from "../lib/v4-core/src/types/Currency.sol"; +import {BalanceDelta} from "../lib/v4-core/src/types/BalanceDelta.sol"; +import {SafeCast} from "../lib/v4-core/src/libraries/SafeCast.sol"; +import {Position} from "../lib/v4-core/src/libraries/Position.sol"; +import {StateLibrary} from "../lib/v4-core/src/libraries/StateLibrary.sol"; +import {TransientStateLibrary} from "../lib/v4-core/src/libraries/TransientStateLibrary.sol"; import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; import {ERC721Permit_v4} from "./base/ERC721Permit_v4.sol"; diff --git a/src/V4Router.sol b/src/V4Router.sol index b33d8d904..cdda869ad 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.26; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; -import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; -import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; -import {BipsLibrary} from "@uniswap/v4-core/src/libraries/BipsLibrary.sol"; +import {IPoolManager} from "../lib/v4-core/src/interfaces/IPoolManager.sol"; +import {BalanceDelta} from "../lib/v4-core/src/types/BalanceDelta.sol"; +import {PoolKey} from "../lib/v4-core/src/types/PoolKey.sol"; +import {Currency} from "../lib/v4-core/src/types/Currency.sol"; +import {TickMath} from "../lib/v4-core/src/libraries/TickMath.sol"; +import {SafeCast} from "../lib/v4-core/src/libraries/SafeCast.sol"; +import {BipsLibrary} from "../lib/v4-core/src/libraries/BipsLibrary.sol"; import {PathKey, PathKeyLibrary} from "./libraries/PathKey.sol"; import {CalldataDecoder} from "./libraries/CalldataDecoder.sol"; diff --git a/src/base/BaseActionsRouter.sol b/src/base/BaseActionsRouter.sol index 56e311906..100fa8535 100644 --- a/src/base/BaseActionsRouter.sol +++ b/src/base/BaseActionsRouter.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.0; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IPoolManager} from "../../lib/v4-core/src/interfaces/IPoolManager.sol"; import {SafeCallback} from "./SafeCallback.sol"; import {CalldataDecoder} from "../libraries/CalldataDecoder.sol"; import {ActionConstants} from "../libraries/ActionConstants.sol"; diff --git a/src/base/DeltaResolver.sol b/src/base/DeltaResolver.sol index ccde3d4d5..4f58b9acb 100644 --- a/src/base/DeltaResolver.sol +++ b/src/base/DeltaResolver.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.24; -import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; -import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Currency} from "../../lib/v4-core/src/types/Currency.sol"; +import {TransientStateLibrary} from "../../lib/v4-core/src/libraries/TransientStateLibrary.sol"; +import {IPoolManager} from "../../lib/v4-core/src/interfaces/IPoolManager.sol"; import {ImmutableState} from "./ImmutableState.sol"; import {ActionConstants} from "../libraries/ActionConstants.sol"; diff --git a/src/base/ImmutableState.sol b/src/base/ImmutableState.sol index 8e30f1a4b..6961a3d39 100644 --- a/src/base/ImmutableState.sol +++ b/src/base/ImmutableState.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IPoolManager} from "../../lib/v4-core/src/interfaces/IPoolManager.sol"; /// @title Immutable State /// @notice A collection of immutable state variables, commonly used across multiple contracts diff --git a/src/base/Notifier.sol b/src/base/Notifier.sol index 558e85b7f..60944b957 100644 --- a/src/base/Notifier.sol +++ b/src/base/Notifier.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.0; import {ISubscriber} from "../interfaces/ISubscriber.sol"; import {INotifier} from "../interfaces/INotifier.sol"; -import {CustomRevert} from "@uniswap/v4-core/src/libraries/CustomRevert.sol"; -import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {CustomRevert} from "../../lib/v4-core/src/libraries/CustomRevert.sol"; +import {BalanceDelta} from "../../lib/v4-core/src/types/BalanceDelta.sol"; import {PositionInfo} from "../libraries/PositionInfoLibrary.sol"; /// @notice Notifier is used to opt in to sending updates to external contracts about position modifications or transfers diff --git a/src/base/PoolInitializer.sol b/src/base/PoolInitializer.sol index 1f42ab1ff..dad90e035 100644 --- a/src/base/PoolInitializer.sol +++ b/src/base/PoolInitializer.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import {ImmutableState} from "./ImmutableState.sol"; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolKey} from "../../lib/v4-core/src/types/PoolKey.sol"; /// @title Pool Initializer /// @notice Initializes a Uniswap v4 Pool diff --git a/src/base/SafeCallback.sol b/src/base/SafeCallback.sol index cda2b4a04..b1d5bcb6f 100644 --- a/src/base/SafeCallback.sol +++ b/src/base/SafeCallback.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.0; -import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol"; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IUnlockCallback} from "../../lib/v4-core/src/interfaces/callback/IUnlockCallback.sol"; +import {IPoolManager} from "../../lib/v4-core/src/interfaces/IPoolManager.sol"; import {ImmutableState} from "./ImmutableState.sol"; /// @title Safe Callback diff --git a/src/interfaces/IPositionManager.sol b/src/interfaces/IPositionManager.sol index 616b51ccb..acbef1160 100644 --- a/src/interfaces/IPositionManager.sol +++ b/src/interfaces/IPositionManager.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolKey} from "../../lib/v4-core/src/types/PoolKey.sol"; import {PositionInfo} from "../libraries/PositionInfoLibrary.sol"; import {INotifier} from "./INotifier.sol"; diff --git a/src/interfaces/ISubscriber.sol b/src/interfaces/ISubscriber.sol index 1e6f04762..08f08d640 100644 --- a/src/interfaces/ISubscriber.sol +++ b/src/interfaces/ISubscriber.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {BalanceDelta} from "../../lib/v4-core/src/types/BalanceDelta.sol"; /// @notice Interface that a Subscriber contract should implement to receive updates from the v4 position manager interface ISubscriber { diff --git a/src/interfaces/IV4Router.sol b/src/interfaces/IV4Router.sol index 13ed4775f..9649e56f4 100644 --- a/src/interfaces/IV4Router.sol +++ b/src/interfaces/IV4Router.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolKey} from "../../lib/v4-core/src/types/PoolKey.sol"; +import {Currency} from "../../lib/v4-core/src/types/Currency.sol"; import {PathKey} from "../libraries/PathKey.sol"; /// @title IV4Router diff --git a/src/libraries/CalldataDecoder.sol b/src/libraries/CalldataDecoder.sol index 00cf6e33e..91611d6a9 100644 --- a/src/libraries/CalldataDecoder.sol +++ b/src/libraries/CalldataDecoder.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.0; -import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {Currency} from "../../lib/v4-core/src/types/Currency.sol"; import {IV4Router} from "../interfaces/IV4Router.sol"; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolKey} from "../../lib/v4-core/src/types/PoolKey.sol"; /// @title Library for abi decoding in calldata library CalldataDecoder { diff --git a/src/libraries/PathKey.sol b/src/libraries/PathKey.sol index b3fa1e7f8..6a9b9ad5f 100644 --- a/src/libraries/PathKey.sol +++ b/src/libraries/PathKey.sol @@ -1,9 +1,9 @@ //SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; -import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {Currency} from "../../lib/v4-core/src/types/Currency.sol"; +import {IHooks} from "../../lib/v4-core/src/interfaces/IHooks.sol"; +import {PoolKey} from "../../lib/v4-core/src/types/PoolKey.sol"; struct PathKey { Currency intermediateCurrency; diff --git a/src/libraries/PositionInfoLibrary.sol b/src/libraries/PositionInfoLibrary.sol index 981500559..3fef0fed0 100644 --- a/src/libraries/PositionInfoLibrary.sol +++ b/src/libraries/PositionInfoLibrary.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolKey} from "../../lib/v4-core/src/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "../../lib/v4-core/src/types/PoolId.sol"; /** * @dev PositionInfo is a packed version of solidity structure. diff --git a/src/libraries/SlippageCheck.sol b/src/libraries/SlippageCheck.sol index 48700c153..a0687a9b6 100644 --- a/src/libraries/SlippageCheck.sol +++ b/src/libraries/SlippageCheck.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; -import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {BalanceDelta} from "../../lib/v4-core/src/types/BalanceDelta.sol"; +import {SafeCast} from "../../lib/v4-core/src/libraries/SafeCast.sol"; /// @title Slippage Check Library /// @notice a library for checking if a delta exceeds a maximum ceiling or fails to meet a minimum floor diff --git a/test/.DS_Store b/test/.DS_Store new file mode 100644 index 000000000..a27ee0e1e Binary files /dev/null and b/test/.DS_Store differ diff --git a/test/commands.md b/test/commands.md new file mode 100644 index 000000000..a70afad20 --- /dev/null +++ b/test/commands.md @@ -0,0 +1,145 @@ +Neon-EVM + +Deploy contract +``` +forge create --rpc-url http://10.211.55.4:9090/solana --private-key 0x3b2c445449050b34e6d448190f889723c1a5b3cbace32412e135d6a9054f73c3 test/UnorderedNonce.t.sol:UnorderedNonceTest --legacy +``` + +Response: +``` +[⠢] Compiling... +No files changed, compilation skipped +2024-09-10T11:13:46.502034Z ERROR alloy_rpc_client::poller: failed to poll err=server returned an error response: error code -32700: Invalid JSON was received by the server, data: {"errors":["The parameter 'JsonRpcRequest.params': Input should be a valid array.","The parameter 'list[JsonRpcRequest]': Input should be a valid array."]} +Deployer: 0x59C8FeC66f80f2B598E5102fBC66f448784E93B6 +Deployed to: 0xE514CdAfad97431d25e35C42AdC816ac77ad11B1 +Transaction hash: 0xfcb21e962d022f58ae6e5408058db23bdf34ce86c7bc75570fcd4c91383cbf06 +``` + +Call method (contract address may vary because these examples for several attemps) +``` +cast call 0x935394A0a127e147A87B75745f740d26626c6Ca7 "setUp()" --rpc-url http://10.211.55.4:9090/solana +``` +Response: +``` +server returned an error response: error code -32602: Invalid params, data: {"errors":["The parameter 'tx.input': Extra inputs are not permitted."]} +``` +Raw communication: +``` +POST /solana HTTP/1.1 +content-type: application/json +accept: */* +host: 10.211.55.4:9090 +content-length: 47 + +{"method":"eth_chainId","id":0,"jsonrpc":"2.0"}HTTP/1.1 200 OK +content-length: 40 +allow: POST, GET +content-type: application/json; charset=utf-8 +x-process-time: 1.942699 +date: Tue, 10 Sep 2024 11:09:33 GMT + +{"jsonrpc":"2.0","id":0,"result":"0x6f"}POST /solana HTTP/1.1 +content-type: application/json +accept: */* +host: 10.211.55.4:9090 +content-length: 226 + +{"method":"eth_call","params":[{"from":"0x0000000000000000000000000000000000000000","to":"0x935394a0a127e147a87b75745f740d26626c6ca7","input":"0x0a9254e4","data":"0x0a9254e4","chainId":"0x6f"},"latest"],"id":1,"jsonrpc":"2.0"}HTTP/1.1 200 OK +content-length: 155 +allow: POST, GET +content-type: application/json; charset=utf-8 +x-process-time: 3.507192 +date: Tue, 10 Sep 2024 11:09:33 GMT + +{"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"Invalid params","data":{"errors":["The parameter 'tx.input': Extra inputs are not permitted."]}}} +``` + +Send (with transaction creation) +``` +cast send 0xe2Ff9C3b17b07B1983EfCD12680a2e64b272A803 "setUp()" --rpc-url http://10.211.55.4:9090/solana --private-key 0x54c773d44823cfc56fcc58716ee3100ad0c6b432b14058681e81041fe351f61f +``` +Response: +``` +server returned an error response: error code -32601: the method eth_feeHistory does not exist/is not available +``` + +Anvil + +Deploy contract +``` +forge create --rpc-url http://127.0.0.1:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 test/UnorderedNonce.t.sol:UnorderedNonceTest --legacy +``` +Call method +``` +cast call 0x5FbDB2315678afecb367f032d93F642f64180aa3 "setUp()" --rpc-url http://127.0.0.1:8545 +``` +Response: +``` +0x +``` +Raw communication +``` +POST / HTTP/1.1 +content-type: application/json +accept: */* +host: 127.0.0.1:8545 +content-length: 47 + +{"method":"eth_chainId","id":0,"jsonrpc":"2.0"}HTTP/1.1 200 OK +content-type: application/json +content-length: 42 +vary: origin, access-control-request-method, access-control-request-headers +access-control-allow-origin: * +date: Tue, 10 Sep 2024 11:07:41 GMT + +{"jsonrpc":"2.0","id":0,"result":"0x7a69"}POST / HTTP/1.1 +content-type: application/json +accept: */* +host: 127.0.0.1:8545 +content-length: 228 + +{"method":"eth_call","params":[{"from":"0x0000000000000000000000000000000000000000","to":"0x5fbdb2315678afecb367f032d93f642f64180aa3","input":"0x0a9254e4","data":"0x0a9254e4","chainId":"0x7a69"},"latest"],"id":1,"jsonrpc":"2.0"}HTTP/1.1 200 OK +content-type: application/json +content-length: 38 +vary: origin, access-control-request-method, access-control-request-headers +access-control-allow-origin: * +date: Tue, 10 Sep 2024 11:07:41 GMT + +{"jsonrpc":"2.0","id":1,"result":"0x"} +``` + +Send (with transaction creation) +``` +cast send 0x5FbDB2315678afecb367f032d93F642f64180aa3 "setUp()" --rpc-url http://127.0.0.1:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +``` +Response: + +``` +blockHash 0x7001cc5d55d9452d85c62d7c5dfbdbef29b3f03667fb91df71dac66e61dee2d2 +blockNumber 2 +contractAddress +cumulativeGasUsed 157354 +effectiveGasPrice 1898069742 +from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +gasUsed 157354 +logs [] +logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +root 0xff0187c4b5d716e7acf7b0e561b93727c1cdeffc5c4c658013adb1c3c9d3b2ad +status 1 (success) +transactionHash 0xa26a2a0ac06d619809e878241e42603da9e151a4d15a004a497a99171db89d43 +transactionIndex 0 +type 2 +blobGasPrice 1 +blobGasUsed +authorizationList +to 0x5FbDB2315678afecb367f032d93F642f64180aa3 +``` + + +``` +cast send 0x5FbDB2315678afecb367f032d93F642f64180aa3 "testLowNonces()" --rpc-url http://127.0.0.1:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +``` + +``` +server returned an error response: error code 3: execution reverted, data: "0x" +``` \ No newline at end of file diff --git a/test/mocks/MockV4Router.sol b/test/mocks/MockV4Router.sol index fc935bb39..ac7b4f325 100644 --- a/test/mocks/MockV4Router.sol +++ b/test/mocks/MockV4Router.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {IPoolManager} from "../../lib/v4-core/src/interfaces/IPoolManager.sol"; +import {Currency, CurrencyLibrary} from "../../lib/v4-core/src/types/Currency.sol"; import {V4Router} from "../../src/V4Router.sol"; import {ReentrancyLock} from "../../src/base/ReentrancyLock.sol"; import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; diff --git a/test/router/V4Router.t.sol b/test/router/V4Router.t.sol index e32da0529..9f1caa9cd 100644 --- a/test/router/V4Router.t.sol +++ b/test/router/V4Router.t.sol @@ -1,21 +1,33 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {Currency, CurrencyLibrary} from "../../lib/v4-core/src/types/Currency.sol"; import {IV4Router} from "../../src/interfaces/IV4Router.sol"; import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol"; import {Plan, Planner} from "../shared/Planner.sol"; import {Actions} from "../../src/libraries/Actions.sol"; import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; +import {MockV4Router} from "../mocks/MockV4Router.sol"; +import {PositionManager} from "../../src/PositionManager.sol"; contract V4RouterTest is RoutingTestHelpers { using CurrencyLibrary for Currency; using Planner for Plan; - address alice = makeAddr("ALICE"); + address alice; + address payable ra; + address payable ma; + address payable pos_manager; + + constructor(address payable routerAddr, address payable managerAddr, address payable positionManagerAddr) { + ra = routerAddr; + ma = managerAddr; + alice = msg.sender; + pos_manager = positionManagerAddr; + } function setUp() public { - setupRouterCurrenciesAndPoolsWithLiquidity(); + setupRouterCurrenciesAndPoolsWithLiquidity(ra, ma, pos_manager); plan = Planner.init(); } @@ -39,6 +51,27 @@ contract V4RouterTest is RoutingTestHelpers { abi.encodeWithSelector(IV4Router.V4TooLittleReceived.selector, expectedAmountOut + 1, expectedAmountOut) ); router.executeActions(data); + // vm.expectRevert(IV4Router.V4TooLittleReceived.selector); + // router.executeActions(data); + } + + function uintToString(uint256 _value) public pure returns (string memory) { + if (_value == 0) { + return "0"; + } + uint256 temp = _value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (_value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(_value % 10))); + _value /= 10; + } + return string(buffer); } function test_swapExactInputSingle_zeroForOne_takeToMsgSender() public { @@ -52,11 +85,16 @@ contract V4RouterTest is RoutingTestHelpers { (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) = _finalizeAndExecuteSwap(key0.currency0, key0.currency1, amountIn); - assertEq(currency0.balanceOf(address(router)), 0); - assertEq(currency1.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); + // assertEq(currency0.balanceOf(address(router)), 0); + require(currency0.balanceOf(address(router)) == 0, "currency0 balance of router is not 0"); + // assertEq(currency1.balanceOf(address(router)), 0); + require(currency1.balanceOf(address(router)) == 0, "currency0 balance of router is not 0"); + // assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); + require( + inputBalanceBefore - inputBalanceAfter == amountIn, + string.concat(uintToString(inputBalanceBefore - inputBalanceAfter), " != ", uintToString(amountIn)) + ); + // assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); } function test_swapExactInputSingle_zeroForOne_takeToRecipient() public { @@ -76,13 +114,31 @@ contract V4RouterTest is RoutingTestHelpers { uint256 aliceOutputBalanceAfter = key0.currency1.balanceOf(alice); - assertEq(currency0.balanceOf(address(router)), 0); - assertEq(currency1.balanceOf(address(router)), 0); + // assertEq(currency0.balanceOf(address(router)), 0); + require(currency0.balanceOf(address(router)) == 0, "currency0 balance of router is not 0"); + // assertEq(currency1.balanceOf(address(router)), 0); + require(currency1.balanceOf(address(router)) == 0, "currency1 balance of router is not 0"); - assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); - // this contract's output balance has not changed because funds went to alice - assertEq(outputBalanceAfter, outputBalanceBefore); - assertEq(aliceOutputBalanceAfter - aliceOutputBalanceBefore, expectedAmountOut); + // assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); + require( + inputBalanceBefore - inputBalanceAfter == amountIn, + string.concat(uintToString(inputBalanceBefore - inputBalanceAfter), " != ", uintToString(amountIn)) + ); + // // this contract's output balance has not changed because funds went to alice + // assertEq(outputBalanceAfter, outputBalanceBefore); + require( + outputBalanceAfter == outputBalanceBefore, + string.concat(uintToString(outputBalanceAfter), " != ", uintToString(outputBalanceBefore)) + ); + // assertEq(aliceOutputBalanceAfter - aliceOutputBalanceBefore, expectedAmountOut); + require( + aliceOutputBalanceAfter - aliceOutputBalanceBefore == expectedAmountOut, + string.concat( + uintToString(aliceOutputBalanceAfter - aliceOutputBalanceBefore), + " != ", + uintToString(expectedAmountOut) + ) + ); } // This is not a real use-case in isolation, but will be used in the UniversalRouter if a v4 @@ -97,718 +153,34 @@ contract V4RouterTest is RoutingTestHelpers { plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); // the router holds no funds before - assertEq(currency0.balanceOf(address(router)), 0); - assertEq(currency1.balanceOf(address(router)), 0); + // assertEq(currency0.balanceOf(address(router)), 0); + require(currency0.balanceOf(address(router)) == 0, "currency0 balance of router is not 0"); + // assertEq(currency1.balanceOf(address(router)), 0); + require((currency1.balanceOf(address(router))) == 0, "currency1 balance of router is not 0"); // swap with the router as the take recipient (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) = _finalizeAndExecuteSwap(key0.currency0, key0.currency1, amountIn, ActionConstants.ADDRESS_THIS); // the output tokens have been left in the router - assertEq(currency0.balanceOf(address(router)), 0); - assertEq(currency1.balanceOf(address(router)), expectedAmountOut); - - assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); - // this contract's output balance has not changed because funds went to the router - assertEq(outputBalanceAfter, outputBalanceBefore); - } - - // This is not a real use-case in isolation, but will be used in the UniversalRouter if a v4 - // swap is before another swap on v2/v3 - function test_swapExactInputSingle_zeroForOne_takeToRouter() public { - uint256 amountIn = 1 ether; - uint256 expectedAmountOut = 992054607780215625; - - IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); - - plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0, expectedAmountOut * 12 / 10)); - // take the entire open delta to the router's address - plan = - plan.add(Actions.TAKE, abi.encode(key0.currency1, ActionConstants.ADDRESS_THIS, ActionConstants.OPEN_DELTA)); - bytes memory data = plan.encode(); - - // the router holds no funds before - assertEq(currency0.balanceOf(address(router)), 0); - assertEq(currency1.balanceOf(address(router)), 0); - uint256 inputBalanceBefore = key0.currency0.balanceOfSelf(); - uint256 outputBalanceBefore = key0.currency1.balanceOfSelf(); - - router.executeActions(data); - - // the output tokens have been left in the router - assertEq(currency0.balanceOf(address(router)), 0); - assertEq(currency1.balanceOf(address(router)), expectedAmountOut); - uint256 inputBalanceAfter = key0.currency0.balanceOfSelf(); - uint256 outputBalanceAfter = key0.currency1.balanceOfSelf(); - - assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); - // this contract's output balance has not changed because funds went to the router - assertEq(outputBalanceAfter, outputBalanceBefore); - } - - function test_swapExactInputSingle_oneForZero() public { - uint256 amountIn = 1 ether; - uint256 expectedAmountOut = 992054607780215625; - - IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, false, uint128(amountIn), 0, 0, bytes("")); - plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - - (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(key0.currency1, key0.currency0, amountIn); - - assertEq(currency0.balanceOf(address(router)), 0); - assertEq(currency1.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); - } - - function test_swapExactInput_revertsForAmountOut() public { - uint256 amountIn = 1 ether; - uint256 expectedAmountOut = 992054607780215625; - - tokenPath.push(currency0); - tokenPath.push(currency1); - IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); - params.amountOutMinimum = uint128(expectedAmountOut + 1); - - plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER); - - vm.expectRevert( - abi.encodeWithSelector(IV4Router.V4TooLittleReceived.selector, 992054607780215625 + 1, 992054607780215625) + // assertEq(currency0.balanceOf(address(router)), 0); + require(currency0.balanceOf(address(router)) == 0, "currency0 balance of router is not 0"); + // assertEq(currency1.balanceOf(address(router)), expectedAmountOut); + require( + currency1.balanceOf(address(router)) == expectedAmountOut, + "currency1 balance of router is not expectedAmountOut" ); - router.executeActions(data); - } - - function test_swapExactIn_1Hop_zeroForOne() public { - uint256 amountIn = 1 ether; - uint256 expectedAmountOut = 992054607780215625; - - tokenPath.push(currency0); - tokenPath.push(currency1); - IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); - plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - - (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(currency0, currency1, amountIn); - - assertEq(currency0.balanceOf(address(router)), 0); - assertEq(currency1.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); - } - - function test_swapExactIn_1Hop_oneForZero() public { - uint256 amountIn = 1 ether; - uint256 expectedAmountOut = 992054607780215625; - - tokenPath.push(currency1); - tokenPath.push(currency0); - IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); - - plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - - (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(currency1, currency0, amountIn); - - assertEq(currency0.balanceOf(address(router)), 0); - assertEq(currency1.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); - } - - function test_swapExactIn_2Hops() public { - uint256 amountIn = 1 ether; - uint256 expectedAmountOut = 984211133872795298; - - tokenPath.push(currency0); - tokenPath.push(currency1); - tokenPath.push(currency2); - IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); - - plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - - uint256 intermediateBalanceBefore = currency1.balanceOfSelf(); - - (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(currency0, currency2, amountIn); - - // check intermediate token balances - assertEq(intermediateBalanceBefore, currency1.balanceOfSelf()); - assertEq(currency0.balanceOf(address(router)), 0); - assertEq(currency1.balanceOf(address(router)), 0); - assertEq(currency2.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); - } - - function test_swapExactIn_3Hops() public { - uint256 amountIn = 1 ether; - uint256 expectedAmountOut = 976467664490096191; - - tokenPath.push(currency0); - tokenPath.push(currency1); - tokenPath.push(currency2); - tokenPath.push(currency3); - IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); - - plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - - (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(currency0, currency3, amountIn); - - // check intermediate tokens werent left in the router - assertEq(currency0.balanceOf(address(router)), 0); - assertEq(currency1.balanceOf(address(router)), 0); - assertEq(currency2.balanceOf(address(router)), 0); - assertEq(currency3.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); - } - - function test_swap_settleRouterBalance_swapOpenDelta() public { - uint256 amountIn = 1 ether; - uint256 expectedAmountOut = 992054607780215625; - - key0.currency0.transfer(address(router), amountIn); - - // amount in of 0 to show it should use the open delta - IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, ActionConstants.OPEN_DELTA, 0, 0, bytes("")); - - plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, ActionConstants.CONTRACT_BALANCE, false)); - plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, MIN_TAKE_AMOUNT)); - - bytes memory data = plan.encode(); - - uint256 callerInputBefore = key0.currency0.balanceOfSelf(); - uint256 routerInputBefore = key0.currency0.balanceOf(address(router)); - uint256 callerOutputBefore = key0.currency1.balanceOfSelf(); - router.executeActions(data); - - uint256 callerInputAfter = key0.currency0.balanceOfSelf(); - uint256 routerInputAfter = key0.currency0.balanceOf(address(router)); - uint256 callerOutputAfter = key0.currency1.balanceOfSelf(); - - // caller didnt pay, router paid, caller received the output - assertEq(callerInputBefore, callerInputAfter); - assertEq(routerInputBefore - amountIn, routerInputAfter); - assertEq(callerOutputBefore + expectedAmountOut, callerOutputAfter); - } - - /*////////////////////////////////////////////////////////////// - ETH -> ERC20 and ERC20 -> ETH EXACT INPUT - //////////////////////////////////////////////////////////////*/ - - function test_nativeIn_swapExactInputSingle() public { - uint256 amountIn = 1 ether; - uint256 expectedAmountOut = 992054607780215625; - - IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(nativeKey, true, uint128(amountIn), 0, 0, bytes("")); - - plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - - (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(nativeKey.currency0, nativeKey.currency1, amountIn); - - assertEq(nativeKey.currency0.balanceOf(address(router)), 0); - assertEq(nativeKey.currency1.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); - } - - function test_nativeOut_swapExactInputSingle() public { - uint256 amountIn = 1 ether; - uint256 expectedAmountOut = 992054607780215625; - - // native output means we need !zeroForOne - IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(nativeKey, false, uint128(amountIn), 0, 0, bytes("")); - - plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - - (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(nativeKey.currency1, nativeKey.currency0, amountIn); - - assertEq(nativeKey.currency0.balanceOf(address(router)), 0); - assertEq(nativeKey.currency1.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); - } - - function test_nativeIn_swapExactIn_1Hop() public { - uint256 amountIn = 1 ether; - uint256 expectedAmountOut = 992054607780215625; - - tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); - tokenPath.push(nativeKey.currency1); - IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); - plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - - (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(CurrencyLibrary.ADDRESS_ZERO, nativeKey.currency1, amountIn); - - assertEq(nativeKey.currency0.balanceOf(address(router)), 0); - assertEq(nativeKey.currency1.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); - } - - function test_nativeOut_swapExactIn_1Hop() public { - uint256 amountIn = 1 ether; - uint256 expectedAmountOut = 992054607780215625; - - tokenPath.push(nativeKey.currency1); - tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); - IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); - - plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - - (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(nativeKey.currency1, CurrencyLibrary.ADDRESS_ZERO, amountIn); - - assertEq(nativeKey.currency0.balanceOf(address(router)), 0); - assertEq(nativeKey.currency1.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); - } - - function test_nativeIn_swapExactIn_2Hops() public { - uint256 amountIn = 1 ether; - uint256 expectedAmountOut = 984211133872795298; - - // the initialized nativeKey is (native, currency0) - tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); - tokenPath.push(currency0); - tokenPath.push(currency1); - IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); - - plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - - uint256 intermediateBalanceBefore = currency0.balanceOfSelf(); - - (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(CurrencyLibrary.ADDRESS_ZERO, currency1, amountIn); - - // check intermediate token balances - assertEq(intermediateBalanceBefore, currency0.balanceOfSelf()); - - assertEq(nativeKey.currency0.balanceOf(address(router)), 0); - assertEq(nativeKey.currency1.balanceOf(address(router)), 0); - assertEq(currency1.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); - } - - function test_nativeOut_swapExactIn_2Hops() public { - uint256 amountIn = 1 ether; - uint256 expectedAmountOut = 984211133872795298; - // the initialized nativeKey is (native, currency0) - tokenPath.push(currency1); - tokenPath.push(currency0); - tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); - IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); - - plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - - uint256 intermediateBalanceBefore = currency0.balanceOfSelf(); - - (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(currency1, CurrencyLibrary.ADDRESS_ZERO, amountIn); - - // check intermediate token balances - assertEq(intermediateBalanceBefore, currency0.balanceOfSelf()); - assertEq(nativeKey.currency0.balanceOf(address(router)), 0); - assertEq(nativeKey.currency1.balanceOf(address(router)), 0); - assertEq(currency1.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); - } - - function test_swap_nativeIn_settleRouterBalance_swapOpenDelta() public { - uint256 amountIn = 1 ether; - uint256 expectedAmountOut = 992054607780215625; - - nativeKey.currency0.transfer(address(router), amountIn); - - // amount in of 0 to show it should use the open delta - IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(nativeKey, true, ActionConstants.OPEN_DELTA, 0, 0, bytes("")); - - plan = plan.add(Actions.SETTLE, abi.encode(nativeKey.currency0, ActionConstants.CONTRACT_BALANCE, false)); - plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(nativeKey.currency1, MIN_TAKE_AMOUNT)); - - bytes memory data = plan.encode(); - - uint256 callerInputBefore = nativeKey.currency0.balanceOfSelf(); - uint256 routerInputBefore = nativeKey.currency0.balanceOf(address(router)); - uint256 callerOutputBefore = nativeKey.currency1.balanceOfSelf(); - router.executeActions(data); - - uint256 callerInputAfter = nativeKey.currency0.balanceOfSelf(); - uint256 routerInputAfter = nativeKey.currency0.balanceOf(address(router)); - uint256 callerOutputAfter = nativeKey.currency1.balanceOfSelf(); - - // caller didnt pay, router paid, caller received the output - assertEq(callerInputBefore, callerInputAfter); - assertEq(routerInputBefore - amountIn, routerInputAfter); - assertEq(callerOutputBefore + expectedAmountOut, callerOutputAfter); - } - - /*//////////////////////////////////////////////////////////////å - ERC20 -> ERC20 EXACT OUTPUT - //////////////////////////////////////////////////////////////*/ - - function test_swapExactOutputSingle_revertsForAmountIn() public { - uint256 amountOut = 1 ether; - uint256 expectedAmountIn = 1008049273448486163; - - IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams( - key0, true, uint128(amountOut), uint128(expectedAmountIn - 1), 0, bytes("") - ); - - plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER); - - vm.expectRevert( - abi.encodeWithSelector(IV4Router.V4TooMuchRequested.selector, expectedAmountIn - 1, expectedAmountIn) - ); - router.executeActions(data); - } - - function test_swapExactOutputSingle_zeroForOne() public { - uint256 amountOut = 1 ether; - uint256 expectedAmountIn = 1008049273448486163; - - IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams( - key0, true, uint128(amountOut), uint128(expectedAmountIn + 1), 0, bytes("") - ); - - plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); - - (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(key0.currency0, key0.currency1, expectedAmountIn); - - assertEq(currency0.balanceOf(address(router)), 0); - assertEq(currency1.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); - } - - function test_swapExactOutputSingle_oneForZero() public { - uint256 amountOut = 1 ether; - uint256 expectedAmountIn = 1008049273448486163; - - IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams( - key0, false, uint128(amountOut), uint128(expectedAmountIn + 1), 0, bytes("") - ); - - plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); - - (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(key0.currency1, key0.currency0, expectedAmountIn); - - assertEq(currency0.balanceOf(address(router)), 0); - assertEq(currency1.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); - } - - function test_swapExactOutputSingle_swapOpenDelta() public { - uint256 expectedAmountIn = 1008049273448486163; - - IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams( - key0, true, ActionConstants.OPEN_DELTA, uint128(expectedAmountIn + 1), 0, bytes("") - ); - - plan = plan.add(Actions.TAKE, abi.encode(key0.currency1, ActionConstants.ADDRESS_THIS, 1 ether)); - plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); - plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, ActionConstants.OPEN_DELTA, true)); - - bytes memory data = plan.encode(); - - uint256 callerInputBefore = key0.currency0.balanceOfSelf(); - uint256 routerInputBefore = key0.currency1.balanceOfSelf(); - uint256 callerOutputBefore = key0.currency1.balanceOfSelf(); - - router.executeActions(data); - - uint256 callerInputAfter = key0.currency0.balanceOfSelf(); - uint256 routerInputAfter = key0.currency1.balanceOfSelf(); - uint256 callerOutputAfter = key0.currency1.balanceOfSelf(); - - // caller paid - assertEq(callerInputBefore - expectedAmountIn, callerInputAfter); - assertEq(routerInputBefore, routerInputAfter); - assertEq(callerOutputBefore, callerOutputAfter); - } - - function test_swapExactOut_revertsForAmountIn() public { - uint256 amountOut = 1 ether; - uint256 expectedAmountIn = 1008049273448486163; - - tokenPath.push(currency0); - tokenPath.push(currency1); - IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); - params.amountInMaximum = uint128(expectedAmountIn - 1); - - plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER); - - vm.expectRevert( - abi.encodeWithSelector(IV4Router.V4TooMuchRequested.selector, expectedAmountIn - 1, expectedAmountIn) - ); - router.executeActions(data); - } - - function test_swapExactOut_1Hop_zeroForOne() public { - uint256 amountOut = 1 ether; - uint256 expectedAmountIn = 1008049273448486163; - - tokenPath.push(currency0); - tokenPath.push(currency1); - IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); - - plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - - (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(key0.currency0, key0.currency1, expectedAmountIn); - - assertEq(currency0.balanceOf(address(router)), 0); - assertEq(currency1.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); - } - - function test_swapExactOut_1Hop_oneForZero() public { - uint256 amountOut = 1 ether; - uint256 expectedAmountIn = 1008049273448486163; - - tokenPath.push(currency1); - tokenPath.push(currency0); - IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); - - plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - - (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(currency1, currency0, expectedAmountIn); - - assertEq(currency0.balanceOf(address(router)), 0); - assertEq(currency1.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); - } - - function test_swapExactOut_2Hops() public { - uint256 amountOut = 1 ether; - uint256 expectedAmountIn = 1016204441757464409; - - tokenPath.push(currency0); - tokenPath.push(currency1); - tokenPath.push(currency2); - IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); - - uint256 intermediateBalanceBefore = currency1.balanceOfSelf(); - - plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - - (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(currency0, currency2, expectedAmountIn); - - assertEq(intermediateBalanceBefore, currency1.balanceOfSelf()); - assertEq(currency0.balanceOf(address(router)), 0); - assertEq(currency1.balanceOf(address(router)), 0); - assertEq(currency2.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); - } - - function test_swapExactOut_3Hops() public { - uint256 amountOut = 1 ether; - uint256 expectedAmountIn = 1024467570922834110; - - tokenPath.push(currency0); - tokenPath.push(currency1); - tokenPath.push(currency2); - tokenPath.push(currency3); - IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); - - plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - - (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(currency0, currency3, expectedAmountIn); - - assertEq(currency0.balanceOf(address(router)), 0); - assertEq(currency1.balanceOf(address(router)), 0); - assertEq(currency2.balanceOf(address(router)), 0); - assertEq(currency3.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); - } - - function test_swapExactOut_swapOpenDelta() public { - uint256 expectedAmountIn = 1008049273448486163; - - tokenPath.push(currency0); - tokenPath.push(currency1); - - IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, ActionConstants.OPEN_DELTA); - - plan = plan.add(Actions.TAKE, abi.encode(key0.currency1, ActionConstants.ADDRESS_THIS, 1 ether)); - plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, ActionConstants.OPEN_DELTA, true)); - - bytes memory data = plan.encode(); - - uint256 callerInputBefore = key0.currency0.balanceOfSelf(); - uint256 routerInputBefore = key0.currency1.balanceOfSelf(); - uint256 callerOutputBefore = key0.currency1.balanceOfSelf(); - - router.executeActions(data); - - uint256 callerInputAfter = key0.currency0.balanceOfSelf(); - uint256 routerInputAfter = key0.currency1.balanceOfSelf(); - uint256 callerOutputAfter = key0.currency1.balanceOfSelf(); - - // caller paid - assertEq(callerInputBefore - expectedAmountIn, callerInputAfter); - assertEq(routerInputBefore, routerInputAfter); - assertEq(callerOutputBefore, callerOutputAfter); - } - - /*////////////////////////////////////////////////////////////// - ETH -> ERC20 and ERC20 -> ETH EXACT OUTPUT - //////////////////////////////////////////////////////////////*/ - - function test_nativeIn_swapExactOutputSingle_sweepExcessETH() public { - uint256 amountOut = 1 ether; - uint256 expectedAmountIn = 1008049273448486163; - - IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams( - nativeKey, true, uint128(amountOut), uint128(expectedAmountIn + 1), 0, bytes("") + // assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); + require( + inputBalanceBefore - inputBalanceAfter == amountIn, + string.concat(uintToString(inputBalanceBefore - inputBalanceAfter), " != ", uintToString(amountIn)) ); - - plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); - - (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteNativeInputExactOutputSwap(nativeKey.currency0, nativeKey.currency1, expectedAmountIn); - - assertEq(nativeKey.currency0.balanceOf(address(router)), 0); - assertEq(nativeKey.currency1.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); - } - - function test_nativeOut_swapExactOutputSingle() public { - uint256 amountOut = 1 ether; - uint256 expectedAmountIn = 1008049273448486163; - - IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams( - nativeKey, false, uint128(amountOut), uint128(expectedAmountIn + 1), 0, bytes("") - ); - - plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); - - (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(nativeKey.currency1, nativeKey.currency0, expectedAmountIn); - - assertEq(nativeKey.currency0.balanceOf(address(router)), 0); - assertEq(nativeKey.currency1.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); - } - - function test_nativeIn_swapExactOut_1Hop_sweepExcessETH() public { - uint256 amountOut = 1 ether; - uint256 expectedAmountIn = 1008049273448486163; - - tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); - tokenPath.push(nativeKey.currency1); - IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); - - plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - - (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteNativeInputExactOutputSwap( - CurrencyLibrary.ADDRESS_ZERO, nativeKey.currency1, expectedAmountIn + // this contract's output balance has not changed because funds went to the router + // assertEq(outputBalanceAfter, outputBalanceBefore); + require( + outputBalanceAfter == outputBalanceBefore, + string.concat(uintToString(outputBalanceAfter), " != ", uintToString(outputBalanceBefore)) ); - - assertEq(nativeKey.currency0.balanceOf(address(router)), 0); - assertEq(nativeKey.currency1.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); - } - - function test_nativeOut_swapExactOut_1Hop() public { - uint256 amountOut = 1 ether; - uint256 expectedAmountIn = 1008049273448486163; - - tokenPath.push(nativeKey.currency1); - tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); - IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); - - plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - - (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(nativeKey.currency1, CurrencyLibrary.ADDRESS_ZERO, expectedAmountIn); - - assertEq(nativeKey.currency0.balanceOf(address(router)), 0); - assertEq(nativeKey.currency1.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); - } - - function test_nativeIn_swapExactOut_2Hops_sweepExcessETH() public { - uint256 amountOut = 1 ether; - uint256 expectedAmountIn = 1016204441757464409; - - // the initialized nativeKey is (native, currency0) - tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); - tokenPath.push(currency0); - tokenPath.push(currency1); - IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); - - uint256 intermediateBalanceBefore = currency0.balanceOfSelf(); - - plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - - (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteNativeInputExactOutputSwap(CurrencyLibrary.ADDRESS_ZERO, currency1, expectedAmountIn); - - assertEq(intermediateBalanceBefore, currency0.balanceOfSelf()); - assertEq(currency1.balanceOf(address(router)), 0); - assertEq(currency0.balanceOf(address(router)), 0); - assertEq(CurrencyLibrary.ADDRESS_ZERO.balanceOf(address(router)), 0); - - assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); } } diff --git a/test/router/V4Router2.t.sol b/test/router/V4Router2.t.sol new file mode 100644 index 000000000..614381cef --- /dev/null +++ b/test/router/V4Router2.t.sol @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {Currency, CurrencyLibrary} from "../../lib/v4-core/src/types/Currency.sol"; +import {IV4Router} from "../../src/interfaces/IV4Router.sol"; +import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol"; +import {Plan, Planner} from "../shared/Planner.sol"; +import {Actions} from "../../src/libraries/Actions.sol"; +import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; +import {MockV4Router} from "../mocks/MockV4Router.sol"; +import {PositionManager} from "../../src/PositionManager.sol"; +import {IHooks} from "../../lib/v4-core/src/interfaces/IHooks.sol"; + +contract V4RouterTest is RoutingTestHelpers { + using CurrencyLibrary for Currency; + using Planner for Plan; + + address alice; + address payable ra; + address payable ma; + address payable pos_manager; + + constructor(address payable routerAddr, address payable managerAddr, address payable positionManagerAddr) { + ra = routerAddr; + ma = managerAddr; + alice = msg.sender; + pos_manager = positionManagerAddr; + } + + function setUp() public { + setupRouterCurrenciesAndPoolsWithLiquidity(ra, ma, pos_manager); + } + + function test_swapExactInputSingle_oneForZero() public { + plan = Planner.init(); + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + IV4Router.ExactInputSingleParams memory params = + IV4Router.ExactInputSingleParams(key0, false, uint128(amountIn), 0, 0, bytes("")); + plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteSwap(key0.currency1, key0.currency0, amountIn); + + require(currency0.balanceOf(address(router)) == 0, "currency0 balance should be 0"); + require(currency1.balanceOf(address(router)) == 0, "currency1 balance should be 0"); + + require(inputBalanceBefore - inputBalanceAfter == amountIn, "input balance should be equal to amountIn"); + require( + outputBalanceAfter - outputBalanceBefore == expectedAmountOut, + "output balance should be equal to expectedAmountOut" + ); + } + + function test_swapExactInput_revertsForAmountOut() public { + plan = Planner.init(); + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + tokenPath.push(currency0); + tokenPath.push(currency1); + IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); + params.amountOutMinimum = uint128(expectedAmountOut + 1); + + plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER); + + vm.expectRevert( + abi.encodeWithSelector(IV4Router.V4TooLittleReceived.selector, 992054607780215625 + 1, 992054607780215625) + ); + router.executeActions(data); + } + + function test_swapExactIn_1Hop_zeroForOne() public { + plan = Planner.init(); + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + tokenPath.push(currency0); + tokenPath.push(currency1); + IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); + plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); + + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteSwap(currency0, currency1, amountIn); + + // assertEq(currency0.balanceOf(address(router)), 0); + require(currency0.balanceOf(address(router)) == 0, "currency0 balance should be 0"); + // assertEq(currency1.balanceOf(address(router)), 0); + require(currency1.balanceOf(address(router)) == 0, "currency1 balance should be 0"); + + // assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); + require(inputBalanceBefore - inputBalanceAfter == amountIn, "input balance should be equal to amountIn"); + // assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); + require( + outputBalanceAfter - outputBalanceBefore == expectedAmountOut, + "output balance should be equal to expectedAmountOut" + ); + } + + function test_swapExactIn_1Hop_oneForZero() public { + plan = Planner.init(); + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + tokenPath.push(currency1); + tokenPath.push(currency0); + IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); + + plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); + + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteSwap(currency1, currency0, amountIn); + + // assertEq(currency0.balanceOf(address(router)), 0); + require(currency0.balanceOf(address(router)) == 0, "currency0 balance should be 0"); + // assertEq(currency1.balanceOf(address(router)), 0); + require(currency1.balanceOf(address(router)) == 0, "currency1 balance should be 0"); + + // assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); + require(inputBalanceBefore - inputBalanceAfter == amountIn, "input balance should be equal to amountIn"); + // assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); + require( + outputBalanceAfter - outputBalanceBefore == expectedAmountOut, + "output balance should be equal to expectedAmountOut" + ); + } + + function test_swapExactIn_2Hops() public { + plan = Planner.init(); + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 984211133872795298; + + tokenPath.push(currency0); + tokenPath.push(currency1); + tokenPath.push(currency2); + IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); + + plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); + + uint256 intermediateBalanceBefore = currency1.balanceOfSelf(); + + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteSwap(currency0, currency2, amountIn); + + // check intermediate token balances + // assertEq(intermediateBalanceBefore, currency1.balanceOfSelf()); + require( + intermediateBalanceBefore == currency1.balanceOfSelf(), + "intermediate balance should be equal to currency1 balance" + ); + // assertEq(currency0.balanceOf(address(router)), 0); + require(currency0.balanceOf(address(router)) == 0, "currency0 balance should be 0"); + // assertEq(currency1.balanceOf(address(router)), 0); + require(currency1.balanceOf(address(router)) == 0, "currency1 balance should be 0"); + // assertEq(currency2.balanceOf(address(router)), 0); + require(currency2.balanceOf(address(router)) == 0, "currency2 balance should be 0"); + + // assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); + require(inputBalanceBefore - inputBalanceAfter == amountIn, "input balance should be equal to amountIn"); + // assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); + require( + outputBalanceAfter - outputBalanceBefore == expectedAmountOut, + "output balance should be equal to expectedAmountOut" + ); + } +} diff --git a/test/router/V4Router3.t.sol b/test/router/V4Router3.t.sol new file mode 100644 index 000000000..46dd67e97 --- /dev/null +++ b/test/router/V4Router3.t.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {Currency, CurrencyLibrary} from "../../lib/v4-core/src/types/Currency.sol"; +import {IV4Router} from "../../src/interfaces/IV4Router.sol"; +import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol"; +import {Plan, Planner} from "../shared/Planner.sol"; +import {Actions} from "../../src/libraries/Actions.sol"; +import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; +import {MockV4Router} from "../mocks/MockV4Router.sol"; +import {PositionManager} from "../../src/PositionManager.sol"; + +contract V4RouterTest is RoutingTestHelpers { + using CurrencyLibrary for Currency; + using Planner for Plan; + + address alice; + address payable ra; + address payable ma; + address payable pos_manager; + + constructor(address payable routerAddr, address payable managerAddr, address payable positionManagerAddr) { + ra = routerAddr; + ma = managerAddr; + alice = msg.sender; + pos_manager = positionManagerAddr; + } + + function setUp() public { + setupRouterCurrenciesAndPoolsWithLiquidity(ra, ma, pos_manager); + } + + function test_swapExactIn_3Hops() public { + plan = Planner.init(); + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 976467664490096191; + + tokenPath.push(currency0); + tokenPath.push(currency1); + tokenPath.push(currency2); + tokenPath.push(currency3); + IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); + + plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); + + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteSwap(currency0, currency3, amountIn); + + // check intermediate tokens werent left in the router + // assertEq(currency0.balanceOf(address(router)), 0); + require(currency0.balanceOf(address(router)) == 0, "currency0 balance should be 0"); + // assertEq(currency1.balanceOf(address(router)), 0); + require(currency1.balanceOf(address(router)) == 0, "currency1 balance should be 0"); + // assertEq(currency2.balanceOf(address(router)), 0); + require(currency2.balanceOf(address(router)) == 0, "currency2 balance should be 0"); + // assertEq(currency3.balanceOf(address(router)), 0); + require(currency3.balanceOf(address(router)) == 0, "currency3 balance should be 0"); + + // assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); + require(inputBalanceBefore - inputBalanceAfter == amountIn, "input balance should be equal to amountIn"); + // assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); + require( + outputBalanceAfter - outputBalanceBefore == expectedAmountOut, + "output balance should be equal to expectedAmountOut" + ); + } + + function test_swap_settleRouterBalance_swapOpenDelta() public { + plan = Planner.init(); + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + key0.currency0.transfer(address(router), amountIn); + + // amount in of 0 to show it should use the open delta + IV4Router.ExactInputSingleParams memory params = + IV4Router.ExactInputSingleParams(key0, true, ActionConstants.OPEN_DELTA, 0, 0, bytes("")); + + plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, ActionConstants.CONTRACT_BALANCE, false)); + plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, MIN_TAKE_AMOUNT)); + + bytes memory data = plan.encode(); + + uint256 callerInputBefore = key0.currency0.balanceOfSelf(); + uint256 routerInputBefore = key0.currency0.balanceOf(address(router)); + uint256 callerOutputBefore = key0.currency1.balanceOfSelf(); + router.executeActions(data); + + uint256 callerInputAfter = key0.currency0.balanceOfSelf(); + uint256 routerInputAfter = key0.currency0.balanceOf(address(router)); + uint256 callerOutputAfter = key0.currency1.balanceOfSelf(); + + // caller didnt pay, router paid, caller received the output + // assertEq(callerInputBefore, callerInputAfter); + require(callerInputBefore == callerInputAfter, "caller input before should be equal to caller input after"); + // assertEq(routerInputBefore - amountIn, routerInputAfter); + require( + routerInputBefore - amountIn == routerInputAfter, + "router input before should be equal to router input after" + ); + // assertEq(callerOutputBefore + expectedAmountOut, callerOutputAfter); + require( + callerOutputBefore + expectedAmountOut == callerOutputAfter, + "caller output before should be equal to caller output after" + ); + } + + function test_nativeIn_swapExactInputSingle() public { + plan = Planner.init(); + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + IV4Router.ExactInputSingleParams memory params = + IV4Router.ExactInputSingleParams(nativeKey, true, uint128(amountIn), 0, 0, bytes("")); + + plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteSwap(nativeKey.currency0, nativeKey.currency1, amountIn); + + // assertEq(nativeKey.currency0.balanceOf(address(router)), 0); + require(nativeKey.currency0.balanceOf(address(router)) == 0, "router balance should be equal 0"); + // assertEq(nativeKey.currency1.balanceOf(address(router)), 0); + require(nativeKey.currency1.balanceOf(address(router)) == 0, "router balance should be equal 0"); + + // assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); + require( + outputBalanceAfter - outputBalanceBefore == expectedAmountOut, + "output balance after should be equal expected amount out" + ); + // assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); + require( + outputBalanceAfter - outputBalanceBefore == expectedAmountOut, + "output balance after should be equal expected amount out" + ); + } +} diff --git a/test/router/V4Router4.t.sol b/test/router/V4Router4.t.sol new file mode 100644 index 000000000..e04495049 --- /dev/null +++ b/test/router/V4Router4.t.sol @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {Currency, CurrencyLibrary} from "../../lib/v4-core/src/types/Currency.sol"; +import {IV4Router} from "../../src/interfaces/IV4Router.sol"; +import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol"; +import {Plan, Planner} from "../shared/Planner.sol"; +import {Actions} from "../../src/libraries/Actions.sol"; +import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; +import {MockV4Router} from "../mocks/MockV4Router.sol"; +import {PositionManager} from "../../src/PositionManager.sol"; + +contract V4RouterTest is RoutingTestHelpers { + using CurrencyLibrary for Currency; + using Planner for Plan; + + address alice; + address payable ra; + address payable ma; + address payable pos_manager; + + constructor(address payable routerAddr, address payable managerAddr, address payable positionManagerAddr) { + ra = routerAddr; + ma = managerAddr; + alice = msg.sender; + pos_manager = positionManagerAddr; + } + + function setUp() public { + setupRouterCurrenciesAndPoolsWithLiquidity(ra, ma, pos_manager); + plan = Planner.init(); + } + + function uintToString(uint256 _value) public pure returns (string memory) { + if (_value == 0) { + return "0"; + } + uint256 temp = _value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (_value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(_value % 10))); + _value /= 10; + } + return string(buffer); + } + + function test_nativeOut_swapExactInputSingle() public { + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + // native output means we need !zeroForOne + IV4Router.ExactInputSingleParams memory params = + IV4Router.ExactInputSingleParams(nativeKey, false, uint128(amountIn), 0, 0, bytes("")); + + plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteSwap(nativeKey.currency1, nativeKey.currency0, amountIn); + + // assertEq(nativeKey.currency0.balanceOf(address(router)), 0); + require(nativeKey.currency0.balanceOf(address(router)) == 0, "router balance should be equal 0"); + // assertEq(nativeKey.currency1.balanceOf(address(router)), 0); + require(nativeKey.currency1.balanceOf(address(router)) == 0, "router balance should be equal 0"); + + // assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); + require( + inputBalanceBefore - inputBalanceAfter == amountIn, + "input balance before should be equal input balance after" + ); + // assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); + require( + outputBalanceAfter - outputBalanceBefore == expectedAmountOut, + string.concat( + "output balance ", + uintToString(outputBalanceAfter - outputBalanceBefore), + " after should be equal expected amount out ", + uintToString(expectedAmountOut) + ) + ); + } + + function test_nativeIn_swapExactIn_1Hop() public { + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); + tokenPath.push(nativeKey.currency1); + IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); + plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); + + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteSwap(CurrencyLibrary.ADDRESS_ZERO, nativeKey.currency1, amountIn); + + // assertEq(nativeKey.currency0.balanceOf(address(router)), 0); + require(nativeKey.currency0.balanceOf(address(router)) == 0, "router balance should be equal 0"); + // assertEq(nativeKey.currency1.balanceOf(address(router)), 0); + require(nativeKey.currency1.balanceOf(address(router)) == 0, "router balance should be equal 0"); + + // assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); + require( + inputBalanceBefore - inputBalanceAfter == amountIn, + "input balance before should be equal input balance after" + ); + // assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); + require( + outputBalanceAfter - outputBalanceBefore == expectedAmountOut, + "output balance after should be equal expected amount out" + ); + } + + function test_nativeOut_swapExactIn_1Hop() public { + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + tokenPath.push(nativeKey.currency1); + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); + IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); + + plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); + + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteSwap(nativeKey.currency1, CurrencyLibrary.ADDRESS_ZERO, amountIn); + + // assertEq(nativeKey.currency0.balanceOf(address(router)), 0); + require(nativeKey.currency0.balanceOf(address(router)) == 0, "router balance should be equal 0"); + // assertEq(nativeKey.currency1.balanceOf(address(router)), 0); + require(nativeKey.currency1.balanceOf(address(router)) == 0, "router balance should be equal 0"); + + // assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); + require( + inputBalanceBefore - inputBalanceAfter == amountIn, + "input balance before should be equal input balance after" + ); + // assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); + require( + outputBalanceAfter - outputBalanceBefore == expectedAmountOut, + string.concat( + "output balance ", + uintToString(outputBalanceAfter - outputBalanceBefore), + " after should be equal expected amount out ", + uintToString(expectedAmountOut) + ) + ); + } + + function test_nativeIn_swapExactIn_2Hops() public { + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 984211133872795298; + + // the initialized nativeKey is (native, currency0) + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); + tokenPath.push(currency0); + tokenPath.push(currency1); + IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); + + plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); + + uint256 intermediateBalanceBefore = currency0.balanceOfSelf(); + + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteSwap(CurrencyLibrary.ADDRESS_ZERO, currency1, amountIn); + + // check intermediate token balances + // assertEq(intermediateBalanceBefore, currency0.balanceOfSelf()); + require( + intermediateBalanceBefore == currency0.balanceOfSelf(), + "intermediate balance before should be equal currency0 balance of self" + ); + + // assertEq(nativeKey.currency0.balanceOf(address(router)), 0); + require(nativeKey.currency0.balanceOf(address(router)) == 0, "router balance should be equal 0"); + // assertEq(nativeKey.currency1.balanceOf(address(router)), 0); + require(nativeKey.currency1.balanceOf(address(router)) == 0, "router balance should be equal 0"); + // assertEq(currency1.balanceOf(address(router)), 0); + require(currency1.balanceOf(address(router)) == 0, "router balance should be equal 0"); + + // assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); + require( + inputBalanceBefore - inputBalanceAfter == amountIn, + "output balance after should be equal expected amount out" + ); + // assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); + require( + outputBalanceAfter - outputBalanceBefore == expectedAmountOut, + "output balance after should be equal expected amount out" + ); + } +} diff --git a/test/router/V4Router5.t.sol b/test/router/V4Router5.t.sol new file mode 100644 index 000000000..559c180d4 --- /dev/null +++ b/test/router/V4Router5.t.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {Currency, CurrencyLibrary} from "../../lib/v4-core/src/types/Currency.sol"; +import {IV4Router} from "../../src/interfaces/IV4Router.sol"; +import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol"; +import {Plan, Planner} from "../shared/Planner.sol"; +import {Actions} from "../../src/libraries/Actions.sol"; +import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; +import {MockV4Router} from "../mocks/MockV4Router.sol"; +import {PositionManager} from "../../src/PositionManager.sol"; + +contract V4RouterTest is RoutingTestHelpers { + using CurrencyLibrary for Currency; + using Planner for Plan; + + address alice; + address payable ra; + address payable ma; + address payable pos_manager; + + constructor(address payable routerAddr, address payable managerAddr, address payable positionManagerAddr) { + ra = routerAddr; + ma = managerAddr; + alice = msg.sender; + pos_manager = positionManagerAddr; + } + + function setUp() public { + setupRouterCurrenciesAndPoolsWithLiquidity(ra, ma, pos_manager); + } + + function uintToString(uint256 _value) public pure returns (string memory) { + if (_value == 0) { + return "0"; + } + uint256 temp = _value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (_value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(_value % 10))); + _value /= 10; + } + return string(buffer); + } + + function test_nativeOut_swapExactIn_2Hops() public { + plan = Planner.init(); + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 984211133872795298; + + // the initialized nativeKey is (native, currency0) + tokenPath.push(currency1); + tokenPath.push(currency0); + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); + IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); + + plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); + + uint256 intermediateBalanceBefore = currency0.balanceOfSelf(); + + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteSwap(currency1, CurrencyLibrary.ADDRESS_ZERO, amountIn); + + // check intermediate token balances + // assertEq(intermediateBalanceBefore, currency0.balanceOfSelf()); + require(intermediateBalanceBefore == currency0.balanceOfSelf(), "intermediate balance should be equal"); + // assertEq(nativeKey.currency0.balanceOf(address(router)), 0); + require(nativeKey.currency0.balanceOf(address(router)) == 0, "router balance should be equal 0"); + // assertEq(nativeKey.currency1.balanceOf(address(router)), 0); + require(nativeKey.currency1.balanceOf(address(router)) == 0, "router balance should be equal 0"); + // assertEq(currency1.balanceOf(address(router)), 0); + require(currency1.balanceOf(address(router)) == 0, "router balance should be equal 0"); + + // assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); + require( + inputBalanceBefore - inputBalanceAfter == amountIn, + "input balance before should be equal input balance after" + ); + // assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); + require( + outputBalanceAfter - outputBalanceBefore == expectedAmountOut, + string.concat( + "output balance ", + uintToString(outputBalanceAfter - outputBalanceBefore), + " after should be equal expected amount out ", + uintToString(expectedAmountOut) + ) + ); + } + + function test_swap_nativeIn_settleRouterBalance_swapOpenDelta() public { + plan = Planner.init(); + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + nativeKey.currency0.transfer(address(router), amountIn); + + // amount in of 0 to show it should use the open delta + IV4Router.ExactInputSingleParams memory params = + IV4Router.ExactInputSingleParams(nativeKey, true, ActionConstants.OPEN_DELTA, 0, 0, bytes("")); + + plan = plan.add(Actions.SETTLE, abi.encode(nativeKey.currency0, ActionConstants.CONTRACT_BALANCE, false)); + plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(nativeKey.currency1, MIN_TAKE_AMOUNT)); + + bytes memory data = plan.encode(); + + uint256 callerInputBefore = nativeKey.currency0.balanceOfSelf(); + uint256 routerInputBefore = nativeKey.currency0.balanceOf(address(router)); + uint256 callerOutputBefore = nativeKey.currency1.balanceOfSelf(); + router.executeActions(data); + + uint256 callerInputAfter = nativeKey.currency0.balanceOfSelf(); + uint256 routerInputAfter = nativeKey.currency0.balanceOf(address(router)); + uint256 callerOutputAfter = nativeKey.currency1.balanceOfSelf(); + + // caller didnt pay, router paid, caller received the output + // assertEq(callerInputBefore, callerInputAfter); + require(callerInputBefore == callerInputAfter, "caller input before should be equal to caller input after"); + // assertEq(routerInputBefore - amountIn, routerInputAfter); + require( + routerInputBefore - amountIn == routerInputAfter, + "router input before should be equal to router input after" + ); + // assertEq(callerOutputBefore + expectedAmountOut, callerOutputAfter); + require( + callerOutputBefore + expectedAmountOut == callerOutputAfter, + "caller output before should be equal to caller output after" + ); + } +} diff --git a/test/router/V4Router6.t.sol b/test/router/V4Router6.t.sol new file mode 100644 index 000000000..82d627b83 --- /dev/null +++ b/test/router/V4Router6.t.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {Currency, CurrencyLibrary} from "../../lib/v4-core/src/types/Currency.sol"; +import {IV4Router} from "../../src/interfaces/IV4Router.sol"; +import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol"; +import {Plan, Planner} from "../shared/Planner.sol"; +import {Actions} from "../../src/libraries/Actions.sol"; +import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; +import {MockV4Router} from "../mocks/MockV4Router.sol"; +import {PositionManager} from "../../src/PositionManager.sol"; + +contract V4RouterTest is RoutingTestHelpers { + using CurrencyLibrary for Currency; + using Planner for Plan; + + address alice; + address payable ra; + address payable ma; + address payable pos_manager; + + constructor(address payable routerAddr, address payable managerAddr, address payable positionManagerAddr) { + ra = routerAddr; + ma = managerAddr; + alice = msg.sender; + pos_manager = positionManagerAddr; + } + + function setUp() public { + setupRouterCurrenciesAndPoolsWithLiquidity(ra, ma, pos_manager); + } + + function uintToString(uint256 _value) public pure returns (string memory) { + if (_value == 0) { + return "0"; + } + uint256 temp = _value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (_value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(_value % 10))); + _value /= 10; + } + return string(buffer); + } + + // /*//////////////////////////////////////////////////////////////å + // ERC20 -> ERC20 EXACT OUTPUT + // //////////////////////////////////////////////////////////////*/ + + function test_swapExactOutputSingle_revertsForAmountIn() public { + plan = Planner.init(); + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1008049273448486163; + + IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams( + key0, true, uint128(amountOut), uint128(expectedAmountIn - 1), 0, bytes("") + ); + + plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER); + + vm.expectRevert(IV4Router.V4TooMuchRequested.selector); + router.executeActions(data); + } + + function test_swapExactOutputSingle_zeroForOne() public { + plan = Planner.init(); + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1008049273448486163; + + IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams( + key0, true, uint128(amountOut), uint128(expectedAmountIn + 1), 0, bytes("") + ); + + plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); + + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteSwap(key0.currency0, key0.currency1, expectedAmountIn); + + // assertEq(currency0.balanceOf(address(router)), 0); + require(currency0.balanceOf(address(router)) == 0, "currency0 balance should be 0"); + // assertEq(currency1.balanceOf(address(router)), 0); + require(currency1.balanceOf(address(router)) == 0, "currency1 balance should be 0"); + + // assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); + require( + inputBalanceBefore - inputBalanceAfter == expectedAmountIn, + "input balance before should be equal to input balance after" + ); + // assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); + require( + outputBalanceAfter - outputBalanceBefore == amountOut, "output balance after should be equal to amount out" + ); + } + + function test_swapExactOutputSingle_oneForZero() public { + plan = Planner.init(); + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1008049273448486163; + + IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams( + key0, false, uint128(amountOut), uint128(expectedAmountIn + 1), 0, bytes("") + ); + + plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); + + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteSwap(key0.currency1, key0.currency0, expectedAmountIn); + + // assertEq(currency0.balanceOf(address(router)), 0); + require(currency0.balanceOf(address(router)) == 0, "currency0 balance should be 0"); + // assertEq(currency1.balanceOf(address(router)), 0); + require(currency1.balanceOf(address(router)) == 0, "currency1 balance should be 0"); + + // assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); + require( + inputBalanceBefore - inputBalanceAfter == expectedAmountIn, + "input balance before should be equal to input balance after" + ); + // assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); + require( + outputBalanceAfter - outputBalanceBefore == amountOut, "output balance after should be equal to amount out" + ); + } + + function test_swapExactOut_revertsForAmountIn() public { + plan = Planner.init(); + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1008049273448486163; + + tokenPath.push(currency0); + tokenPath.push(currency1); + IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); + params.amountInMaximum = uint128(expectedAmountIn - 1); + + plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER); + + vm.expectRevert( + abi.encodeWithSelector(IV4Router.V4TooMuchRequested.selector, expectedAmountIn - 1, expectedAmountIn) + ); + router.executeActions(data); + } +} diff --git a/test/router/V4Router7.t.sol b/test/router/V4Router7.t.sol new file mode 100644 index 000000000..94c7360ed --- /dev/null +++ b/test/router/V4Router7.t.sol @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {Currency, CurrencyLibrary} from "../../lib/v4-core/src/types/Currency.sol"; +import {IV4Router} from "../../src/interfaces/IV4Router.sol"; +import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol"; +import {Plan, Planner} from "../shared/Planner.sol"; +import {Actions} from "../../src/libraries/Actions.sol"; +import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; +import {MockV4Router} from "../mocks/MockV4Router.sol"; +import {PositionManager} from "../../src/PositionManager.sol"; + +contract V4RouterTest is RoutingTestHelpers { + using CurrencyLibrary for Currency; + using Planner for Plan; + + address alice; + address payable ra; + address payable ma; + address payable pos_manager; + + constructor(address payable routerAddr, address payable managerAddr, address payable positionManagerAddr) { + ra = routerAddr; + ma = managerAddr; + alice = msg.sender; + pos_manager = positionManagerAddr; + } + + function setUp() public { + setupRouterCurrenciesAndPoolsWithLiquidity(ra, ma, pos_manager); + plan = Planner.init(); + } + + function uintToString(uint256 _value) public pure returns (string memory) { + if (_value == 0) { + return "0"; + } + uint256 temp = _value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (_value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(_value % 10))); + _value /= 10; + } + return string(buffer); + } + + function test_swapExactOut_1Hop_zeroForOne() public { + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1008049273448486163; + + tokenPath.push(currency0); + tokenPath.push(currency1); + IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); + + plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); + + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteSwap(key0.currency0, key0.currency1, expectedAmountIn); + + require(currency0.balanceOf(address(router)) == 0, "currency0 balance should be 0"); + // assertEq(currency0.balanceOf(address(router)), 0); + require(currency1.balanceOf(address(router)) == 0, "currency1 balance should be 0"); + // assertEq(currency1.balanceOf(address(router)), 0); + + // assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); + require( + inputBalanceBefore - inputBalanceAfter == expectedAmountIn, + "input balance should be equal to expectedAmountIn" + ); + // assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); + require(outputBalanceAfter - outputBalanceBefore == amountOut, "output balance should be equal to amountOut"); + } + + function test_swapExactOut_1Hop_oneForZero() public { + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1008049273448486163; + + tokenPath.push(currency1); + tokenPath.push(currency0); + IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); + + plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); + + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteSwap(currency1, currency0, expectedAmountIn); + + // assertEq(currency0.balanceOf(address(router)), 0); + require(currency0.balanceOf(address(router)) == 0, "currency0 balance should be 0"); + // assertEq(currency1.balanceOf(address(router)), 0); + require(currency1.balanceOf(address(router)) == 0, "currency1 balance should be 0"); + + // assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); + require( + inputBalanceBefore - inputBalanceAfter == expectedAmountIn, + "input balance should be equal to expectedAmountIn" + ); + // assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); + require(outputBalanceAfter - outputBalanceBefore == amountOut, "output balance should be equal to amountOut"); + } + + function test_swapExactOut_2Hops() public { + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1016204441757464409; + + tokenPath.push(currency0); + tokenPath.push(currency1); + tokenPath.push(currency2); + IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); + + uint256 intermediateBalanceBefore = currency1.balanceOfSelf(); + + plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); + + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteSwap(currency0, currency2, expectedAmountIn); + + // assertEq(intermediateBalanceBefore, currency1.balanceOfSelf()); + require(intermediateBalanceBefore == currency1.balanceOfSelf(), "intermediate balance should be equal"); + // assertEq(currency0.balanceOf(address(router)), 0); + require(currency0.balanceOf(address(router)) == 0, "currency0 balance should be 0"); + // assertEq(currency1.balanceOf(address(router)), 0); + require(currency1.balanceOf(address(router)) == 0, "currency1 balance should be 0"); + // assertEq(currency2.balanceOf(address(router)), 0); + require(currency2.balanceOf(address(router)) == 0, "currency2 balance should be 0"); + + // assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); + require( + inputBalanceBefore - inputBalanceAfter == expectedAmountIn, + "input balance should be equal to expectedAmountIn" + ); + // assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); + require(outputBalanceAfter - outputBalanceBefore == amountOut, "output balance should be equal to amountOut"); + } + + function test_swapExactOut_3Hops() public { + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1024467570922834110; + + tokenPath.push(currency0); + tokenPath.push(currency1); + tokenPath.push(currency2); + tokenPath.push(currency3); + IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); + + plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); + + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteSwap(currency0, currency3, expectedAmountIn); + + // assertEq(currency0.balanceOf(address(router)), 0); + require(currency0.balanceOf(address(router)) == 0, "currency0 balance should be 0"); + // assertEq(currency1.balanceOf(address(router)), 0); + require(currency1.balanceOf(address(router)) == 0, "currency1 balance should be 0"); + // assertEq(currency2.balanceOf(address(router)), 0); + require(currency2.balanceOf(address(router)) == 0, "currency2 balance should be 0"); + // assertEq(currency3.balanceOf(address(router)), 0); + require(currency3.balanceOf(address(router)) == 0, "currency3 balance should be 0"); + + // assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); + require( + inputBalanceBefore - inputBalanceAfter == expectedAmountIn, + "input balance should be equal to expectedAmountIn" + ); + // assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); + require(outputBalanceAfter - outputBalanceBefore == amountOut, "output balance should be equal to amountOut"); + } +} diff --git a/test/router/V4Router8.t.sol b/test/router/V4Router8.t.sol new file mode 100644 index 000000000..7c22bd5f1 --- /dev/null +++ b/test/router/V4Router8.t.sol @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {Currency, CurrencyLibrary} from "../../lib/v4-core/src/types/Currency.sol"; +import {IV4Router} from "../../src/interfaces/IV4Router.sol"; +import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol"; +import {Plan, Planner} from "../shared/Planner.sol"; +import {Actions} from "../../src/libraries/Actions.sol"; +import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; +import {MockV4Router} from "../mocks/MockV4Router.sol"; +import {PositionManager} from "../../src/PositionManager.sol"; + +contract V4RouterTest is RoutingTestHelpers { + using CurrencyLibrary for Currency; + using Planner for Plan; + + address alice; + address payable ra; + address payable ma; + address payable pos_manager; + + constructor(address payable routerAddr, address payable managerAddr, address payable positionManagerAddr) { + ra = routerAddr; + ma = managerAddr; + alice = msg.sender; + pos_manager = positionManagerAddr; + } + + function setUp() public { + setupRouterCurrenciesAndPoolsWithLiquidity(ra, ma, pos_manager); + } + + function uintToString(uint256 _value) public pure returns (string memory) { + if (_value == 0) { + return "0"; + } + uint256 temp = _value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (_value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(_value % 10))); + _value /= 10; + } + return string(buffer); + } + + // /*////////////////////////////////////////////////////////////// + // ETH -> ERC20 and ERC20 -> ETH EXACT OUTPUT + // //////////////////////////////////////////////////////////////*/ + + function test_nativeIn_swapExactOutputSingle_sweepExcessETH() public { + plan = Planner.init(); + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1008049273448486163; + + IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams( + nativeKey, true, uint128(amountOut), uint128(expectedAmountIn + 1), 0, bytes("") + ); + + plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); + + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteNativeInputExactOutputSwap(nativeKey.currency0, nativeKey.currency1, expectedAmountIn); + + // // assertEq(nativeKey.currency0.balanceOf(address(router)), 0); + require( + nativeKey.currency0.balanceOf(address(router)) == 0, + string.concat( + "currency0 balance should be 0", " got ", uintToString(nativeKey.currency0.balanceOf(address(router))) + ) + ); + // // assertEq(nativeKey.currency1.balanceOf(address(router)), 0); + require(nativeKey.currency1.balanceOf(address(router)) == 0, "currency1 balance should be 0"); + + // // assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); + require( + inputBalanceBefore - inputBalanceAfter == expectedAmountIn, + string.concat( + "input balance ", + uintToString(inputBalanceBefore - inputBalanceAfter), + " should be equal to expectedAmountIn ", + uintToString(expectedAmountIn) + ) + ); + // // assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); + require(outputBalanceAfter - outputBalanceBefore == amountOut, "output balance should be equal to amountOut"); + } + + function test_nativeOut_swapExactOutputSingle() public { + plan = Planner.init(); + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1008049273448486163; + + IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams( + nativeKey, false, uint128(amountOut), uint128(expectedAmountIn + 1), 0, bytes("") + ); + + plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); + + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteSwap(nativeKey.currency1, nativeKey.currency0, expectedAmountIn); + + // assertEq(nativeKey.currency0.balanceOf(address(router)), 0); + require(nativeKey.currency0.balanceOf(address(router)) == 0, "router balance should be equal 0"); + // assertEq(nativeKey.currency1.balanceOf(address(router)), 0); + require(nativeKey.currency1.balanceOf(address(router)) == 0, "router balance should be equal 0"); + + // assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); + require( + inputBalanceBefore - inputBalanceAfter == expectedAmountIn, + "input balance before should be equal input balance after" + ); + // assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); + require( + outputBalanceAfter - outputBalanceBefore == amountOut, + string.concat( + "output balance after ", + uintToString(outputBalanceAfter - outputBalanceBefore), + " should be equal to amountOut ", + uintToString(amountOut) + ) + ); + } + + function test_nativeIn_swapExactOut_1Hop_sweepExcessETH() public { + plan = Planner.init(); + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1008049273448486163; + + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); + tokenPath.push(nativeKey.currency1); + IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); + + plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); + + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteNativeInputExactOutputSwap( + CurrencyLibrary.ADDRESS_ZERO, nativeKey.currency1, expectedAmountIn + ); + + // assertEq(nativeKey.currency0.balanceOf(address(router)), 0); + require(nativeKey.currency0.balanceOf(address(router)) == 0, "router balance should be equal 0"); + // assertEq(nativeKey.currency1.balanceOf(address(router)), 0); + require(nativeKey.currency1.balanceOf(address(router)) == 0, "router balance should be equal 0"); + + // assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); + require( + inputBalanceBefore - inputBalanceAfter == expectedAmountIn, + "input balance before should be equal input balance after" + ); + // assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); + require( + outputBalanceAfter - outputBalanceBefore == amountOut, "output balance after should be equal to amountOut" + ); + } + + function test_nativeOut_swapExactOut_1Hop() public { + plan = Planner.init(); + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1008049273448486163; + + tokenPath.push(nativeKey.currency1); + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); + IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); + + plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); + + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteSwap(nativeKey.currency1, CurrencyLibrary.ADDRESS_ZERO, expectedAmountIn); + + // assertEq(nativeKey.currency0.balanceOf(address(router)), 0); + require(nativeKey.currency0.balanceOf(address(router)) == 0, "router balance should be equal 0"); + // assertEq(nativeKey.currency1.balanceOf(address(router)), 0); + require(nativeKey.currency1.balanceOf(address(router)) == 0, "router balance should be equal 0"); + + // assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); + require( + inputBalanceBefore - inputBalanceAfter == expectedAmountIn, + "input balance before should be equal input balance after" + ); + // assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); + require( + outputBalanceAfter - outputBalanceBefore == amountOut, + string.concat( + "output balance after ", + uintToString(outputBalanceAfter - outputBalanceBefore), + " should be equal expected amount out ", + uintToString(amountOut) + ) + ); + } +} diff --git a/test/router/V4Router9.t.sol b/test/router/V4Router9.t.sol new file mode 100644 index 000000000..d1a705101 --- /dev/null +++ b/test/router/V4Router9.t.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {Currency, CurrencyLibrary} from "../../lib/v4-core/src/types/Currency.sol"; +import {IV4Router} from "../../src/interfaces/IV4Router.sol"; +import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol"; +import {Plan, Planner} from "../shared/Planner.sol"; +import {Actions} from "../../src/libraries/Actions.sol"; +import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; +import {PositionManager} from "../../src/PositionManager.sol"; +import {IHooks} from "../../lib/v4-core/src/interfaces/IHooks.sol"; + +contract V4RouterTest is RoutingTestHelpers { + using CurrencyLibrary for Currency; + using Planner for Plan; + + address alice; + address payable ra; + address payable ma; + address payable pos_manager; + + constructor(address payable routerAddr, address payable managerAddr, address payable positionManagerAddr) { + ra = routerAddr; + ma = managerAddr; + alice = msg.sender; + pos_manager = positionManagerAddr; + } + + function setUp() public { + setupRouterCurrenciesAndPoolsWithLiquidity(ra, ma, pos_manager); + } + + function uintToString(uint256 _value) public pure returns (string memory) { + if (_value == 0) { + return "0"; + } + uint256 temp = _value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (_value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(_value % 10))); + _value /= 10; + } + return string(buffer); + } + + function test_nativeIn_swapExactOut_2Hops_sweepExcessETH() public { + plan = Planner.init(); + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1016204441757464409; + + // the initialized nativeKey is (native, currency0) + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); + tokenPath.push(currency0); + tokenPath.push(currency1); + IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); + + uint256 intermediateBalanceBefore = currency0.balanceOfSelf(); + + plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); + + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteNativeInputExactOutputSwap(CurrencyLibrary.ADDRESS_ZERO, currency1, expectedAmountIn); + + // assertEq(intermediateBalanceBefore, currency0.balanceOfSelf()); + require( + intermediateBalanceBefore == currency0.balanceOfSelf(), + string.concat( + "intermediateBalanceBefore != currency0.balanceOfSelf(), got ", uintToString(currency0.balanceOfSelf()) + ) + ); + // assertEq(currency1.balanceOf(address(router)), 0); + require(currency1.balanceOf(address(router)) == 0, "currency1 balance should be 0"); + // assertEq(currency0.balanceOf(address(router)), 0); + require(currency0.balanceOf(address(router)) == 0, "currency0 balance should be 0"); + // assertEq(CurrencyLibrary.ADDRESS_ZERO.balanceOf(address(router)), 0); + require(CurrencyLibrary.ADDRESS_ZERO.balanceOf(address(router)) == 0, "router balance should be 0"); + + // assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); + require( + inputBalanceBefore - inputBalanceAfter == expectedAmountIn, + string.concat( + "inputBalanceBefore - inputBalanceAfter != expectedAmountIn, got ", + uintToString(inputBalanceBefore - inputBalanceAfter) + ) + ); + // assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); + require( + outputBalanceAfter - outputBalanceBefore == amountOut, + string.concat( + "outputBalanceAfter - outputBalanceBefore != amountOut, got ", + uintToString(outputBalanceAfter - outputBalanceBefore) + ) + ); + } + + // This is not a real use-case in isolation, but will be used in the UniversalRouter if a v4 + // swap is before another swap on v2/v3 + function test_swapExactInputSingle_zeroForOne_takeToRouter() public { + plan = Planner.init(); + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + IV4Router.ExactInputSingleParams memory params = + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + + plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0, expectedAmountOut * 12 / 10)); + // take the entire open delta to the router's address + plan = + plan.add(Actions.TAKE, abi.encode(key0.currency1, ActionConstants.ADDRESS_THIS, ActionConstants.OPEN_DELTA)); + bytes memory data = plan.encode(); + + // the router holds no funds before + // assertEq(currency0.balanceOf(address(router)), 0); + require( + currency0.balanceOf(address(router)) == 0, + string.concat( + "before currency0 balance of router is not 0, got ", uintToString(currency0.balanceOf(address(router))) + ) + ); + // assertEq(currency1.balanceOf(address(router)), 0); + require(currency1.balanceOf(address(router)) == 0, "currency1 balance of router is not 0"); + uint256 inputBalanceBefore = key0.currency0.balanceOfSelf(); + uint256 outputBalanceBefore = key0.currency1.balanceOfSelf(); + + router.executeActions(data); + + // the output tokens have been left in the router + // assertEq(currency0.balanceOf(address(router)), 0); + require( + currency0.balanceOf(address(router)) == 0, + string.concat( + "output currency0 balance of router is not 0, got ", uintToString(currency0.balanceOf(address(router))) + ) + ); + // assertEq(currency1.balanceOf(address(router)), expectedAmountOut); + require( + currency1.balanceOf(address(router)) == expectedAmountOut, + "currency1 balance of router is not expectedAmountOut" + ); + uint256 inputBalanceAfter = key0.currency0.balanceOfSelf(); + uint256 outputBalanceAfter = key0.currency1.balanceOfSelf(); + + // assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); + require( + inputBalanceBefore - inputBalanceAfter == amountIn, + string.concat(uintToString(inputBalanceBefore - inputBalanceAfter), " != ", uintToString(amountIn)) + ); + // this contract's output balance has not changed because funds went to the router + // assertEq(outputBalanceAfter, outputBalanceBefore); + require( + outputBalanceAfter == outputBalanceBefore, + string.concat(uintToString(outputBalanceAfter), " != ", uintToString(outputBalanceBefore)) + ); + } +} diff --git a/test/shared/HookSavesDelta.sol b/test/shared/HookSavesDelta.sol index fa11a90b9..f540a7d7e 100644 --- a/test/shared/HookSavesDelta.sol +++ b/test/shared/HookSavesDelta.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {BalanceDelta, BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {IPoolManager} from "../../lib/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "../../lib/v4-core/src/types/PoolKey.sol"; +import {BalanceDelta, BalanceDeltaLibrary} from "../../lib/v4-core/src/types/BalanceDelta.sol"; -import {BaseTestHooks} from "@uniswap/v4-core/src/test/BaseTestHooks.sol"; +import {BaseTestHooks} from "../../lib/v4-core/src/test/BaseTestHooks.sol"; /// @notice This contract is NOT a production use contract. It is meant to be used in testing to verify the delta amounts against changes in a user's balance. contract HookSavesDelta is BaseTestHooks { diff --git a/test/shared/LiquidityOperations.sol b/test/shared/LiquidityOperations.sol index 80d2d81e7..5af8931f8 100644 --- a/test/shared/LiquidityOperations.sol +++ b/test/shared/LiquidityOperations.sol @@ -2,12 +2,12 @@ pragma solidity ^0.8.24; import {CommonBase} from "forge-std/Base.sol"; -import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; -import {BalanceDelta, toBalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; -import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol"; -import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {Currency} from "../../lib/v4-core/src/types/Currency.sol"; +import {BalanceDelta, toBalanceDelta} from "../../lib/v4-core/src/types/BalanceDelta.sol"; +import {PoolKey} from "../../lib/v4-core/src/types/PoolKey.sol"; +import {TickMath} from "../../lib/v4-core/src/libraries/TickMath.sol"; +import {LiquidityAmounts} from "../../lib/v4-core/test/utils/LiquidityAmounts.sol"; +import {SafeCast} from "../../lib/v4-core/src/libraries/SafeCast.sol"; import {PositionManager, Actions} from "../../src/PositionManager.sol"; import {PositionConfig} from "./PositionConfig.sol"; diff --git a/test/shared/Planner.sol b/test/shared/Planner.sol index 979f12151..aca2bf88c 100644 --- a/test/shared/Planner.sol +++ b/test/shared/Planner.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolKey} from "../../lib/v4-core/src/types/PoolKey.sol"; import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; import {Actions} from "../../src/libraries/Actions.sol"; -import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {Currency} from "../../lib/v4-core/src/types/Currency.sol"; import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; struct Plan { diff --git a/test/shared/PositionConfig.sol b/test/shared/PositionConfig.sol index a2ab832bd..c4bf3496b 100644 --- a/test/shared/PositionConfig.sol +++ b/test/shared/PositionConfig.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.24; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolKey} from "../../lib/v4-core/src/types/PoolKey.sol"; // A helper struct used for tests struct PositionConfig { diff --git a/test/shared/RoutingTestHelpers.sol b/test/shared/RoutingTestHelpers.sol index 3bde34caa..1852804bf 100644 --- a/test/shared/RoutingTestHelpers.sol +++ b/test/shared/RoutingTestHelpers.sol @@ -2,51 +2,86 @@ pragma solidity ^0.8.24; import "forge-std/Test.sol"; -import {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol"; +import {PoolModifyLiquidityTest} from "../../lib/v4-core/src/test/PoolModifyLiquidityTest.sol"; import {MockV4Router} from "../mocks/MockV4Router.sol"; import {Plan, Planner} from "../shared/Planner.sol"; import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; -import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {IHooks} from "../../lib/v4-core/src/interfaces/IHooks.sol"; import {PathKey} from "../../src/libraries/PathKey.sol"; import {Actions} from "../../src/libraries/Actions.sol"; - -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; -import {PositionManager} from "../../src/PositionManager.sol"; -import {IERC20} from "forge-std/interfaces/IERC20.sol"; +import {Currency, CurrencyLibrary} from "../../lib/v4-core/src/types/Currency.sol"; +import {IPoolManager} from "../../lib/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "../../lib/v4-core/src/types/PoolKey.sol"; import {LiquidityOperations} from "./LiquidityOperations.sol"; import {IV4Router} from "../../src/interfaces/IV4Router.sol"; import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; +import {Constants} from "../../lib/v4-core/test/utils/Constants.sol"; +import {PoolManager} from "../../lib/v4-core/src/PoolManager.sol"; +import {PoolId} from "../../lib/v4-core/src/types/PoolId.sol"; +import {SortTokens} from "../../lib/v4-core/test/utils/SortTokens.sol"; +import {LPFeeLibrary} from "../../lib/v4-core/src/libraries/LPFeeLibrary.sol"; +import {StateLibrary} from "../../lib/v4-core/src/libraries/StateLibrary.sol"; +import {BalanceDelta} from "../../lib/v4-core/src/types/BalanceDelta.sol"; +import {PoolSwapTest} from "../../lib/v4-core/src/test/PoolSwapTest.sol"; +import {LiquidityAmounts} from "../../lib/v4-core/test/utils/LiquidityAmounts.sol"; /// @notice A shared test contract that wraps the v4-core deployers contract and exposes basic helpers for swapping with the router. -contract RoutingTestHelpers is Test, Deployers { +contract RoutingTestHelpers is Test { using Planner for Plan; + using LPFeeLibrary for uint24; + using StateLibrary for IPoolManager; - PoolModifyLiquidityTest positionManager; - MockV4Router router; + // Helpful test constants + bytes constant ZERO_BYTES = Constants.ZERO_BYTES; + uint160 constant SQRT_PRICE_1_1 = Constants.SQRT_PRICE_1_1; + uint160 constant SQRT_PRICE_1_2 = Constants.SQRT_PRICE_1_2; + uint160 constant SQRT_PRICE_2_1 = Constants.SQRT_PRICE_2_1; + uint160 constant SQRT_PRICE_1_4 = Constants.SQRT_PRICE_1_4; + uint160 constant SQRT_PRICE_4_1 = Constants.SQRT_PRICE_4_1; uint256 MAX_SETTLE_AMOUNT = type(uint256).max; uint256 MIN_TAKE_AMOUNT = 0; + IPoolManager.ModifyLiquidityParams public LIQUIDITY_PARAMS = + IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1e18, salt: 0}); + IPoolManager.ModifyLiquidityParams public REMOVE_LIQUIDITY_PARAMS = + IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: -1e18, salt: 0}); + IPoolManager.SwapParams public SWAP_PARAMS = + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); + + PoolModifyLiquidityTest positionManager; + PoolModifyLiquidityTest modifyLiquidityRouter; + PoolSwapTest swapRouter; + MockV4Router router; + IPoolManager manager; + // nativeKey is already defined in Deployers.sol + PoolKey key; + PoolKey nativeKey; + PoolKey uninitializedKey; + PoolKey uninitializedNativeKey; + PoolKey key0; PoolKey key1; PoolKey key2; // currency0 and currency1 are defined in Deployers.sol + Currency internal currency0; + Currency internal currency1; Currency currency2; Currency currency3; Currency[] tokenPath; Plan plan; - function setupRouterCurrenciesAndPoolsWithLiquidity() public { - deployFreshManager(); - - router = new MockV4Router(manager); - positionManager = new PoolModifyLiquidityTest(manager); + function setupRouterCurrenciesAndPoolsWithLiquidity( + address payable routerAddr, + address payable managerAddr, + address payable posManagerAddr + ) public { + router = MockV4Router(routerAddr); + manager = IPoolManager(managerAddr); + positionManager = PoolModifyLiquidityTest(posManagerAddr); MockERC20[] memory tokens = deployTokensMintAndApprove(4); @@ -61,6 +96,14 @@ contract RoutingTestHelpers is Test, Deployers { key2 = createPoolWithLiquidity(currency2, currency3, address(0)); } + function deployTokens(uint8 count, uint256 totalSupply) internal returns (MockERC20[] memory tokens) { + tokens = new MockERC20[](count); + for (uint8 i = 0; i < count; i++) { + tokens[i] = new MockERC20("TEST", "TEST", 18); + tokens[i].mint(address(this), totalSupply); + } + } + function deployTokensMintAndApprove(uint8 count) internal returns (MockERC20[] memory) { MockERC20[] memory tokens = deployTokens(count, 2 ** 128); for (uint256 i = 0; i < count; i++) { @@ -95,38 +138,6 @@ contract RoutingTestHelpers is Test, Deployers { ); } - function _getExactInputParams(Currency[] memory _tokenPath, uint256 amountIn) - internal - pure - returns (IV4Router.ExactInputParams memory params) - { - PathKey[] memory path = new PathKey[](_tokenPath.length - 1); - for (uint256 i = 0; i < _tokenPath.length - 1; i++) { - path[i] = PathKey(_tokenPath[i + 1], 3000, 60, IHooks(address(0)), bytes("")); - } - - params.currencyIn = _tokenPath[0]; - params.path = path; - params.amountIn = uint128(amountIn); - params.amountOutMinimum = 0; - } - - function _getExactOutputParams(Currency[] memory _tokenPath, uint256 amountOut) - internal - pure - returns (IV4Router.ExactOutputParams memory params) - { - PathKey[] memory path = new PathKey[](_tokenPath.length - 1); - for (uint256 i = _tokenPath.length - 1; i > 0; i--) { - path[i - 1] = PathKey(_tokenPath[i - 1], 3000, 60, IHooks(address(0)), bytes("")); - } - - params.currencyOut = _tokenPath[_tokenPath.length - 1]; - params.path = path; - params.amountOut = uint128(amountOut); - params.amountInMaximum = type(uint128).max; - } - function _finalizeAndExecuteSwap( Currency inputCurrency, Currency outputCurrency, @@ -186,10 +197,43 @@ contract RoutingTestHelpers is Test, Deployers { bytes memory data = plan.finalizeSwap(inputCurrency, outputCurrency, ActionConstants.MSG_SENDER); // send too much ETH to mimic slippage - uint256 value = expectedAmountIn + 0.1 ether; - router.executeActionsAndSweepExcessETH{value: value}(data); + // uint256 value = expectedAmountIn + 0.1 ether; + // router.executeActionsAndSweepExcessETH{value: value}(data); + router.executeActions{value: expectedAmountIn}(data); inputBalanceAfter = inputCurrency.balanceOfSelf(); outputBalanceAfter = outputCurrency.balanceOfSelf(); } + + function _getExactInputParams(Currency[] memory _tokenPath, uint256 amountIn) + internal + pure + returns (IV4Router.ExactInputParams memory params) + { + PathKey[] memory path = new PathKey[](_tokenPath.length - 1); + for (uint256 i = 0; i < _tokenPath.length - 1; i++) { + path[i] = PathKey(_tokenPath[i + 1], 3000, 60, IHooks(address(0)), bytes("")); + } + + params.currencyIn = _tokenPath[0]; + params.path = path; + params.amountIn = uint128(amountIn); + params.amountOutMinimum = 0; + } + + function _getExactOutputParams(Currency[] memory _tokenPath, uint256 amountOut) + internal + pure + returns (IV4Router.ExactOutputParams memory params) + { + PathKey[] memory path = new PathKey[](_tokenPath.length - 1); + for (uint256 i = _tokenPath.length - 1; i > 0; i--) { + path[i - 1] = PathKey(_tokenPath[i - 1], 3000, 60, IHooks(address(0)), bytes("")); + } + + params.currencyOut = _tokenPath[_tokenPath.length - 1]; + params.path = path; + params.amountOut = uint128(amountOut); + params.amountInMaximum = type(uint128).max; + } } diff --git a/test_v4_router.py b/test_v4_router.py new file mode 100644 index 000000000..b77be0a5f --- /dev/null +++ b/test_v4_router.py @@ -0,0 +1,345 @@ +import pytest + +from utils import decode_function_signature + + +class TestV4Router: + + @pytest.mark.parametrize( + "contract_name, call_method", + [ + pytest.param( + "V4Router.t.sol", + "test_swapExactInputSingle_revertsForAmountOut()", + marks=pytest.mark.xfail(reson="execution reverted"), + ), + ("V4Router.t.sol", "test_swapExactInputSingle_zeroForOne_takeToMsgSender()"), + pytest.param( + "V4Router.t.sol", + "test_swapExactInputSingle_zeroForOne_takeToRecipient()", + marks=pytest.mark.xfail(reson="'too many accounts: 65 > 64', 'no data'"), + ), + pytest.param( + "V4Router.t.sol", + "test_swapExactInputSingle_zeroForOne_takeAllToRouter()", + marks=pytest.mark.xfail(reson="'too many accounts: 65 > 64', 'no data'"), + ), + ], + ) + @pytest.mark.contract_filename(name="V4Router.t.sol") + def test_swap_functions(self, contract_name, call_method, accounts, v4_router_test, web3_client, faucet): + contract = v4_router_test[contract_name] + print(f"V4RouterTest: {contract.address}") + + sender_account = accounts[0] + faucet.request_neon(contract.address, 300) + + tx = web3_client.make_raw_tx(sender_account) + intruction_tx = contract.functions.setUp().build_transaction(tx) + receipt = web3_client.send_transaction(sender_account, intruction_tx) + + call_data = decode_function_signature(call_method) + + tx = web3_client.make_raw_tx(sender_account, to=contract.address, data=call_data, estimate_gas=True) + receipt = web3_client.send_transaction(sender_account, tx) + assert receipt["status"] == 1 + + @pytest.mark.parametrize( + "contract_name, call_method", + [ + ("V4Router2.t.sol", "test_swapExactInputSingle_oneForZero()"), + pytest.param( + "V4Router2.t.sol", + "test_swapExactInput_revertsForAmountOut()", + marks=pytest.mark.xfail(reson="'execution reverted', '0x'"), + ), + ("V4Router2.t.sol", "test_swapExactIn_1Hop_zeroForOne()"), + pytest.param( + "V4Router2.t.sol", + "test_swapExactIn_2Hops()", + marks=pytest.mark.xfail(reson="'0x486aa307', '0x486aa307'"), + ), + ], + ) + @pytest.mark.contract_filename(name="V4Router2.t.sol") + def test_swap_functions_2(self, contract_name, call_method, accounts, v4_router_test, web3_client, faucet): + contract = v4_router_test[contract_name] + print(f"V4RouterTest: {contract.address}") + + sender_account = accounts[0] + faucet.request_neon(contract.address, 300) + + tx = web3_client.make_raw_tx(sender_account) + intruction_tx = contract.functions.setUp().build_transaction(tx) + receipt = web3_client.send_transaction(sender_account, intruction_tx) + + call_data = decode_function_signature(call_method) + + tx = web3_client.make_raw_tx(sender_account, to=contract.address, data=call_data, estimate_gas=True) + receipt = web3_client.send_transaction(sender_account, tx) + assert receipt["status"] == 1 + + @pytest.mark.parametrize( + "contract_name, call_method", + [ + ("V4Router3.t.sol", "test_swapExactIn_3Hops()"), + ("V4Router3.t.sol", "test_swap_settleRouterBalance_swapOpenDelta()"), + ("V4Router3.t.sol", "test_nativeIn_swapExactInputSingle()"), + pytest.param( + "V4Router4.t.sol", + "test_nativeOut_swapExactInputSingle()", + marks=pytest.mark.xfail( + reson="execution reverted: output balance 0 after should be equal expected amount out 992054607780215625" + ), + ), + ], + ) + @pytest.mark.contract_filename(name="V4Router3.t.sol") + def test_swap_functions_3(self, contract_name, call_method, accounts, v4_router_test, web3_client, faucet): + contract = v4_router_test[contract_name] + print(f"V4RouterTest: {contract.address}") + + sender_account = accounts[0] + faucet.request_neon(contract.address, 300) + + tx = web3_client.make_raw_tx(sender_account) + intruction_tx = contract.functions.setUp().build_transaction(tx) + receipt = web3_client.send_transaction(sender_account, intruction_tx) + + call_data = decode_function_signature(call_method) + + tx = web3_client.make_raw_tx(sender_account, to=contract.address, data=call_data, estimate_gas=True) + receipt = web3_client.send_transaction(sender_account, tx) + assert receipt["status"] == 1 + + @pytest.mark.parametrize( + "contract_name, call_method", + [ + ("V4Router4.t.sol", "test_nativeIn_swapExactIn_1Hop()"), + pytest.param( + "V4Router4.t.sol", + "test_nativeOut_swapExactIn_1Hop()", + marks=pytest.mark.xfail(reson="'too many accounts: 65 > 64', 'no data'"), + ), + pytest.param( + "V4Router4.t.sol", + "test_nativeIn_swapExactIn_2Hops()", + marks=pytest.mark.xfail(reson="'too many accounts: 65 > 64', 'no data'"), + ), + ], + ) + @pytest.mark.contract_filename(name="V4Router4.t.sol") + def test_swap_functions_4(self, contract_name, call_method, accounts, v4_router_test, web3_client, faucet): + contract = v4_router_test[contract_name] + print(f"V4RouterTest: {contract.address}") + + sender_account = accounts[0] + faucet.request_neon(contract.address, 300) + + tx = web3_client.make_raw_tx(sender_account) + intruction_tx = contract.functions.setUp().build_transaction(tx) + receipt = web3_client.send_transaction(sender_account, intruction_tx) + + call_data = decode_function_signature(call_method) + + tx = web3_client.make_raw_tx(sender_account, to=contract.address, data=call_data, estimate_gas=True) + receipt = web3_client.send_transaction(sender_account, tx) + assert receipt["status"] == 1 + + @pytest.mark.parametrize( + "contract_name, call_method", + [ + pytest.param( + "V4Router5.t.sol", + "test_nativeOut_swapExactIn_2Hops()", + marks=pytest.mark.xfail( + reson="utput balance 0 after should be equal expected amount out 984211133872795298" + ), + ), + ("V4Router5.t.sol", "test_swap_nativeIn_settleRouterBalance_swapOpenDelta()"), + ], + ) + @pytest.mark.contract_filename(name="V4Router5.t.sol") + def test_swap_functions_5(self, contract_name, call_method, accounts, v4_router_test, web3_client, faucet): + contract = v4_router_test[contract_name] + print(f"V4RouterTest: {contract.address}") + + sender_account = accounts[0] + faucet.request_neon(contract.address, 300) + + tx = web3_client.make_raw_tx(sender_account) + intruction_tx = contract.functions.setUp().build_transaction(tx) + receipt = web3_client.send_transaction(sender_account, intruction_tx) + + call_data = decode_function_signature(call_method) + + tx = web3_client.make_raw_tx(sender_account, to=contract.address, data=call_data, estimate_gas=True) + receipt = web3_client.send_transaction(sender_account, tx) + assert receipt["status"] == 1 + + @pytest.mark.parametrize( + "contract_name, call_method", + [ + pytest.param( + "V4Router6.t.sol", + "test_swapExactOutputSingle_revertsForAmountIn()", + marks=pytest.mark.xfail(reson="'execution reverted', '0x'"), + ), + ("V4Router6.t.sol", "test_swapExactOutputSingle_zeroForOne()"), + ("V4Router6.t.sol", "test_swapExactOutputSingle_oneForZero()"), + pytest.param( + "V4Router6.t.sol", + "test_swapExactOut_revertsForAmountIn()", + marks=pytest.mark.xfail(reson="'execution reverted', '0x'"), + ), + ], + ) + @pytest.mark.contract_filename(name="V4Router6.t.sol") + def test_swap_functions_6(self, contract_name, call_method, accounts, v4_router_test, web3_client, faucet): + contract = v4_router_test[contract_name] + print(f"V4RouterTest: {contract.address}") + + sender_account = accounts[0] + faucet.request_neon(contract.address, 300) + + tx = web3_client.make_raw_tx(sender_account) + intruction_tx = contract.functions.setUp().build_transaction(tx) + receipt = web3_client.send_transaction(sender_account, intruction_tx) + + call_data = decode_function_signature(call_method) + + tx = web3_client.make_raw_tx(sender_account, to=contract.address, data=call_data, estimate_gas=True) + receipt = web3_client.send_transaction(sender_account, tx) + assert receipt["status"] == 1 + + @pytest.mark.parametrize( + "contract_name, call_method", + [ + pytest.param( + "V4Router7.t.sol", + "test_swapExactOut_1Hop_zeroForOne()", + marks=pytest.mark.xfail( + reson="'0x3351b2600000000000000000000000009dcced5ebf32f6dce66366e8e77faa4ca69c7260', '0x3351b2600000000000000000000000009dcced5ebf32f6dce66366e8e77faa4ca69c7260'" + ), + ), + pytest.param( + "V4Router7.t.sol", + "test_swapExactOut_1Hop_oneForZero()", + marks=pytest.mark.xfail(reson="0x486aa307', '0x486aa307'"), + ), + pytest.param( + "V4Router7.t.sol", + "test_swapExactOut_2Hops()", + marks=pytest.mark.xfail(reson="'0x486aa307', '0x486aa307'"), + ), + pytest.param( + "V4Router7.t.sol", + "test_swapExactOut_3Hops()", + marks=pytest.mark.xfail(reson="'0x486aa307', '0x486aa307'"), + ), + ], + ) + @pytest.mark.contract_filename(name="V4Router7.t.sol") + def test_swap_functions_7(self, contract_name, call_method, accounts, v4_router_test, web3_client, faucet): + contract = v4_router_test[contract_name] + print(f"V4RouterTest: {contract.address}") + + sender_account = accounts[0] + faucet.request_neon(contract.address, 300) + + tx = web3_client.make_raw_tx(sender_account) + intruction_tx = contract.functions.setUp().build_transaction(tx) + receipt = web3_client.send_transaction(sender_account, intruction_tx) + + call_data = decode_function_signature(call_method) + + tx = web3_client.make_raw_tx(sender_account, to=contract.address, data=call_data, estimate_gas=True) + receipt = web3_client.send_transaction(sender_account, tx) + assert receipt["status"] == 1 + + @pytest.mark.parametrize( + "contract_name, call_method", + [ + ("V4Router8.t.sol", "test_nativeIn_swapExactOutputSingle_sweepExcessETH()"), + pytest.param( + "V4Router8.t.sol", + "test_nativeOut_swapExactOutputSingle()", + marks=pytest.mark.xfail( + reson="'execution reverted: output balance after 0 should be equal to amountOut 1000000000000000000'" + ), + ), + ("V4Router8.t.sol", "test_nativeIn_swapExactOut_1Hop_sweepExcessETH()"), + pytest.param( + "V4Router8.t.sol", + "test_nativeOut_swapExactOut_1Hop()", + marks=pytest.mark.xfail(reson="'0x486aa307', '0x486aa307"), + ), + ], + ) + @pytest.mark.contract_filename(name="V4Router8.t.sol") + def test_swap_functions_8(self, contract_name, call_method, accounts, v4_router_test, web3_client, faucet): + contract = v4_router_test[contract_name] + print(f"V4RouterTest: {contract.address}") + + sender_account = accounts[0] + faucet.request_neon(contract.address, 300) + + tx = web3_client.make_raw_tx(sender_account) + intruction_tx = contract.functions.setUp().build_transaction(tx) + receipt = web3_client.send_transaction(sender_account, intruction_tx) + + call_data = decode_function_signature(call_method) + + tx = web3_client.make_raw_tx(sender_account, to=contract.address, data=call_data, estimate_gas=True) + receipt = web3_client.send_transaction(sender_account, tx) + assert receipt["status"] == 1 + + @pytest.mark.parametrize( + "contract_name, call_method", + [ + ("V4Router9.t.sol", "test_nativeIn_swapExactOut_2Hops_sweepExcessETH()"), + ], + ) + @pytest.mark.contract_filename(name="V4Router9.t.sol") + def test_swap_functions_9(self, contract_name, call_method, accounts, v4_router_test, web3_client, faucet): + contract = v4_router_test[contract_name] + print(f"V4RouterTest: {contract.address}") + + sender_account = accounts[0] + faucet.request_neon(contract.address, 300) + + tx = web3_client.make_raw_tx(sender_account) + intruction_tx = contract.functions.setUp().build_transaction(tx) + receipt = web3_client.send_transaction(sender_account, intruction_tx) + + call_data = decode_function_signature(call_method) + + tx = web3_client.make_raw_tx(sender_account, to=contract.address, data=call_data, estimate_gas=True) + receipt = web3_client.send_transaction(sender_account, tx) + assert receipt["status"] == 1 + + @pytest.mark.parametrize( + "contract_name, call_method", + [ + ("V4Router9.t.sol", "test_swapExactInputSingle_zeroForOne_takeToRouter()"), + ], + ) + @pytest.mark.contract_filename(name="V4Router9.t.sol") + def test_swap_functions_9_without_balance( + self, contract_name, call_method, accounts, v4_router_test, web3_client, faucet + ): + contract = v4_router_test[contract_name] + print(f"V4RouterTest: {contract.address}") + + sender_account = accounts[0] + faucet.request_neon(contract.address, 200) + + tx = web3_client.make_raw_tx(sender_account) + intruction_tx = contract.functions.setUp().build_transaction(tx) + receipt = web3_client.send_transaction(sender_account, intruction_tx) + + call_data = decode_function_signature(call_method) + + tx = web3_client.make_raw_tx(sender_account, to=contract.address, data=call_data, estimate_gas=True) + receipt = web3_client.send_transaction(sender_account, tx) + assert receipt["status"] == 1 diff --git a/utils.py b/utils.py new file mode 100644 index 000000000..31ff0c9fc --- /dev/null +++ b/utils.py @@ -0,0 +1,370 @@ +import json +import pathlib +import time +import typing as tp +import urllib +from decimal import Decimal +from enum import Enum + +import eth_account.signers.local +import requests +import solcx +import web3 +from eth_utils import keccak +from solcx import link_code + + +class Unit(Enum): + WEI = "wei" + KWEI = "kwei" + MWEI = "mwei" + GWEI = "gwei" + MICRO_ETHER = "microether" + MILLI_ETHER = "milliether" + ETHER = "ether" + + def lower(self): + return self.value + + +def wait_condition(func_cond, timeout_sec=15, delay=0.5): + start_time = time.time() + while True: + if time.time() - start_time > timeout_sec: + raise TimeoutError(f"The condition not reached within {timeout_sec} sec") + try: + if func_cond(): + break + + except Exception as e: + print(f"Error during waiting: {e}") + time.sleep(delay) + return True + + +def get_contract_abi(name, compiled): + for key in compiled.keys(): + if name == key.rsplit(":")[-1]: + return compiled[key] + + +def get_contract_interface( + contract: str, + version: str, + contract_name: tp.Optional[str] = None, + import_remapping: tp.Optional[dict] = None, + libraries: tp.Optional[dict] = None, +): + if not contract.endswith(".sol"): + contract += ".sol" + if contract_name is None: + if "/" in contract: + contract_name = contract.rsplit("/", 1)[1].rsplit(".", 1)[0] + else: + contract_name = contract.rsplit(".", 1)[0] + + solcx.install_solc(version) + if contract.startswith("/"): + contract_path = pathlib.Path(contract) + else: + contract_path = (pathlib.Path.cwd() / "contracts" / f"{contract}").absolute() + if not contract_path.exists(): + contract_path = (pathlib.Path.cwd() / f"{contract}").absolute() + + assert contract_path.exists(), f"Can't found contract: {contract_path}" + + compiled = solcx.compile_files( + [contract_path], + output_values=["abi", "bin"], + solc_version=version, + import_remappings=import_remapping, + allow_paths=["."], + optimize=True, + optimize_runs=0, + optimize_yul=False, + ) # this allow_paths isn't very good... + contract_interface = get_contract_abi(contract_name, compiled) + if libraries: + contract_interface["bin"] = link_code(contract_interface["bin"], libraries) + + return contract_interface + + +class InputTestConstants(Enum): + NEW_USER_REQUEST_AMOUNT = 200 + + +class Web3Client: + def __init__( + self, + proxy_url: str, + tracer_url: tp.Optional[tp.Any] = None, + session: tp.Optional[tp.Any] = None, + ): + self._proxy_url = proxy_url + self._tracer_url = tracer_url + self._chain_id = None + self._web3 = web3.Web3(web3.HTTPProvider(proxy_url, session=session, request_kwargs={"timeout": 30})) + + def __getattr__(self, item): + return getattr(self._web3, item) + + @property + def native_token_name(self): + if self._proxy_url.split("/")[-1] != "solana": + return self._proxy_url.split("/")[-1].upper() + else: + return "NEON" + + @property + def chain_id(self): + if self._chain_id is None: + self._chain_id = self._web3.eth.chain_id + return self._chain_id + + def gas_price(self): + gas = self._web3.eth.gas_price + return gas + + def create_account(self): + return self._web3.eth.account.create() + + def get_nonce( + self, + address: tp.Union[eth_account.signers.local.LocalAccount, str], + block: str = "pending", + ): + address = address if isinstance(address, str) else address.address + return self._web3.eth.get_transaction_count(address, block) + + def wait_for_transaction_receipt(self, tx_hash, timeout=120): + return self._web3.eth.wait_for_transaction_receipt(tx_hash, timeout=timeout) + + def deploy_contract( + self, + from_: eth_account.signers.local.LocalAccount, + abi, + bytecode: str, + gas: tp.Optional[int] = 0, + gas_price: tp.Optional[int] = None, + constructor_args: tp.Optional[tp.List] = None, + value=0, + ) -> web3.types.TxReceipt: + """Proxy doesn't support send_transaction""" + gas_price = gas_price or self.gas_price() + constructor_args = constructor_args or [] + + contract = self._web3.eth.contract(abi=abi, bytecode=bytecode) + transaction = contract.constructor(*constructor_args).build_transaction( + { + "from": from_.address, + "gas": gas, + "gasPrice": gas_price, + "nonce": self.get_nonce(from_), + "value": value, + "chainId": self.chain_id, + } + ) + + if transaction["gas"] == 0: + transaction["gas"] = self._web3.eth.estimate_gas(transaction) + + signed_tx = self._web3.eth.account.sign_transaction(transaction, from_.key) + tx = self._web3.eth.send_raw_transaction(signed_tx.raw_transaction) + return self._web3.eth.wait_for_transaction_receipt(tx) + + def make_raw_tx( + self, + from_: tp.Union[str, eth_account.signers.local.LocalAccount], + to: tp.Optional[tp.Union[str, eth_account.signers.local.LocalAccount]] = None, + amount: tp.Optional[tp.Union[int, float, Decimal]] = None, + gas: tp.Optional[int] = None, + gas_price: tp.Optional[int] = None, + nonce: tp.Optional[int] = None, + chain_id: tp.Optional[int] = None, + data: tp.Optional[tp.Union[str, bytes]] = None, + estimate_gas=False, + ) -> dict: + if isinstance(from_, eth_account.signers.local.LocalAccount): + transaction = {"from": from_.address} + else: + transaction = {"from": from_} + + if to: + if isinstance(to, eth_account.signers.local.LocalAccount): + transaction["to"] = to.address + if isinstance(to, str): + transaction["to"] = to + if amount: + transaction["value"] = amount + if data: + transaction["data"] = data + if nonce is None: + transaction["nonce"] = self.get_nonce(from_) + else: + transaction["nonce"] = nonce + + if chain_id is None: + transaction["chainId"] = self.chain_id + elif chain_id: + transaction["chainId"] = chain_id + + if gas_price is None: + gas_price = self.gas_price() + transaction["gasPrice"] = gas_price + if estimate_gas and not gas: + gas = self._web3.eth.estimate_gas(transaction) + if gas: + transaction["gas"] = gas + return transaction + + def send_transaction( + self, + account: eth_account.signers.local.LocalAccount, + transaction: tp.Dict, + gas_multiplier: tp.Optional[float] = None, # fix for some event depends transactions + timeout: int = 120, + ) -> web3.types.TxReceipt: + instruction_tx = self._web3.eth.account.sign_transaction(transaction, account.key) + signature = self._web3.eth.send_raw_transaction(instruction_tx.raw_transaction) + return self._web3.eth.wait_for_transaction_receipt(signature, timeout=timeout) + + def deploy_and_get_contract( + self, + contract: str, + version: str, + account: eth_account.signers.local.LocalAccount, + contract_name: tp.Optional[str] = None, + constructor_args: tp.Optional[tp.Any] = None, + import_remapping: tp.Optional[dict] = None, + libraries: tp.Optional[dict] = None, + gas: tp.Optional[int] = 0, + value=0, + ) -> tp.Tuple[tp.Any, web3.types.TxReceipt]: + contract_interface = get_contract_interface( + contract, + version, + contract_name=contract_name, + import_remapping=import_remapping, + libraries=libraries, + ) + + contract_deploy_tx = self.deploy_contract( + account, + abi=contract_interface["abi"], + bytecode=contract_interface["bin"], + constructor_args=constructor_args, + gas=gas, + value=value, + ) + + contract = self.eth.contract(address=contract_deploy_tx["contractAddress"], abi=contract_interface["abi"]) + + return contract, contract_deploy_tx + + def get_balance( + self, + address: tp.Union[str, eth_account.signers.local.LocalAccount], + unit=Unit.WEI, + ): + if not isinstance(address, str): + address = address.address + balance = self._web3.eth.get_balance(address, "pending") + if unit != Unit.WEI: + balance = self._web3.from_wei(balance, unit.value) + return balance + + def send_tokens( + self, + from_: eth_account.signers.local.LocalAccount, + to: tp.Union[str, eth_account.signers.local.LocalAccount], + value: int, + gas: tp.Optional[int] = None, + gas_price: tp.Optional[int] = None, + nonce: int = None, + ) -> web3.types.TxReceipt: + transaction = self.make_raw_tx( + from_, to, amount=value, gas=gas, gas_price=gas_price, nonce=nonce, estimate_gas=True + ) + + signed_tx = self.eth.account.sign_transaction(transaction, from_.key) + tx = self.eth.send_raw_transaction(signed_tx.raw_transaction) + return self.eth.wait_for_transaction_receipt(tx) + + +class NeonChainWeb3Client(Web3Client): + def __init__( + self, + proxy_url: str, + tracer_url: tp.Optional[tp.Any] = None, + session: tp.Optional[tp.Any] = None, + ): + super().__init__(proxy_url, tracer_url, session) + + def create_account_with_balance( + self, + faucet, + amount: int = InputTestConstants.NEW_USER_REQUEST_AMOUNT.value, + ): + """Creates a new account with balance""" + account = self.create_account() + faucet.request_neon(account.address, amount=amount) + return account + + +class Faucet: + def __init__( + self, + faucet_url: str, + web3_client: NeonChainWeb3Client, + session: tp.Optional[tp.Any] = None, + ): + self._url = faucet_url + self._session = session or requests.Session() + self.web3_client = web3_client + + def request_neon(self, address: str, amount: int = 100) -> requests.Response: + assert address.startswith("0x") + url = urllib.parse.urljoin(self._url, "request_neon") + balance_before = self.web3_client.get_balance(address) + response = self._session.post(url, json={"amount": amount, "wallet": address}) + counter = 0 + while "Blockhash not found" in response.text and counter < 3: + time.sleep(3) + response = self._session.post(url, json={"amount": amount, "wallet": address}) + counter += 1 + assert response.ok, "Faucet returned error: {}, status code: {}, url: {}".format( + response.text, response.status_code, response.url + ) + wait_condition(lambda: self.web3_client.get_balance(address) > balance_before) + return response + + +class EthAccounts: + def __init__(self, web3_client: NeonChainWeb3Client, faucet): + self._web3_client = web3_client + self._faucet = faucet + self._accounts = [] + self.accounts_collector = [] + + def __getitem__(self, item): + if len(self._accounts) < (item + 1): + for _ in range(item + 1 - len(self._accounts)): + account = self._web3_client.create_account_with_balance(self._faucet) + + self._accounts.append(account) + self.accounts_collector.append(account) + return self._accounts[item] + + def create_account(self, balance=InputTestConstants.NEW_USER_REQUEST_AMOUNT.value): + if balance > 0: + account = self._web3_client.create_account_with_balance(self._faucet, balance) + else: + account = self._web3_client.create_account() + self.accounts_collector.append(account) + return account + + +def decode_function_signature(function_name: str) -> str: + data = keccak(text=function_name)[:4] + return "0x" + data.hex()