Skip to content

Commit

Permalink
tests: e2e twap test
Browse files Browse the repository at this point in the history
  • Loading branch information
mfw78 committed Jan 28, 2024
1 parent d779b43 commit 51f24b9
Show file tree
Hide file tree
Showing 2 changed files with 212 additions and 5 deletions.
67 changes: 65 additions & 2 deletions test/FundingModule.t.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,69 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0 <0.9.0;

import {FundingModuleTestHarness, IERC20, ISafe} from "./FundingModule/FundingModuleTestHarness.sol";
import "./FundingModule/FundingModuleTestHarness.sol";
import "lib/composable-cow/src/types/twap/TWAP.sol";

contract FundingModuleTestE2E is FundingModuleTestHarness {}
contract FundingModuleTestE2E is FundingModuleTestHarness {
function setUp() public virtual override(FundingModuleTestHarness) {
super.setUp();
setUpTokenAmounts();
}

function testFundingModuleE2E() public {
setUpComposableCow();
setUpTrampoline();

// 1. Fund the source safe with the funding amount
setUpTokenAmounts();

// 2. Fund the destination safe with some AMM tokens (initial)
setUpAmmReserves();

// 3. Ensure that the staging safe has allowance on the source safe
// NOTE: This is already handled in `super.setUp()`

// 4. Create the TWAP on the staging safe
TWAPOrder.Data memory twapData = getTWAPOrder();
uint256 startTime = block.timestamp + 1 minutes;
vm.warp(startTime);
IConditionalOrder.ConditionalOrderParams memory params =
super.createOrder(twap, keccak256("twap"), abi.encode(twapData));
_createWithContext(address(stagingSafe), params, currentBlockTimestampFactory, bytes(""), false);

// 5. Make sure the vault relayer has sufficient allowance on the staging safe
vm.prank(address(stagingSafe));
token0.approve(address(relayer), twapData.n * twapData.partSellAmount);

// 6. Setup the hooks
HooksTrampoline.Hook[] memory preHooks = new HooksTrampoline.Hook[](1);
preHooks[0] = HooksTrampoline.Hook({
target: address(fundingModule),
callData: abi.encodeWithSelector(FundingModule.pull.selector),
gasLimit: 50000
});
HooksTrampoline.Hook[] memory postHooks = new HooksTrampoline.Hook[](1);
postHooks[0] = HooksTrampoline.Hook({
target: address(fundingModule),
callData: abi.encodeWithSelector(FundingModule.push.selector),
gasLimit: 40000
});

// 7. Iterate over each part of the TWAP and settle it
for (uint256 i = 0; i < twapData.n; i++) {
vm.warp(startTime + (i * twapData.t));

// 7.2. Get the order and signature for the current part of the TWAP
(GPv2Order.Data memory order, bytes memory signature) =
composableCow.getTradeableOrderWithSignature(address(stagingSafe), params, bytes(""), new bytes32[](0));

// 7.3. Settle the current part of the TWAP
settleWithHooks(address(stagingSafe), bob, order, signature, bytes4(0), preHooks, postHooks);
}

// Checks
assertEq(token0.balanceOf(address(fundingSrc)), FUNDING_AMOUNT - (twapData.n * twapData.partSellAmount));
assertEq(token0.balanceOf(address(stagingSafe)), 0);
assertEq(token1.balanceOf(address(stagingSafe)), 0);
}
}
150 changes: 147 additions & 3 deletions test/FundingModule/FundingModuleTestHarness.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0 <0.9.0;

import {Base} from "lib/composable-cow/test/Base.t.sol";
import "lib/composable-cow/test/ComposableCoW.base.t.sol";
import {
GPv2Trade,
GPv2Interaction,
GPv2Signing
} from "lib/composable-cow/lib/cowprotocol/src/contracts/GPv2Settlement.sol";
import {GPv2TradeEncoder} from "lib/composable-cow/test/vendored/GPv2TradeEncoder.sol";
import "lib/composable-cow/src/value_factories/CurrentBlockTimestampFactory.sol";
import "lib/composable-cow/src/types/twap/TWAP.sol";
import {FundingModule, IERC20, ISafe} from "../../src/FundingModule.sol";
import "lib/hooks-trampoline/src/HooksTrampoline.sol";

abstract contract FundingModuleTestHarness is BaseComposableCoWTest {
using SafeLib for Safe;
using TestAccountLib for TestAccount;

abstract contract FundingModuleTestHarness is Base {
// --- constants
uint256 constant FUNDING_AMOUNT = 1_000_000e18;
uint256 constant AMM_TOKEN0_AMOUNT = 50_000e18;
Expand All @@ -14,11 +26,13 @@ abstract contract FundingModuleTestHarness is Base {

// --- variables
FundingModule fundingModule;
HooksTrampoline trampoline;
IValueFactory currentBlockTimestampFactory;
ISafe stagingSafe;
address fundingSrc;
address fundingDst;

function setUp() public virtual override(Base) {
function setUp() public virtual override(BaseComposableCoWTest) {
super.setUp();

stagingSafe = ISafe(address(safe1));
Expand Down Expand Up @@ -56,4 +70,134 @@ abstract contract FundingModuleTestHarness is Base {
// deal some tokens to the staging safe to create a residual
deal(address(token1), address(stagingSafe), BOUGHT_AMOUNT);
}

function setUpTrampoline() internal {
// create a trampoline
trampoline = new HooksTrampoline(address(settlement));
}

function setUpComposableCow() internal {
// deploy the current block timestamp factory
currentBlockTimestampFactory = new CurrentBlockTimestampFactory();
}

function getTWAPOrder() internal returns (TWAPOrder.Data memory) {
// Assemble the TWAP bundle
TWAPOrder.Data memory bundle = TWAPOrder.Data({
sellToken: token0,
buyToken: token1,
receiver: address(stagingSafe),
partSellAmount: SELL_AMOUNT,
minPartLimit: 1,
t0: 0,
n: 10,
t: 3600,
span: 0,
// The below appData should be set correctly for the hooks when
// used on-chain in the real system.
appData: keccak256("twapWithHooks")
});
return bundle;
}

function settleWithHooks(
address who,
TestAccount memory counterParty,
GPv2Order.Data memory order,
bytes memory bundleBytes,
bytes4 _revertSelector,
HooksTrampoline.Hook[] memory preHooks,
HooksTrampoline.Hook[] memory postHooks
) internal {
// Generate counter party's order
GPv2Order.Data memory counterOrder = GPv2Order.Data({
sellToken: order.buyToken,
buyToken: order.sellToken,
receiver: address(0),
sellAmount: order.buyAmount,
buyAmount: order.sellAmount,
validTo: order.validTo,
appData: order.appData,
feeAmount: 0,
kind: GPv2Order.KIND_BUY,
partiallyFillable: false,
buyTokenBalance: GPv2Order.BALANCE_ERC20,
sellTokenBalance: GPv2Order.BALANCE_ERC20
});

bytes memory counterPartySig =
counterParty.signPacked(GPv2Order.hash(counterOrder, settlement.domainSeparator()));

// Authorize the GPv2VaultRelayer to spend bob's sell token
vm.prank(counterParty.addr);
IERC20(counterOrder.sellToken).approve(address(relayer), counterOrder.sellAmount);

// first declare the tokens we will be trading
IERC20[] memory tokens = new IERC20[](2);
tokens[0] = IERC20(order.sellToken);
tokens[1] = IERC20(order.buyToken);

// second declare the clearing prices
uint256[] memory clearingPrices = new uint256[](2);
clearingPrices[0] = counterOrder.sellAmount;
clearingPrices[1] = counterOrder.buyAmount;

// third declare the trades
GPv2Trade.Data[] memory trades = new GPv2Trade.Data[](2);

// The safe's order is the first trade
trades[0] = GPv2Trade.Data({
sellTokenIndex: 0,
buyTokenIndex: 1,
receiver: order.receiver,
sellAmount: order.sellAmount,
buyAmount: order.buyAmount,
validTo: order.validTo,
appData: order.appData,
feeAmount: order.feeAmount,
flags: GPv2TradeEncoder.encodeFlags(order, GPv2Signing.Scheme.Eip1271),
executedAmount: order.sellAmount,
signature: abi.encodePacked(who, bundleBytes)
});

// Bob's order is the second trade
trades[1] = GPv2Trade.Data({
sellTokenIndex: 1,
buyTokenIndex: 0,
receiver: address(0),
sellAmount: counterOrder.sellAmount,
buyAmount: counterOrder.buyAmount,
validTo: counterOrder.validTo,
appData: counterOrder.appData,
feeAmount: counterOrder.feeAmount,
flags: GPv2TradeEncoder.encodeFlags(counterOrder, GPv2Signing.Scheme.Eip712),
executedAmount: counterOrder.sellAmount,
signature: counterPartySig
});

// fourth declare the interactions
GPv2Interaction.Data[][3] memory interactions =
[wrapHooks(preHooks), new GPv2Interaction.Data[](0), wrapHooks(postHooks)];

// finally we can execute the settlement
vm.prank(solver.addr);
if (_revertSelector == bytes4(0)) {
settlement.settle(tokens, clearingPrices, trades, interactions);
} else {
vm.expectRevert(_revertSelector);
settlement.settle(tokens, clearingPrices, trades, interactions);
}
}

function wrapHooks(HooksTrampoline.Hook[] memory hooks)
internal
returns (GPv2Interaction.Data[] memory trampolined)
{
trampolined = new GPv2Interaction.Data[](1);
trampolined[0] = GPv2Interaction.Data({
target: address(trampoline),
callData: abi.encodeWithSelector(trampoline.execute.selector, hooks),
value: 0
});
}
}

0 comments on commit 51f24b9

Please sign in to comment.