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 21 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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
180680
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
194596
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
204389
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
258082
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
188122
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
147064
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
132627
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
156380
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
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);
}
}
}
106 changes: 106 additions & 0 deletions src/lib/PriorityOrderLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// 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 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 block at which the order becomes active
uint256 startBlock;
zhongeric marked this conversation as resolved.
Show resolved Hide resolved
// 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;
}

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

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

bytes32 private constant PRIORITY_OUTPUT_TOKEN_TYPE_HASH = keccak256(PRIORITY_OUTPUT_TOKEN_TYPE);

bytes internal constant ORDER_TYPE = abi.encodePacked(
"PriorityOrder(",
"OrderInfo info,",
"uint256 startBlock,",
"address inputToken,",
"uint256 inputAmount,",
"uint256 inputMpsPerPriorityFeeWei,",
"PriorityOutput[] outputs)",
OrderInfoLib.ORDER_INFO_TYPE,
PRIORITY_OUTPUT_TOKEN_TYPE
);
bytes32 internal constant ORDER_TYPE_HASH = keccak256(ORDER_TYPE);

string private constant TOKEN_PERMISSIONS_TYPE = "TokenPermissions(address token,uint256 amount)";
string internal constant PERMIT2_ORDER_TYPE =
string(abi.encodePacked("PriorityOrder witness)", ORDER_TYPE, TOKEN_PERMISSIONS_TYPE));

/// @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.startBlock,
order.input.token,
order.input.amount,
order.input.mpsPerPriorityFeeWei,
hash(order.outputs)
zhongeric marked this conversation as resolved.
Show resolved Hide resolved
)
);
}
}
81 changes: 81 additions & 0 deletions src/reactors/PriorityOrderReactor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import {BaseReactor} from "./BaseReactor.sol";
import {Permit2Lib} from "../lib/Permit2Lib.sol";
import {PriorityOrderLib, PriorityOrder, PriorityInput, PriorityOutput} from "../lib/PriorityOrderLib.sol";
import {PriorityFeeLib} from "../lib/PriorityFeeLib.sol";
import {SignedOrder, ResolvedOrder} from "../base/ReactorStructs.sol";
import {IPermit2} from "permit2/src/interfaces/IPermit2.sol";

/// @notice Reactor for priority orders
/// @dev only supported on chains which use priority fee transaction ordering
contract PriorityOrderReactor is BaseReactor {
using Permit2Lib for ResolvedOrder;
using PriorityOrderLib for PriorityOrder;
using PriorityFeeLib for PriorityInput;
using PriorityFeeLib for PriorityOutput[];

error InvalidDeadline();
error OrderNotFillable();
error InputOutputScaling();

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

/// @inheritdoc BaseReactor
/// @notice a tx's priority fee must be equal to the difference between the tx gas price and the block's base fee
/// this may not be the case on all chains
function _resolve(SignedOrder calldata signedOrder)
internal
view
override
returns (ResolvedOrder memory resolvedOrder)
{
PriorityOrder memory priorityOrder = abi.decode(signedOrder.order, (PriorityOrder));
_validateOrder(priorityOrder);

uint256 priorityFee = tx.gasprice - block.basefee;
zhongeric marked this conversation as resolved.
Show resolved Hide resolved
resolvedOrder = ResolvedOrder({
info: priorityOrder.info,
input: priorityOrder.input.scale(priorityFee),
outputs: priorityOrder.outputs.scale(priorityFee),
sig: signedOrder.sig,
hash: priorityOrder.hash()
});
}

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

/// @notice validate the priority order fields
/// - deadline must be in the future
/// - startBlock must be in the past
/// - if input scales with priority fee, outputs must not scale
/// @dev Throws if the order is invalid
function _validateOrder(PriorityOrder memory order) internal view {
if (order.info.deadline < block.timestamp) {
revert InvalidDeadline();
}

if (order.startBlock > block.number) {
revert OrderNotFillable();
}

if (order.input.mpsPerPriorityFeeWei > 0) {
for (uint256 i = 0; i < order.outputs.length; i++) {
if (order.outputs[i].mpsPerPriorityFeeWei > 0) {
zhongeric marked this conversation as resolved.
Show resolved Hide resolved
revert InputOutputScaling();
}
}
}
}
}
Loading
Loading