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: Initial implementation of rebate via priority fee #103

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions src/base/ReactorStructs.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ struct RelayOrder {
Input input;
// The fee offered for the order
FeeEscalator fee;
// The rebate offered for the order
Rebate rebate;
// encoded data relayed to the universal router
bytes universalRouterCalldata;
}
Expand Down Expand Up @@ -48,6 +50,13 @@ struct FeeEscalator {
uint256 endTime;
}

/// @notice A RelayOrder can specify a rebate to be paid to the swapper
struct Rebate {
address token;
uint256 minAmount;
uint256 bpsPerGas;
}

/// @dev Extneral struct including a generic encoded order and swapper signature
/// The order is decoded as a RelayOrder
struct SignedOrder {
Expand Down
42 changes: 42 additions & 0 deletions src/lib/RebateLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import {ISignatureTransfer} from "permit2/src/interfaces/ISignatureTransfer.sol";
import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol";
import {Rebate} from "../base/ReactorStructs.sol";
import {ReactorErrors} from "../base/ReactorErrors.sol";

/// @notice Handles the EIP712 defined typehash and hashing for FeeEscalator, and performs escalation calculations
library RebateLib {
using FixedPointMathLib for uint256;

uint256 public constant BPS = 10000;

bytes internal constant REBATE_TYPESTRING = abi.encodePacked(
"Rebate(",
"address token,",
"uint256 minAmount,",
"uint256 bpsPerGas)"
);

bytes32 internal constant REBATE_TYPEHASH = keccak256(REBATE_TYPESTRING);

/// @notice
function resolve(uint256 minAmount, uint256 bpsPerGas)
internal
view
returns (uint256 resolvedAmount)
{
if(bpsPerGas == 0) {
return minAmount;
}
uint256 priorityFee = tx.gasprice - block.basefee;
return (minAmount * (BPS + (priorityFee * bpsPerGas))) / BPS;
}

function hash(Rebate memory rebate) internal pure returns (bytes32) {
return keccak256(
abi.encode(REBATE_TYPEHASH, rebate.token, rebate.minAmount, rebate.bpsPerGas)
);
}
}
21 changes: 18 additions & 3 deletions src/lib/RelayOrderLib.sol
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import {ERC20} from "solmate/src/tokens/ERC20.sol";
import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol";
import {IPermit2} from "permit2/src/interfaces/IPermit2.sol";
import {ISignatureTransfer} from "permit2/src/interfaces/ISignatureTransfer.sol";
import {PermitHash} from "permit2/src/libraries/PermitHash.sol";
import {RelayOrder, FeeEscalator, Input, RelayOrderInfo} from "../base/ReactorStructs.sol";
import {RelayOrder, FeeEscalator, Input, Rebate, RelayOrderInfo} from "../base/ReactorStructs.sol";
import {ReactorErrors} from "../base/ReactorErrors.sol";
import {FeeEscalatorLib} from "./FeeEscalatorLib.sol";
import {InputLib} from "./InputLib.sol";
import {RebateLib} from "./RebateLib.sol";
import {RelayOrderInfoLib} from "./RelayOrderInfoLib.sol";

library RelayOrderLib {
using SafeTransferLib for ERC20;
using RelayOrderLib for RelayOrder;
using FeeEscalatorLib for FeeEscalator;
using RelayOrderInfoLib for RelayOrderInfo;
using InputLib for Input;
using RebateLib for Rebate;

// EIP712 notes that nested structs should be ordered alphabetically.
// With our added RelayOrder witness, the top level type becomes
Expand All @@ -26,22 +31,24 @@ library RelayOrderLib {
"RelayOrder witness)",
FeeEscalatorLib.FEE_ESCALATOR_TYPESTRING,
InputLib.INPUT_TYPESTRING,
RebateLib.REBATE_TYPESTRING,
RelayOrderLib.TOPLEVEL_RELAY_ORDER_TYPESTRING,
RelayOrderInfoLib.RELAY_ORDER_INFO_TYPESTRING,
PermitHash._TOKEN_PERMISSIONS_TYPESTRING
)
);

bytes internal constant TOPLEVEL_RELAY_ORDER_TYPESTRING = abi.encodePacked(
"RelayOrder(", "RelayOrderInfo info,", "Input input,", "FeeEscalator fee,", "bytes universalRouterCalldata)"
"RelayOrder(", "RelayOrderInfo info,", "Input input,", "FeeEscalator fee,", "Rebate rebate", "bytes universalRouterCalldata)"
);

// EIP712 notes that nested structs should be ordered alphabetically:
// FeeEscalator, Input, OrderInfo
// FeeEscalator, Input, Rebate, RelayOrderInfo
bytes internal constant FULL_RELAY_ORDER_TYPESTRING = abi.encodePacked(
RelayOrderLib.TOPLEVEL_RELAY_ORDER_TYPESTRING,
FeeEscalatorLib.FEE_ESCALATOR_TYPESTRING,
InputLib.INPUT_TYPESTRING,
RebateLib.REBATE_TYPESTRING,
RelayOrderInfoLib.RELAY_ORDER_INFO_TYPESTRING
);

Expand Down Expand Up @@ -110,6 +117,13 @@ library RelayOrderLib {
);
}

function handleRebate(RelayOrder memory order) internal {
uint256 resolvedAmount = RebateLib.resolve(order.rebate.minAmount, order.rebate.bpsPerGas);
if (resolvedAmount > 0) {
ERC20(order.rebate.token).safeTransfer(order.info.swapper, resolvedAmount);
}
}

/// @notice hash The given order
/// @param order The order to hash
/// @return The eip-712 order hash
Expand All @@ -120,6 +134,7 @@ library RelayOrderLib {
order.info.hash(),
order.input.hash(),
order.fee.hash(),
order.rebate.hash(),
keccak256(order.universalRouterCalldata)
)
);
Expand Down
3 changes: 3 additions & 0 deletions src/reactors/RelayOrderReactor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ contract RelayOrderReactor is Multicall, ReactorEvents, ReactorErrors, IRelayOrd
}
}

// rebate the swapper if necessary
order.handleRebate();

emit Relay(orderHash, msg.sender, order.info.swapper, order.info.nonce);
}

Expand Down
57 changes: 36 additions & 21 deletions test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {AddressBuilder} from "permit2/test/utils/AddressBuilder.sol";
import {AmountBuilder} from "permit2/test/utils/AmountBuilder.sol";
import {ISignatureTransfer} from "permit2/src/interfaces/ISignatureTransfer.sol";
import {IPermit2} from "permit2/src/interfaces/IPermit2.sol";
import {Input, RelayOrderInfo, FeeEscalator, SignedOrder} from "../../../src/base/ReactorStructs.sol";
import {Input, RelayOrderInfo, FeeEscalator, Rebate, SignedOrder} from "../../../src/base/ReactorStructs.sol";
import {ReactorEvents} from "../../../src/base/ReactorEvents.sol";
import {IRelayOrderReactor} from "../../../src/interfaces/IRelayOrderReactor.sol";
import {RelayOrderReactor} from "../../../src/reactors/RelayOrderReactor.sol";
Expand All @@ -18,6 +18,7 @@ import {RelayOrderInfoBuilder} from "../util/RelayOrderInfoBuilder.sol";
import {InputBuilder} from "../util/InputBuilder.sol";
import {RelayOrderBuilder} from "../util/RelayOrderBuilder.sol";
import {FeeEscalatorBuilder} from "../util/FeeEscalatorBuilder.sol";
import {RebateBuilder} from "../util/RebateBuilder.sol";
import {PermitSignature} from "../util/PermitSignature.sol";
import {MethodParameters, Interop} from "../util/Interop.sol";
import {ReactorEvents} from "../../../src/base/ReactorEvents.sol";
Expand Down Expand Up @@ -119,6 +120,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS

Input memory input = InputBuilder.init(tokenIn).withAmount(100 * ONE).withRecipient(UNIVERSAL_ROUTER);
FeeEscalator memory fee = FeeEscalatorBuilder.init(gasToken).withStartAmount(10 * ONE).withEndAmount(10 * ONE);
Rebate memory rebate = RebateBuilder.init(tokenOut);

uint256 amountOutMin = 95 * USDC_ONE;
MethodParameters memory methodParameters = readFixture(json, "._UNISWAP_V3_DAI_USDC");
Expand All @@ -128,7 +130,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS
).withNonce(1);

RelayOrder memory order =
RelayOrderBuilder.init(orderInfo, input, fee).withUniversalRouterCalldata(methodParameters.data);
RelayOrderBuilder.init(orderInfo, input, fee, rebate).withUniversalRouterCalldata(methodParameters.data);

SignedOrder memory signedOrder =
SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(PERMIT2), order));
Expand Down Expand Up @@ -178,6 +180,8 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS
FeeEscalator memory fee =
FeeEscalatorBuilder.init(gasToken).withStartAmount(10 * USDC_ONE).withEndAmount(10 * USDC_ONE);

Rebate memory rebate = RebateBuilder.init(tokenOut);

uint256 amountOutMin = 95 * USDC_ONE;
MethodParameters memory methodParameters = readFixture(json, "._UNISWAP_V3_DAI_USDC");

Expand All @@ -186,7 +190,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS
).withNonce(1);

RelayOrder memory order =
RelayOrderBuilder.init(orderInfo, input, fee).withUniversalRouterCalldata(methodParameters.data);
RelayOrderBuilder.init(orderInfo, input, fee, rebate).withUniversalRouterCalldata(methodParameters.data);

SignedOrder memory signedOrder =
SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(PERMIT2), order));
Expand Down Expand Up @@ -228,14 +232,16 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS
FeeEscalator memory fee =
FeeEscalatorBuilder.init(gasToken).withStartAmount(10 * USDC_ONE).withEndAmount(10 * USDC_ONE);

Rebate memory rebate = RebateBuilder.init(tokenOut);

RelayOrderInfo memory orderInfo = RelayOrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(
block.timestamp + 100
).withNonce(0);

MethodParameters memory methodParameters = readFixture(json, "._UNISWAP_V3_DAI_USDC");

RelayOrder memory order =
RelayOrderBuilder.init(orderInfo, input, fee).withUniversalRouterCalldata(methodParameters.data);
RelayOrderBuilder.init(orderInfo, input, fee, rebate).withUniversalRouterCalldata(methodParameters.data);

SignedOrder memory signedOrder =
SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(PERMIT2), order));
Expand Down Expand Up @@ -279,31 +285,37 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS
// this swapper has not yet approved the P2 contract
// so we will relay a USDC 2612 permit to the P2 contract first
// making a USDC -> DAI swap
Input memory input = InputBuilder.init(USDC).withAmount(100 * USDC_ONE).withRecipient(UNIVERSAL_ROUTER);
FeeEscalator memory fee =
FeeEscalatorBuilder.init(USDC).withStartAmount(10 * USDC_ONE).withEndAmount(10 * USDC_ONE);
SignedOrder memory signedOrder;
{
Input memory input = InputBuilder.init(USDC).withAmount(100 * USDC_ONE).withRecipient(UNIVERSAL_ROUTER);
FeeEscalator memory fee =
FeeEscalatorBuilder.init(USDC).withStartAmount(10 * USDC_ONE).withEndAmount(10 * USDC_ONE);

MethodParameters memory methodParameters = readFixture(json, "._UNISWAP_V3_USDC_DAI_SWAPPER2");
Rebate memory rebate = RebateBuilder.init(USDC);

RelayOrderInfo memory orderInfo = RelayOrderInfoBuilder.init(address(reactor)).withSwapper(swapper2)
.withDeadline(block.timestamp + 100).withNonce(0);
MethodParameters memory methodParameters = readFixture(json, "._UNISWAP_V3_USDC_DAI_SWAPPER2");

RelayOrder memory order =
RelayOrderBuilder.init(orderInfo, input, fee).withUniversalRouterCalldata(methodParameters.data);
RelayOrderInfo memory orderInfo = RelayOrderInfoBuilder.init(address(reactor)).withSwapper(swapper2)
.withDeadline(block.timestamp + 100).withNonce(0);

RelayOrder memory order =
RelayOrderBuilder.init(orderInfo, input, fee, rebate).withUniversalRouterCalldata(methodParameters.data);

signedOrder =
SignedOrder(abi.encode(order), signOrder(swapper2PrivateKey, address(PERMIT2), order));

// TODO: This snapshot should always pull tokens in from permit2 and then expose an option to benchmark it with an an allowance on the UR vs. without.
// For this test, we should benchmark that the user has not permitted permit2, and also has not approved the UR.
_snapshotClassicSwapCall(USDC, 100 * USDC_ONE, methodParameters, "testPermitAndExecute");
}

SignedOrder memory signedOrder =
SignedOrder(abi.encode(order), signOrder(swapper2PrivateKey, address(PERMIT2), order));

// build multicall data
bytes[] memory data = new bytes[](2);
data[0] =
abi.encodeWithSelector(reactor.permit.selector, address(USDC), swapper2, spender, amount, deadline, v, r, s);
data[1] = abi.encodeWithSignature("execute((bytes,bytes),address)", signedOrder, filler);

// TODO: This snapshot should always pull tokens in from permit2 and then expose an option to benchmark it with an an allowance on the UR vs. without.
// For this test, we should benchmark that the user has not permitted permit2, and also has not approved the UR.
_snapshotClassicSwapCall(USDC, 100 * USDC_ONE, methodParameters, "testPermitAndExecute");

_checkpointBalances(swapper2, filler, USDC, DAI, USDC);

vm.prank(filler);
Expand Down Expand Up @@ -337,6 +349,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS
Input memory input = InputBuilder.init(DAI).withAmount(100 * ONE).withRecipient(UNIVERSAL_ROUTER);
FeeEscalator memory fee =
FeeEscalatorBuilder.init(USDC).withStartAmount(10 * USDC_ONE).withEndAmount(10 * USDC_ONE);
Rebate memory rebate = RebateBuilder.init(USDC);

uint256 amountOutMin = 51651245170979377; // with 5% slipapge at forked block
MethodParameters memory methodParameters = readFixture(json, "._UNISWAP_V3_DAI_ETH");
Expand All @@ -345,7 +358,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS
block.timestamp + 100
).withNonce(0);
RelayOrder memory order =
RelayOrderBuilder.init(orderInfo, input, fee).withUniversalRouterCalldata(methodParameters.data);
RelayOrderBuilder.init(orderInfo, input, fee, rebate).withUniversalRouterCalldata(methodParameters.data);

SignedOrder memory signedOrder =
SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(PERMIT2), order));
Expand Down Expand Up @@ -383,6 +396,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS
Input memory input = InputBuilder.init(DAI).withAmount(100 * ONE).withRecipient(UNIVERSAL_ROUTER);
FeeEscalator memory fee =
FeeEscalatorBuilder.init(USDC).withStartAmount(10 * USDC_ONE).withEndAmount(10 * USDC_ONE);
Rebate memory rebate = RebateBuilder.init(USDC);

MethodParameters memory methodParameters =
readFixture(json, "._UNISWAP_V3_DAI_USDC_RECIPIENT_REACTOR_WITH_SWEEP");
Expand All @@ -392,7 +406,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS
).withNonce(0);

RelayOrder memory order =
RelayOrderBuilder.init(orderInfo, input, fee).withUniversalRouterCalldata(methodParameters.data);
RelayOrderBuilder.init(orderInfo, input, fee, rebate).withUniversalRouterCalldata(methodParameters.data);

SignedOrder memory signedOrder =
SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(PERMIT2), order));
Expand All @@ -418,6 +432,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS
Input memory input = InputBuilder.init(tokenIn).withAmount(100 * ONE).withRecipient(UNIVERSAL_ROUTER);
FeeEscalator memory fee =
FeeEscalatorBuilder.init(gasToken).withStartAmount(10 * USDC_ONE).withEndAmount(10 * USDC_ONE);
Rebate memory rebate = RebateBuilder.init(tokenOut);

uint256 amountOutMin = 95 * USDC_ONE;
MethodParameters memory methodParameters = readFixture(json, "._UNISWAP_V3_DAI_USDC");
Expand All @@ -427,7 +442,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS
).withNonce(1);

RelayOrder memory order =
RelayOrderBuilder.init(orderInfo, input, fee).withUniversalRouterCalldata(methodParameters.data);
RelayOrderBuilder.init(orderInfo, input, fee, rebate).withUniversalRouterCalldata(methodParameters.data);

SignedOrder memory signedOrder =
SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(PERMIT2), order));
Expand Down
4 changes: 2 additions & 2 deletions test/foundry-tests/lib/RelayOrderLib.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,14 @@ contract RelayOrderLibTest is Test, DeployPermit2, PermitSignature {
// Note: This doesn't check for 712 correctness, just accounts for accidental changes to the lib file
function test_Permit2WitnessStubTypestring_isCorrect() public {
bytes memory typestring =
"RelayOrder witness)FeeEscalator(address token,uint256 startAmount,uint256 endAmount,uint256 startTime,uint256 endTime)Input(address token,uint256 amount,address recipient)RelayOrder(RelayOrderInfo info,Input input,FeeEscalator fee,bytes universalRouterCalldata)RelayOrderInfo(address reactor,address swapper,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 amount)";
"RelayOrder witness)FeeEscalator(address token,uint256 startAmount,uint256 endAmount,uint256 startTime,uint256 endTime)Input(address token,uint256 amount,address recipient)Rebate(address token,uint256 minAmount,uint256 bpsPerGas)RelayOrder(RelayOrderInfo info,Input input,FeeEscalator fee,bytes universalRouterCalldata)RelayOrderInfo(address reactor,address swapper,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 amount)";
assertEq(string(typestring), RelayOrderLib.PERMIT2_ORDER_TYPE);
}

// Note: This doesn't check for 712 correctness, just accounts for accidental changes to the lib file
function test_RelayOrderTypestring_isCorrect() public {
bytes memory typestring =
"RelayOrder(RelayOrderInfo info,Input input,FeeEscalator fee,bytes universalRouterCalldata)FeeEscalator(address token,uint256 startAmount,uint256 endAmount,uint256 startTime,uint256 endTime)Input(address token,uint256 amount,address recipient)RelayOrderInfo(address reactor,address swapper,uint256 nonce,uint256 deadline)";
"RelayOrder(RelayOrderInfo info,Input input,FeeEscalator fee,Rebate rebate,bytes universalRouterCalldata)FeeEscalator(address token,uint256 startAmount,uint256 endAmount,uint256 startTime,uint256 endTime)Input(address token,uint256 amount,address recipient)Rebate(address token,uint256 minAmount,uint256 bpsPerGas)RelayOrderInfo(address reactor,address swapper,uint256 nonce,uint256 deadline)";
assertEq(typestring, RelayOrderLib.FULL_RELAY_ORDER_TYPESTRING);
assertEq(keccak256(typestring), RelayOrderLib.FULL_RELAY_ORDER_TYPEHASH);
}
Expand Down
Loading
Loading