Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .forge-snapshots/settler_balancerV3VIP_USDC-USDT.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .forge-snapshots/settler_balancerV3_USDC-USDT.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .forge-snapshots/settler_ekuboExtension_USDT-WETH.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .forge-snapshots/settler_ekuboV2Extension_USDC-WETH.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .forge-snapshots/settler_ekuboV2_USDC-USDT.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .forge-snapshots/settler_ekuboV2_USDC-WETH.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .forge-snapshots/settler_ekubo_USDC-USDT.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .forge-snapshots/settler_ekubo_USDC-WETH.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .forge-snapshots/settler_metaTxn_balancerV3_USDC-USDT.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .forge-snapshots/settler_pancakeInfinity_USDT-CAKE.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .forge-snapshots/settler_pancakeInfinity_USDT-WBNB.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ See `.github/workflows/test.yml` and `.github/workflows/integration.yml` for the
## Key Constants

```solidity
uint256 internal constant BASIS = 10_000; // BPS denominator
uint256 internal constant BASIS = 1_000_000; // ppm (parts-per-million) proportion denominator
IERC20 internal constant ETH_ADDRESS = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
```

Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

### Breaking changes

* All proportional-amount action arguments are now denominated in parts-per-million (`ppm`; denominator 1_000_000) instead of basis points (`bps`; denominator 10_000)
* Affects the `bps` (now `ppm`) argument of `UNISWAPV3`, `UNISWAPV2`, `BASIC`, `VELODROME`, `MAKERPSM`, `DODOV1`, `DODOV2`, `MAVERICKV2`, `EULERSWAP`, `HANJI`, `UNISWAPV4`, `BALANCERV3`, `PANCAKE_INFINITY`, `EKUBO`, and `EKUBOV3` actions.
* Affects the `maxBps` (now `maxPpm`) argument of `POSITIVE_SLIPPAGE`
* Action selectors are unchanged (the arguments are `uint256` at the ABI level); only the interpretation of the value changes
* In the packed `fills` encoding of `UNISWAPV4`, `BALANCERV3`, `PANCAKE_INFINITY`, `EKUBO`, and `EKUBOV3`, the leading proportion field of each fill (and the corresponding field of the non-VIP header) widens from 2 bytes to 3 bytes
* `BALANCERV3` wrap/unwrap flags move from bits 15/14 to bits 23/22 of the fill's proportion field
* The Ekubo forwarding-extension flag moves from bit 15 to bit 23
* The Permit2 balance-proportional sell amount sentinel widens: `permit.permitted.amount > type(uint256).max - 1_000_000` now encodes a proportion in `ppm` as `type(uint256).max - (1_000_000 - ppm)`
* SolidlyV3 UniV3 fork removed from Sonic

### Non-breaking changes
Expand Down
2 changes: 1 addition & 1 deletion sh/initial_description_crosschain_intent.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ This provides enhanced security at the cost of some gas and inconvenience.
encoded in calldata. Actions are ABIEncoded with a selector. See
`ICrossChainIntentSettlerActions` for the available options. Typically, actions
are parametrized by the `recipient` where tokens are sent after the trade and
the `bps` specifying a proportion of the `Settler` contract's token balance to
the `ppm` specifying a proportion of the `Settler` contract's token balance to
be liquidated in the action.

## Action encoding
Expand Down
2 changes: 1 addition & 1 deletion sh/initial_description_intent.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ This provides enhanced security at the cost of some gas and inconvenience.
0x Settler settles swaps by performing a sequence of actions encoded in
calldata. Actions are ABIEncoded with a selector. See `ISettlerActions` for the
available options. Typically, actions are parametrized by the `recipient` where
tokens are sent after the trade and the `bps` specifying a proportion of the
tokens are sent after the trade and the `ppm` specifying a proportion of the
`Settler` contract's token balance to be liquidated in the action.

## Action encoding
Expand Down
2 changes: 1 addition & 1 deletion sh/initial_description_metatx.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ This provides enhanced security at the cost of some gas and inconvenience.
0x Settler settles swaps by performing a sequence of actions encoded in
calldata. Actions are ABIEncoded with a selector. See `ISettlerActions` for the
available options. Typically, actions are parametrized by the `recipient` where
tokens are sent after the trade and the `bps` specifying a proportion of the
tokens are sent after the trade and the `ppm` specifying a proportion of the
`Settler` contract's token balance to be liquidated in the action.

## Action encoding
Expand Down
2 changes: 1 addition & 1 deletion sh/initial_description_taker.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ allowance target, `AllowanceHolder` is provided (see above).
0x Settler settles swaps by performing a sequence of actions encoded in
calldata. Actions are ABIEncoded with a selector. See `ISettlerActions` for the
available options. Typically, actions are parametrized by the `recipient` where
tokens are sent after the trade and the `bps` specifying a proportion of the
tokens are sent after the trade and the `ppm` specifying a proportion of the
`Settler` contract's token balance to be liquidated in the action.

## Action encoding
Expand Down
32 changes: 16 additions & 16 deletions src/ISettlerActions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ interface ISettlerActions {
function UNISWAPV4(
address recipient,
address sellToken,
uint256 bps,
uint256 ppm,
bool feeOnTransfer,
uint256 hashMul,
uint256 hashMod,
Expand Down Expand Up @@ -84,7 +84,7 @@ interface ISettlerActions {
function BALANCERV3(
address recipient,
address sellToken,
uint256 bps,
uint256 ppm,
bool feeOnTransfer,
uint256 hashMul,
uint256 hashMod,
Expand Down Expand Up @@ -114,7 +114,7 @@ interface ISettlerActions {
function PANCAKE_INFINITY(
address recipient,
address sellToken,
uint256 bps,
uint256 ppm,
bool feeOnTransfer,
uint256 hashMul,
uint256 hashMod,
Expand Down Expand Up @@ -144,7 +144,7 @@ interface ISettlerActions {
/// @dev Trades against UniswapV3 using the contracts balance for funding
// Pre-req: Funded
// Post-req: Payout
function UNISWAPV3(address recipient, uint256 bps, bytes memory path, uint256 amountOutMin) external;
function UNISWAPV3(address recipient, uint256 ppm, bytes memory path, uint256 amountOutMin) external;
/// @dev Trades against UniswapV3 using user funds via Permit2 for funding
function UNISWAPV3_VIP(
address recipient,
Expand All @@ -161,7 +161,7 @@ interface ISettlerActions {
uint256 amountOutMin
) external;

function MAKERPSM(address recipient, uint256 bps, bool buyGem, uint256 amountOutMin, address psm, address dai)
function MAKERPSM(address recipient, uint256 ppm, bool buyGem, uint256 amountOutMin, address psm, address dai)
external;

function CURVE_TRICRYPTO_VIP(
Expand All @@ -178,25 +178,25 @@ interface ISettlerActions {
uint256 minBuyAmount
) external;

function DODOV1(address sellToken, uint256 bps, address pool, bool quoteForBase, uint256 minBuyAmount) external;
function DODOV1(address sellToken, uint256 ppm, address pool, bool quoteForBase, uint256 minBuyAmount) external;
function DODOV2(
address recipient,
address sellToken,
uint256 bps,
uint256 ppm,
address pool,
bool quoteForBase,
uint256 minBuyAmount
) external;

function VELODROME(address recipient, uint256 bps, address pool, uint24 swapInfo, uint256 minBuyAmount) external;
function VELODROME(address recipient, uint256 ppm, address pool, uint24 swapInfo, uint256 minBuyAmount) external;

/// @dev Trades against MaverickV2 using the contracts balance for funding
/// This action does not use the MaverickV2 callback, so it takes an arbitrary pool address to make calls against.
/// Passing `tokenAIn` as a parameter actually saves gas relative to introspecting the pool's `tokenA()` accessor.
function MAVERICKV2(
address recipient,
address sellToken,
uint256 bps,
uint256 ppm,
address pool,
bool tokenAIn,
int32 tickLimit,
Expand All @@ -210,24 +210,24 @@ interface ISettlerActions {
function UNISWAPV2(
address recipient,
address sellToken,
uint256 bps,
uint256 ppm,
address pool,
uint24 swapInfo,
uint256 amountOutMin
) external;

function POSITIVE_SLIPPAGE(address payable recipient, address token, uint256 expectedAmount, uint256 maxBps)
function POSITIVE_SLIPPAGE(address payable recipient, address token, uint256 expectedAmount, uint256 maxPpm)
external;

/// @dev Trades against a basic AMM which follows the approval, transferFrom(msg.sender) interaction
// Pre-req: Funded
// Post-req: Payout
function BASIC(address sellToken, uint256 bps, address pool, uint256 offset, bytes calldata data) external;
function BASIC(address sellToken, uint256 ppm, address pool, uint256 offset, bytes calldata data) external;

function EKUBO(
address recipient,
address sellToken,
uint256 bps,
uint256 ppm,
bool feeOnTransfer,
uint256 hashMul,
uint256 hashMod,
Expand All @@ -238,7 +238,7 @@ interface ISettlerActions {
function EKUBOV3(
address recipient,
address sellToken,
uint256 bps,
uint256 ppm,
bool feeOnTransfer,
uint256 hashMul,
uint256 hashMod,
Expand Down Expand Up @@ -270,7 +270,7 @@ interface ISettlerActions {
function EULERSWAP(
address recipient,
address sellToken,
uint256 bps,
uint256 ppm,
address pool,
bool zeroForOne,
uint256 amountOutMin
Expand Down Expand Up @@ -311,7 +311,7 @@ interface ISettlerActions {

function HANJI(
address sellToken,
uint256 bps,
uint256 ppm,
address pool,
uint256 sellScalingFactor,
uint256 buyScalingFactor,
Expand Down
2 changes: 1 addition & 1 deletion src/SettlerAbstract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ abstract contract SettlerAbstract is Permit2PaymentAbstract {
string internal constant SLIPPAGE_TYPE = "Slippage(address recipient,address buyToken,uint256 minAmountOut)";
bytes32 internal constant SLIPPAGE_TYPEHASH = 0xdc83993a2ffc65b01b71ed08790b6e39c5c55d76937b62a3b5085b02071f1259;

uint256 internal constant BASIS = 10_000;
uint256 internal constant BASIS = 1_000_000;
IERC20 internal constant ETH_ADDRESS = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);

constructor() {
Expand Down
20 changes: 10 additions & 10 deletions src/SettlerBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -136,34 +136,34 @@ abstract contract SettlerBase is ISettlerBase, Basic, RfqOrderSettlement, Uniswa

fillRfqOrderSelfFunded(recipient, permit, maker, makerSig, takerToken, maxTakerAmount);
} else if (action == uint32(ISettlerActions.UNISWAPV3.selector)) {
(address recipient, uint256 bps, bytes memory path, uint256 amountOutMin) =
(address recipient, uint256 ppm, bytes memory path, uint256 amountOutMin) =
abi.decode(data, (address, uint256, bytes, uint256));

sellToUniswapV3(recipient, bps, path, amountOutMin);
sellToUniswapV3(recipient, ppm, path, amountOutMin);
} else if (action == uint32(ISettlerActions.UNISWAPV2.selector)) {
(address recipient, address sellToken, uint256 bps, address pool, uint24 swapInfo, uint256 amountOutMin) =
(address recipient, address sellToken, uint256 ppm, address pool, uint24 swapInfo, uint256 amountOutMin) =
abi.decode(data, (address, address, uint256, address, uint24, uint256));

sellToUniswapV2(recipient, sellToken, bps, pool, swapInfo, amountOutMin);
sellToUniswapV2(recipient, sellToken, ppm, pool, swapInfo, amountOutMin);
} else if (action == uint32(ISettlerActions.BASIC.selector)) {
(IERC20 sellToken, uint256 bps, address pool, uint256 offset, bytes memory _data) =
(IERC20 sellToken, uint256 ppm, address pool, uint256 offset, bytes memory _data) =
abi.decode(data, (IERC20, uint256, address, uint256, bytes));

basicSellToPool(sellToken, bps, pool, offset, _data);
basicSellToPool(sellToken, ppm, pool, offset, _data);
} else if (action == uint32(ISettlerActions.VELODROME.selector)) {
(address recipient, uint256 bps, IVelodromePair pool, uint24 swapInfo, uint256 minAmountOut) =
(address recipient, uint256 ppm, IVelodromePair pool, uint24 swapInfo, uint256 minAmountOut) =
abi.decode(data, (address, uint256, IVelodromePair, uint24, uint256));

sellToVelodrome(recipient, bps, pool, swapInfo, minAmountOut);
sellToVelodrome(recipient, ppm, pool, swapInfo, minAmountOut);
} else if (action == uint32(ISettlerActions.POSITIVE_SLIPPAGE.selector)) {
(address payable recipient, IERC20 token, uint256 expectedAmount, uint256 maxBps) =
(address payable recipient, IERC20 token, uint256 expectedAmount, uint256 maxPpm) =
abi.decode(data, (address, IERC20, uint256, uint256));
bool isETH = (token == ETH_ADDRESS);
uint256 balance = isETH ? address(this).balance : token.fastBalanceOf(address(this));
if (balance > expectedAmount) {
uint256 cap;
unchecked {
cap = balance * maxBps / BASIS;
cap = balance * maxPpm / BASIS;
balance -= expectedAmount;
}
balance = (balance > cap).ternary(cap, balance);
Expand Down
4 changes: 2 additions & 2 deletions src/bridge/BridgeSettlerBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@ abstract contract BridgeSettlerBase is SettlerBridgeAbstract, Basic, Relay, Laye
ALLOWANCE_HOLDER.exec(settler, token, amount, payable(settler), settlerData);
}
} else if (action == uint32(IBridgeSettlerActions.BASIC.selector)) {
(address bridgeToken, uint256 bps, address pool, uint256 offset, bytes memory bridgeData) =
(address bridgeToken, uint256 ppm, address pool, uint256 offset, bytes memory bridgeData) =
abi.decode(data, (address, uint256, address, uint256, bytes));

basicSellToPool(IERC20(bridgeToken), bps, pool, offset, bridgeData);
basicSellToPool(IERC20(bridgeToken), ppm, pool, offset, bridgeData);
} else if (action == uint32(IBridgeSettlerActions.BRIDGE_ERC20_TO_RELAY.selector)) {
(address token, address to, bytes32 requestId) = abi.decode(data, (address, address, bytes32));
bridgeERC20ToRelay(IERC20(token), to, requestId);
Expand Down
2 changes: 1 addition & 1 deletion src/bridge/IBridgeSettlerActions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface IBridgeSettlerActions {

/// @dev Bridge through a Bridge that follows the approval, transferFrom(msg.sender) interaction
/// Pre-req: Funded
function BASIC(address bridgeToken, uint256 bps, address pool, uint256 offset, bytes calldata data) external;
function BASIC(address bridgeToken, uint256 ppm, address pool, uint256 offset, bytes calldata data) external;

/// @dev Bridge ERC20 tokens through Relay
function BRIDGE_ERC20_TO_RELAY(address token, address to, bytes32 requestId) external;
Expand Down
20 changes: 10 additions & 10 deletions src/chains/Arbitrum/Common.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,40 +77,40 @@ abstract contract ArbitrumMixin is
(
address recipient,
IERC20 sellToken,
uint256 bps,
uint256 ppm,
bool feeOnTransfer,
uint256 hashMul,
uint256 hashMod,
bytes memory fills,
uint256 amountOutMin
) = abi.decode(data, (address, IERC20, uint256, bool, uint256, uint256, bytes, uint256));

sellToUniswapV4(recipient, sellToken, bps, feeOnTransfer, hashMul, hashMod, fills, amountOutMin);
sellToUniswapV4(recipient, sellToken, ppm, feeOnTransfer, hashMul, hashMod, fills, amountOutMin);
} else if (action == uint32(ISettlerActions.BALANCERV3.selector)) {
(
address recipient,
IERC20 sellToken,
uint256 bps,
uint256 ppm,
bool feeOnTransfer,
uint256 hashMul,
uint256 hashMod,
bytes memory fills,
uint256 amountOutMin
) = abi.decode(data, (address, IERC20, uint256, bool, uint256, uint256, bytes, uint256));

sellToBalancerV3(recipient, sellToken, bps, feeOnTransfer, hashMul, hashMod, fills, amountOutMin);
sellToBalancerV3(recipient, sellToken, ppm, feeOnTransfer, hashMul, hashMod, fills, amountOutMin);
} else if (action == uint32(ISettlerActions.MAVERICKV2.selector)) {
(
address recipient,
IERC20 sellToken,
uint256 bps,
uint256 ppm,
IMaverickV2Pool pool,
bool tokenAIn,
int32 tickLimit,
uint256 minBuyAmount
) = abi.decode(data, (address, IERC20, uint256, IMaverickV2Pool, bool, int32, uint256));

sellToMaverickV2(recipient, sellToken, bps, pool, tokenAIn, tickLimit, minBuyAmount);
sellToMaverickV2(recipient, sellToken, ppm, pool, tokenAIn, tickLimit, minBuyAmount);
} else if (action == uint32(ISettlerActions.BEBOP.selector)) {
(
address recipient,
Expand All @@ -124,15 +124,15 @@ abstract contract ArbitrumMixin is

sellToBebop(payable(recipient), sellToken, order, makerSignature, amountOutMin);
} else if (action == uint32(ISettlerActions.DODOV2.selector)) {
(address recipient, IERC20 sellToken, uint256 bps, IDodoV2 dodo, bool quoteForBase, uint256 minBuyAmount) =
(address recipient, IERC20 sellToken, uint256 ppm, IDodoV2 dodo, bool quoteForBase, uint256 minBuyAmount) =
abi.decode(data, (address, IERC20, uint256, IDodoV2, bool, uint256));

sellToDodoV2(recipient, sellToken, bps, dodo, quoteForBase, minBuyAmount);
sellToDodoV2(recipient, sellToken, ppm, dodo, quoteForBase, minBuyAmount);
} else if (action == uint32(ISettlerActions.DODOV1.selector)) {
(IERC20 sellToken, uint256 bps, IDodoV1 dodo, bool quoteForBase, uint256 minBuyAmount) =
(IERC20 sellToken, uint256 ppm, IDodoV1 dodo, bool quoteForBase, uint256 minBuyAmount) =
abi.decode(data, (IERC20, uint256, IDodoV1, bool, uint256));

sellToDodoV1(sellToken, bps, dodo, quoteForBase, minBuyAmount);
sellToDodoV1(sellToken, ppm, dodo, quoteForBase, minBuyAmount);
} else if (action == uint32(ISettlerActions.RENEGADE.selector)) {
(address target, IERC20 sellToken, bool baseForQuote, bytes memory renegadeData, uint256 minBuyAmount) =
abi.decode(data, (address, IERC20, bool, bytes, uint256));
Expand Down
Loading