Skip to content

Commit

Permalink
Merge branch 'main' into amm-docs
Browse files Browse the repository at this point in the history
  • Loading branch information
fedgiac committed Feb 5, 2024
2 parents 3e58cf9 + b0fae5b commit 2abb759
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 5 deletions.
10 changes: 10 additions & 0 deletions src/ConstantProduct.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ contract ConstantProduct is IConditionalOrderGenerator {
IERC20 token0;
/// The second of the tokens traded by this AMM.
IERC20 token1;
/// The minimum amount of token0 that needs to be traded for an order
/// to be created on getTradeableOrder.
uint256 minTradedToken0;
/// An onchain source for the price of the two tokens. The price should
/// be expressed in terms of amount of token0 per amount of token1.
IPriceOracle priceOracle;
Expand Down Expand Up @@ -99,6 +102,7 @@ contract ConstantProduct is IConditionalOrderGenerator {
// isn't the AMM best price.
uint256 selfReserve0TimesPriceDenominator = selfReserve0 * priceDenominator;
uint256 selfReserve1TimesPriceNumerator = selfReserve1 * priceNumerator;
uint256 tradedAmountToken0;
if (selfReserve1TimesPriceNumerator < selfReserve0TimesPriceDenominator) {
sellToken = token0;
buyToken = token1;
Expand All @@ -109,6 +113,7 @@ contract ConstantProduct is IConditionalOrderGenerator {
priceNumerator * selfReserve0,
Math.Rounding.Up
);
tradedAmountToken0 = sellAmount;
} else {
sellToken = token1;
buyToken = token0;
Expand All @@ -119,6 +124,11 @@ contract ConstantProduct is IConditionalOrderGenerator {
priceDenominator * selfReserve1,
Math.Rounding.Up
);
tradedAmountToken0 = buyAmount;
}

if (tradedAmountToken0 < data.minTradedToken0) {
revert IConditionalOrder.OrderNotValid("traded amount too small");
}

order = GPv2Order.Data(
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/IPriceOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ interface IPriceOracle {
* oracle implementation. For example, it could be a specific pool id for
* balancer, or the address of a specific price feed for Chainlink.
* We recommend this data be implemented as the abi-encoding of a dedicated
* data struct for ease of type-checking and decoding the input.
* data struct for ease of type-checking and decoding the input.
* @return priceNumerator The numerator of the price, expressed in amount of
* token1 per amount of token0.
* @return priceDenominator The denominator of the price, expressed in
Expand Down
17 changes: 14 additions & 3 deletions test/ConstantProduct/ConstantProductTestHarness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ abstract contract ConstantProductTestHarness is BaseComposableCoWTest {
return ConstantProduct.Data(
IERC20(USDC),
IERC20(WETH),
0,
uniswapV2PriceOracle,
abi.encode(UniswapV2PriceOracle.Data(IUniswapV2Pair(DEFAULT_PAIR))),
DEFAULT_APPDATA
Expand Down Expand Up @@ -80,9 +81,8 @@ abstract contract ConstantProductTestHarness is BaseComposableCoWTest {
}

// This function calls `getTradeableOrder` while filling all unused
// parameters with arbitrary data. Since every tradeable order is supposed
// to be executable, it also immediately checks that the order is valid.
function getTradeableOrderWrapper(address owner, ConstantProduct.Data memory staticInput)
// parameters with arbitrary data.
function getTradeableOrderUncheckedWrapper(address owner, ConstantProduct.Data memory staticInput)
internal
view
returns (GPv2Order.Data memory order)
Expand All @@ -94,6 +94,17 @@ abstract contract ConstantProductTestHarness is BaseComposableCoWTest {
abi.encode(staticInput),
bytes("offchain input")
);
}

// This function calls `getTradeableOrder` while filling all unused
// parameters with arbitrary data. It also immediately checks that the order
// is valid.
function getTradeableOrderWrapper(address owner, ConstantProduct.Data memory staticInput)
internal
view
returns (GPv2Order.Data memory order)
{
order = getTradeableOrderUncheckedWrapper(owner, staticInput);
verifyWrapper(owner, staticInput, order);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity ^0.8.13;

import {ConstantProductTestHarness} from "../ConstantProductTestHarness.sol";
import {ConstantProduct, GPv2Order} from "../../../src/ConstantProduct.sol";
import {ConstantProduct, GPv2Order, IConditionalOrder} from "../../../src/ConstantProduct.sol";

abstract contract ValidateOrderParametersTest is ConstantProductTestHarness {
function testValidOrderParameters() public {
Expand Down Expand Up @@ -38,4 +38,32 @@ abstract contract ValidateOrderParametersTest is ConstantProductTestHarness {
order = getTradeableOrderWrapper(orderOwner, defaultData);
assertEq(order.validTo, 2 * constantProduct.MAX_ORDER_DURATION());
}

function testRevertsIfAmountTooLowOnSellToken() public {
ConstantProduct.Data memory defaultData = setUpDefaultData();
setUpDefaultReserves(orderOwner);
setUpDefaultReferencePairReserves(42, 1337);

GPv2Order.Data memory order = getTradeableOrderWrapper(orderOwner, defaultData);
require(order.sellToken == defaultData.token0, "test was design for token0 to be the sell token");
defaultData.minTradedToken0 = order.sellAmount;
order = getTradeableOrderWrapper(orderOwner, defaultData);
defaultData.minTradedToken0 = order.sellAmount + 1;
vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.OrderNotValid.selector, "traded amount too small"));
getTradeableOrderUncheckedWrapper(orderOwner, defaultData);
}

function testRevertsIfAmountTooLowOnBuyToken() public {
ConstantProduct.Data memory defaultData = setUpDefaultData();
setUpDefaultReserves(orderOwner);
setUpDefaultReferencePairReserves(1337, 42);

GPv2Order.Data memory order = getTradeableOrderWrapper(orderOwner, defaultData);
require(order.buyToken == defaultData.token0, "test was design for token0 to be the buy token");
defaultData.minTradedToken0 = order.buyAmount;
order = getTradeableOrderWrapper(orderOwner, defaultData);
defaultData.minTradedToken0 = order.buyAmount + 1;
vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.OrderNotValid.selector, "traded amount too small"));
getTradeableOrderUncheckedWrapper(orderOwner, defaultData);
}
}
1 change: 1 addition & 0 deletions test/ConstantProduct/verify/ValidateAmmMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ abstract contract ValidateAmmMath is ConstantProductTestHarness {
data = ConstantProduct.Data(
order.sellToken,
order.buyToken,
0,
uniswapV2PriceOracle,
abi.encode(abi.encode(UniswapV2PriceOracle.Data(pair))),
order.appData
Expand Down

0 comments on commit 2abb759

Please sign in to comment.