Skip to content

Commit

Permalink
Merge branch 'feat/v0.3.0' into feat/bring-back-tau
Browse files Browse the repository at this point in the history
  • Loading branch information
kinrezC authored Apr 10, 2024
2 parents 9112779 + 1557985 commit 3ac78d3
Show file tree
Hide file tree
Showing 10 changed files with 74 additions and 47 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
> Version: `v0.3.0`
# DFMM

This repository contains the smart contracts source code for the DFMM protocol.



## What is DFMM?

The DFMM protocol is a novel portfolio management system designed to leverage external strategy contracts. This system separates the operational logic and the mathematical model: the `DFMM` core contract completely relies on the strategies to validate the state of each interaction. On the other hand, this core contract acts as a single entry point and performs all the operations, such as:
Expand Down
20 changes: 12 additions & 8 deletions src/ConstantSum/ConstantSum.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
FixedPointMathLib,
computeTradingFunction,
computeSwapDeltaLiquidity,
computeDeltaLiquidity
computeDeltaLiquidityRoundDown,
computeDeltaLiquidityRoundUp
} from "./ConstantSumMath.sol";
import {
decodePriceUpdate,
Expand Down Expand Up @@ -67,8 +68,9 @@ contract ConstantSum is PairStrategy {
ConstantSumParams memory params;

(reserves, params) = abi.decode(data, (uint256[], ConstantSumParams));
totalLiquidity =
computeDeltaLiquidity(reserves[0], reserves[1], params.price);
totalLiquidity = computeDeltaLiquidityRoundDown(
reserves[0], reserves[1], params.price
);

if (pool.reserves.length != 2 || reserves.length != 2) {
revert InvalidReservesLength();
Expand Down Expand Up @@ -106,8 +108,9 @@ contract ConstantSum is PairStrategy {
(uint256 deltaX, uint256 deltaY, uint256 minDeltaL) =
abi.decode(data, (uint256, uint256, uint256));

deltaLiquidity =
computeDeltaLiquidity(deltaX, deltaY, internalParams[poolId].price);
deltaLiquidity = computeDeltaLiquidityRoundDown(
deltaX, deltaY, internalParams[poolId].price
);
if (deltaLiquidity < minDeltaL) revert InvalidDeltaLiquidity();

deltas = new uint256[](2);
Expand Down Expand Up @@ -145,9 +148,10 @@ contract ConstantSum is PairStrategy {
(uint256 deltaX, uint256 deltaY, uint256 maxDeltaL) =
abi.decode(data, (uint256, uint256, uint256));

deltaLiquidity =
computeDeltaLiquidity(deltaX, deltaY, internalParams[poolId].price);
if (maxDeltaL > deltaLiquidity) revert InvalidDeltaLiquidity();
deltaLiquidity = computeDeltaLiquidityRoundUp(
deltaX, deltaY, internalParams[poolId].price
);
if (deltaLiquidity > maxDeltaL) revert InvalidDeltaLiquidity();

deltas = new uint256[](2);
deltas[0] = deltaX;
Expand Down
14 changes: 11 additions & 3 deletions src/ConstantSum/ConstantSumMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,30 @@ function computeInitialPoolData(
return abi.encode(reserves, params);
}

function computeDeltaLiquidity(
function computeDeltaLiquidityRoundUp(
uint256 deltaX,
uint256 deltaY,
uint256 price
) pure returns (uint256) {
return price.mulWadUp(deltaX) + deltaY;
}

function computeDeltaLiquidityRoundDown(
uint256 deltaX,
uint256 deltaY,
uint256 price
) pure returns (uint256) {
return price.mulWadDown(deltaX) + deltaY;
}

function computeSwapDeltaLiquidity(
uint256 delta,
ConstantSumParams memory params,
bool isSwapXForY
) pure returns (uint256) {
if (isSwapXForY) {
return (params.swapFee).mulWadUp(delta);
return params.swapFee.mulWadUp(delta.mulWadUp(params.price));
} else {
return (params.swapFee).mulDivUp(delta, params.price);
return params.swapFee.mulWadUp(delta);
}
}
23 changes: 8 additions & 15 deletions src/ConstantSum/ConstantSumSolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,6 @@ contract ConstantSumSolver {
return computeInitialPoolData(rx, ry, params);
}

struct SimulateSwapState {
uint256 amountOut;
uint256 deltaLiquidity;
}

function simulateSwap(
uint256 poolId,
bool swapXIn,
Expand All @@ -56,39 +51,37 @@ contract ConstantSumSolver {
IStrategy(strategy).getPoolParams(poolId), (ConstantSumParams)
);

SimulateSwapState memory state;
uint256 amountOut;

if (swapXIn) {
computeSwapDeltaLiquidity(amountIn, poolParams, true);
state.amountOut = amountIn.mulWadDown(poolParams.price).mulWadDown(
amountOut = amountIn.mulWadDown(poolParams.price).mulWadDown(
ONE - poolParams.swapFee
);

if (pool.reserves[1] < state.amountOut) {
if (pool.reserves[1] < amountOut) {
revert NotEnoughLiquidity();
}
} else {
computeSwapDeltaLiquidity(amountIn, poolParams, false);
state.amountOut = (ONE - poolParams.swapFee).mulWadDown(amountIn)
amountOut = (ONE - poolParams.swapFee).mulWadDown(amountIn)
.divWadDown(poolParams.price);

if (pool.reserves[0] < state.amountOut) {
if (pool.reserves[0] < amountOut) {
revert NotEnoughLiquidity();
}
}

bytes memory swapData;

if (swapXIn) {
swapData = abi.encode(0, 1, amountIn, state.amountOut);
swapData = abi.encode(0, 1, amountIn, amountOut);
} else {
swapData = abi.encode(1, 0, amountIn, state.amountOut);
swapData = abi.encode(1, 0, amountIn, amountOut);
}

(bool valid,,,,,,) = IStrategy(strategy).validateSwap(
address(this), poolId, pool, swapData
);
return (valid, state.amountOut, swapData);
return (valid, amountOut, swapData);
}

function preparePriceUpdate(uint256 newPrice)
Expand Down
38 changes: 26 additions & 12 deletions src/DFMM.sol
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,17 @@ contract DFMM is IDFMM {

for (uint256 i = 0; i < tokensLength; i++) {
address token = params.tokens[i];
uint256 decimals = ERC20(params.tokens[i]).decimals();
uint256 decimals = ERC20(token).decimals();

if (decimals > 18 || decimals < 6) {
revert InvalidTokenDecimals();
}

for (uint256 j = i + 1; j < tokensLength; j++) {
if (token == params.tokens[j]) {
revert InvalidDuplicateTokens();
unchecked {
for (uint256 j = i + 1; j < tokensLength; j++) {
if (token == params.tokens[j]) {
revert InvalidDuplicateTokens();
}
}
}
}
Expand Down Expand Up @@ -284,9 +286,13 @@ contract DFMM is IDFMM {
// Internals

/**
* @dev Transfers `amounts` of `tokens` from the sender to the contract. Note
* that if any ETH is present in the contract, it will be wrapped to WETH and
* used if sufficient. Any excess of ETH will be sent back to the sender.
* @notice Transfers `amounts` of `tokens` from the sender to the contract.
* @dev Note that for pools with `WETH` as a token, the contract will accept
* full payments in native ether. If the contract receives more ether than
* the amount of `WETH` needed, the contract will refund the remaining ether.
* If the contract receives less ether than the amount of `WETH` needed, the
* contract will refund the native ether and request the full amount of `WETH`
* needed instead.
* @param tokens An array of token addresses to transfer.
* @param amounts An array of amounts to transfer expressed in WAD.
*/
Expand All @@ -304,23 +310,30 @@ contract DFMM is IDFMM {
downscaleUp(amount, computeScalingFactor(token));
uint256 preBalance = ERC20(token).balanceOf(address(this));

if (token == weth && address(this).balance >= amount) {
// note: `msg.value` can be used safely in a loop because `weth` is a unique token,
// therefore we only enter this branch once.
if (token == weth && msg.value >= amount) {
WETH(payable(weth)).deposit{ value: amount }();
} else {
SafeTransferLib.safeTransferFrom(
ERC20(token), msg.sender, address(this), downscaledAmount
);
}

// If not enough native ether was sent as payment
// or too much ether was sent,
// refund all the remaining ether back to the sender.
if (token == weth && msg.value != 0) {
SafeTransferLib.safeTransferETH(
msg.sender, address(this).balance
);
}

uint256 postBalance = ERC20(token).balanceOf(address(this));
if (postBalance < preBalance + downscaledAmount) {
revert InvalidTransfer();
}
}

if (address(this).balance > 0) {
SafeTransferLib.safeTransferETH(msg.sender, address(this).balance);
}
}

/**
Expand All @@ -337,6 +350,7 @@ contract DFMM is IDFMM {
} else {
uint256 downscaledAmount =
downscaleDown(amount, computeScalingFactor(token));
if (downscaledAmount == 0) return;
uint256 preBalance = ERC20(token).balanceOf(address(this));
SafeTransferLib.safeTransfer(ERC20(token), to, downscaledAmount);
uint256 postBalance = ERC20(token).balanceOf(address(this));
Expand Down
2 changes: 1 addition & 1 deletion src/GeometricMean/G3MMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function computeTradingFunction(
uint256 a = uint256(int256(rX.divWadUp(L)).powWad(int256(params.wX)));
uint256 b = uint256(int256(rY.divWadUp(L)).powWad(int256(params.wY)));

return int256(a.mulWadUp(b)) - int256(1 ether);
return int256(a.mulWadUp(b)) - int256(ONE);
}

function computeDeltaGivenDeltaLRoundUp(
Expand Down
2 changes: 1 addition & 1 deletion src/LogNormal/LogNormal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ contract LogNormal is PairStrategy {
(reserves, totalLiquidity, params) =
abi.decode(data, (uint256[], uint256, LogNormalParams));

if (params.mean < MIN_WIDTH || params.mean > MAX_MEAN) {
if (params.mean < MIN_MEAN || params.mean > MAX_MEAN) {
revert InvalidMean();
}

Expand Down
10 changes: 5 additions & 5 deletions src/NTokenGeometricMean/NTokenGeometricMeanMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function computeTradingFunction(
uint256 accumulator = ONE;
for (uint256 i = 0; i < reserves.length; i++) {
uint256 a = uint256(
int256(reserves[i].divWadDown(L)).powWad(int256(params.weights[i]))
int256(reserves[i].divWadUp(L)).powWad(int256(params.weights[i]))
);
accumulator = accumulator.mulWadUp(a);
}
Expand All @@ -30,15 +30,15 @@ function computeDeltaGivenDeltaLRoundUp(
uint256 deltaLiquidity,
uint256 totalLiquidity
) pure returns (uint256) {
return reserve.mulWadUp(deltaLiquidity.divWadUp(totalLiquidity));
return reserve.mulDivUp(deltaLiquidity, totalLiquidity);
}

function computeDeltaGivenDeltaLRoundDown(
uint256 reserve,
uint256 deltaLiquidity,
uint256 totalLiquidity
) pure returns (uint256) {
return reserve.mulWadDown(deltaLiquidity.divWadDown(totalLiquidity));
return reserve.mulDivDown(deltaLiquidity, totalLiquidity);
}

function computeL(
Expand Down Expand Up @@ -137,7 +137,7 @@ function computeSwapDeltaLiquidity(
uint256 weight,
uint256 swapFee
) pure returns (uint256) {
return weight.mulWadDown(swapFee).mulWadDown(totalLiquidity).mulWadDown(
amountIn.divWadDown(reserve)
return weight.mulWadUp(swapFee).mulWadUp(totalLiquidity).mulWadUp(
amountIn.divWadUp(reserve)
);
}
5 changes: 3 additions & 2 deletions test/ConstantSum/unit/Allocate.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity ^0.8.13;

import { ConstantSumSetUp } from "./SetUp.sol";
import {
computeDeltaLiquidity,
computeDeltaLiquidityRoundDown,
ConstantSumParams
} from "src/ConstantSum/ConstantSumMath.sol";

Expand All @@ -15,7 +15,8 @@ contract ConstantSumAllocateTest is ConstantSumSetUp {
ConstantSumParams memory params =
abi.decode(constantSum.getPoolParams(POOL_ID), (ConstantSumParams));

uint256 deltaL = computeDeltaLiquidity(deltaX, deltaY, params.price);
uint256 deltaL =
computeDeltaLiquidityRoundDown(deltaX, deltaY, params.price);
dfmm.allocate(POOL_ID, abi.encode(deltaX, deltaY, deltaL));
}
}
3 changes: 3 additions & 0 deletions test/DFMM/unit/Init.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ contract DFMMInit is DFMMSetUp, Script {
bytes defaultData =
abi.encode(valid, initialInvariant, defaultReserves, initialLiquidity);

/// @notice for handling ether refunds
receive() external payable { }

function test_DFMM_init_StoresStrategyInitialReservesAndLiquidity()
public
{
Expand Down

0 comments on commit 3ac78d3

Please sign in to comment.