Implement adapter and test templates

This commit is contained in:
pistomat
2023-12-09 19:46:02 +01:00
parent 8af168a774
commit 1118502162
8 changed files with 105 additions and 17 deletions

View File

@@ -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/<your-adapter-name>
cp ./evm/src/template ./evm/src/<your-adapter-name>
```
2. Implement the `ISwapAdapter` interface in the `./evm/src/<your-adapter-name>.sol` file.
3. Create tests for your implementation in the `./evm/test/<your-adapter-name>.t.sol` file, again based on the template `./evm/test/TemplateSwapAdapter.t.sol`.
Implement the `ISwapAdapter` interface in the `./evm/src/<your-adapter-name>.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 `<your-adapter-name>.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
```

View File

@@ -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_) {

View File

@@ -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");
}
}

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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 {}
}

View File

@@ -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 {