From 1118502162651f697dc505d239e4365e3625128d Mon Sep 17 00:00:00 2001 From: pistomat Date: Sat, 9 Dec 2023 19:46:02 +0100 Subject: [PATCH] Implement adapter and test templates --- docs/logic/vm-integration/README.md | 27 ++++++--- evm/src/balancer-v2/BalancerV2SwapAdapter.sol | 3 +- evm/src/interfaces/ISwapAdapter.sol | 2 +- evm/src/template/TemplateSwapAdapter.sol | 58 +++++++++++++++++++ evm/src/uniswap-v2/UniswapV2SwapAdapter.sol | 8 ++- evm/test/BalancerV2SwapAdapter.t.sol | 3 +- evm/test/TemplateSwapAdapter.t.sol | 18 ++++++ evm/test/UniswapV2SwapAdapter.t.sol | 3 +- 8 files changed, 105 insertions(+), 17 deletions(-) create mode 100644 evm/test/TemplateSwapAdapter.t.sol diff --git a/docs/logic/vm-integration/README.md b/docs/logic/vm-integration/README.md index 0ccd34a..bcc5e58 100644 --- a/docs/logic/vm-integration/README.md +++ b/docs/logic/vm-integration/README.md @@ -15,7 +15,7 @@ Following exchanges have been integrated using VM approach: ### Prerequisites -1. Install [Foundry](https://book.getfoundry.sh/getting-started/installation#using-foundryup). +1. Install [Foundry](https://book.getfoundry.sh/getting-started/installation#using-foundryup), start by downloading and installing the Foundry installer: ```bash curl -L https://foundry.paradigm.xyz | bash ``` @@ -37,18 +37,27 @@ Following exchanges have been integrated using VM approach: ### Understanding the ISwapAdapter -1. Read the the documentation of the [Ethereum Solidity interface](ethereum-solidity.md). It describes the functions that need to be implemented as well as the manifest file. -2. Additionally read through the docstring of the [ISwapAdapter.sol](../../../evm/src/interfaces/ISwapAdapter.sol) interface and the [ISwapAdapterTypes.sol](../../../evm/src/interfaces/ISwapAdapterTypes.sol) interface which defines the data types and errors used by the adapter interface. -3. You can also generate the documentation locally and the look at the generated documentation in the `./docs` folder: +Read the the documentation of the [Ethereum Solidity interface](ethereum-solidity.md). It describes the functions that need to be implemented as well as the manifest file. +Additionally read through the docstring of the [ISwapAdapter.sol](../../../evm/src/interfaces/ISwapAdapter.sol) interface and the [ISwapAdapterTypes.sol](../../../evm/src/interfaces/ISwapAdapterTypes.sol) interface which defines the data types and errors used by the adapter interface. +You can also generate the documentation locally and the look at the generated documentation in the `./docs` folder: ```bash - cd ./propeller-protocol-lib/evm/ + cd ./evm/ forge doc ``` ### Implementing the ISwapAdapter interface -1. Your integration should be in a separate directory in the `evm/src` folder. Start by cloning the template directory: +Your integration should be in a separate directory in the `evm/src` folder. Start by cloning the template directory: ```bash - cp -r ./evm/src/template ./evm/src/ + cp ./evm/src/template ./evm/src/ ``` -2. Implement the `ISwapAdapter` interface in the `./evm/src/.sol` file. -3. Create tests for your implementation in the `./evm/test/.t.sol` file, again based on the template `./evm/test/TemplateSwapAdapter.t.sol`. +Implement the `ISwapAdapter` interface in the `./evm/src/.sol` file. There are two reference implementations, one for Uniswap V2 and the other for Balancer V2. +### Testing your implementation +Clone the `evm/test/TemplateSwapAdapter.t.sol` file and rename it to `.t.sol`. Implement the tests for your adapter, make sure all implemented functions are tested and working correctly. Look at the examples of `UniswapV2SwapAdapter.t.sol` and `BalancerV2SwapAdapter.t.sol` for reference. The [Foundry test guide](https://book.getfoundry.sh/forge/tests) is a good reference, especially the chapter for [Fuzz testing](https://book.getfoundry.sh/forge/fuzz-testing), which is used in both the Uniswap and Balancer tests. + +We are using fork testing, i.e. we are running a local Ethereum node and fork the mainnet state. This allows us to test the integration against the real contracts and real data. To run the tests, you need to set the `ETH_RPC_URL` environment variable to the URL of an ethereum RPC. It can be your own node or a public one, like [Alchemy](https://www.alchemy.com/) or [Infura](https://infura.io/). + +Finally, run the tests with: + ```bash + cd ./evm + forge test + ``` diff --git a/evm/src/balancer-v2/BalancerV2SwapAdapter.sol b/evm/src/balancer-v2/BalancerV2SwapAdapter.sol index 1b37799..56d1d6e 100644 --- a/evm/src/balancer-v2/BalancerV2SwapAdapter.sol +++ b/evm/src/balancer-v2/BalancerV2SwapAdapter.sol @@ -3,14 +3,13 @@ pragma experimental ABIEncoderV2; pragma solidity ^0.8.13; import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; -import "forge-std/Test.sol"; // Maximum Swap In/Out Ratio - 0.3 // https://balancer.gitbook.io/balancer/core-concepts/protocol/limitations#v2-limits uint256 constant RESERVE_LIMIT_FACTOR = 4; uint256 constant SWAP_DEADLINE_SEC = 1000; -contract BalancerV2SwapAdapter is ISwapAdapter, Test { +contract BalancerV2SwapAdapter is ISwapAdapter { IVault immutable vault; constructor(address payable vault_) { diff --git a/evm/src/interfaces/ISwapAdapter.sol b/evm/src/interfaces/ISwapAdapter.sol index 430ec98..178f2c6 100644 --- a/evm/src/interfaces/ISwapAdapter.sol +++ b/evm/src/interfaces/ISwapAdapter.sol @@ -68,7 +68,7 @@ interface ISwapAdapter is ISwapAdapterTypes { /// @notice Retrieves the limits for each token. /// @dev Retrieve the maximum limits of a token that can be traded. The /// limit is reached when the change in the received amounts is zero or - /// close to zero or when the swap fails because of the pools restrictions. + /// close to zero or when the swap fails because of the pools restrictions. /// Overestimate if in doubt rather than underestimate. The /// swap function should not error with `LimitExceeded` if called with /// amounts below the limit. diff --git a/evm/src/template/TemplateSwapAdapter.sol b/evm/src/template/TemplateSwapAdapter.sol index e69de29..5c8d638 100644 --- a/evm/src/template/TemplateSwapAdapter.sol +++ b/evm/src/template/TemplateSwapAdapter.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma experimental ABIEncoderV2; +pragma solidity ^0.8.13; + +import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; + +/// @title TemplateSwapAdapter +/// @dev This is a template for a swap adapter. +/// Rename it to your own protocol's name and implement it according to the +/// specification. +contract TemplateSwapAdapter is ISwapAdapter { + function price( + bytes32 _poolId, + IERC20 _sellToken, + IERC20 _buyToken, + uint256[] memory _specifiedAmounts + ) external view override returns (Fraction[] memory _prices) { + revert NotImplemented("TemplateSwapAdapter.price"); + } + + function swap( + bytes32 poolId, + IERC20 sellToken, + IERC20 buyToken, + OrderSide side, + uint256 specifiedAmount + ) external returns (Trade memory trade) { + revert NotImplemented("TemplateSwapAdapter.swap"); + } + + function getLimits(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) + external + returns (uint256[] memory limits) + { + revert NotImplemented("TemplateSwapAdapter.getLimits"); + } + + function getCapabilities(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) + external + returns (Capability[] memory capabilities) + { + revert NotImplemented("TemplateSwapAdapter.getCapabilities"); + } + + function getTokens(bytes32 poolId) + external + returns (IERC20[] memory tokens) + { + revert NotImplemented("TemplateSwapAdapter.getTokens"); + } + + function getPoolIds(uint256 offset, uint256 limit) + external + returns (bytes32[] memory ids) + { + revert NotImplemented("TemplateSwapAdapter.getPoolIds"); + } +} diff --git a/evm/src/uniswap-v2/UniswapV2SwapAdapter.sol b/evm/src/uniswap-v2/UniswapV2SwapAdapter.sol index fde236a..cd07683 100644 --- a/evm/src/uniswap-v2/UniswapV2SwapAdapter.sol +++ b/evm/src/uniswap-v2/UniswapV2SwapAdapter.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.13; import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; -// Uniswap handles arbirary amounts, but we limit the amount to 10x just in case +// Uniswap handles arbirary amounts, but we limit the amount to 10x just in case uint256 constant RESERVE_LIMIT_FACTOR = 10; contract UniswapV2SwapAdapter is ISwapAdapter { @@ -119,7 +119,8 @@ contract UniswapV2SwapAdapter is ISwapAdapter { return amountOut; } - /// @notice Given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset + /// @notice Given an input amount of an asset and pair reserves, returns the + /// maximum output amount of the other asset /// @param amountIn The amount of the token being sold. /// @param reserveIn The reserve of the token being sold. /// @param reserveOut The reserve of the token being bought. @@ -173,7 +174,8 @@ contract UniswapV2SwapAdapter is ISwapAdapter { return amount; } - /// @notice Given an output amount of an asset and pair reserves, returns a required input amount of the other asset + /// @notice Given an output amount of an asset and pair reserves, returns a + /// required input amount of the other asset /// @param amountOut The amount of the token being bought. /// @param reserveIn The reserve of the token being sold. /// @param reserveOut The reserve of the token being bought. diff --git a/evm/test/BalancerV2SwapAdapter.t.sol b/evm/test/BalancerV2SwapAdapter.t.sol index 554e660..fcb6328 100644 --- a/evm/test/BalancerV2SwapAdapter.t.sol +++ b/evm/test/BalancerV2SwapAdapter.t.sol @@ -95,7 +95,8 @@ contract BalancerV2SwapAdapterTest is Test, ISwapAdapterTypes { if (side == OrderSide.Buy) { vm.assume(specifiedAmount < limits[1]); - // TODO calculate the amountIn by using price function as in testPriceDecreasing + // TODO calculate the amountIn by using price function as in + // testPriceDecreasing deal(address(BAL), address(this), type(uint256).max); BAL.approve(address(adapter), type(uint256).max); } else { diff --git a/evm/test/TemplateSwapAdapter.t.sol b/evm/test/TemplateSwapAdapter.t.sol new file mode 100644 index 0000000..8ace710 --- /dev/null +++ b/evm/test/TemplateSwapAdapter.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "src/interfaces/ISwapAdapterTypes.sol"; +import "src/libraries/FractionMath.sol"; + +/// @title TemplateSwapAdapterTest +/// @dev This is a template for a swap adapter test. +/// Test all functions that are implemented in your swap adapter, the two test included here are just an example. +/// Feel free to use UniswapV2SwapAdapterTest and BalancerV2SwapAdapterTest as a reference. +contract TemplateSwapAdapterTest is Test, ISwapAdapterTypes { + using FractionMath for Fraction; + + function testPriceFuzz(uint256 amount0, uint256 amount1) public {} + + function testSwapFuzz(uint256 specifiedAmount) public {} +} \ No newline at end of file diff --git a/evm/test/UniswapV2SwapAdapter.t.sol b/evm/test/UniswapV2SwapAdapter.t.sol index 0c4762f..960d0e1 100644 --- a/evm/test/UniswapV2SwapAdapter.t.sol +++ b/evm/test/UniswapV2SwapAdapter.t.sol @@ -73,7 +73,8 @@ contract UniswapV2PairFunctionTest is Test, ISwapAdapterTypes { if (side == OrderSide.Buy) { vm.assume(specifiedAmount < limits[1]); - // TODO calculate the amountIn by using price function as in BalancerV2 testPriceDecreasing + // TODO calculate the amountIn by using price function as in + // BalancerV2 testPriceDecreasing deal(address(USDC), address(this), type(uint256).max); USDC.approve(address(adapter), type(uint256).max); } else {