diff --git a/evm/foundry.toml b/evm/foundry.toml index 992083f..7010ce4 100644 --- a/evm/foundry.toml +++ b/evm/foundry.toml @@ -9,7 +9,6 @@ mainnet = "${ETH_RPC_URL}" [fmt] line_length = 80 -wrap_comments = true [etherscan] mainnet = { key = "${ETHERSCAN_MAINNET_KEY}" } diff --git a/evm/interfaces/ISwapAdapter.sol b/evm/interfaces/ISwapAdapter.sol new file mode 100644 index 0000000..9995783 --- /dev/null +++ b/evm/interfaces/ISwapAdapter.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.13; + +import "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; +import "interfaces/ISwapAdapterTypes.sol"; + +/// @title ISwapAdapterTypes +/// @dev Implement this interface to support propeller routing through your pairs. +/// @dev Before implementing the interface we need to introduce three function for a +/// @dev given pair: The swap(x), gas(x) and price(x) functions: +/// @dev The swap function accepts some specified token amount: x and returns the +/// @dev amount y a user can get by swapping x through the venue. +/// @dev The gas function simply returns the estimated gas cost given a specified +/// @dev amount x. +/// @dev Last but not least, the price function is the derivative of the swap +/// @dev function. It represents the best possible price a user can get from a +/// @dev pair after swapping x of the specified token. +/// @dev During calls to swap and getLimits, the caller can be assumed to +/// @dev have the required sell or buy token balance as well as unlimited approvals +/// @dev to this contract. +interface ISwapAdapter is ISwapAdapterTypes { + /// @notice Calculates pair prices for specified amounts (optional). + /// @dev The returned prices should include all dex fees, in case the fee + /// @dev is dynamic, the returned price is expected to include the minimum fee. + /// @dev Ideally this method should be implemented, although it is optional as + /// @dev the price function can be numerically estimated from the swap function. + /// @dev In case it is not available it should be flagged via capabilities and + /// @dev calling it should revert using the `NotImplemented` error. + /// @dev The method needs to be implemented as view as this is usually more efficient + /// @dev and can be run in parallel. + /// @dev all. + /// @param pairId The ID of the trading pair. + /// @param sellToken The token being sold. + /// @param buyToken The token being bought. + /// @param sellAmounts The specified amounts used for price calculation. + /// @return prices array of prices as fractions corresponding to the provided amounts. + function price( + bytes32 pairId, + IERC20 sellToken, + IERC20 buyToken, + uint256[] memory sellAmounts + ) external view returns (Fraction[] memory prices); + + /// @notice Simulates swapping tokens on a given pair. + /// @dev This function should be state modifying meaning it should actually execute + /// @dev the swap and change the state of the evm accordingly. + /// @dev Please include a gas usage estimate for each amount. This can be achieved + /// @dev e.g. by using the `gasleft()` function. + /// @dev The return type trade, has a price attribute which should contain the + /// value of `price(specifiedAmount)`. As this is optional, defined via + /// `Capability.PriceFunction`, it is valid to return a zero value for this + /// price in that case it will be estimated numerically. To return zero use + /// Fraction(0, 1). + /// @param pairId The ID of the trading pair. + /// @param sellToken The token being sold. + /// @param buyToken The token being bought. + /// @param side The side of the trade (Sell or Buy). + /// @param specifiedAmount The amount to be traded. + /// @return trade Trade struct representing the executed trade. + function swap( + bytes32 pairId, + IERC20 sellToken, + IERC20 buyToken, + SwapSide side, + uint256 specifiedAmount + ) external returns (Trade memory trade); + + /// @notice Retrieves the limits for each token. + /// @dev Retrieve the maximum limits of a token that can be traded. The limit is reached + /// @dev when the change in the received amounts is zero or close to zero. If in doubt + /// @dev over estimate. The swap function should not error with `LimitExceeded` if + /// @dev called with amounts below the limit. + /// @param pairId The ID of the trading pair. + /// @return An array of limits. + function getLimits(bytes32 pairId, SwapSide side) + external + returns (uint256[] memory); + + /// @notice Retrieves the capabilities of the selected pair. + /// @param pairId The ID of the trading pair. + /// @return An array of Capabilities. + function getCapabilities(bytes32 pairId, IERC20 sellToken, IERC20 buyToken) + external + returns (Capabilities[] memory); + + /// @notice Retrieves the tokens in the selected pair. + /// @dev Mainly used for testing as this is redundant with the required substreams + /// @dev implementation. + /// @param pairId The ID of the trading pair. + /// @return tokens array of IERC20 contracts. + function getTokens(bytes32 pairId) + external + returns (IERC20[] memory tokens); + + /// @notice Retrieves a range of pool IDs. + /// @dev Mainly used for testing it is alright to not return all available pools here. + /// @dev Nevertheless this is useful to test against the substreams implementation. If + /// @dev implemented it safes time writing custom tests. + /// @param offset The starting index from which to retrieve pool IDs. + /// @param limit The maximum number of pool IDs to retrieve. + /// @return ids array of pool IDs. + function getPoolIds(uint256 offset, uint256 limit) + external + returns (bytes32[] memory ids); +} diff --git a/evm/src/interfaces/ISwapAdapterTypes.sol b/evm/interfaces/ISwapAdapterTypes.sol similarity index 73% rename from evm/src/interfaces/ISwapAdapterTypes.sol rename to evm/interfaces/ISwapAdapterTypes.sol index 3825593..abe5392 100644 --- a/evm/src/interfaces/ISwapAdapterTypes.sol +++ b/evm/interfaces/ISwapAdapterTypes.sol @@ -4,16 +4,14 @@ pragma solidity ^0.8.13; import "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; interface ISwapAdapterTypes { - /// @dev The SwapSide enum represents possible sides of a trade: Sell or - /// Buy. E.g. if SwapSide is Sell, the sell amount is interpreted to be - /// fixed. + /// @dev The SwapSide enum represents possible sides of a trade: Sell or Buy. + /// @dev E.g. if SwapSide is Sell, the sell amount is interpreted to be fixed. enum SwapSide { Sell, Buy } - /// @dev The Capabilities enum represents possible features of a trading - /// pair. + /// @dev The Capabilities enum represents possible features of a trading pair. enum Capabilities { Unset, // Support SwapSide.Sell values (required) @@ -24,20 +22,22 @@ interface ISwapAdapterTypes { PriceFunction, // Support tokens that charge a fee on transfer (optional) FeeOnTransfer, - // The pair does not suffer from price impact and mantains a constant - // price for increasingly larger speficied amounts. (optional) + // The pair does not suffer from price impact and mantains + // a constant price for increasingly larger speficied amounts. + // (optional) ConstantPrice, - // Indicates that the pair does not read it's own token balances while - // swapping. (optional) + // Indicates that the pair does not read it's own token balances + // while swapping. (optional) TokenBalanceIndependent, - // Indicates that prices are returned scaled, else it is assumed prices - // still require scaling by token decimals. + // Indicates that prices are returned scaled, else it is assumed + // prices still require scaling by token decimals. ScaledPrices } /// @dev Representation used for rational numbers such as prices. struct Fraction { - uint256 numerator; + // TODO: rename numerator + uint256 nominator; uint256 denominator; } @@ -49,10 +49,11 @@ interface ISwapAdapterTypes { } /// @dev The Unavailable error is thrown when a pool or swap is not - /// available for unexpected reason. E.g. it was paused due to a bug. + /// @dev available for unexpected reason, e.g. because it was paused + /// @dev due to a bug. error Unavailable(string reason); - /// @dev The LimitExceeded error is thrown when a limit has been exceeded. - /// E.g. the specified amount can't be traded safely. + /// @dev The LimitExceeded error is thrown when a limit has been + /// @dev exceeded. E.g. the specified amount can't be traded safely. error LimitExceeded(uint256 limit); } diff --git a/evm/remappings.txt b/evm/remappings.txt index 2c8eb8b..d5cf6c1 100644 --- a/evm/remappings.txt +++ b/evm/remappings.txt @@ -1,4 +1,4 @@ -interfaces/=src/interfaces/ +interfaces/=interfaces/ forge-std/=lib/forge-std/src/ openzeppelin-contracts/=lib/openzeppelin-contracts/ src/=src/ \ No newline at end of file diff --git a/evm/src/interfaces/ISwapAdapter.sol b/evm/src/interfaces/ISwapAdapter.sol deleted file mode 100644 index c138586..0000000 --- a/evm/src/interfaces/ISwapAdapter.sol +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.13; - -import "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; -import "interfaces/ISwapAdapterTypes.sol"; - -/// @title ISwapAdapterTypes -/// @dev Implement this interface to support propeller routing through your -/// pairs. Before implementing the interface we need to introduce three function -/// for a given pair: The swap(x), gas(x) and price(x) functions: The swap -/// function accepts some specified token amount: x and returns the amount y a -/// user can get by swapping x through the venue. The gas function simply -/// returns the estimated gas cost given a specified amount x. Last but not -/// least, the price function is the derivative of the swap function. It -/// represents the best possible price a user can get from a pair after swapping -/// x of the specified token. During calls to swap and getLimits, the caller can -/// be assumed to have the required sell or buy token balance as well as -/// unlimited approvals to this contract. -interface ISwapAdapter is ISwapAdapterTypes { - /// @notice Calculates pair prices for specified amounts (optional). - /// @dev The returned prices should include all dex fees, in case the fee is - /// dynamic, the returned price is expected to include the minimum fee. - /// Ideally this method should be implemented, although it is optional as - /// the price function can be numerically estimated from the swap function. - /// In case it is not available it should be flagged via capabilities and - /// calling it should revert using the `NotImplemented` error. The method - /// needs to be implemented as view as this is usually more efficient and - /// can be run in parallel. all. - /// @param pairId The ID of the trading pair. - /// @param sellToken The token being sold. - /// @param buyToken The token being bought. - /// @param sellAmounts The specified amounts used for price calculation. - /// @return prices array of prices as fractions corresponding to the - /// provided amounts. - function price( - bytes32 pairId, - IERC20 sellToken, - IERC20 buyToken, - uint256[] memory sellAmounts - ) external view returns (Fraction[] memory prices); - - /** - * @notice Simulates swapping tokens on a given pair. - * @dev This function should be state modifying meaning it should actually - * execute the swap and change the state of the evm accordingly. Please - * include a gas usage estimate for each amount. This can be achieved e.g. - * by using the `gasleft()` function. The return type trade, has a price - * attribute which should contain the value of `price(specifiedAmount)`. As - * this is optional, defined via `Capability.PriceFunction`, it is valid to - * return a zero value for this price in that case it will be estimated - * numerically. To return zero use Fraction(0, 1). - * @param pairId The ID of the trading pair. - * @param sellToken The token being sold. - * @param buyToken The token being bought. - * @param side The side of the trade (Sell or Buy). - * @param specifiedAmount The amount to be traded. - * @return trade Trade struct representing the executed trade. - */ - function swap( - bytes32 pairId, - IERC20 sellToken, - IERC20 buyToken, - SwapSide side, - uint256 specifiedAmount - ) external returns (Trade memory trade); - - /// @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. If in doubt over estimate. The swap function should not - /// error with `LimitExceeded` if called with amounts below the limit. - /// @param pairId The ID of the trading pair. - /// @return An array of limits. - function getLimits(bytes32 pairId, SwapSide side) - external - returns (uint256[] memory); - - /// @notice Retrieves the capabilities of the selected pair. - /// @param pairId The ID of the trading pair. - /// @return An array of Capabilities. - function getCapabilities(bytes32 pairId, IERC20 sellToken, IERC20 buyToken) - external - returns (Capabilities[] memory); - - /// @notice Retrieves the tokens in the selected pair. - /// @dev Mainly used for testing as this is redundant with the required - /// substreams implementation. - /// @param pairId The ID of the trading pair. - /// @return tokens array of IERC20 contracts. - function getTokens(bytes32 pairId) - external - returns (IERC20[] memory tokens); - - /// @notice Retrieves a range of pool IDs. - /// @dev Mainly used for testing it is alright to not return all available - /// pools here. Nevertheless this is useful to test against the substreams - /// implementation. If implemented it safes time writing custom tests. - /// @param offset The starting index from which to retrieve pool IDs. - /// @param limit The maximum number of pool IDs to retrieve. - /// @return ids array of pool IDs. - function getPoolIds(uint256 offset, uint256 limit) - external - returns (bytes32[] memory ids); -} diff --git a/evm/src/uniswap-v2/UniswapV2SwapAdapter.sol b/evm/src/uniswap-v2/UniswapV2SwapAdapter.sol index aac2bef..d86c505 100644 --- a/evm/src/uniswap-v2/UniswapV2SwapAdapter.sol +++ b/evm/src/uniswap-v2/UniswapV2SwapAdapter.sol @@ -101,8 +101,7 @@ contract UniswapV2SwapAdapter is ISwapAdapter { return amountOut; } - // given an input amount of an asset and pair reserves, returns the maximum - // output amount of the other asset + // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset function getAmountOut( uint256 amountIn, uint256 reserveIn, @@ -143,8 +142,7 @@ contract UniswapV2SwapAdapter is ISwapAdapter { return amount; } - // given an output amount of an asset and pair reserves, returns a required - // input amount of the other asset + // given an output amount of an asset and pair reserves, returns a required input amount of the other asset function getAmountIn( uint256 amountOut, uint256 reserveIn, diff --git a/evm/test/UniswapV2SwapAdapter.t.sol b/evm/test/UniswapV2SwapAdapter.t.sol index 8a82725..78ebe7f 100644 --- a/evm/test/UniswapV2SwapAdapter.t.sol +++ b/evm/test/UniswapV2SwapAdapter.t.sol @@ -33,7 +33,7 @@ contract UniswapV2PairFunctionTest is Test, ISwapAdapterTypes { pairFunctions.price(pair, WETH, USDC, amounts); for (uint256 i = 0; i < prices.length; i++) { - assertGt(prices[i].numerator, 0); + assertGt(prices[i].nominator, 0); assertGt(prices[i].denominator, 0); } } @@ -61,15 +61,14 @@ contract UniswapV2PairFunctionTest is Test, ISwapAdapterTypes { pure returns (int8) { - uint256 crossProduct1 = frac1.numerator * frac2.denominator; - uint256 crossProduct2 = frac2.numerator * frac1.denominator; + uint256 crossProduct1 = frac1.nominator * frac2.denominator; + uint256 crossProduct2 = frac2.nominator * frac1.denominator; - // fractions are equal - if (crossProduct1 == crossProduct2) return 0; - // frac1 is greater than frac2 - else if (crossProduct1 > crossProduct2) return 1; - // frac1 is less than frac2 - else return -1; + if (crossProduct1 == crossProduct2) return 0; // fractions are equal + + else if (crossProduct1 > crossProduct2) return 1; // frac1 is greater than frac2 + + else return -1; // frac1 is less than frac2 } function testSwapFuzz(uint256 amount, bool isBuy) public { @@ -128,6 +127,6 @@ contract UniswapV2PairFunctionTest is Test, ISwapAdapterTypes { function testGetLimits() public { bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); - uint256[] memory limits = pairFunctions.getLimits(pair, SwapSide.Sell); + pairFunctions.getLimits(pair, SwapSide.Sell); } }