Skip to content
This repository has been archived by the owner on Nov 11, 2022. It is now read-only.

Commit

Permalink
Merge pull request #24 from GalloDaSballo/feat-v3
Browse files Browse the repository at this point in the history
Feat v3
  • Loading branch information
GalloDaSballo authored Aug 1, 2022
2 parents 12bf694 + a419a60 commit 2da6026
Show file tree
Hide file tree
Showing 55 changed files with 3,841 additions and 279 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ __pycache__
build/
reports/
.env
venv/

# Node/npm
node_modules/
92 changes: 82 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,61 @@
# Fair Selling

## Release V0.2 - Pricer - BribesProcessor - CowswapSeller
A [BadgerDAO](https://app.badger.com/) sponsored repo of Open Source Contracts for:
- Integrating Smart Contracts with Cowswap
- Non-Custodial handling of tokens via BribesProcessors
- Calculating onChain Prices
- Executing the best onChain Swap

# CoswapSeller
## Why bother

We understand that we cannot prove a optimal price because at any time a new source of liquidity may be available and the contract cannot adapt.

However we believe that given a set of constraints (available Dexes, handpicked), we can efficiently compute the best trade available to us

In exploring this issue we aim to:
- Find the most gas-efficient way to get the best executable price (currently 120 /150k per quote, from 1.6MLN)
- Finding the most reliable price we can, to determine if an offer is fair or unfair (Cowswap integration)
- Can we create a "trustless swap" that is provably not frontrun nor manipulated?
- How would such a "self-defending" contract act and how would it be able to defend itself, get the best quote, and be certain of it (with statistical certainty)

## Current Release V0.3 - Pricer - BribesProcessor - CowswapSeller

# Notable Contracts
## CowswapSeller

OnChain Integration with Cowswap, all the functions you want to:
- Verify an Order
- Retrieve the OrderUid from the orderData
- Validate an order through basic security checks (price is correct, sends to correct recipient)
- Integrated with an onChain Pricer (see below), to offer stronger execution guarantees

# BribesProcessor
## BribesProcessor

Anti-rug technlogy, allows a Multi-sig to rapidly process cowswap orders, without allowing the Multi to rug
Anti-rug technplogy, allows a Multi-sig to rapidly process CowSwap orders, without allowing the Multi to rug
Allows tokens to be rescued without the need for governance via the `ragequit` function

# MainnetPricing
- `AuraBribesProcessor` -> Processor for Votium Bribes earned by `bveAura`
- `VotiumBribesProcessor` -> Processor for Votium Bribes earned by `bveCVX`

## OnChainPricingMainnet

Given a tokenIn, tokenOut and AmountIn, returns a Quote from the most popular dexes

## Dexes Support
- `OnChainPricingMainnet` -> Fully onChain math to find best, single source swap (no fragmented swaps yet)
- `OnChainPricingMainnetLenient` -> Slippage tollerant version of the Pricer

### Dexes Support
- Curve
- UniV2
- UniV3
- Balancer
- Sushi

Covering >80% TVL on Mainnet.
Covering >80% TVL on Mainnet. (Prob even more)

## Example Usage

NOTE: Because of Balancer and UniV3 (go bug their devs pls), the following functions are not view, you must `.call` them from offchain to avoid spending gas
BREAKING CHANGE: V3 is back to `view` even for Balancer and UniV3 functions

### isPairSupported

Expand All @@ -45,7 +70,7 @@ NOTE: This is not proof of optimality

In Brownie
```python
quote = pricer.isPairSupported.call(t_in, t_out, amt_in) ## Add .call to avoid paying for the tx
quote = pricer.isPairSupported(t_in, t_out, amt_in)
```

### findOptimalSwap
Expand All @@ -59,11 +84,58 @@ NOTE: While the function says optimal, this is not optimal, just best of the bun

In Brownie
```python
quote = pricer.findOptimalSwap.call(t_in, t_out, amt_in) ## Add .call to avoid paying for the tx
quote = pricer.findOptimalSwap(t_in, t_out, amt_in)
```


# Mainnet Pricing Lenient

Variation of Pricer with a slippage tollerance



# Notable Tests

## Proof that the math is accurate with gas savings

These tests compare the PricerV3 (150k per quote) against V2 (1.6MLN per quote)

```
brownie test tests/heuristic_equivalency/test_heuristic_equivalency.py
```

## Benchmark specific AMM quotes
TODO: Improve to just use the specific quote

```
brownie test tests/gas_benchmark/benchmark_pricer_gas.py --gas
```

## Benchmark coverage of top DeFi Tokens

TODO: Add like 200 tokens
TODO: Compare against Coingecko API or smth

```
brownie test tests/gas_benchmark/benchmark_token_coverage.py --gas
```

## Notable Test from V2

Run V3 Pricer against V2, to confirm results are correct, but with gas savings

```
brownie test tests/heuristic_equivalency/test_heuristic_equivalency.py
```


# Deployments

WARNING: This list is not maintained and may be out of date or incorrect. DYOR.

`bveCVX Bribes Processor`: https://etherscan.io/address/0xb2bf1d48f2c2132913278672e6924efda3385de2

`bveAURA Bribes Processor`: https://etherscan.io/address/0x0b6198b324e12a002b60162f8a130d6aedabd04c

Pricers can be found by checking `processor.pricer()`
5 changes: 4 additions & 1 deletion brownie-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ dependencies:
# path remapping to support imports from GitHub/NPM
compiler:
solc:
version: 0.8.10
# version: 0.8.10
remappings:
- "@oz=OpenZeppelin/[email protected]/contracts/"

reports:
exclude_contracts:
- SafeERC20
- IERC20
- ReentrancyGuard
- Address
2 changes: 1 addition & 1 deletion contracts/AuraBribesProcessor.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: GPL-2.0
pragma solidity 0.8.10;


Expand Down
136 changes: 136 additions & 0 deletions contracts/BalancerSwapSimulator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.7.6;
pragma abicoder v2;

import "./libraries/balancer/BalancerFixedPoint.sol";
import "./libraries/balancer/BalancerStableMath.sol";

struct ExactInQueryParam{
address tokenIn;
address tokenOut;
uint256 balanceIn;
uint256 weightIn;
uint256 balanceOut;
uint256 weightOut;
uint256 amountIn;
uint256 swapFeePercentage;
}

struct ExactInStableQueryParam{
address[] tokens;
uint256[] balances;
uint256 currentAmp;
uint256 tokenIndexIn;
uint256 tokenIndexOut;
uint256 amountIn;
uint256 swapFeePercentage;
}

interface IERC20Metadata {
function decimals() external view returns (uint8);
}

/// @dev Swap Simulator for Balancer V2
contract BalancerSwapSimulator {
uint256 internal constant _MAX_IN_RATIO = 0.3e18;

/// @dev reference https://github.com/balancer-labs/balancer-v2-monorepo/blob/master/pkg/pool-weighted/contracts/WeightedMath.sol#L78
function calcOutGivenIn(ExactInQueryParam memory _query) public view returns (uint256) {
/**********************************************************************************************
// outGivenIn //
// aO = amountOut //
// bO = balanceOut //
// bI = balanceIn / / bI \ (wI / wO) \ //
// aI = amountIn aO = bO * | 1 - | -------------------------- | ^ | //
// wI = weightIn \ \ ( bI + aI ) / / //
// wO = weightOut //
**********************************************************************************************/

// upscale all balances and amounts
_query.amountIn = _subtractSwapFeeAmount(_query.amountIn, _query.swapFeePercentage);

uint256 _scalingFactorIn = _computeScalingFactorWeightedPool(_query.tokenIn);
_query.amountIn = BalancerMath.mul(_query.amountIn, _scalingFactorIn);
_query.balanceIn = BalancerMath.mul(_query.balanceIn, _scalingFactorIn);
require(_query.balanceIn > _query.amountIn, "!amtIn");

uint256 _scalingFactorOut = _computeScalingFactorWeightedPool(_query.tokenOut);
_query.balanceOut = BalancerMath.mul(_query.balanceOut, _scalingFactorOut);

require(_query.amountIn <= BalancerFixedPoint.mulDown(_query.balanceIn, _MAX_IN_RATIO), "!maxIn");

uint256 denominator = BalancerFixedPoint.add(_query.balanceIn, _query.amountIn);
uint256 base = BalancerFixedPoint.divUp(_query.balanceIn, denominator);
uint256 exponent = BalancerFixedPoint.divDown(_query.weightIn, _query.weightOut);
uint256 power = BalancerFixedPoint.powUp(base, exponent);

uint256 _scaledOut = BalancerFixedPoint.mulDown(_query.balanceOut, BalancerFixedPoint.complement(power));
return BalancerMath.divDown(_scaledOut, _scalingFactorOut);
}

/// @dev reference https://etherscan.io/address/0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb2#code#F1#L244
function calcOutGivenInForStable(ExactInStableQueryParam memory _query) public view returns (uint256) {
/**************************************************************************************************************
// outGivenIn token x for y - polynomial equation to solve //
// ay = amount out to calculate //
// by = balance token out //
// y = by - ay (finalBalanceOut) //
// D = invariant D D^(n+1) //
// A = amplification coefficient y^2 + ( S - ---------- - D) * y - ------------- = 0 //
// n = number of tokens (A * n^n) A * n^2n * P //
// S = sum of final balances but y //
// P = product of final balances but y //
**************************************************************************************************************/

// upscale all balances and amounts
uint256 _tkLen = _query.tokens.length;
uint256[] memory _scalingFactors = new uint256[](_tkLen);
for (uint256 i = 0;i < _tkLen;++i){
_scalingFactors[i] = _computeScalingFactor(_query.tokens[i]);
}

_query.amountIn = _subtractSwapFeeAmount(_query.amountIn, _query.swapFeePercentage);
_query.balances = _upscaleStableArray(_query.balances, _scalingFactors);
_query.amountIn = _upscaleStable(_query.amountIn, _scalingFactors[_query.tokenIndexIn]);

uint256 invariant = BalancerStableMath._calculateInvariant(_query.currentAmp, _query.balances, true);

_query.balances[_query.tokenIndexIn] = BalancerFixedPoint.add(_query.balances[_query.tokenIndexIn], _query.amountIn);
uint256 finalBalanceOut = BalancerStableMath._getTokenBalanceGivenInvariantAndAllOtherBalances(_query.currentAmp, _query.balances, invariant, _query.tokenIndexOut);

uint256 _scaledOut = BalancerFixedPoint.sub(_query.balances[_query.tokenIndexOut], BalancerFixedPoint.add(finalBalanceOut, 1));
return _downscaleStable(_scaledOut, _scalingFactors[_query.tokenIndexOut]);
}

/// @dev scaling factors for weighted pool: reference https://etherscan.io/address/0xc45d42f801105e861e86658648e3678ad7aa70f9#code#F24#L474
function _computeScalingFactorWeightedPool(address token) private view returns (uint256) {
return 10**BalancerFixedPoint.sub(18, IERC20Metadata(token).decimals());
}

/// @dev scaling factors for stable pool: reference https://etherscan.io/address/0x06df3b2bbb68adc8b0e302443692037ed9f91b42#code#F12#L510
function _computeScalingFactor(address token) internal view returns (uint256) {
return BalancerFixedPoint.ONE * 10**BalancerFixedPoint.sub(18, IERC20Metadata(token).decimals());
}

function _upscaleStableArray(uint256[] memory amounts, uint256[] memory scalingFactors) internal pure returns (uint256[] memory) {
uint256 _len = amounts.length;
for (uint256 i = 0; i < _len;++i) {
amounts[i] = _upscaleStable(amounts[i], scalingFactors[i]);
}
return amounts;
}

function _upscaleStable(uint256 amount, uint256 scalingFactor) internal pure returns (uint256) {
return BalancerFixedPoint.mulDown(amount, scalingFactor);
}

function _downscaleStable(uint256 amount, uint256 scalingFactor) internal pure returns (uint256) {
return BalancerFixedPoint.divDown(amount, scalingFactor);
}

function _subtractSwapFeeAmount(uint256 amount, uint256 _swapFeePercentage) public view returns (uint256) {
uint256 feeAmount = BalancerFixedPoint.mulUp(amount, _swapFeePercentage);
return BalancerFixedPoint.sub(amount, feeAmount);
}

}
21 changes: 19 additions & 2 deletions contracts/CowSwapSeller.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: GPL-2.0
pragma solidity 0.8.10;


Expand Down Expand Up @@ -29,7 +29,7 @@ struct Quote {
uint256[] poolFees; // specific pool fees involved in the optimal swap path, typically in Uniswap V3
}
interface OnChainPricing {
function findOptimalSwap(address tokenIn, address tokenOut, uint256 amountIn) external returns (Quote memory);
function findOptimalSwap(address tokenIn, address tokenOut, uint256 amountIn) external view returns (Quote memory);
}
// END OnchainPricing

Expand Down Expand Up @@ -142,11 +142,15 @@ contract CowSwapSeller is ReentrancyGuard {
domainSeparator = SETTLEMENT.domainSeparator();
}

/// @dev Set the Pricer Contract used to determine if a Order is fair
/// @param newPricer - the new pricer
function setPricer(OnChainPricing newPricer) external {
require(msg.sender == DEV_MULTI);
pricer = newPricer;
}

/// @dev Set the Manager, the account that can process the tokens
/// @param newManager - the new manager
function setManager(address newManager) external {
require(msg.sender == manager);
manager = newManager;
Expand Down Expand Up @@ -192,6 +196,9 @@ contract CowSwapSeller is ReentrancyGuard {
}
}

/// @dev Given the orderData, returns an orderId
/// @param orderData - All the information for a Cowswap Order
/// @return bytes - the OrderId
function getOrderID(Data calldata orderData) public view returns (bytes memory) {
// Allocated
bytes memory orderUid = new bytes(UID_LENGTH);
Expand All @@ -203,6 +210,13 @@ contract CowSwapSeller is ReentrancyGuard {
return orderUid;
}

/// @dev Given the orderData and the orderUid
/// Verify the parameter match the id and do basic checks for price and recipient
/// @notice Virtual so you can override, e.g. for Limit Orders by other contracts
/// @notice Reverts on lack of basic validation
/// However it returns false if the slippage check didn't pass
/// Meaning it won't revert if you've been quoted a bad price
/// @return bool - Whether it passed the slippage checks
function checkCowswapOrder(Data calldata orderData, bytes memory orderUid) public virtual returns(bool) {
// Verify we get the same ID
// NOTE: technically superfluous as we could just derive the id and setPresignature with that
Expand Down Expand Up @@ -232,6 +246,8 @@ contract CowSwapSeller is ReentrancyGuard {


/// @dev This is the function you want to use to perform a swap on Cowswap via this smart contract
/// @param orderData - The data for the order, see {Data}
/// @param orderUid - the identifier for the order
function _doCowswapOrder(Data calldata orderData, bytes memory orderUid) internal nonReentrant {
require(msg.sender == manager);

Expand All @@ -248,6 +264,7 @@ contract CowSwapSeller is ReentrancyGuard {

/// @dev Allows to cancel a cowswap order perhaps if it took too long or was with invalid parameters
/// @notice This function performs no checks, there's a high change it will revert if you send it with fluff parameters
/// @param orderUid - The id of the order to cancel
function _cancelCowswapOrder(bytes memory orderUid) internal nonReentrant {
require(msg.sender == manager);

Expand Down
Loading

0 comments on commit 2da6026

Please sign in to comment.