Skip to content

Commit

Permalink
Merge branch 'main' into audit-v3-09
Browse files Browse the repository at this point in the history
  • Loading branch information
alanhwu committed Oct 16, 2024
2 parents 14d77df + 8f35a85 commit f174dd5
Show file tree
Hide file tree
Showing 32 changed files with 156 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
198680
199172
2 changes: 1 addition & 1 deletion .forge-snapshots/Base-V3DutchOrder-ExecuteBatch.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
230916
231968
Original file line number Diff line number Diff line change
@@ -1 +1 @@
244376
245818
Original file line number Diff line number Diff line change
@@ -1 +1 @@
301741
303592
Original file line number Diff line number Diff line change
@@ -1 +1 @@
224442
225494
2 changes: 1 addition & 1 deletion .forge-snapshots/Base-V3DutchOrder-ExecuteSingle.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
165065
165555
Original file line number Diff line number Diff line change
@@ -1 +1 @@
150627
151117
Original file line number Diff line number Diff line change
@@ -1 +1 @@
174381
174866
2 changes: 1 addition & 1 deletion .forge-snapshots/Base-V3DutchOrder-RevertInvalidNonce.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
43685
44191
2 changes: 1 addition & 1 deletion .forge-snapshots/Base-V3DutchOrder-V3-ExclusiveFiller.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
168989
169490
2 changes: 1 addition & 1 deletion .forge-snapshots/Base-V3DutchOrder-V3-InputOverride.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
169070
169571
2 changes: 1 addition & 1 deletion .forge-snapshots/Base-V3DutchOrder-V3-OutputOverride.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
169013
169514
2 changes: 1 addition & 1 deletion .forge-snapshots/V3-DutchDecay.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
13179
13371
2 changes: 1 addition & 1 deletion .forge-snapshots/V3-DutchDecayBounded.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1199
1193
2 changes: 1 addition & 1 deletion .forge-snapshots/V3-DutchDecayFullyDecayed.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
6677
6779
2 changes: 1 addition & 1 deletion .forge-snapshots/V3-DutchDecayFullyDecayedNegative.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
6365
6467
2 changes: 1 addition & 1 deletion .forge-snapshots/V3-DutchDecayNegative.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1271
1265
2 changes: 1 addition & 1 deletion .forge-snapshots/V3-DutchDecayNoDecayYet.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4703
4697
2 changes: 1 addition & 1 deletion .forge-snapshots/V3-DutchDecayNoDecayYetNegative.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4703
4697
2 changes: 1 addition & 1 deletion .forge-snapshots/V3-DutchDecayRange.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1271
1265
2 changes: 1 addition & 1 deletion .forge-snapshots/V3-ExtendedMultiPointDutchDecay.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
88354
88450
2 changes: 1 addition & 1 deletion .forge-snapshots/V3-LocateCurvePositionMulti.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
20492
20330
2 changes: 1 addition & 1 deletion .forge-snapshots/V3-LocateCurvePositionSingle.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
6860
6830
2 changes: 1 addition & 1 deletion .forge-snapshots/V3-MultiPointDutchDecay.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
26242
26539
2 changes: 2 additions & 0 deletions src/lib/MathExt.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ library MathExt {
/// @param min The minimum bound for the result.
/// @param max The maximum bound for the result.
/// @return r The result of the bounded addition.
/// @dev This function reverts when `b == type(int256).min` due to integer overflow.
function boundedAdd(uint256 a, int256 b, uint256 min, uint256 max) internal pure returns (uint256 r) {
r = boundedSub(a, 0 - b, min, max);
}
Expand All @@ -33,6 +34,7 @@ library MathExt {
/// @param min The minimum bound for the result.
/// @param max The maximum bound for the result.
/// @return r The result of the bounded subtraction.
/// @dev This function reverts when `b == type(int256).min` due to integer overflow.
function boundedSub(uint256 a, int256 b, uint256 min, uint256 max) internal pure returns (uint256 r) {
if (b < 0) {
// If b is negative, add its absolute value to a
Expand Down
32 changes: 19 additions & 13 deletions src/lib/NonlinearDutchDecayLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {OutputToken, InputToken} from "../base/ReactorStructs.sol";
import {V3DutchOutput, V3DutchInput, NonlinearDutchDecay} from "../lib/V3DutchOrderLib.sol";
import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol";
import {MathExt} from "./MathExt.sol";
import {Math} from "openzeppelin-contracts/utils/math/Math.sol";
import {Uint16ArrayLibrary, Uint16Array, fromUnderlying} from "../types/Uint16Array.sol";
import {DutchDecayLib} from "./DutchDecayLib.sol";

Expand All @@ -17,11 +18,12 @@ library NonlinearDutchDecayLib {
/// @notice thrown when the decay curve is invalid
error InvalidDecayCurve();

/// @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
/// @notice Calculates the decayed amount based on the current block and the defined curve
/// @param curve The nonlinear decay curve definition
/// @param startAmount The initial amount at the start of the decay
/// @param decayStartBlock The absolute block number when the decay begins
/// @dev Expects the relativeBlocks in curve to be strictly increasing
/// @return decayedAmount The amount after applying the decay, bounded by minAmount and maxAmount
function decay(
NonlinearDutchDecay memory curve,
uint256 startAmount,
Expand All @@ -38,8 +40,9 @@ library NonlinearDutchDecayLib {
if (decayStartBlock >= block.number || curve.relativeAmounts.length == 0) {
return startAmount.bound(minAmount, maxAmount);
}

uint16 blockDelta = uint16(block.number - decayStartBlock);
// If the blockDelta is larger than type(uint16).max, a downcast overflow will occur
// We prevent this by capping the blockDelta to type(uint16).max to express a full decay
uint16 blockDelta = uint16(Math.min(block.number - decayStartBlock, type(uint16).max));
(uint16 startPoint, uint16 endPoint, int256 relStartAmount, int256 relEndAmount) =
locateCurvePosition(curve, blockDelta);
// get decay of only the relative amounts
Expand All @@ -48,13 +51,16 @@ library NonlinearDutchDecayLib {
return startAmount.boundedSub(curveDelta, minAmount, maxAmount);
}

/// @notice Locates the current position on the curve
/// @param curve The curve to search
/// @param currentRelativeBlock The current relative position
/// @return startPoint The relative block before the current position
/// @return endPoint The relative block after the current position
/// @return startAmount The relative amount before the current position
/// @return endAmount The relative amount after the current position
/// @notice Locates the current position on the decay curve based on the elapsed blocks
/// @param curve The nonlinear decay curve definition
/// @param currentRelativeBlock The number of blocks elapsed since decayStartBlock
/// @return startPoint The relative block number of the previous curve point
/// @return endPoint The relative block number of the next curve point
/// @return startAmount The relative change from initial amount at the previous curve point
/// @return endAmount The relative change from initial amount at the next curve point
/// @dev The returned amounts are changes relative to the initial startAmount, not absolute values
/// @dev If currentRelativeBlock is before the first curve point, startPoint and startAmount will be 0
/// @dev If currentRelativeBlock is after the last curve point, both points will be the last curve point
function locateCurvePosition(NonlinearDutchDecay memory curve, uint16 currentRelativeBlock)
internal
pure
Expand Down
12 changes: 7 additions & 5 deletions src/lib/V3DutchOrderLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ library V3DutchOrderLib {
"uint256 maxAmount,",
"uint256 adjustmentPerGweiBaseFee)"
);
bytes32 internal constant V3_DUTCH_INPUT_TYPE_HASH = keccak256(V3_DUTCH_INPUT_TYPE);
bytes32 internal constant V3_DUTCH_INPUT_TYPE_HASH =
keccak256(abi.encodePacked(V3_DUTCH_INPUT_TYPE, NON_LINEAR_DECAY_TYPE));
bytes internal constant V3_DUTCH_OUTPUT_TYPE = abi.encodePacked(
"V3DutchOutput(",
"address token,",
Expand All @@ -105,7 +106,8 @@ library V3DutchOrderLib {
"uint256 minAmount,",
"uint256 adjustmentPerGweiBaseFee)"
);
bytes32 internal constant V3_DUTCH_OUTPUT_TYPE_HASH = keccak256(V3_DUTCH_OUTPUT_TYPE);
bytes32 internal constant V3_DUTCH_OUTPUT_TYPE_HASH =
keccak256(abi.encodePacked(V3_DUTCH_OUTPUT_TYPE, NON_LINEAR_DECAY_TYPE));
bytes internal constant NON_LINEAR_DECAY_TYPE =
abi.encodePacked("NonlinearDutchDecay(", "uint256 relativeBlocks,", "int256[] relativeAmounts)");
bytes32 internal constant NON_LINEAR_DECAY_TYPE_HASH = keccak256(NON_LINEAR_DECAY_TYPE);
Expand Down Expand Up @@ -208,9 +210,9 @@ library V3DutchOrderLib {
}

/// @notice get the digest of the cosigner data
/// @param order the priorityOrder
/// @param order the V3DutchOrder
/// @param orderHash the hash of the order
function cosignerDigest(V3DutchOrder memory order, bytes32 orderHash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(orderHash, abi.encode(order.cosignerData)));
function cosignerDigest(V3DutchOrder memory order, bytes32 orderHash) internal view returns (bytes32) {
return keccak256(abi.encodePacked(orderHash, block.chainid, abi.encode(order.cosignerData)));
}
}
24 changes: 14 additions & 10 deletions src/reactors/V3DutchOrderReactor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {Math} from "openzeppelin-contracts/utils/math/Math.sol";
import {CosignerLib} from "../lib/CosignerLib.sol";

/// @notice Reactor for V3 dutch orders
/// @dev V3 orders must be cosigned by the specified cosigner to override starting block and value
/// @dev V3 orders must be cosigned by the specified cosigner to set the starting block and override the value
/// @dev resolution behavior:
/// - If cosignature is invalid or not from specified cosigner, revert
/// - If inputAmount is 0, then use baseInput
Expand Down Expand Up @@ -111,17 +111,21 @@ contract V3DutchOrderReactor is BaseReactor {
function _updateWithGasAdjustment(V3DutchOrder memory order) internal view {
// Positive means an increase in gas
int256 gasDeltaWei = block.basefee.sub(order.startingBaseFee);

// Round in favor of swapper
int256 inputDelta = _computeDelta(order.baseInput.adjustmentPerGweiBaseFee, gasDeltaWei);
order.baseInput.startAmount = order.baseInput.startAmount.boundedAdd(inputDelta, 0, order.baseInput.maxAmount);

// Gas increase should increase input
if (order.baseInput.adjustmentPerGweiBaseFee != 0) {
// Round in favor of swapper
int256 inputDelta = _computeDelta(order.baseInput.adjustmentPerGweiBaseFee, gasDeltaWei);
order.baseInput.startAmount = order.baseInput.startAmount.boundedAdd(inputDelta, 0, order.baseInput.maxAmount);
}
// Gas increase should decrease output
uint256 outputsLength = order.baseOutputs.length;
for (uint256 i = 0; i < outputsLength; i++) {
V3DutchOutput memory output = order.baseOutputs[i];
// Round in favor of swapper
int256 outputDelta = _computeDelta(output.adjustmentPerGweiBaseFee, gasDeltaWei);
output.startAmount = output.startAmount.boundedSub(outputDelta, output.minAmount, type(uint256).max);
if (output.adjustmentPerGweiBaseFee != 0) {
// Round in favor of swapper
int256 outputDelta = _computeDelta(output.adjustmentPerGweiBaseFee, gasDeltaWei);
output.startAmount = output.startAmount.boundedSub(outputDelta, output.minAmount, type(uint256).max);
}
}
}

Expand All @@ -137,7 +141,7 @@ contract V3DutchOrderReactor is BaseReactor {

/// @notice validate the dutch order fields
/// - deadline must have not passed
/// - cosigner is valid if specified
/// - cosigner must always be provided and sign the cosignerData
/// @dev Throws if the order is invalid
function _validateOrder(bytes32 orderHash, V3DutchOrder memory order) internal view {
if (order.info.deadline < block.timestamp) {
Expand Down
2 changes: 1 addition & 1 deletion src/types/Uint16Array.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ library Uint16ArrayLibrary {
}
unchecked {
uint256 shiftAmount = n * 16;
uint16 result = uint16((Uint16Array.unwrap(packedData) >> shiftAmount) & 0xFFFF);
uint16 result = uint16(Uint16Array.unwrap(packedData) >> shiftAmount);
return result;
}
}
Expand Down
46 changes: 46 additions & 0 deletions test/lib/EIP712.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {Test} from "forge-std/Test.sol";
import {NonlinearDutchDecay, V3DutchOrderLib, V3DutchOutput} from "../../src/lib/V3DutchOrderLib.sol";

contract EIP712Test is Test {
function test_NonlinearDutchDecayHash() public pure {
assertEq(
V3DutchOrderLib.NON_LINEAR_DECAY_TYPE_HASH,
hex"30c39ae6ecb284279579f99803ba5d7b54275a8a6a04180056b5031b8c19a01a"
);

int256[] memory amounts = new int256[](1);
amounts[0] = 1;
NonlinearDutchDecay memory curve = NonlinearDutchDecay(1, amounts);
assertEq(V3DutchOrderLib.hash(curve), hex"ad28931e960b684a49cdf1aca21bd966df5bac39996c5a0615c0a54f2f22a06f");
}

function test_V3DutchInputHash() public pure {
assertEq(
V3DutchOrderLib.V3_DUTCH_INPUT_TYPE_HASH,
hex"2cc4ccc271072d8b406616a16a6e9a3935dea10f0eb920f44737e1855ecc68eb"
);
}

function test_V3DutchOutputHash() public pure {
assertEq(
V3DutchOrderLib.V3_DUTCH_OUTPUT_TYPE_HASH,
hex"7fd857ffad1736e72f90e17c9d15cabe562b86b45c6e618ae0e7fa92c4a6fde9"
);

address token = address(0);
uint256 startAmount = 21;
int256[] memory amounts = new int256[](1);
amounts[0] = 1;
NonlinearDutchDecay memory curve = NonlinearDutchDecay(1, amounts);
address recipient = address(0);
uint256 minAmount = 20;
uint256 adjustmentPerGweiBaseFee = 0;
V3DutchOutput memory output =
V3DutchOutput(token, startAmount, curve, recipient, minAmount, adjustmentPerGweiBaseFee);
assertEq(V3DutchOrderLib.hash(output), hex"c57ac5e0436939ec593af412dde4a05d4972a0a8a56bbdb63ca7cd949c5326e2");
}

function test_OrderTypeHash() public pure {
assertEq(V3DutchOrderLib.ORDER_TYPE_HASH, hex"186c8af0344af94faab60c9dc413f68b8ca7aea1aded04a300c7fa35562ed1b7");
}
}
41 changes: 41 additions & 0 deletions test/lib/NonLinearDutchDecayLib.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {DutchDecayLib} from "../../src/lib/DutchDecayLib.sol";
import {NonlinearDutchDecayLib} from "../../src/lib/NonlinearDutchDecayLib.sol";
import {V3DutchOutput, V3DutchInput, NonlinearDutchDecay} from "../../src/lib/V3DutchOrderLib.sol";
import {Uint16Array, toUint256} from "../../src/types/Uint16Array.sol";
import {Math} from "openzeppelin-contracts/utils/math/Math.sol";
import {ArrayBuilder} from "../util/ArrayBuilder.sol";
import {CurveBuilder} from "../util/CurveBuilder.sol";

Expand Down Expand Up @@ -474,4 +475,44 @@ contract NonlinearDutchDecayLibTest is Test, GasSnapshot {
vm.expectRevert(NonlinearDutchDecayLib.InvalidDecayCurve.selector);
mockNonlinearDutchDecayLibContract.decay(curve, startAmount, decayStartBlock, 1 ether, 1 ether);
}

function testFuzzDutchDecayBeyondUint16Max(
uint16 lastValidBlock, // For curve
uint256 decayAmountFuzz, // For curve
// decay(curve, startAmount, decayStartBlock, minAmount, maxAmount);
uint256 startAmount,
uint256 decayStartBlock,
uint256 minAmount,
uint256 maxAmount,
uint256 currentBlock
) public {
vm.assume(decayStartBlock < type(uint256).max - type(uint16).max);
vm.assume(lastValidBlock > 0);
vm.assume(startAmount > 0 && startAmount < uint256(type(int256).max));
vm.assume(maxAmount >= startAmount);
minAmount = bound(minAmount, 0, startAmount);
// bound only takes uint256, so we need to limit decayAmountFuzz to int256.max
// because we cast it to int256 in the decay function
decayAmountFuzz = bound(decayAmountFuzz, minAmount, startAmount);

// Testing that we get a fully decayed curve instead of overflowed mistake
// This will happen when the block delta is larger than type(uint16).max
vm.assume(currentBlock > decayStartBlock + type(uint16).max);

uint16[] memory blocks = new uint16[](1);
blocks[0] = lastValidBlock;

int256[] memory decayAmounts = new int256[](1);
decayAmounts[0] = int256(decayAmountFuzz);

NonlinearDutchDecay memory curve = CurveBuilder.multiPointCurve(blocks, decayAmounts);

vm.roll(currentBlock);
uint256 decayed = NonlinearDutchDecayLib.decay(curve, startAmount, decayStartBlock, minAmount, maxAmount);
assertEq(
decayed,
Math.max(startAmount - decayAmountFuzz, minAmount),
"Should be fully decayed for block delta beyond uint16.max"
);
}
}
4 changes: 2 additions & 2 deletions test/reactors/V3DutchOrderReactor.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1501,8 +1501,8 @@ contract V3DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest {
(order,) = createAndSignDutchOrder(request);
}

function cosignOrder(bytes32 orderHash, CosignerData memory cosignerData) private pure returns (bytes memory sig) {
bytes32 msgHash = keccak256(abi.encodePacked(orderHash, abi.encode(cosignerData)));
function cosignOrder(bytes32 orderHash, CosignerData memory cosignerData) private view returns (bytes memory sig) {
bytes32 msgHash = keccak256(abi.encodePacked(orderHash, block.chainid, abi.encode(cosignerData)));
(uint8 v, bytes32 r, bytes32 s) = vm.sign(cosignerPrivateKey, msgHash);
sig = bytes.concat(r, s, bytes1(v));
}
Expand Down

0 comments on commit f174dd5

Please sign in to comment.