Skip to content

Commit

Permalink
fix: tests
Browse files Browse the repository at this point in the history
  • Loading branch information
marktoda committed Dec 14, 2023
1 parent a5cb865 commit f0172ec
Show file tree
Hide file tree
Showing 12 changed files with 361 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
189606
1 change: 1 addition & 0 deletions .forge-snapshots/Base-V2DutchOrder-ExecuteBatch.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
212188
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
222748
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
277211
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
205714
1 change: 1 addition & 0 deletions .forge-snapshots/Base-V2DutchOrder-ExecuteSingle.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
155794
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
141360
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
165115
89 changes: 89 additions & 0 deletions src/lib/V2DutchOrderLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import {OrderInfo} from "../base/ReactorStructs.sol";
import {DutchOutput, DutchInput, DutchOrderLib} from "./DutchOrderLib.sol";
import {OrderInfoLib} from "./OrderInfoLib.sol";

struct V2DutchOrderInner {
// generic order information
OrderInfo info;
// The address which must cosign the ful order
address cosigner;
// The tokens that the swapper will provide when settling the order
DutchInput input;
// The tokens that must be received to satisfy the order
DutchOutput[] outputs;
}

struct V2DutchOrder {
// Inner order
V2DutchOrderInner inner;
// The time at which the DutchOutputs start decaying
uint256 decayStartTime;
// The time at which price becomes static
uint256 decayEndTime;
// The address who has exclusive rights to the order until decayStartTime
address exclusiveFiller;
// The amount in bps that a non-exclusive filler needs to improve the outputs by to be able to fill the order
uint256 exclusivityOverrideBps;
// The tokens that the swapper will provide when settling the order
uint256 inputOverride;
// The tokens that must be received to satisfy the order
uint256[] outputOverrides;
}

struct CosignedV2DutchOrder {
V2DutchOrder order;
bytes signature;
}

/// @notice helpers for handling v2 dutch order objects
library V2DutchOrderLib {
using DutchOrderLib for DutchOutput[];
using OrderInfoLib for OrderInfo;

bytes internal constant V2_DUTCH_ORDER_TYPE = abi.encodePacked(
"V2DutchOrder(",
"OrderInfo info,",
"address cosigner,",
"address inputToken,",
"uint256 inputStartAmount,",
"uint256 inputEndAmount,",
"DutchOutput[] outputs)"
);

bytes internal constant ORDER_TYPE = abi.encodePacked(
V2_DUTCH_ORDER_TYPE, DutchOrderLib.DUTCH_OUTPUT_TYPE, OrderInfoLib.ORDER_INFO_TYPE
);
bytes32 internal constant ORDER_TYPE_HASH = keccak256(ORDER_TYPE);

/// @dev Note that sub-structs have to be defined in alphabetical order in the EIP-712 spec
string internal constant PERMIT2_ORDER_TYPE = string(
abi.encodePacked(
"V2DutchOrder witness)",
DutchOrderLib.DUTCH_OUTPUT_TYPE,
OrderInfoLib.ORDER_INFO_TYPE,
DutchOrderLib.TOKEN_PERMISSIONS_TYPE,
V2_DUTCH_ORDER_TYPE
)
);

/// @notice hash the given order
/// @param order the order to hash
/// @return the eip-712 order hash
function hash(V2DutchOrder memory order) internal pure returns (bytes32) {
V2DutchOrderInner memory inner = order.inner;
return keccak256(
abi.encode(
ORDER_TYPE_HASH,
inner.info.hash(),
inner.cosigner,
inner.input.token,
inner.input.startAmount,
inner.input.endAmount,
inner.outputs.hash()
)
);
}
}
135 changes: 135 additions & 0 deletions src/reactors/V2DutchOrderReactor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import {BaseReactor} from "./BaseReactor.sol";
import {IPermit2} from "permit2/src/interfaces/IPermit2.sol";
import {ExclusivityOverrideLib} from "../lib/ExclusivityOverrideLib.sol";
import {Permit2Lib} from "../lib/Permit2Lib.sol";
import {DutchDecayLib} from "../lib/DutchDecayLib.sol";
import {V2DutchOrderLib, V2DutchOrder, V2DutchOrderInner, CosignedV2DutchOrder, DutchOutput, DutchInput} from "../lib/V2DutchOrderLib.sol";
import {SignedOrder, ResolvedOrder} from "../base/ReactorStructs.sol";

/// @notice Reactor for v2 dutch orders
/// @dev V2 orders must be cosigned by the specified cosigner to override timings and starting values
/// @dev resolution behavior:
/// - If cosignature is invalid or not from specified cosigner, revert
/// - If inputOverride is 0, then use inner inputs
/// - If inputOverride is nonzero, then ensure it is less than specified input and replace startAmount
/// - For each DutchOutput:
/// - If override is 0, then use inner output
/// - If override is nonzero, then ensure it is greater than specified output and replace startAmount
contract V2DutchOrderReactor is BaseReactor {
using Permit2Lib for ResolvedOrder;
using V2DutchOrderLib for V2DutchOrder;
using DutchDecayLib for DutchOutput[];
using DutchDecayLib for DutchInput;
using ExclusivityOverrideLib for ResolvedOrder;

/// @notice thrown when an order's deadline is before its end time
error DeadlineBeforeEndTime();

/// @notice thrown when an order's end time is before its start time
error OrderEndTimeBeforeStartTime();

/// @notice thrown when an order's inputs and outputs both decay
error InputAndOutputDecay();

/// @notice thrown when an order's cosignature does not match the expected cosigner
error InvalidCosignature();

/// @notice thrown when an order's input override is greater than the specified
error InvalidInputOverride();

/// @notice thrown when an order's output override is greater than the specified
error InvalidOutputOverride();

constructor(IPermit2 _permit2, address _protocolFeeOwner) BaseReactor(_permit2, _protocolFeeOwner) {}

/// @inheritdoc BaseReactor
function resolve(SignedOrder calldata signedOrder)
internal
view
virtual
override
returns (ResolvedOrder memory resolvedOrder)
{
CosignedV2DutchOrder memory cosignedOrder = abi.decode(signedOrder.order, (CosignedV2DutchOrder));
_validateOrder(cosignedOrder);
V2DutchOrder memory order = cosignedOrder.order;
_updateWithOverrides(order);

resolvedOrder = ResolvedOrder({
info: order.inner.info,
input: order.inner.input.decay(order.decayStartTime, order.decayEndTime),
outputs: order.inner.outputs.decay(order.decayStartTime, order.decayEndTime),
sig: signedOrder.sig,
hash: order.hash()
});
resolvedOrder.handleOverride(order.exclusiveFiller, order.decayStartTime, order.exclusivityOverrideBps);
}

/// @inheritdoc BaseReactor
function transferInputTokens(ResolvedOrder memory order, address to) internal override {
permit2.permitWitnessTransferFrom(
order.toPermit(),
order.transferDetails(to),
order.info.swapper,
order.hash,
V2DutchOrderLib.PERMIT2_ORDER_TYPE,
order.sig
);
}

function _updateWithOverrides(V2DutchOrder memory order) internal pure {
if (order.inputOverride != 0) order.inner.input.startAmount = order.inputOverride;

for (uint256 i = 0; i < order.inner.outputs.length; i++) {
DutchOutput memory output = order.inner.outputs[i];
uint256 outputOverride = order.outputOverrides[i];
if (outputOverride != 0) output.startAmount = outputOverride;
}
}

/// @notice validate the dutch order fields
/// - deadline must be greater than or equal than decayEndTime
/// - decayEndTime must be greater than or equal to decayStartTime
/// - if there's input decay, outputs must not decay
/// @dev Throws if the order is invalid
function _validateOrder(CosignedV2DutchOrder memory cosigned) internal pure {
V2DutchOrder memory order = cosigned.order;
if (order.inner.info.deadline < order.decayEndTime) {
revert DeadlineBeforeEndTime();
}

if (order.decayEndTime < order.decayStartTime) {
revert OrderEndTimeBeforeStartTime();
}

(bytes32 r, bytes32 s) = abi.decode(cosigned.signature, (bytes32, bytes32));
uint8 v = uint8(cosigned.signature[64]);
address signer = ecrecover(keccak256(abi.encode(order)), v, r, s);
if (order.inner.cosigner != signer) {
revert InvalidCosignature();
}

if (order.inputOverride != 0 && order.inputOverride > order.inner.input.startAmount) {
revert InvalidInputOverride();
}

if (order.inner.input.startAmount != order.inner.input.endAmount) {
unchecked {
for (uint256 i = 0; i < order.inner.outputs.length; i++) {
DutchOutput memory output = order.inner.outputs[i];
if (output.startAmount != output.endAmount) {
revert InputAndOutputDecay();
}

uint256 outputOverride = order.outputOverrides[i];
if (outputOverride < output.startAmount) {
revert InvalidOutputOverride();
}
}
}
}
}
}
108 changes: 108 additions & 0 deletions test/reactors/V2DutchOrderReactor.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import {Test} from "forge-std/Test.sol";
import {DeployPermit2} from "../util/DeployPermit2.sol";
import {
V2DutchOrder,
V2DutchOrderLib,
V2DutchOrderInner,
V2DutchOrderReactor,
CosignedV2DutchOrder,
ResolvedOrder,
DutchOutput,
DutchInput,
BaseReactor
} from "../../src/reactors/V2DutchOrderReactor.sol";
import {OrderInfo, InputToken, SignedOrder, OutputToken} from "../../src/base/ReactorStructs.sol";
import {DutchDecayLib} from "../../src/lib/DutchDecayLib.sol";
import {CurrencyLibrary, NATIVE} from "../../src/lib/CurrencyLibrary.sol";
import {OrderInfoBuilder} from "../util/OrderInfoBuilder.sol";
import {MockERC20} from "../util/mock/MockERC20.sol";
import {OutputsBuilder} from "../util/OutputsBuilder.sol";
import {MockFillContract} from "../util/mock/MockFillContract.sol";
import {MockFillContractWithOutputOverride} from "../util/mock/MockFillContractWithOutputOverride.sol";
import {PermitSignature} from "../util/PermitSignature.sol";
import {ReactorEvents} from "../../src/base/ReactorEvents.sol";
import {BaseReactorTest} from "../base/BaseReactor.t.sol";

contract V2DutchOrderTest is PermitSignature, DeployPermit2, BaseReactorTest {
using OrderInfoBuilder for OrderInfo;
using V2DutchOrderLib for V2DutchOrder;

uint256 constant cosignerPrivateKey = 0x99999999;

function name() public pure override returns (string memory) {
return "V2DutchOrder";
}

function createReactor() public override returns (BaseReactor) {
return new V2DutchOrderReactor(permit2, PROTOCOL_FEE_OWNER);
}

/// @dev Create and return a basic single Dutch limit order along with its signature, orderHash, and orderInfo
/// TODO: Support creating a single dutch order with multiple outputs
function createAndSignOrder(ResolvedOrder memory request)
public
view
override
returns (SignedOrder memory signedOrder, bytes32 orderHash)
{
DutchOutput[] memory outputs = new DutchOutput[](request.outputs.length);
for (uint256 i = 0; i < request.outputs.length; i++) {
OutputToken memory output = request.outputs[i];
outputs[i] = DutchOutput({
token: output.token,
startAmount: output.amount,
endAmount: output.amount,
recipient: output.recipient
});
}

V2DutchOrderInner memory inner = V2DutchOrderInner({
info: request.info,
cosigner: vm.addr(cosignerPrivateKey),
input: DutchInput(request.input.token, request.input.amount, request.input.amount),
outputs: outputs
});

uint256[] memory outputOverrides = new uint256[](request.outputs.length);
for (uint256 i = 0; i < request.outputs.length; i++) {
outputOverrides[i] = 0;
}

V2DutchOrder memory order = V2DutchOrder({
inner: inner,
decayStartTime: block.timestamp,
decayEndTime: request.info.deadline,
exclusiveFiller: address(0),
exclusivityOverrideBps: 300,
inputOverride: 0,
outputOverrides: outputOverrides
});
orderHash = order.hash();
CosignedV2DutchOrder memory cosigned = CosignedV2DutchOrder({
order: order,
signature: cosignOrder(order)
});
return (SignedOrder(abi.encode(cosigned), signOrder(swapperPrivateKey, address(permit2), order)), orderHash);
}

function cosignOrder(V2DutchOrder memory order) private pure returns (bytes memory sig) {
bytes32 msgHash = keccak256(abi.encode(order));
(uint8 v, bytes32 r, bytes32 s) = vm.sign(cosignerPrivateKey, msgHash);
sig = bytes.concat(r, s, bytes1(v));
}

function generateSignedOrders(V2DutchOrder[] memory orders)
private
view
returns (SignedOrder[] memory result)
{
result = new SignedOrder[](orders.length);
for (uint256 i = 0; i < orders.length; i++) {
bytes memory sig = signOrder(swapperPrivateKey, address(permit2), orders[i]);
result[i] = SignedOrder(abi.encode(orders[i]), sig);
}
}
}
Loading

0 comments on commit f0172ec

Please sign in to comment.