Skip to content

Commit

Permalink
Merge pull request #275 from balancer-labs/develop
Browse files Browse the repository at this point in the history
4.0.1-beta.1
  • Loading branch information
John Grant authored Jul 8, 2022
2 parents 4a159c3 + 0531698 commit 0e853ce
Show file tree
Hide file tree
Showing 10 changed files with 2,626 additions and 25 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ node_modules
yarn-error.log
.DS_Store
.env
dist/
dist/
cache/
12 changes: 12 additions & 0 deletions hardhat.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
require('@nomiclabs/hardhat-ethers');

/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
networks: {
hardhat: {
chainId: 1,
},
},
};
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@balancer-labs/sor",
"version": "4.0.1-beta.0",
"version": "4.0.1-beta.1",
"license": "GPL-3.0-only",
"main": "dist/index.js",
"module": "dist/index.esm.js",
Expand All @@ -10,7 +10,8 @@
"prepack": "yarn build",
"test": "TS_NODE_PROJECT='tsconfig.testing.json' nyc mocha -r ts-node/register test/*.spec.ts",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"lint": "eslint ./src ./test --ext .ts --max-warnings 0"
"lint": "eslint ./src ./test --ext .ts --max-warnings 0",
"node": "npx hardhat node --fork $(grep ALCHEMY_URL .env | cut -d '=' -f2) --fork-block-number 14828550"
},
"husky": {
"hooks": {
Expand All @@ -25,6 +26,7 @@
"dist"
],
"devDependencies": {
"@balancer-labs/typechain": "^1.0.0",
"@ethersproject/abi": "^5.4.1",
"@ethersproject/address": "^5.4.0",
"@ethersproject/bignumber": "^5.4.2",
Expand All @@ -33,6 +35,7 @@
"@ethersproject/providers": "^5.4.4",
"@ethersproject/wallet": "^5.4.0",
"@georgeroman/balancer-v2-pools": "0.0.7",
"@nomiclabs/hardhat-ethers": "^2.0.6",
"@rollup/plugin-commonjs": "^20.0.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.0.4",
Expand All @@ -50,6 +53,7 @@
"eslint": "^7.32.0",
"eslint-plugin-mocha-no-only": "^1.1.1",
"eslint-plugin-prettier": "^3.4.1",
"hardhat": "^2.9.9",
"husky": "^4.2.1",
"lodash.clonedeep": "^4.5.0",
"lodash.set": "^4.3.2",
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ export { MetaStablePool } from './pools/metaStablePool/metaStablePool';
export { PhantomStablePool } from './pools/phantomStablePool/phantomStablePool';
export { LinearPool } from './pools/linearPool/linearPool';
export { getSpotPriceAfterSwapForPath } from './router/helpersClass';
export * as WeightedMaths from './pools/weightedPool/weightedMath';
export * as StableMaths from './pools/stablePool/stableMath';
311 changes: 310 additions & 1 deletion src/pools/weightedPool/weightedMath.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { formatFixed } from '@ethersproject/bignumber';
import { BigNumber as OldBigNumber, bnum } from '../../utils/bignumber';
import { WeightedPoolPairData } from './weightedPool';
import { MathSol } from '../../utils/basicOperations';
import { MathSol, BZERO } from '../../utils/basicOperations';

// The following function are BigInt versions implemented by Sergio.
// BigInt was requested from integrators as it is more efficient.
Expand Down Expand Up @@ -115,6 +115,315 @@ export function _spotPriceAfterSwapTokenInForExactTokenOutBigInt(
// )
}

/**
* Calculates BPT for given tokens in. Note all numbers use upscaled amounts. e.g. 1USDC = 1e18.
* @param balances Pool balances.
* @param normalizedWeights Token weights.
* @param amountsIn Amount of each token.
* @param bptTotalSupply Total BPT of pool.
* @param swapFeePercentage Swap fee percentage.
* @returns BPT out.
*/
export function _calcBptOutGivenExactTokensIn(
balances: bigint[],
normalizedWeights: bigint[],
amountsIn: bigint[],
bptTotalSupply: bigint,
swapFeePercentage: bigint
): bigint {
const balanceRatiosWithFee = new Array<bigint>(amountsIn.length);

let invariantRatioWithFees = BZERO;
for (let i = 0; i < balances.length; i++) {
balanceRatiosWithFee[i] = MathSol.divDownFixed(
MathSol.add(balances[i], amountsIn[i]),
balances[i]
);
invariantRatioWithFees = MathSol.add(
invariantRatioWithFees,
MathSol.mulDownFixed(balanceRatiosWithFee[i], normalizedWeights[i])
);
}

let invariantRatio = MathSol.ONE;
for (let i = 0; i < balances.length; i++) {
let amountInWithoutFee: bigint;

if (balanceRatiosWithFee[i] > invariantRatioWithFees) {
const nonTaxableAmount = MathSol.mulDownFixed(
balances[i],
MathSol.sub(invariantRatioWithFees, MathSol.ONE)
);
const taxableAmount = MathSol.sub(amountsIn[i], nonTaxableAmount);
const swapFee = MathSol.mulUpFixed(
taxableAmount,
swapFeePercentage
);
amountInWithoutFee = MathSol.add(
nonTaxableAmount,
MathSol.sub(taxableAmount, swapFee)
);
} else {
amountInWithoutFee = amountsIn[i];
}

const balanceRatio = MathSol.divDownFixed(
MathSol.add(balances[i], amountInWithoutFee),
balances[i]
);

invariantRatio = MathSol.mulDownFixed(
invariantRatio,
MathSol.powDown(balanceRatio, normalizedWeights[i])
);
}

if (invariantRatio > MathSol.ONE) {
return MathSol.mulDownFixed(
bptTotalSupply,
MathSol.sub(invariantRatio, MathSol.ONE)
);
} else {
return BZERO;
}
}

export function _calcTokensOutGivenExactBptIn(
balances: bigint[],
bptAmountIn: bigint,
totalBPT: bigint
): bigint[] {
/**********************************************************************************************
// exactBPTInForTokensOut //
// (per token) //
// aO = amountOut / bptIn \ //
// b = balance a0 = b * | --------------------- | //
// bptIn = bptAmountIn \ totalBPT / //
// bpt = totalBPT //
**********************************************************************************************/

// Since we're computing an amount out, we round down overall. This means rounding down on both the
// multiplication and division.

const bptRatio = MathSol.divDownFixed(bptAmountIn, totalBPT);

const amountsOut = new Array<bigint>(balances.length);
for (let i = 0; i < balances.length; i++) {
amountsOut[i] = MathSol.mulDownFixed(balances[i], bptRatio);
}

return amountsOut;
}

export function _calcTokenOutGivenExactBptIn(
balance: bigint,
normalizedWeight: bigint,
bptAmountIn: bigint,
bptTotalSupply: bigint,
swapFeePercentage: bigint
): bigint {
/*****************************************************************************************
// exactBPTInForTokenOut //
// a = amountOut //
// b = balance / / totalBPT - bptIn \ (1 / w) \ //
// bptIn = bptAmountIn a = b * | 1 - | -------------------------- | ^ | //
// bpt = totalBPT \ \ totalBPT / / //
// w = weight //
*****************************************************************************************/

// Token out, so we round down overall. The multiplication rounds down, but the power rounds up (so the base
// rounds up). Because (totalBPT - bptIn) / totalBPT <= 1, the exponent rounds down.
// Calculate the factor by which the invariant will decrease after burning BPTAmountIn
const invariantRatio = MathSol.divUpFixed(
MathSol.sub(bptTotalSupply, bptAmountIn),
bptTotalSupply
);
// Calculate by how much the token balance has to decrease to match invariantRatio
const balanceRatio = MathSol.powUpFixed(
invariantRatio,
MathSol.divDownFixed(MathSol.ONE, normalizedWeight)
);

// Because of rounding up, balanceRatio can be greater than one. Using complement prevents reverts.
const amountOutWithoutFee = MathSol.mulDownFixed(
balance,
MathSol.complementFixed(balanceRatio)
);

// We can now compute how much excess balance is being withdrawn as a result of the virtual swaps, which result
// in swap fees.

// Swap fees are typically charged on 'token in', but there is no 'token in' here, so we apply it
// to 'token out'. This results in slightly larger price impact. Fees are rounded up.
const taxableAmount = MathSol.mulUpFixed(
amountOutWithoutFee,
MathSol.complementFixed(normalizedWeight)
);
const nonTaxableAmount = MathSol.sub(amountOutWithoutFee, taxableAmount);
const taxableAmountMinusFees = MathSol.mulUpFixed(
taxableAmount,
MathSol.complementFixed(swapFeePercentage)
);

return MathSol.add(nonTaxableAmount, taxableAmountMinusFees);
}

export function _calcBptInGivenExactTokensOut(
balances: bigint[],
normalizedWeights: bigint[],
amountsOut: bigint[],
bptTotalSupply: bigint,
swapFeePercentage: bigint
): bigint {
// BPT in, so we round up overall.
const balanceRatiosWithoutFee = new Array<bigint>(amountsOut.length);

let invariantRatioWithoutFees = BZERO;
for (let i = 0; i < balances.length; i++) {
balanceRatiosWithoutFee[i] = MathSol.divUpFixed(
MathSol.sub(balances[i], amountsOut[i]),
balances[i]
);
invariantRatioWithoutFees = MathSol.add(
invariantRatioWithoutFees,
MathSol.mulUpFixed(balanceRatiosWithoutFee[i], normalizedWeights[i])
);
}

const invariantRatio = _computeExitExactTokensOutInvariantRatio(
balances,
normalizedWeights,
amountsOut,
balanceRatiosWithoutFee,
invariantRatioWithoutFees,
swapFeePercentage
);

return MathSol.mulUpFixed(
bptTotalSupply,
MathSol.complementFixed(invariantRatio)
);
}

/**
* @dev Intermediate function to avoid stack-too-deep errors.
*/
function _computeExitExactTokensOutInvariantRatio(
balances: bigint[],
normalizedWeights: bigint[],
amountsOut: bigint[],
balanceRatiosWithoutFee: bigint[],
invariantRatioWithoutFees: bigint,
swapFeePercentage: bigint
): bigint {
let invariantRatio = MathSol.ONE;

for (let i = 0; i < balances.length; i++) {
// Swap fees are typically charged on 'token in', but there is no 'token in' here, so we apply it to
// 'token out'. This results in slightly larger price impact.

let amountOutWithFee;
if (invariantRatioWithoutFees > balanceRatiosWithoutFee[i]) {
const nonTaxableAmount = MathSol.mulDownFixed(
balances[i],
MathSol.complementFixed(invariantRatioWithoutFees)
);
const taxableAmount = MathSol.sub(amountsOut[i], nonTaxableAmount);
const taxableAmountPlusFees = MathSol.divUpFixed(
taxableAmount,
MathSol.complementFixed(swapFeePercentage)
);

amountOutWithFee = MathSol.add(
nonTaxableAmount,
taxableAmountPlusFees
);
} else {
amountOutWithFee = amountsOut[i];
}

const balanceRatio = MathSol.divDownFixed(
MathSol.sub(balances[i], amountOutWithFee),
balances[i]
);

invariantRatio = MathSol.mulDownFixed(
invariantRatio,
MathSol.powDown(balanceRatio, normalizedWeights[i])
);
}
return invariantRatio;
}

// Invariant is used to collect protocol swap fees by comparing its value between two times.
// So we can round always to the same direction. It is also used to initiate the BPT amount
// and, because there is a minimum BPT, we round down the invariant.
export function _calculateInvariant(
normalizedWeights: bigint[],
balances: bigint[]
): bigint {
/**********************************************************************************************
// invariant _____ //
// wi = weight index i | | wi //
// bi = balance index i | | bi ^ = i //
// i = invariant //
**********************************************************************************************/

let invariant = MathSol.ONE;
for (let i = 0; i < normalizedWeights.length; i++) {
invariant = MathSol.mulDownFixed(
invariant,
MathSol.powDown(balances[i], normalizedWeights[i])
);
}

if (invariant < 0) throw Error('Weighted Invariant < 0');

return invariant;
}

export function _calcDueProtocolSwapFeeBptAmount(
totalSupply: bigint,
previousInvariant: bigint,
currentInvariant: bigint,
protocolSwapFeePercentage: bigint
): bigint {
// We round down to prevent issues in the Pool's accounting, even if it means paying slightly less in protocol
// fees to the Vault.
const growth = MathSol.divDownFixed(currentInvariant, previousInvariant);

// Shortcut in case there was no growth when comparing the current against the previous invariant.
// This shouldn't happen outside of rounding errors, but have this safeguard nonetheless to prevent the Pool
// from entering a locked state in which joins and exits revert while computing accumulated swap fees.
if (growth <= MathSol.ONE) {
return BZERO;
}

// Assuming the Pool is balanced and token weights have not changed, a growth of the invariant translates into
// proportional growth of all token balances. The protocol is due a percentage of that growth: more precisely,
// it is due `k = protocol fee * (growth - 1) * balance / growth` for each token.
// We compute the amount of BPT to mint for the protocol that would allow it to proportionally exit the Pool and
// receive these balances. Note that the total BPT supply will increase when minting, so we need to account for
// this in order to compute the percentage of Pool ownership the protocol will have.

// The formula is:
//
// toMint = supply * k / (1 - k)

// We compute protocol fee * (growth - 1) / growth, as we'll use that value twice.
// There is no need to use SafeMath since we already checked growth is strictly greater than one.
const k = MathSol.divDownFixed(
MathSol.mulDownFixed(protocolSwapFeePercentage, growth - MathSol.ONE),
growth
);
const numerator = MathSol.mulDownFixed(totalSupply, k);
const denominator = MathSol.complementFixed(k);

return denominator == BZERO
? BZERO
: MathSol.divDownFixed(numerator, denominator);
}

// The following functions are TS versions originally implemented by Fernando
// All functions came from https://www.wolframcloud.com/obj/fernando.martinel/Published/SOR_equations_published.nb
// PairType = 'token->BPT'
Expand Down
Loading

0 comments on commit 0e853ce

Please sign in to comment.