From efc11714c9fa662994c092539f8f313f155785c7 Mon Sep 17 00:00:00 2001 From: Federico Giacon <58218759+fedgiac@users.noreply.github.com> Date: Wed, 7 Feb 2024 12:49:54 +0000 Subject: [PATCH 1/2] Add deploy scripts (#11) * Add deploy scripts * Fix script used in DeployConstantProductTest * Fix script used in DeployAllContractsTest --- README.md | 11 +++++++++++ script/Counter.s.sol | 12 ------------ script/Deploy.sol | 12 ------------ script/DeployAllContracts.s.sol | 18 ++++++++++++++++++ script/single-deployment/ConstantProduct.s.sol | 17 +++++++++++++++++ .../UniswapV2PriceOracle.s.sol | 17 +++++++++++++++++ test/script/DeployAllContracts.t.sol | 18 ++++++++++++++++++ .../DeployConstantProduct.s.sol | 18 ++++++++++++++++++ .../DeployUniswapV2PriceOracle.sol | 18 ++++++++++++++++++ 9 files changed, 117 insertions(+), 24 deletions(-) delete mode 100644 script/Counter.s.sol delete mode 100644 script/Deploy.sol create mode 100644 script/DeployAllContracts.s.sol create mode 100644 script/single-deployment/ConstantProduct.s.sol create mode 100644 script/single-deployment/UniswapV2PriceOracle.s.sol create mode 100644 test/script/DeployAllContracts.t.sol create mode 100644 test/script/single-deployment/DeployConstantProduct.s.sol create mode 100644 test/script/single-deployment/DeployUniswapV2PriceOracle.sol diff --git a/README.md b/README.md index 70cd790..fbda5a0 100644 --- a/README.md +++ b/README.md @@ -36,3 +36,14 @@ $ forge test ```shell $ forge fmt ``` + +### Deploy + +All contracts in this repo can be deployed and verified on the block explorer as follows: + +```sh +export ETHERSCAN_API_KEY='your API key here' +PK='the private key of the deployer' +ETH_RPC_URL='https://rpc.node.url.here.example.com' +forge script 'script/DeployAllContracts.s.sol:DeployAllContracts' -vvvv --rpc-url "$ETH_RPC_URL" --private-key "$PK" --verify --broadcast +``` \ No newline at end of file diff --git a/script/Counter.s.sol b/script/Counter.s.sol deleted file mode 100644 index df9ee8b..0000000 --- a/script/Counter.s.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; - -contract CounterScript is Script { - function setUp() public {} - - function run() public { - vm.broadcast(); - } -} diff --git a/script/Deploy.sol b/script/Deploy.sol deleted file mode 100644 index 57bdf42..0000000 --- a/script/Deploy.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; - -contract DeployScript is Script { - function setUp() public {} - - function run() public { - vm.broadcast(); - } -} diff --git a/script/DeployAllContracts.s.sol b/script/DeployAllContracts.s.sol new file mode 100644 index 0000000..30fc13a --- /dev/null +++ b/script/DeployAllContracts.s.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import {Script} from "forge-std/Script.sol"; + +import {DeployUniswapV2PriceOracle, UniswapV2PriceOracle} from "./single-deployment/UniswapV2PriceOracle.s.sol"; +import {DeployConstantProduct, ConstantProduct} from "./single-deployment/ConstantProduct.s.sol"; + +contract DeployAllContracts is DeployConstantProduct, DeployUniswapV2PriceOracle { + function run() public override(DeployConstantProduct, DeployUniswapV2PriceOracle) { + deployAll(); + } + + function deployAll() public returns (ConstantProduct constantProduct, UniswapV2PriceOracle uniswapV2PriceOracle) { + constantProduct = deployConstantProduct(); + uniswapV2PriceOracle = deployUniswapV2PriceOracle(); + } +} diff --git a/script/single-deployment/ConstantProduct.s.sol b/script/single-deployment/ConstantProduct.s.sol new file mode 100644 index 0000000..61c8948 --- /dev/null +++ b/script/single-deployment/ConstantProduct.s.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import {Script} from "forge-std/Script.sol"; + +import {ConstantProduct} from "src/ConstantProduct.sol"; + +contract DeployConstantProduct is Script { + function run() public virtual { + deployConstantProduct(); + } + + function deployConstantProduct() internal returns (ConstantProduct) { + vm.broadcast(); + return new ConstantProduct(); + } +} diff --git a/script/single-deployment/UniswapV2PriceOracle.s.sol b/script/single-deployment/UniswapV2PriceOracle.s.sol new file mode 100644 index 0000000..5a04a9e --- /dev/null +++ b/script/single-deployment/UniswapV2PriceOracle.s.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import {Script} from "forge-std/Script.sol"; + +import {UniswapV2PriceOracle} from "src/oracles/UniswapV2PriceOracle.sol"; + +contract DeployUniswapV2PriceOracle is Script { + function run() public virtual { + deployUniswapV2PriceOracle(); + } + + function deployUniswapV2PriceOracle() internal returns (UniswapV2PriceOracle) { + vm.broadcast(); + return new UniswapV2PriceOracle(); + } +} diff --git a/test/script/DeployAllContracts.t.sol b/test/script/DeployAllContracts.t.sol new file mode 100644 index 0000000..7b8f548 --- /dev/null +++ b/test/script/DeployAllContracts.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import {Test} from "forge-std/Test.sol"; + +import {DeployAllContracts} from "script/DeployAllContracts.s.sol"; + +contract DeployAllContractsTest is Test { + DeployAllContracts script; + + function setUp() public { + script = new DeployAllContracts(); + } + + function testDoesNotRevert() public { + script.run(); + } +} diff --git a/test/script/single-deployment/DeployConstantProduct.s.sol b/test/script/single-deployment/DeployConstantProduct.s.sol new file mode 100644 index 0000000..0deb2b1 --- /dev/null +++ b/test/script/single-deployment/DeployConstantProduct.s.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import {Test} from "forge-std/Test.sol"; + +import {DeployConstantProduct} from "script/single-deployment/ConstantProduct.s.sol"; + +contract DeployConstantProductTest is Test { + DeployConstantProduct script; + + function setUp() public { + script = new DeployConstantProduct(); + } + + function testDoesNotRevert() public { + script.run(); + } +} diff --git a/test/script/single-deployment/DeployUniswapV2PriceOracle.sol b/test/script/single-deployment/DeployUniswapV2PriceOracle.sol new file mode 100644 index 0000000..05536dc --- /dev/null +++ b/test/script/single-deployment/DeployUniswapV2PriceOracle.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import {Test} from "forge-std/Test.sol"; + +import {DeployUniswapV2PriceOracle} from "script/single-deployment/UniswapV2PriceOracle.s.sol"; + +contract DeployUniswapV2PriceOracleTest is Test { + DeployUniswapV2PriceOracle script; + + function setUp() public { + script = new DeployUniswapV2PriceOracle(); + } + + function testDoesNotRevert() public { + script.run(); + } +} From 82f34c52ebc5e914f0b48ce45b6003ce9492a243 Mon Sep 17 00:00:00 2001 From: Federico Giacon <58218759+fedgiac@users.noreply.github.com> Date: Wed, 7 Feb 2024 13:44:55 +0000 Subject: [PATCH 2/2] Tell watchtower to retry if order is too small (#10) --- src/ConstantProduct.sol | 5 ++- src/interfaces/IWatchtowerCustomErrors.sol | 19 ++++++++++ .../ValidateOrderParametersTest.sol | 35 +++++++++++++++++-- 3 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 src/interfaces/IWatchtowerCustomErrors.sol diff --git a/src/ConstantProduct.sol b/src/ConstantProduct.sol index fbfbc35..46190d4 100644 --- a/src/ConstantProduct.sol +++ b/src/ConstantProduct.sol @@ -12,6 +12,7 @@ import { } from "lib/composable-cow/src/BaseConditionalOrder.sol"; import {IPriceOracle} from "./interfaces/IPriceOracle.sol"; +import {IWatchtowerCustomErrors} from "./interfaces/IWatchtowerCustomErrors.sol"; /** * @title CoW AMM @@ -128,7 +129,9 @@ contract ConstantProduct is IConditionalOrderGenerator { } if (tradedAmountToken0 < data.minTradedToken0) { - revert IConditionalOrder.OrderNotValid("traded amount too small"); + revert IWatchtowerCustomErrors.PollTryAtEpoch( + Utils.validToBucket(MAX_ORDER_DURATION) + 1, "traded amount too small" + ); } order = GPv2Order.Data( diff --git a/src/interfaces/IWatchtowerCustomErrors.sol b/src/interfaces/IWatchtowerCustomErrors.sol new file mode 100644 index 0000000..6e081bf --- /dev/null +++ b/src/interfaces/IWatchtowerCustomErrors.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +/** + * @title Watchtower Custom Error Interface + * @author CoW Protocol Developers + * @dev An interface that collects all custom error message for the watchtower. + * Different error messages lead to different watchtower behaviors when creating + * an order. + * @dev The watchtower is a service that automatically posts orders to the CoW + * Protocol orderbook at regular intervals. + */ +contract IWatchtowerCustomErrors { + /** + * No order is currently available for trading, but the watchtower should + * try again at the specified timestamp. + */ + error PollTryAtEpoch(uint256 timestamp, string message); +} diff --git a/test/ConstantProduct/getTradeableOrder/ValidateOrderParametersTest.sol b/test/ConstantProduct/getTradeableOrder/ValidateOrderParametersTest.sol index bdbb509..f036c5c 100644 --- a/test/ConstantProduct/getTradeableOrder/ValidateOrderParametersTest.sol +++ b/test/ConstantProduct/getTradeableOrder/ValidateOrderParametersTest.sol @@ -2,7 +2,8 @@ pragma solidity ^0.8.13; import {ConstantProductTestHarness} from "../ConstantProductTestHarness.sol"; -import {ConstantProduct, GPv2Order, IConditionalOrder} from "../../../src/ConstantProduct.sol"; +import {ConstantProduct, GPv2Order, IConditionalOrder} from "src/ConstantProduct.sol"; +import {IWatchtowerCustomErrors} from "src/interfaces/IWatchtowerCustomErrors.sol"; abstract contract ValidateOrderParametersTest is ConstantProductTestHarness { function testValidOrderParameters() public { @@ -44,12 +45,26 @@ abstract contract ValidateOrderParametersTest is ConstantProductTestHarness { setUpDefaultReserves(orderOwner); setUpDefaultReferencePairReserves(42, 1337); + uint256 smallOffset = 42; + require(smallOffset < constantProduct.MAX_ORDER_DURATION()); + uint256 nextTimestamp = 1337 * constantProduct.MAX_ORDER_DURATION() + smallOffset; + uint256 nextBucket = 1338 * constantProduct.MAX_ORDER_DURATION(); + require( + nextTimestamp % constantProduct.MAX_ORDER_DURATION() != 0, + "test was designed so that the timestamp doesn't fall exactly at the start of a bucket, please change the offset" + ); + vm.warp(nextTimestamp); + 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")); + vm.expectRevert( + abi.encodeWithSelector( + IWatchtowerCustomErrors.PollTryAtEpoch.selector, nextBucket + 1, "traded amount too small" + ) + ); getTradeableOrderUncheckedWrapper(orderOwner, defaultData); } @@ -58,12 +73,26 @@ abstract contract ValidateOrderParametersTest is ConstantProductTestHarness { setUpDefaultReserves(orderOwner); setUpDefaultReferencePairReserves(1337, 42); + uint256 smallOffset = 42; + require(smallOffset < constantProduct.MAX_ORDER_DURATION()); + uint256 nextTimestamp = 1337 * constantProduct.MAX_ORDER_DURATION() + smallOffset; + uint256 nextBucket = 1338 * constantProduct.MAX_ORDER_DURATION(); + require( + nextTimestamp % constantProduct.MAX_ORDER_DURATION() != 0, + "test was designed so that the timestamp doesn't fall exactly at the start of a bucket, please change the offset" + ); + vm.warp(nextTimestamp); + 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")); + vm.expectRevert( + abi.encodeWithSelector( + IWatchtowerCustomErrors.PollTryAtEpoch.selector, nextBucket + 1, "traded amount too small" + ) + ); getTradeableOrderUncheckedWrapper(orderOwner, defaultData); } }