Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BAL Hookathon - VolatilityLoyaltyHook #105

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
324 changes: 53 additions & 271 deletions README.md

Large diffs are not rendered by default.

71 changes: 71 additions & 0 deletions packages/foundry/contracts/factories/ConstantProductFactoryV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;

import {
LiquidityManagement,
PoolRoleAccounts,
TokenConfig
} from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol";
import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";
import { BasePoolFactory } from "@balancer-labs/v3-pool-utils/contracts/BasePoolFactory.sol";

import { ConstantProductPool } from "../pools/ConstantProductPool.sol";

/**
* @title Constant Product Factory
* @notice This custom pool factory is based on the example from the Balancer v3 docs
* https://docs-v3.balancer.fi/build-a-custom-amm/build-an-amm/deploy-custom-amm-using-factory.html
*/
contract ConstantProductFactoryV2 is BasePoolFactory {
error OnlyTwoTokenPoolsAllowed();

/**
* @dev The pool's creationCode is used to deploy pools via CREATE3
* @notice The pool creationCode cannot be changed after the factory has been deployed
* @param vault The contract instance of the Vault
* @param pauseWindowDuration The period ( starting from deployment of this factory ) during which pools can be paused and unpaused
*/
constructor(
IVault vault,
uint32 pauseWindowDuration
) BasePoolFactory(vault, pauseWindowDuration, type(ConstantProductPool).creationCode) {}

/**
* @notice Deploys a new pool and registers it with the vault
* @param name The name of the pool
* @param symbol The symbol of the pool
* @param salt The salt value that will be passed to create3 deployment
* @param tokens An array of descriptors for the tokens the pool will manage
* @param swapFeePercentage Initial swap fee percentage
* @param protocolFeeExempt true, the pool's initial aggregate fees will be set to 0
* @param roleAccounts Addresses the Vault will allow to change certain pool settings
* @param poolHooksContract Contract that implements the hooks for the pool
* @param liquidityManagement Liquidity management flags with implemented methods
*/
function create(
string memory name,
string memory symbol,
bytes32 salt,
TokenConfig[] memory tokens,
uint256 swapFeePercentage,
bool protocolFeeExempt,
PoolRoleAccounts memory roleAccounts,
address poolHooksContract,
LiquidityManagement memory liquidityManagement
) external returns (address pool) {
if (tokens.length != 2) revert OnlyTwoTokenPoolsAllowed();

// First deploy a new pool
pool = _create(abi.encode(getVault(), name, symbol), salt);
// Then register the pool
_registerPoolWithVault(
pool,
tokens,
swapFeePercentage,
protocolFeeExempt,
roleAccounts,
poolHooksContract,
liquidityManagement
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity ^0.8.24;

import "./library/Buffer.sol";
import "./library/Samples.sol";

import "forge-std/console.sol";

contract PoolPriceOracle {
using Buffer for uint256;
using Samples for bytes32;

uint256 private constant _MAX_SAMPLE_DURATION = 1 seconds;

mapping(uint256 => bytes32) private _samples;

struct LogPairPrices {
int256 logPairPrice;
uint256 timestamp;
}

function findAllSamples(uint256 latestIndex, uint256 ago) public view returns (LogPairPrices[] memory) {
console.log("(findAllSamples) Here 1", latestIndex, ago);
uint256 blockTimeStamp = block.timestamp;
console.log("(findAllSamples) blockTimeStamp", blockTimeStamp);
uint256 lookUpTime = blockTimeStamp - ago;
console.log("(findAllSamples) lookUpTime", lookUpTime);
uint256 offset = latestIndex < Buffer.SIZE ? 0 : latestIndex.next();
console.log("(findAllSamples) offset", offset);

uint256 low = 0;
uint256 high = latestIndex < Buffer.SIZE ? latestIndex : Buffer.SIZE - 1;
uint256 mid;

uint256 sampleTimestamp;

while (low <= high) {
console.log("(findAllSamples) low", low);
console.log("(findAllSamples) high", high);

uint256 midWithoutOffset = (high + low) / 2;
mid = midWithoutOffset.add(offset);
console.log("(findAllSamples) midWithoutOffset, mid", midWithoutOffset, mid);

(, uint256 timestamp) = getSample(mid.add(offset));

sampleTimestamp = timestamp;
console.log("(findAllSamples) sampleTimestamp", sampleTimestamp);

if (sampleTimestamp < lookUpTime) {
console.log("(findAllSamples) sampleTimestamp < lookUpTime is true");
low = midWithoutOffset + 1;
} else if (sampleTimestamp > lookUpTime) {
console.log("(findAllSamples) sampleTimestamp > lookUpTime is true");
high = midWithoutOffset - 1;
} else {
console.log("(findAllSamples) break low high mid", low, high, mid);
console.log("(findAllSamples) break midWithoutOffset", midWithoutOffset);
break;
}
}

console.log("(findAllSamples) out low high mid", low, high, mid);
uint256 lowerBound;
uint256 upperBound;

if (latestIndex < Buffer.SIZE) {
lowerBound = sampleTimestamp <= lookUpTime ? mid : mid > 0 ? mid - 1 : 0;
upperBound = latestIndex;
} else {
lowerBound = sampleTimestamp >= lookUpTime ? mid : mid.prev();
upperBound = Buffer.SIZE - 1;
}

console.log("(findAllSamples) lowerBound, upperBound, offset", lowerBound, upperBound, offset);

LogPairPrices[] memory logPairPrices = new LogPairPrices[](upperBound - lowerBound + 1);

for (uint256 i = lowerBound; i <= upperBound; i = i.next()) {
(int256 logPairPrice, uint256 timestamp) = getSample(i.add(offset));
console.log("(findAllSamples) i, timestamp", i, timestamp, uint256(logPairPrice));
logPairPrices[i - lowerBound] = (LogPairPrices(logPairPrice, timestamp));
}

return logPairPrices;
}

function getSample(uint256 index) public view returns (int256 logPairPrice, uint256 timestamp) {
// add error for buffer size

bytes32 sample = _getSample(index);
return sample.unpack();
}

function _processPriceData(
uint256 latestSampleCreationTimestamp,
uint256 latestIndex,
int256 logPairPrice
) internal returns (uint256) {
bytes32 sample = _getSample(latestIndex).update(logPairPrice, block.timestamp);

bool newSample = block.timestamp - latestSampleCreationTimestamp >= _MAX_SAMPLE_DURATION;
latestIndex = newSample ? latestIndex.next() : latestIndex;

_samples[latestIndex] = sample;

return latestIndex;
}

function _getSample(uint256 index) internal view returns (bytes32) {
return _samples[index];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity ^0.8.24;

library Buffer {
uint256 internal constant SIZE = 1024;

function prev(uint256 index) internal pure returns (uint256) {
return sub(index, 1);
}

function next(uint256 index) internal pure returns (uint256) {
return add(index, 1);
}

function add(uint256 index, uint256 offset) internal pure returns (uint256) {
return (index + offset) % SIZE;
}

function sub(uint256 index, uint256 offset) internal pure returns (uint256) {
return (index + SIZE - offset) % SIZE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity ^0.8.24;

import "../library/WordCodec.sol";

library Samples {
using WordCodec for int256;
using WordCodec for uint256;
using WordCodec for bytes32;

uint256 internal constant _TIMESTAMP_OFFSET = 0;
uint256 internal constant _INST_LOG_PAIR_PRICE_OFFSET = 31;

function update(bytes32 sample, int256 instLogPairPrice, uint256 currentTimestamp) internal pure returns (bytes32) {
return pack(instLogPairPrice, currentTimestamp);
}

function timestamp(bytes32 sample) internal pure returns (uint256) {
return sample.decodeUint31(_TIMESTAMP_OFFSET);
}

function _instLogPairPrice(bytes32 sample) private pure returns (int256) {
return sample.decodeInt22(_INST_LOG_PAIR_PRICE_OFFSET);
}

function pack(int256 instLogPairPrice, uint256 _timestamp) internal pure returns (bytes32) {
return instLogPairPrice.encodeInt22(_INST_LOG_PAIR_PRICE_OFFSET) | _timestamp.encodeUint31(_TIMESTAMP_OFFSET);
}

function unpack(bytes32 sample) internal pure returns (int256 logPairPrice, uint256 _timestamp) {
logPairPrice = _instLogPairPrice(sample);
_timestamp = timestamp(sample);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity ^0.8.24;

import { LogExpMath } from "@balancer-labs/v3-solidity-utils/contracts/math/LogExpMath.sol";
import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";

/* solhint-disable private-vars-leading-underscore */

library WeightedOracleMath {
using FixedPoint for uint256;

int256 private constant _LOG_COMPRESSION_FACTOR = 1e14;
int256 private constant _HALF_LOG_COMPRESSION_FACTOR = 0.5e14;

/**
* @dev Calculates the logarithm of the spot price of token B in token A.
*
* The return value is a 4 decimal fixed-point number: use `_fromLowResLog` to recover the original value.
*/
function _calcLogSpotPrice(
uint256 normalizedWeightA,
uint256 balanceA,
uint256 normalizedWeightB,
uint256 balanceB
) internal pure returns (int256) {
// Max balances are 2^112 and min weights are 0.01, so the division never overflows.

// The rounding direction is irrelevant as we're about to introduce a much larger error when converting to log
// space. We use `divUp` as it prevents the result from being zero, which would make the logarithm revert. A
// result of zero is therefore only possible with zero balances, which are prevented via other means.
uint256 spotPrice = balanceA.divUp(normalizedWeightA).divUp(balanceB.divUp(normalizedWeightB));
return _toLowResLog(spotPrice);
}

/**
* @dev Calculates the price of BPT in a token. `logBptTotalSupply` should be the result of calling `_toLowResLog`
* with the current BPT supply.
*
* The return value is a 4 decimal fixed-point number: use `_fromLowResLog` to recover the original value.
*/
function _calcLogBPTPrice(
uint256 normalizedWeight,
uint256 balance,
int256 logBptTotalSupply
) internal pure returns (int256) {
// BPT price = (balance / weight) / total supply
// Since we already have ln(total supply) and want to compute ln(BPT price), we perform the computation in log
// space directly: ln(BPT price) = ln(balance / weight) - ln(total supply)

// The rounding direction is irrelevant as we're about to introduce a much larger error when converting to log
// space. We use `divUp` as it prevents the result from being zero, which would make the logarithm revert. A
// result of zero is therefore only possible with zero balances, which are prevented via other means.
int256 logBalanceOverWeight = _toLowResLog(balance.divUp(normalizedWeight));

// Because we're subtracting two values in log space, this value has a larger error (+-0.0001 instead of
// +-0.00005), which results in a final larger relative error of around 0.1%.
return logBalanceOverWeight - logBptTotalSupply;
}

/**
* @dev Returns the natural logarithm of `value`, dropping most of the decimal places to arrive at a value that,
* when passed to `_fromLowResLog`, will have a maximum relative error of ~0.05% compared to `value`.
*
* Values returned from this function should not be mixed with other fixed-point values (as they have a different
* number of digits), but can be added or subtracted. Use `_fromLowResLog` to undo this process and return to an
* 18 decimal places fixed point value.
*
* Because so much precision is lost, the logarithmic values can be stored using much fewer bits than the original
* value required.
*/
function _toLowResLog(uint256 value) internal pure returns (int256) {
int256 ln = LogExpMath.ln(int256(value));

// Rounding division for signed numerator
return
(ln > 0 ? ln + _HALF_LOG_COMPRESSION_FACTOR : ln - _HALF_LOG_COMPRESSION_FACTOR) / _LOG_COMPRESSION_FACTOR;
}

/**
* @dev Restores `value` from logarithmic space. `value` is expected to be the result of a call to `_toLowResLog`,
* any other function that returns 4 decimals fixed point logarithms, or the sum of such values.
*/
function _fromLowResLog(int256 value) internal pure returns (uint256) {
return uint256(LogExpMath.exp(value * _LOG_COMPRESSION_FACTOR));
}
}
Loading