diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 29a521bd58..168d5f3b00 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -125,6 +125,10 @@ { "note": "Pass back fillData from quote reporter", "pr": 2702 + }, + { + "note": "Fix Balancer sampling", + "pr": 2711 } ] }, diff --git a/packages/asset-swapper/contracts/src/BalancerSampler.sol b/packages/asset-swapper/contracts/src/BalancerSampler.sol index 0950cc8e15..8606935574 100644 --- a/packages/asset-swapper/contracts/src/BalancerSampler.sol +++ b/packages/asset-swapper/contracts/src/BalancerSampler.sol @@ -27,6 +27,12 @@ contract BalancerSampler { /// @dev Base gas limit for Balancer calls. uint256 constant private BALANCER_CALL_GAS = 300e3; // 300k + // Balancer math constants + // https://github.com/balancer-labs/balancer-core/blob/master/contracts/BConst.sol + uint256 constant private BONE = 10 ** 18; + uint256 constant private MAX_IN_RATIO = BONE / 2; + uint256 constant private MAX_OUT_RATIO = (BONE / 3) + 1 wei; + struct BalancerState { uint256 takerTokenBalance; uint256 makerTokenBalance; @@ -67,6 +73,11 @@ contract BalancerSampler { poolState.swapFee = pool.getSwapFee(); for (uint256 i = 0; i < numSamples; i++) { + // Handles this revert scenario: + // https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol#L443 + if (takerTokenAmounts[i] > _bmul(poolState.takerTokenBalance, MAX_IN_RATIO)) { + break; + } (bool didSucceed, bytes memory resultData) = poolAddress.staticcall.gas(BALANCER_CALL_GAS)( abi.encodeWithSelector( @@ -120,6 +131,11 @@ contract BalancerSampler { poolState.swapFee = pool.getSwapFee(); for (uint256 i = 0; i < numSamples; i++) { + // Handles this revert scenario: + // https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol#L505 + if (makerTokenAmounts[i] > _bmul(poolState.makerTokenBalance, MAX_OUT_RATIO)) { + break; + } (bool didSucceed, bytes memory resultData) = poolAddress.staticcall.gas(BALANCER_CALL_GAS)( abi.encodeWithSelector( @@ -140,4 +156,27 @@ contract BalancerSampler { takerTokenAmounts[i] = sellAmount; } } + + /// @dev Hacked version of Balancer's `bmul` function, returning 0 instead + /// of reverting. + /// https://github.com/balancer-labs/balancer-core/blob/master/contracts/BNum.sol#L63-L73 + /// @param a The first operand. + /// @param b The second operand. + /// @param c The result of the multiplication, or 0 if `bmul` would've reverted. + function _bmul(uint256 a, uint256 b) + private + pure + returns (uint256 c) + { + uint c0 = a * b; + if (a != 0 && c0 / a != b) { + return 0; + } + uint c1 = c0 + (BONE / 2); + if (c1 < c0) { + return 0; + } + uint c2 = c1 / BONE; + return c2; + } } diff --git a/packages/asset-swapper/src/utils/market_operation_utils/balancer_utils.ts b/packages/asset-swapper/src/utils/market_operation_utils/balancer_utils.ts index 936cd07e37..6a76c29f03 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/balancer_utils.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/balancer_utils.ts @@ -2,6 +2,8 @@ import { BigNumber } from '@0x/utils'; import { bmath, getPoolsWithTokens, parsePoolData } from '@balancer-labs/sor'; import { Decimal } from 'decimal.js'; +import { ZERO_AMOUNT } from './constants'; + // tslint:disable:boolean-naming export interface BalancerPool { @@ -118,6 +120,9 @@ export class BalancerPoolsCache { // tslint:disable completed-docs export function computeBalancerSellQuote(pool: BalancerPool, takerFillAmount: BigNumber): BigNumber { + if (takerFillAmount.isGreaterThan(bmath.bmul(pool.balanceIn, bmath.MAX_IN_RATIO))) { + return ZERO_AMOUNT; + } const weightRatio = pool.weightIn.dividedBy(pool.weightOut); const adjustedIn = bmath.BONE.minus(pool.swapFee) .dividedBy(bmath.BONE) @@ -130,8 +135,8 @@ export function computeBalancerSellQuote(pool: BalancerPool, takerFillAmount: Bi } export function computeBalancerBuyQuote(pool: BalancerPool, makerFillAmount: BigNumber): BigNumber { - if (makerFillAmount.isGreaterThanOrEqualTo(pool.balanceOut)) { - return new BigNumber(0); + if (makerFillAmount.isGreaterThan(bmath.bmul(pool.balanceOut, bmath.MAX_OUT_RATIO))) { + return ZERO_AMOUNT; } const weightRatio = pool.weightOut.dividedBy(pool.weightIn); const diff = pool.balanceOut.minus(makerFillAmount);