Recently the CoW Grants program recieved a noteable [grant proposal](https://forum.cow.fi/t/grant-application-milkman-2-0/1840) for working on a new version for the `milkman` project.
`milkman` received traction with several prominent DAOs, which is indicating demand for such products.
It also uncovered some challenges with the usability of milkman.
- Requiring a user to specify a price checker is cumbersome. Requiring a user
to manually encode bytes to be passed to the price checker is even more cumbersome.
- Price checker development for custom pairs is hindering trading of such pairs.
- Brownie, the original framework used to write Milkman’s tests, makes it hard
for developers to write their own price checkers. I picked Brownie over Foundry
because at the time there was no Solidity HTTP client. But now, there is.
- Sometimes, as was the case for Aave, users of Milkman will prefer a
dedicated support engineer. This is especially true when the user requires
a custom price checker.
Thinking through this problem, below we describe an alternative design for milkman2.0 which could use some existing building blocks and could offer a completely generic default deployment that works for any arbitrary ERC20 token pair with minimal parameters required.
- The user will be able to deploy a ComposablCoW (CC) compatilble safe and add a price guard on top of a sell order. The default offered price guard will use a falling price dutch auction with with just a few parameters settings.
- The reference price could be set as a constant or as a price input.
- The starting price is defined as the reference price + auction premium %
- The closing price is defined as the reference price - auction discount %
- Start time could be set as constant or to start at deployment
- The user also defines auction duration and absolute expiration time.
With different configurations, this can allow for the following use cases:
- generic dutch auction
- fixed price limit order
- milkman1.0 style price checker
- TWAP (gas intensive) by deploying several orders with time delays
### Technical Assessment
Code assessment: https://github.com/kayibal/composable-cow/pull/1
### Overview
A gap analysis was made between the above repository and the use cases required for DAOs as described above in the alternate `Milkman 2.0` (ie. no price checkers, dutch-auction only).
Using the code in the repository as a basis, and taking into account the below assessment, the time requirements would equate to:
1. Contract implementation: 1 day
2. Contract testing: 1 day
3. Watch tower (Web3 Actions) updating: 1 day
4. SDK support in `cow-sdk`: 0.5 day
#### Data requirements
Currently the `staticInput` (ie. `Data` struct):
```solidity
struct Data {
IERC20 sellToken;
IERC20 buyToken;
uint256 sellAmount;
bytes32 appData;
address receiver;
bool isPartiallyFillable;
// the time the auction starts
uint32 startTs;
// how long the auction will run for
uint32 duration;
// time step, after each step a new order with adjusted limit price is emitted
uint32 timeStep;
// == Curve parameters ==
// both oracles need to use the same numeraire
IAggregatorV3Interface sellTokenPriceOracle;
IAggregatorV3Interface buyTokenPriceOracle;
// start and end price need to be in the oracle numeraire
// expected as sell token / buy token price
uint256 startPrice;
uint256 endPrice;
}
```
Instead, given the requirements, at order mining time, we can determine:
1. Starting Price (+ auction premium)
2. Ending Price (- auction discount)
3. Starting time (with offset reference mining time)
4. Starting balance of `sellToken`.
To achieve this, use a custom `IValueFactory`, that takes the parameters:
1. Auction Premium (in BPS or start fixed).
2. Auction Discount (in BPS or end price fixed).
3. Auction time (offset if less than 1yr, otherwise actual start time).
4. Fixed or Oracle (bool).
5. Optional oracle config `bytes`, consisting of `abi.encode(OracleConfig)` where the `OracleConfig` struct consists of:
a. sellTokenOracle
b. buyTokenOracle
c. sellTokenERC20
d. buyTokenERC20
Using `IValueFactory`, fill out the struct:
```solidity
struct Cabinet {
uint256 startPrice;
uint256 endPrice;
uint32 t0;
// by including the `sellAmountBalance` in the struct, we
// can within some realm of certainty restrict the order
// from potentially firing multiple times before `duration`
// expires. This is only a valid consideration when:
// B(safe) >= 2 x sellAmount
uint256 sellAmountBalance;
}
```
In the cabinet, store `H(Cabinet)`. This struct would be required to be passed into `offChainInput`, and subsequently be validated against the `H(Cabinet)` stored in the cabinet.
The `staticInput` (ie. `Data`) struct could now be abbreviated to:
```solidity
struct Data {
IERC20 sellToken;
IERC20 buyToken;
uint256 sellAmount;
bytes32 appData;
address receiver;
bool isPartiallyFillable;
// how long the auction will run for
uint32 duration;
// time step, after each step a new order with adjusted limit price is emitted
uint32 timeStep;
}
```
#### Watch Tower Requirements
Given the use of the `context` this way, there would be the following requirements:
1. This is likely only to be usable with `createWithContext` (ie. not with Merkle Roots, due to the specific nature of the `ctx` generated and stored in the cabinet).
2. It would require some modification of the watch tower, notably:
a. Through normal events emitted, the watch tower can detect that it is a _dutch auction_ order type. No problems here.
b. There is no way to extract the parameters that were passed to `IValueFactory` as the functions is `view` only, and therefore unable to emit events.
c. (b) can be overcome by use of an RPC with `trace` filters in order to extract the original `calldata`. From this, the watch tower would then be able to process the logic required to determine the contents of the `Cabinet` struct, so that this can be passed in as `offChainInput`, and verified against `H(Cabinet)` that is stored in the `cabinet` mapping.