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

Non-linear Block-based Orders #269

Merged
merged 24 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
84 changes: 67 additions & 17 deletions src/lib/NonLinearDutchDecayLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,88 @@ import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol";
import {sub} from "./MathExt.sol";
import {Uint16Array, fromUnderlying} from "../types/Uint16Array.sol";

/// @notice thrown when the decay curve is invalid
error InvalidDecayCurve();

/// @notice helpers for handling non-linear dutch order objects
library NonLinearDutchDecayLib {

using FixedPointMathLib for uint256;
using {sub} for uint256;

/// @notice locates the current position on the curve and calculates the decay
/// @param curve The curve to search
/// @param startAmount The absolute start amount
/// @param decayStartBlock The absolute start block of the decay
function decay(NonLinearDecay memory curve, uint256 startAmount, uint256 decayStartBlock)
internal
view
returns (uint256 decayedAmount)
{
// mismatch of relativeAmounts and relativeBlocks
if(curve.relativeAmounts.length > 16) {
revert InvalidDecayCurve();
}

// handle current block before decay or no decay
if (decayStartBlock >= block.number) {
if (decayStartBlock >= block.number || curve.relativeAmounts.length == 0) {
return startAmount;
}
Uint16Array relativeBlocks = fromUnderlying(curve.relativeBlocks);
uint16 blockDelta = uint16(block.number - decayStartBlock);
// iterate through the points and locate the current segment
for (uint16 i = 0; i < curve.relativeAmounts.length; i++) {
// relativeBlocks is an array of uint16 packed one uint256
Uint16Array relativeBlocks = fromUnderlying(curve.relativeBlocks);
uint16 relativeBlock = relativeBlocks.getElement(i);
if (relativeBlock >= blockDelta) {
uint256 lastAmount = startAmount;
uint16 relativeStartBlock = 0;
if (i != 0) {
lastAmount = startAmount.sub(curve.relativeAmounts[i - 1]);
relativeStartBlock = relativeBlocks.getElement(i - 1);
// Special case for when we need to use the decayStartBlock (0)
if (relativeBlocks.getElement(0) > blockDelta) {
return linearDecay(0, relativeBlocks.getElement(0), blockDelta, startAmount, startAmount.sub(curve.relativeAmounts[0]));
}
// the current pos is within or after the curve
uint16 prev;
uint16 next;
(prev, next) = locateCurvePosition(curve, blockDelta);
uint256 lastAmount = startAmount.sub(curve.relativeAmounts[prev]);
uint256 nextAmount = startAmount.sub(curve.relativeAmounts[next]);
codyborn marked this conversation as resolved.
Show resolved Hide resolved
return linearDecay(relativeBlocks.getElement(prev), relativeBlocks.getElement(next), blockDelta, lastAmount, nextAmount);
}

/// @notice Locates the current position on the curve using a binary search
/// @param curve The curve to search
/// @param currentRelativeBlock The current relative position
/// @return prev The relative block before the current position
/// @return next The relative block after the current position
function locateCurvePosition(NonLinearDecay memory curve, uint16 currentRelativeBlock)
internal
pure
returns (uint16 prev, uint16 next)
{
Uint16Array relativeBlocks = fromUnderlying(curve.relativeBlocks);
uint16 left = 0;
uint16 right = uint16(curve.relativeAmounts.length) - 1;
uint16 mid;

while (left <= right) {
mid = left + (right - left) / 2;
uint16 midBlock = relativeBlocks.getElement(mid);

if (midBlock == currentRelativeBlock) {
return (mid, mid);
} else if (midBlock > currentRelativeBlock) {
if (mid == 0) {
return (mid, mid);
} else if (relativeBlocks.getElement(mid - 1) < currentRelativeBlock) {
return (mid - 1, mid);
} else {
right = mid - 1;
}
} else {
if (mid == curve.relativeAmounts.length - 1) {
return (mid, mid);
} else if (relativeBlocks.getElement(mid + 1) > currentRelativeBlock) {
return (mid, mid + 1);
} else {
left = mid + 1;
}
uint256 nextAmount = startAmount.sub(curve.relativeAmounts[i]);
return linearDecay(relativeStartBlock, relativeBlock, blockDelta, lastAmount, nextAmount);
}
}
// handle current block after last decay block
decayedAmount = startAmount.sub(curve.relativeAmounts[curve.relativeAmounts.length - 1]);
revert InvalidDecayCurve();
}

/// @notice returns the linear interpolation between the two points
Expand All @@ -56,6 +104,9 @@ library NonLinearDutchDecayLib {
uint256 startAmount,
uint256 endAmount
) internal pure returns (uint256) {
if (currentBlock >= endBlock) {
return endAmount;
}
unchecked {
uint256 elapsed = currentBlock - startBlock;
uint256 duration = endBlock - startBlock;
Expand All @@ -67,7 +118,6 @@ library NonLinearDutchDecayLib {
}
}


/// @notice returns a decayed output using the given dutch spec and times
/// @param output The output to decay
/// @param decayStartBlock The block to start decaying
Expand Down
13 changes: 0 additions & 13 deletions src/reactors/NonLinearDutchOrderReactor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ contract NonLinearDutchOrderReactor is BaseReactor {
using NonLinearDutchDecayLib for NonLinearDutchInput;
using ExclusivityLib for ResolvedOrder;

/// @notice thrown when the decay curve is invalid
error InvalidDecayCurve();

/// @notice thrown when an order's deadline is passed
error DeadlineReached();

Expand Down Expand Up @@ -116,19 +113,9 @@ contract NonLinearDutchOrderReactor is BaseReactor {
}

/// @notice validate the dutch order fields
/// - decay curves are defined
/// - deadline must have not passed
/// @dev Throws if the order is invalid
function _validateOrder(bytes32 orderHash, NonLinearDutchOrder memory order) internal pure {
if (order.baseInput.curve.relativeAmounts.length == 0 || order.baseInput.curve.relativeAmounts.length > 16) {
revert InvalidDecayCurve();
}
for (uint256 i = 0; i < order.baseOutputs.length; i++) {
if (order.baseOutputs[i].curve.relativeAmounts.length == 0) {
revert InvalidDecayCurve();
}
}

(bytes32 r, bytes32 s) = abi.decode(order.cosignature, (bytes32, bytes32));
uint8 v = uint8(order.cosignature[64]);
// cosigner signs over (orderHash || cosignerData)
Expand Down
74 changes: 73 additions & 1 deletion test/lib/NonLinearDutchDecayLib.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity ^0.8.0;

import {Test} from "forge-std/Test.sol";
import {console} from "forge-std/console.sol";
import {NonLinearDutchDecayLib} from "../../src/lib/NonLinearDutchDecayLib.sol";
import {NonLinearDutchDecayLib, InvalidDecayCurve} from "../../src/lib/NonLinearDutchDecayLib.sol";
import {NonLinearDutchOutput, NonLinearDutchInput, NonLinearDecay} from "../../src/lib/NonLinearDutchOrderLib.sol";
import {Uint16Array, toUint256} from "../../src/types/Uint16Array.sol";
import {ArrayBuilder} from "../util/ArrayBuilder.sol";
Expand All @@ -19,8 +19,68 @@ contract MockNonLinearDutchDecayLibContract {
contract NonLinearDutchDecayLibTest is Test {
MockNonLinearDutchDecayLibContract mockNonLinearDutchDecayLibContract = new MockNonLinearDutchDecayLibContract();

function testLocateCurvePositionSingle() public {
NonLinearDecay memory curve = NonLinearDecay({
relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 1)),
relativeAmounts: ArrayBuilder.fillInt(1, 0)
});
uint16 prev;
uint16 next;
(prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 1);
assertEq(prev, 0);
assertEq(next, 0);

(prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 2);
assertEq(prev, 0);
assertEq(next, 0);
}


function testLocateCurvePositionMulti() public {
uint16[] memory blocks = new uint16[](3);
blocks[0] = 100; // block 200
blocks[1] = 200; // block 300
blocks[2] = 300; // block 400
int256[] memory decayAmounts = new int256[](3);
decayAmounts[0] = -1 ether; // 2 ether
decayAmounts[1] = 0 ether; // 1 ether
decayAmounts[2] = 1 ether; // 0 ether
NonLinearDecay memory curve =
NonLinearDecay({relativeBlocks: toUint256(blocks), relativeAmounts: decayAmounts});

uint16 prev;
uint16 next;
(prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 150);
assertEq(prev, 0);
assertEq(next, 1);

(prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 200);
assertEq(prev, 1);
assertEq(next, 1);

(prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 250);
assertEq(prev, 1);
assertEq(next, 2);

(prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 300);
assertEq(prev, 2);
assertEq(next, 2);

(prev, next) = NonLinearDutchDecayLib.locateCurvePosition(curve, 350);
assertEq(prev, 2);
assertEq(next, 2);
}

function testDutchDecayNoDecay(uint256 startAmount, uint256 decayStartBlock) public {
// Empty curve
NonLinearDecay memory curve = NonLinearDecay({
relativeBlocks: toUint256(ArrayBuilder.fillUint16(0, 0)),
relativeAmounts: ArrayBuilder.fillInt(0, 0)
});
assertEq(NonLinearDutchDecayLib.decay(curve, startAmount, decayStartBlock), startAmount);

// Single value with 0 amount change
curve = NonLinearDecay({
relativeBlocks: toUint256(ArrayBuilder.fillUint16(1, 1)),
relativeAmounts: ArrayBuilder.fillInt(1, 0)
});
Expand Down Expand Up @@ -279,4 +339,16 @@ contract NonLinearDutchDecayLibTest is Test {
vm.expectRevert();
mockNonLinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock);
}

function testDutchMismatchedDecay() public {
uint256 decayStartBlock = 100;
uint256 startAmount = 1 ether;
NonLinearDecay memory curve = NonLinearDecay({
relativeBlocks: toUint256(ArrayBuilder.fillUint16(16, 1)),
relativeAmounts: ArrayBuilder.fillInt(17, 0)
});
vm.roll(150);
codyborn marked this conversation as resolved.
Show resolved Hide resolved
vm.expectRevert(InvalidDecayCurve.selector);
mockNonLinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock);
}
}
Loading