Skip to content

Commit

Permalink
Binary search
Browse files Browse the repository at this point in the history
  • Loading branch information
codyborn committed Aug 29, 2024
1 parent a0a66d2 commit 5915304
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 31 deletions.
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]);
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);
vm.expectRevert(InvalidDecayCurve.selector);
mockNonLinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock);
}
}

0 comments on commit 5915304

Please sign in to comment.