Skip to content

Commit

Permalink
chore: merge dev
Browse files Browse the repository at this point in the history
  • Loading branch information
wei3erHase committed Jul 23, 2024
2 parents 4967b34 + 4b5fab7 commit 32f249c
Show file tree
Hide file tree
Showing 19 changed files with 1,357 additions and 671 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@cowprotocol/contracts": "github:cowprotocol/contracts.git#a10f40788a",
"@openzeppelin/contracts": "5.0.2",
"composable-cow": "github:cowprotocol/composable-cow.git#24d556b",
"cow-amm": "github:cowprotocol/cow-amm.git#6566128",
"solmate": "github:transmissions11/solmate#c892309"
},
"devDependencies": {
Expand Down
3 changes: 3 additions & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ solmate/=node_modules/solmate/src
@cowprotocol/=node_modules/@cowprotocol/contracts/src/contracts
cowprotocol/=node_modules/@cowprotocol/contracts/src/
@composable-cow/=node_modules/composable-cow/
@cow-amm/=node_modules/cow-amm/src
lib/openzeppelin/=node_modules/@openzeppelin

contracts/=src/contracts
interfaces/=src/interfaces
libraries/=src/libraries
125 changes: 125 additions & 0 deletions src/contracts/BCoWHelper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.25;

import {IBCoWFactory} from 'interfaces/IBCoWFactory.sol';
import {IBCoWPool} from 'interfaces/IBCoWPool.sol';

import {ICOWAMMPoolHelper} from '@cow-amm/interfaces/ICOWAMMPoolHelper.sol';
import {GetTradeableOrder} from '@cow-amm/libraries/GetTradeableOrder.sol';

import {IERC20} from '@cowprotocol/interfaces/IERC20.sol';
import {GPv2Interaction} from '@cowprotocol/libraries/GPv2Interaction.sol';
import {GPv2Order} from '@cowprotocol/libraries/GPv2Order.sol';

import {BMath} from 'contracts/BMath.sol';

/**
* @title BCoWHelper
* @notice Helper contract that allows to trade on CoW Swap Protocol.
* @dev This contract supports only 2-token equal-weights pools.
*/
contract BCoWHelper is ICOWAMMPoolHelper, BMath {
using GPv2Order for GPv2Order.Data;

/// @notice The app data used by this helper's factory.
bytes32 internal immutable _APP_DATA;

/// @inheritdoc ICOWAMMPoolHelper
// solhint-disable-next-line style-guide-casing
address public immutable factory;

constructor(address factory_) {
factory = factory_;
_APP_DATA = IBCoWFactory(factory_).APP_DATA();
}

/// @inheritdoc ICOWAMMPoolHelper
function order(
address pool,
uint256[] calldata prices
)
external
view
returns (
GPv2Order.Data memory order_,
GPv2Interaction.Data[] memory preInteractions,
GPv2Interaction.Data[] memory postInteractions,
bytes memory sig
)
{
address[] memory tokens_ = tokens(pool);

GetTradeableOrder.GetTradeableOrderParams memory params = GetTradeableOrder.GetTradeableOrderParams({
pool: pool,
token0: IERC20(tokens_[0]),
token1: IERC20(tokens_[1]),
// The price of this function is expressed as amount of
// token1 per amount of token0. The `prices` vector is
// expressed the other way around.
priceNumerator: prices[1],
priceDenominator: prices[0],
appData: _APP_DATA
});

order_ = GetTradeableOrder.getTradeableOrder(params);

{
// NOTE: Using calcOutGivenIn for the sell amount in order to avoid possible rounding
// issues that may cause invalid orders. This prevents CoW Protocol back-end from generating
// orders that may be ignored due to rounding-induced reverts.

uint256 balanceToken0 = IERC20(tokens_[0]).balanceOf(pool);
uint256 balanceToken1 = IERC20(tokens_[1]).balanceOf(pool);
(uint256 balanceIn, uint256 balanceOut) =
address(order_.buyToken) == tokens_[0] ? (balanceToken0, balanceToken1) : (balanceToken1, balanceToken0);

order_.sellAmount = calcOutGivenIn({
tokenBalanceIn: balanceIn,
tokenWeightIn: 1e18,
tokenBalanceOut: balanceOut,
tokenWeightOut: 1e18,
tokenAmountIn: order_.buyAmount,
swapFee: 0
});
}

// A ERC-1271 signature on CoW Protocol is composed of two parts: the
// signer address and the valid ERC-1271 signature data for that signer.
bytes memory eip1271sig;
eip1271sig = abi.encode(order_);
sig = abi.encodePacked(pool, eip1271sig);

// Generate the order commitment pre-interaction
bytes32 domainSeparator = IBCoWPool(pool).SOLUTION_SETTLER_DOMAIN_SEPARATOR();
bytes32 orderCommitment = order_.hash(domainSeparator);

preInteractions = new GPv2Interaction.Data[](1);
preInteractions[0] = GPv2Interaction.Data({
target: pool,
value: 0,
callData: abi.encodeWithSelector(IBCoWPool.commit.selector, orderCommitment)
});

return (order_, preInteractions, postInteractions, sig);
}

/// @inheritdoc ICOWAMMPoolHelper
function tokens(address pool) public view virtual returns (address[] memory tokens_) {
// reverts in case pool is not deployed by the helper's factory
if (!IBCoWFactory(factory).isBPool(pool)) {
revert PoolDoesNotExist();
}

// call reverts with `BPool_PoolNotFinalized()` in case pool is not finalized
tokens_ = IBCoWPool(pool).getFinalTokens();

// reverts in case pool is not supported (non-2-token pool)
if (tokens_.length != 2) {
revert PoolDoesNotExist();
}
// reverts in case pool is not supported (non-equal weights)
if (IBCoWPool(pool).getNormalizedWeight(tokens_[0]) != IBCoWPool(pool).getNormalizedWeight(tokens_[1])) {
revert PoolDoesNotExist();
}
}
}
181 changes: 181 additions & 0 deletions test/integration/BCoWHelper.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.24;

import {Test} from 'forge-std/Test.sol';

import {IERC20} from '@cowprotocol/interfaces/IERC20.sol';

import {IBCoWPool} from 'interfaces/IBCoWPool.sol';
import {IBPool} from 'interfaces/IBPool.sol';
import {ISettlement} from 'interfaces/ISettlement.sol';

import {ICOWAMMPoolHelper} from '@cow-amm/interfaces/ICOWAMMPoolHelper.sol';
import {GPv2Interaction} from '@cowprotocol/libraries/GPv2Interaction.sol';
import {GPv2Order} from '@cowprotocol/libraries/GPv2Order.sol';
import {GPv2Trade} from '@cowprotocol/libraries/GPv2Trade.sol';
import {GPv2Signing} from '@cowprotocol/mixins/GPv2Signing.sol';

import {GPv2TradeEncoder} from '@composable-cow/test/vendored/GPv2TradeEncoder.sol';

import {BCoWFactory} from 'contracts/BCoWFactory.sol';
import {BCoWHelper} from 'contracts/BCoWHelper.sol';

contract BCoWHelperIntegrationTest is Test {
using GPv2Order for GPv2Order.Data;

BCoWHelper private helper;

// All hardcoded addresses are mainnet addresses
address public lp = makeAddr('lp');

ISettlement private settlement = ISettlement(0x9008D19f58AAbD9eD0D60971565AA8510560ab41);
address private vaultRelayer;

address private solver = 0x423cEc87f19F0778f549846e0801ee267a917935;

BCoWFactory private ammFactory;
IBPool private weightedPool;
IBPool private basicPool;

IERC20 private constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
IERC20 private constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);

uint256 constant TEN_PERCENT = 0.1 ether;
// NOTE: 1 ETH = 1000 DAI
uint256 constant INITIAL_DAI_BALANCE = 1000 ether;
uint256 constant INITIAL_WETH_BALANCE = 1 ether;
uint256 constant INITIAL_SPOT_PRICE = 0.001e18;

uint256 constant SKEWENESS_RATIO = 95; // -5% skewness
uint256 constant EXPECTED_FINAL_SPOT_PRICE = INITIAL_SPOT_PRICE * 100 / SKEWENESS_RATIO;

function setUp() public {
vm.createSelectFork('mainnet', 20_012_063);

vaultRelayer = address(settlement.vaultRelayer());

ammFactory = new BCoWFactory(address(settlement), bytes32('appData'));
helper = new BCoWHelper(address(ammFactory));

deal(address(DAI), lp, type(uint256).max, false);
deal(address(WETH), lp, type(uint256).max, false);

vm.startPrank(lp);
basicPool = ammFactory.newBPool();
weightedPool = ammFactory.newBPool();

DAI.approve(address(basicPool), type(uint256).max);
WETH.approve(address(basicPool), type(uint256).max);
basicPool.bind(address(DAI), INITIAL_DAI_BALANCE, 4.2e18); // no weight
basicPool.bind(address(WETH), INITIAL_WETH_BALANCE, 4.2e18); // no weight

DAI.approve(address(weightedPool), type(uint256).max);
WETH.approve(address(weightedPool), type(uint256).max);
// NOTE: pool is 80-20 DAI-WETH, has 4xDAI balance than basic, same spot price
weightedPool.bind(address(DAI), 4 * INITIAL_DAI_BALANCE, 8e18); // 80% weight
weightedPool.bind(address(WETH), INITIAL_WETH_BALANCE, 2e18); // 20% weight

// finalize
basicPool.finalize();
weightedPool.finalize();

vm.stopPrank();
}

function testBasicOrder() public {
IBCoWPool pool = IBCoWPool(address(basicPool));

uint256 spotPrice = pool.getSpotPriceSansFee(address(WETH), address(DAI));
assertEq(spotPrice, INITIAL_SPOT_PRICE);

_executeHelperOrder(pool);

uint256 postSpotPrice = pool.getSpotPriceSansFee(address(WETH), address(DAI));
assertEq(postSpotPrice, EXPECTED_FINAL_SPOT_PRICE);
}

// NOTE: reverting test, weighted pools are not supported
function testWeightedOrder() public {
IBCoWPool pool = IBCoWPool(address(weightedPool));

uint256 spotPrice = pool.getSpotPriceSansFee(address(WETH), address(DAI));
assertEq(spotPrice, INITIAL_SPOT_PRICE);

vm.expectRevert(ICOWAMMPoolHelper.PoolDoesNotExist.selector);
helper.order(address(pool), new uint256[](2));
}

function _executeHelperOrder(IBPool pool) internal {
address[] memory tokens = helper.tokens(address(pool));
uint256 daiIndex = 0;
uint256 wethIndex = 1;
assertEq(tokens.length, 2);
assertEq(tokens[daiIndex], address(DAI));
assertEq(tokens[wethIndex], address(WETH));

// Prepare the price vector used in the execution of the settlement in
// CoW Protocol. We skew the price by ~5% towards a cheaper WETH, so
// that the AMM wants to buy WETH.
uint256[] memory prices = new uint256[](2);
// Note: oracle price are expressed in the same format as prices in
// a call to `settle`, where the price vector is expressed so that
// if the first token is DAI and the second WETH then a price of 3000
// DAI per WETH means a price vector of [1, 3000] (if the decimals are
// different, as in WETH/USDC, then the atom amount is what counts).
prices[daiIndex] = INITIAL_WETH_BALANCE;
prices[wethIndex] = INITIAL_DAI_BALANCE * SKEWENESS_RATIO / 100;

// The helper generates the AMM order
GPv2Order.Data memory ammOrder;
GPv2Interaction.Data[] memory preInteractions;
GPv2Interaction.Data[] memory postInteractions;
bytes memory sig;
(ammOrder, preInteractions, postInteractions, sig) = helper.order(address(pool), prices);

// We expect a commit interaction in pre interactions
assertEq(preInteractions.length, 1);
assertEq(postInteractions.length, 0);

// Because of how we changed the price, we expect to buy WETH
assertEq(address(ammOrder.sellToken), address(DAI));
assertEq(address(ammOrder.buyToken), address(WETH));

// Check that the amounts and price aren't unreasonable. We changed the
// price by about 5%, so the amounts aren't expected to change
// significantly more (say, about 2.5% of the original balance).
assertApproxEqRel(ammOrder.sellAmount, INITIAL_DAI_BALANCE * 25 / 1000, TEN_PERCENT);
assertApproxEqRel(ammOrder.buyAmount, INITIAL_WETH_BALANCE * 25 / 1000, TEN_PERCENT);

GPv2Trade.Data[] memory trades = new GPv2Trade.Data[](1);

// pool's trade
trades[0] = GPv2Trade.Data({
sellTokenIndex: 0,
buyTokenIndex: 1,
receiver: ammOrder.receiver,
sellAmount: ammOrder.sellAmount,
buyAmount: ammOrder.buyAmount,
validTo: ammOrder.validTo,
appData: ammOrder.appData,
feeAmount: ammOrder.feeAmount,
flags: GPv2TradeEncoder.encodeFlags(ammOrder, GPv2Signing.Scheme.Eip1271),
executedAmount: ammOrder.sellAmount,
signature: sig
});

GPv2Interaction.Data[][3] memory interactions =
[new GPv2Interaction.Data[](1), new GPv2Interaction.Data[](0), new GPv2Interaction.Data[](0)];

interactions[0][0] = preInteractions[0];

// cast tokens array to IERC20 array
IERC20[] memory ierc20vec;
assembly {
ierc20vec := tokens
}

// finally, settle
vm.prank(solver);
settlement.settle(ierc20vec, prices, trades, interactions);
}
}
1 change: 1 addition & 0 deletions test/integration/BCowPool.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {GPv2Interaction} from '@cowprotocol/libraries/GPv2Interaction.sol';
import {GPv2Order} from '@cowprotocol/libraries/GPv2Order.sol';
import {GPv2Trade} from '@cowprotocol/libraries/GPv2Trade.sol';
import {GPv2Signing} from '@cowprotocol/mixins/GPv2Signing.sol';

import {BCoWConst} from 'contracts/BCoWConst.sol';
import {BCoWFactory} from 'contracts/BCoWFactory.sol';

Expand Down
43 changes: 42 additions & 1 deletion test/manual-smock/MockBCoWFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ import {BCoWFactory, BCoWPool, BFactory, IBCoWFactory, IBPool} from '../../src/c
import {Test} from 'forge-std/Test.sol';

contract MockBCoWFactory is BCoWFactory, Test {
// NOTE: manually added methods (immutable overrides not supported in smock)
function mock_call_APP_DATA(bytes32 _appData) public {
vm.mockCall(address(this), abi.encodeWithSignature('APP_DATA()'), abi.encode(_appData));
}

function expectCall_APP_DATA() public {
vm.expectCall(address(this), abi.encodeWithSignature('APP_DATA()'));
}

// BCoWFactory methods
constructor(address solutionSettler, bytes32 appData) BCoWFactory(solutionSettler, appData) {}

function mock_call_logBCoWPool() public {
Expand All @@ -31,8 +41,39 @@ contract MockBCoWFactory is BCoWFactory, Test {
}

// MockBFactory methods

function set__isBPool(address _key0, bool _value) public {
_isBPool[_key0] = _value;
}

function call__isBPool(address _key0) public view returns (bool) {
return _isBPool[_key0];
}

function set__bDao(address __bDao) public {
_bDao = __bDao;
}

function call__bDao() public view returns (address) {
return _bDao;
}

function mock_call_newBPool(IBPool bPool) public {
vm.mockCall(address(this), abi.encodeWithSignature('newBPool()'), abi.encode(bPool));
}

function mock_call_setBDao(address bDao) public {
vm.mockCall(address(this), abi.encodeWithSignature('setBDao(address)', bDao), abi.encode());
}

function mock_call_collect(IBPool bPool) public {
vm.mockCall(address(this), abi.encodeWithSignature('collect(IBPool)', bPool), abi.encode());
}

function mock_call_isBPool(address bPool, bool _returnParam0) public {
vm.mockCall(address(this), abi.encodeWithSignature('isBPool(address)', bPool), abi.encode(_returnParam0));
}

function mock_call_getBDao(address _returnParam0) public {
vm.mockCall(address(this), abi.encodeWithSignature('getBDao()'), abi.encode(_returnParam0));
}
}
Loading

0 comments on commit 32f249c

Please sign in to comment.