-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
361 additions
and
0 deletions.
There are no files selected for viewing
1 change: 1 addition & 0 deletions
1
.forge-snapshots/Base-V2DutchOrder-BaseExecuteSingleWithFee.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
189606 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
212188 |
1 change: 1 addition & 0 deletions
1
.forge-snapshots/Base-V2DutchOrder-ExecuteBatchMultipleOutputs.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
222748 |
1 change: 1 addition & 0 deletions
1
.forge-snapshots/Base-V2DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
277211 |
1 change: 1 addition & 0 deletions
1
.forge-snapshots/Base-V2DutchOrder-ExecuteBatchNativeOutput.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
205714 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
155794 |
1 change: 1 addition & 0 deletions
1
.forge-snapshots/Base-V2DutchOrder-ExecuteSingleNativeOutput.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
141360 |
1 change: 1 addition & 0 deletions
1
.forge-snapshots/Base-V2DutchOrder-ExecuteSingleValidation.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
165115 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
Oops, something went wrong.