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

feat: add priority tax reactor #252

Merged
merged 27 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a9eb24f
initial commit - priority fee reactor, lib, and structs
zhongeric Jun 24, 2024
d088055
Implement reactor, add tests and library tests
zhongeric Jun 25, 2024
9e3be03
forge fmt
zhongeric Jun 25, 2024
f22304b
Add validation tests
zhongeric Jun 25, 2024
462e73c
nit: comment
zhongeric Jun 25, 2024
b6507ec
change to MPS
zhongeric Jun 25, 2024
733ea41
forge fmt
zhongeric Jun 25, 2024
24e3387
remove unchecked loop
zhongeric Jun 26, 2024
a3e462c
add comment
zhongeric Jun 26, 2024
7d90457
replace mps with pips
zhongeric Jun 26, 2024
4160c07
rename back to mps
zhongeric Jun 26, 2024
939fdd3
fix comment and test
zhongeric Jun 26, 2024
109c580
add comment
zhongeric Jun 28, 2024
45cc9f6
Set maxAmount to original input amount to fix token permissions error
zhongeric Jun 28, 2024
3b5d55b
forge fmt
zhongeric Jun 28, 2024
c0e0cdd
comments: cache value in lib
zhongeric Jul 3, 2024
b0d1a6d
Add explicit edge case priority fee lib tests
zhongeric Jul 3, 2024
91d7201
forge fmt
zhongeric Jul 3, 2024
8bcf907
add tests
zhongeric Jul 3, 2024
7e71e07
add baseFee assert
zhongeric Jul 3, 2024
98b47bc
forge fmt
zhongeric Jul 3, 2024
666d57d
feat: add cosigner logic and minPriorityFee (#254)
zhongeric Jul 11, 2024
7c4330f
fix: cosigner verification edge cases and other fixes (#255)
zhongeric Jul 12, 2024
b26c523
Fix 712 ordering (#258)
zhongeric Jul 17, 2024
25237d0
chore: add better gas coverage for priority orders
zhongeric Jul 17, 2024
77c9cba
fix gas snaps
zhongeric Jul 17, 2024
f77785b
feat: revert quickly if order is already filled (#259)
zhongeric Jul 18, 2024
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
1 change: 1 addition & 0 deletions .forge-snapshots/Base-DutchOrder-RevertInvalidNonce.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
26482
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
26649
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22699
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
184811
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
202966
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
212779
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
266488
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
196492
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
151201
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
136764
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
160512
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
153386
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
153195
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
153191
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
130608
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
13727
1 change: 1 addition & 0 deletions .forge-snapshots/Base-V2DutchOrder-RevertInvalidNonce.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
33446
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}]
optimizer_runs = 1000000

[profile.integration]
no_match_path = ""
match_path = "*/integration/*"
no_match_path = "override/default/*"

[profile.lite.optimizer_details.yulDetails]
stackAllocation = true
Expand Down
19 changes: 19 additions & 0 deletions src/lib/CosignerLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

/// @notice helper library for verifying cosignatures
library CosignerLib {
/// @notice thrown when an order's cosignature does not match the expected cosigner
error InvalidCosignature();

/// @notice verify that a cosignature is valid for a given cosigner and data
/// @param cosigner the address of the cosigner
/// @param data the digest of (orderHash || cosignerData)
/// @param cosignature the cosigner's signature over the data
function verify(address cosigner, bytes32 data, bytes memory cosignature) internal pure {
(bytes32 r, bytes32 s) = abi.decode(cosignature, (bytes32, bytes32));
uint8 v = uint8(cosignature[64]);
address signer = ecrecover(data, v, r, s);
if (cosigner != signer || signer == address(0)) revert InvalidCosignature();
}
}
59 changes: 59 additions & 0 deletions src/lib/PriorityFeeLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import {OutputToken, InputToken} from "../base/ReactorStructs.sol";
import {PriorityOutput, PriorityInput} from "../lib/PriorityOrderLib.sol";
import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol";

/// @notice helpers for handling priority order objects
library PriorityFeeLib {
using FixedPointMathLib for uint256;

/// @notice we denominate priority fees in terms of milli-bips, or one thousandth of a basis point
zhongeric marked this conversation as resolved.
Show resolved Hide resolved
uint256 constant MPS = 1e7;

/// @notice returns a scaled input using the current priority fee and mpsPerPriorityFeeWei
/// @notice this value is bounded by 0 and the input amount
/// @notice the amount is scaled down to favor the swapper
/// @notice maxAmount is set to be the original amount and is used to rebuild the permit2 token permissions struct
/// @param input the input to scale
/// @param priorityFee the current priority fee in wei
/// @return a scaled input
function scale(PriorityInput memory input, uint256 priorityFee) internal pure returns (InputToken memory) {
uint256 scalingFactor = priorityFee * input.mpsPerPriorityFeeWei;
if (scalingFactor >= MPS) {
return InputToken({token: input.token, amount: 0, maxAmount: input.amount});
}
return InputToken({
token: input.token,
amount: input.amount.mulDivDown((MPS - scalingFactor), MPS),
maxAmount: input.amount
});
}

/// @notice returns a scaled output using the current priority fee and mpsPerPriorityFeeWei
/// @notice the amount is scaled up to favor the swapper
/// @param output the output to scale
/// @param priorityFee the current priority fee
/// @return a scaled output
function scale(PriorityOutput memory output, uint256 priorityFee) internal pure returns (OutputToken memory) {
return OutputToken({
token: output.token,
amount: output.amount.mulDivUp((MPS + (priorityFee * output.mpsPerPriorityFeeWei)), MPS),
recipient: output.recipient
});
}

/// @notice returns scaled outputs using the current priority fee and mpsPerPriorityFeeWei
function scale(PriorityOutput[] memory outputs, uint256 priorityFee)
internal
pure
returns (OutputToken[] memory result)
{
uint256 outputLength = outputs.length;
result = new OutputToken[](outputLength);
for (uint256 i = 0; i < outputLength; i++) {
result[i] = scale(outputs[i], priorityFee);
}
}
}
158 changes: 158 additions & 0 deletions src/lib/PriorityOrderLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import {OrderInfo, InputToken, OutputToken} from "../base/ReactorStructs.sol";
import {OrderInfoLib} from "./OrderInfoLib.sol";
import {ERC20} from "solmate/src/tokens/ERC20.sol";

struct PriorityCosignerData {
// the block at which the order can be executed (overrides auctionStartBlock)
uint256 auctionTargetBlock;
}

struct PriorityInput {
ERC20 token;
uint256 amount;
// the less amount of input to be received per wei of priority fee
uint256 mpsPerPriorityFeeWei;
}

struct PriorityOutput {
address token;
uint256 amount;
// the extra amount of output to be paid per wei of priority fee
uint256 mpsPerPriorityFeeWei;
address recipient;
}

/// @dev External struct used to specify priority orders
struct PriorityOrder {
// generic order information
OrderInfo info;
// The address which may cosign the order
address cosigner;
// the block at which the order can be executed
uint256 auctionStartBlock;
// the baseline priority fee for the order, above which additional taxes are applied
uint256 baselinePriorityFeeWei;
// The tokens that the swapper will provide when settling the order
zhongeric marked this conversation as resolved.
Show resolved Hide resolved
PriorityInput input;
// The tokens that must be received to satisfy the order
PriorityOutput[] outputs;
// signed over by the cosigner
PriorityCosignerData cosignerData;
// signature from the cosigner over (orderHash || cosignerData)
bytes cosignature;
}

/// @notice helpers for handling priority order objects
library PriorityOrderLib {
using OrderInfoLib for OrderInfo;

bytes internal constant PRIORITY_INPUT_TOKEN_TYPE =
"PriorityInput(address token,uint256 amount,uint256 mpsPerPriorityFeeWei)";

bytes32 internal constant PRIORITY_INPUT_TOKEN_TYPE_HASH = keccak256(PRIORITY_INPUT_TOKEN_TYPE);

bytes internal constant PRIORITY_OUTPUT_TOKEN_TYPE =
"PriorityOutput(address token,uint256 amount,uint256 mpsPerPriorityFeeWei,address recipient)";

bytes32 internal constant PRIORITY_OUTPUT_TOKEN_TYPE_HASH = keccak256(PRIORITY_OUTPUT_TOKEN_TYPE);

string internal constant TOKEN_PERMISSIONS_TYPE = "TokenPermissions(address token,uint256 amount)";

// EIP712 notes that nested structs should be ordered alphabetically.
// With our added PriorityOrder witness, the top level type becomes
// "PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,PriorityOrder witness)"
// Meaning we order the nested structs as follows:
// OrderInfo, PriorityInput, PriorityOrder, PriorityOutput
string internal constant PERMIT2_ORDER_TYPE = string(
abi.encodePacked(
"PriorityOrder witness)",
OrderInfoLib.ORDER_INFO_TYPE,
PriorityOrderLib.PRIORITY_INPUT_TOKEN_TYPE,
PriorityOrderLib.TOPLEVEL_PRIORITY_ORDER_TYPE,
PriorityOrderLib.PRIORITY_OUTPUT_TOKEN_TYPE,
TOKEN_PERMISSIONS_TYPE
)
);

bytes internal constant TOPLEVEL_PRIORITY_ORDER_TYPE = abi.encodePacked(
"PriorityOrder(",
"OrderInfo info,",
"address cosigner,",
"uint256 auctionStartBlock,",
"uint256 baselinePriorityFeeWei,",
"PriorityInput input,",
"PriorityOutput[] outputs)"
);

// EIP712 notes that nested structs should be ordered alphabetically:
// OrderInfo, PriorityInput, PriorityOutput
bytes internal constant ORDER_TYPE = abi.encodePacked(
PriorityOrderLib.TOPLEVEL_PRIORITY_ORDER_TYPE,
OrderInfoLib.ORDER_INFO_TYPE,
PriorityOrderLib.PRIORITY_INPUT_TOKEN_TYPE,
PriorityOrderLib.PRIORITY_OUTPUT_TOKEN_TYPE
);
bytes32 internal constant ORDER_TYPE_HASH = keccak256(ORDER_TYPE);

/// @notice returns the hash of an input token struct
function hash(PriorityInput memory input) private pure returns (bytes32) {
return
keccak256(abi.encode(PRIORITY_INPUT_TOKEN_TYPE_HASH, input.token, input.amount, input.mpsPerPriorityFeeWei));
}

/// @notice returns the hash of an output token struct
function hash(PriorityOutput memory output) private pure returns (bytes32) {
return keccak256(
abi.encode(
PRIORITY_OUTPUT_TOKEN_TYPE_HASH,
output.token,
output.amount,
output.mpsPerPriorityFeeWei,
output.recipient
)
);
}

/// @notice returns the hash of an output token struct
function hash(PriorityOutput[] memory outputs) private pure returns (bytes32) {
unchecked {
bytes memory packedHashes = new bytes(32 * outputs.length);

for (uint256 i = 0; i < outputs.length; i++) {
bytes32 outputHash = hash(outputs[i]);
assembly {
mstore(add(add(packedHashes, 0x20), mul(i, 0x20)), outputHash)
}
}

return keccak256(packedHashes);
}
}

/// @notice hash the given order
/// @param order the order to hash
/// @return the eip-712 order hash
function hash(PriorityOrder memory order) internal pure returns (bytes32) {
return keccak256(
abi.encode(
ORDER_TYPE_HASH,
order.info.hash(),
order.cosigner,
order.auctionStartBlock,
order.baselinePriorityFeeWei,
hash(order.input),
hash(order.outputs)
zhongeric marked this conversation as resolved.
Show resolved Hide resolved
)
);
}

/// @notice get the digest of the cosigner data
/// @param order the priorityOrder
/// @param orderHash the hash of the order
function cosignerDigest(PriorityOrder memory order, bytes32 orderHash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(orderHash, abi.encode(order.cosignerData)));
}
}
Loading
Loading