Skip to content
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

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

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

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

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_pancakeInfinityVIP_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_pancakeInfinityVIP_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 .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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
* Fix a `metaTx` malleability bug in `CrossChainReceiverFactory`
(contract is not deployed; no funds at risk) after a report in
Immunefi bug 78645
* Add Orvex CL PancakeInfinity fork to RobinHood chain
* Add `PANCAKE_INFINITY`, `PANCAKE_INFINITY_VIP`, and `METATXN_PANCAKE_INFINITY_VIP`

## 2026-07-27

Expand Down
2 changes: 1 addition & 1 deletion src/chains/Base/Common.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {UniswapV4} from "../../core/UniswapV4.sol";
import {IPoolManager} from "../../core/UniswapV4Types.sol";
import {EulerSwap, IEVC, IEulerSwap} from "../../core/EulerSwap.sol";
import {BalancerV3} from "../../core/BalancerV3.sol";
import {PancakeInfinity} from "../../core/PancakeInfinity.sol";
import {PancakeInfinity} from "../../core/pancakeInfinityForks/PancakeInfinity.sol";
import {Renegade, BASE_SELECTOR} from "../../core/Renegade.sol";
import {Bebop} from "../../core/Bebop.sol";
import {Hanji} from "../../core/Hanji.sol";
Expand Down
2 changes: 1 addition & 1 deletion src/chains/Bnb/Common.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {DodoV1, IDodoV1} from "../../core/DodoV1.sol";
import {DodoV2, IDodoV2} from "../../core/DodoV2.sol";
import {UniswapV4} from "../../core/UniswapV4.sol";
import {IPoolManager} from "../../core/UniswapV4Types.sol";
import {PancakeInfinity} from "../../core/PancakeInfinity.sol";
import {PancakeInfinity} from "../../core/pancakeInfinityForks/PancakeInfinity.sol";
import {EulerSwap, IEVC, IEulerSwap} from "../../core/EulerSwap.sol";
import {Bebop} from "../../core/Bebop.sol";

Expand Down
10 changes: 7 additions & 3 deletions src/chains/RobinHood/Common.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,15 @@ import {swapHoodV3Factory, swapHoodV3InitHash, swapHoodV3ForkId} from "../../cor
import {gigaDexV3Factory, gigaDexV3InitHash, gigaDexV3ForkId} from "../../core/univ3forks/GigaDexV3.sol";
import {IAlgebraCallback} from "../../core/univ3forks/Algebra.sol";
import {ROBINHOOD_POOL_MANAGER} from "../../core/UniswapV4Addresses.sol";
import {OrvexCL} from "../../core/pancakeInfinityForks/OrvexCL.sol";

import {FastLogic} from "../../utils/FastLogic.sol";

// Solidity inheritance is stupid
import {SettlerSwapAbstract} from "../../SettlerAbstract.sol";
import {Permit2PaymentAbstract} from "../../core/Permit2PaymentAbstract.sol";

abstract contract RobinHoodMixin is FreeMemory, SettlerBase, UniswapV4, EkuboV3, Hanji {
abstract contract RobinHoodMixin is FreeMemory, SettlerBase, UniswapV4, EkuboV3, Hanji, OrvexCL {
using FastLogic for bool;

constructor() {
Expand All @@ -59,7 +60,8 @@ abstract contract RobinHoodMixin is FreeMemory, SettlerBase, UniswapV4, EkuboV3,
if (super._dispatch(i, action, data, slippage)) {
return true;
} else if ((action == uint32(ISettlerActions.UNISWAPV4.selector))
.or(action == uint32(ISettlerActions.EKUBOV3.selector))) {
.or(action == uint32(ISettlerActions.EKUBOV3.selector))
.or(action == uint32(ISettlerActions.PANCAKE_INFINITY.selector))) {
(
address recipient,
IERC20 sellToken,
Expand All @@ -73,8 +75,10 @@ abstract contract RobinHoodMixin is FreeMemory, SettlerBase, UniswapV4, EkuboV3,

if (action == uint32(ISettlerActions.UNISWAPV4.selector)) {
sellToUniswapV4(recipient, sellToken, bps, feeOnTransfer, hashMul, hashMod, fills, amountOutMin);
} else { // if (action == uint32(ISettlerActions.EKUBOV3.selector))
} else if (action == uint32(ISettlerActions.EKUBOV3.selector)) {
sellToEkuboV3(recipient, sellToken, bps, feeOnTransfer, hashMul, hashMod, fills, amountOutMin);
} else { // if (action == uint32(ISettlerActions.PANCAKE_INFINITY.selector))
sellToPancakeInfinity(recipient, sellToken, bps, feeOnTransfer, hashMul, hashMod, fills, amountOutMin);
}
} else if (action == uint32(ISettlerActions.HANJI.selector)) {
(
Expand Down
7 changes: 5 additions & 2 deletions src/chains/RobinHood/MetaTxn.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ contract RobinHoodSettlerMetaTxn is SettlerMetaTxn, RobinHoodMixin {
if (super._dispatchVIP(action, data, sig)) {
return true;
} else if ((action == uint32(ISettlerActions.METATXN_UNISWAPV4_VIP.selector))
.or(action == uint32(ISettlerActions.METATXN_EKUBOV3_VIP.selector))) {
.or(action == uint32(ISettlerActions.METATXN_EKUBOV3_VIP.selector))
.or(action == uint32(ISettlerActions.METATXN_PANCAKE_INFINITY_VIP.selector))) {
(
address recipient,
ISignatureTransfer.PermitTransferFrom memory permit,
Expand All @@ -47,8 +48,10 @@ contract RobinHoodSettlerMetaTxn is SettlerMetaTxn, RobinHoodMixin {

if (action == uint32(ISettlerActions.METATXN_UNISWAPV4_VIP.selector)) {
sellToUniswapV4VIP(recipient, feeOnTransfer, hashMul, hashMod, fills, permit, sig, amountOutMin);
} else { // if (action == uint32(ISettlerActions.METATXN_EKUBOV3_VIP.selector))
} else if (action == uint32(ISettlerActions.METATXN_EKUBOV3_VIP.selector)) {
sellToEkuboV3VIP(recipient, feeOnTransfer, hashMul, hashMod, fills, permit, sig, amountOutMin);
} else { // if (action == uint32(ISettlerActions.METATXN_PANCAKE_INFINITY_VIP.selector))
sellToPancakeInfinityVIP(recipient, feeOnTransfer, hashMul, hashMod, fills, permit, sig, amountOutMin);
}
} else {
return false;
Expand Down
7 changes: 5 additions & 2 deletions src/chains/RobinHood/TakerSubmitted.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ contract RobinHoodSettler is Settler, RobinHoodMixin {
if (super._dispatchVIP(action, data)) {
return true;
} else if ((action == uint32(ISettlerActions.UNISWAPV4_VIP.selector))
.or(action == uint32(ISettlerActions.EKUBOV3_VIP.selector))) {
.or(action == uint32(ISettlerActions.EKUBOV3_VIP.selector))
.or(action == uint32(ISettlerActions.PANCAKE_INFINITY_VIP.selector))) {
(
address recipient,
ISignatureTransfer.PermitTransferFrom memory permit,
Expand All @@ -41,8 +42,10 @@ contract RobinHoodSettler is Settler, RobinHoodMixin {

if (action == uint32(ISettlerActions.UNISWAPV4_VIP.selector)) {
sellToUniswapV4VIP(recipient, feeOnTransfer, hashMul, hashMod, fills, permit, sig, amountOutMin);
} else { // if (action == uint32(ISettlerActions.EKUBOV3_VIP.selector))
} else if (action == uint32(ISettlerActions.EKUBOV3_VIP.selector)) {
sellToEkuboV3VIP(recipient, feeOnTransfer, hashMul, hashMod, fills, permit, sig, amountOutMin);
} else { // if (action == uint32(ISettlerActions.PANCAKE_INFINITY_VIP.selector))
sellToPancakeInfinityVIP(recipient, feeOnTransfer, hashMul, hashMod, fills, permit, sig, amountOutMin);
}
} else {
return false;
Expand Down
100 changes: 62 additions & 38 deletions src/core/PancakeInfinity.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ interface IPancakeInfinityVault {
function lock(bytes calldata data) external returns (bytes memory);
}

IPancakeInfinityVault constant VAULT = IPancakeInfinityVault(0x238a358808379702088667322f80aC48bAd5e6c4);

/// @notice Interface for the callback executed when an address locks the vault
interface IPancakeInfinityLockCallback {
/// @notice Called by the pool manager on `msg.sender` when a lock is acquired
Expand Down Expand Up @@ -92,9 +90,6 @@ interface IPancakeInfinityCLPoolManager is IPancakeInfinityPoolManager {
returns (BalanceDelta delta);
}

IPancakeInfinityCLPoolManager constant CL_MANAGER =
IPancakeInfinityCLPoolManager(0xa0FfB9c1CE1Fe56963B0321B32E7A0302114058b);

interface IPancakeInfinityBinPoolManager is IPancakeInfinityPoolManager {
/// @notice Peform a swap to a pool
/// @param key The pool key
Expand Down Expand Up @@ -194,10 +189,7 @@ library UnsafePancakeInfinityBinPoolManager {
}
}

IPancakeInfinityBinPoolManager constant BIN_MANAGER =
IPancakeInfinityBinPoolManager(0xC697d2898e0D09264376196696c51D7aBbbAA4a9);

abstract contract PancakeInfinity is SettlerSwapAbstract {
abstract contract PancakeInfinityBase is SettlerSwapAbstract {
using UnsafeMath for uint256;
using UnsafeMath for int256;
using Ternary for bool;
Expand All @@ -214,6 +206,8 @@ abstract contract PancakeInfinity is SettlerSwapAbstract {
assert(address(ETH_ADDRESS) == NotesLib.ETH_ADDRESS);
}

function _PANCAKE_INFINITY_VAULT() internal pure virtual returns (address vault);

//// How to generate `fills` for Pancake Infinity:
////
//// Linearize your DAG of fills by doing a topological sort on the tokens involved. In the
Expand Down Expand Up @@ -279,7 +273,10 @@ abstract contract PancakeInfinity is SettlerSwapAbstract {
amountOutMin
);
bytes memory encodedBuyAmount = _setOperatorAndCall(
address(VAULT), data, uint32(IPancakeInfinityLockCallback.lockAcquired.selector), _pancakeInfinityCallback
_PANCAKE_INFINITY_VAULT(),
data,
uint32(IPancakeInfinityLockCallback.lockAcquired.selector),
_pancakeInfinityCallback
);
// buyAmount = abi.decode(abi.decode(encodedBuyAmount, (bytes)), (uint256));
assembly ("memory-safe") {
Expand Down Expand Up @@ -313,7 +310,10 @@ abstract contract PancakeInfinity is SettlerSwapAbstract {
amountOutMin
);
bytes memory encodedBuyAmount = _setOperatorAndCall(
address(VAULT), data, uint32(IPancakeInfinityLockCallback.lockAcquired.selector), _pancakeInfinityCallback
_PANCAKE_INFINITY_VAULT(),
data,
uint32(IPancakeInfinityLockCallback.lockAcquired.selector),
_pancakeInfinityCallback
);
// buyAmount = abi.decode(abi.decode(encodedBuyAmount, (bytes)), (uint256));
assembly ("memory-safe") {
Expand Down Expand Up @@ -506,33 +506,13 @@ abstract contract PancakeInfinity is SettlerSwapAbstract {

Decoder.overflowCheck(data);

if (uint256(poolManagerId) == 0) {
poolKey.poolManager = CL_MANAGER;

_pancakeInfinitySettleDelta(
state,
IPancakeInfinityCLPoolManager(address(poolKey.poolManager))
.unsafeSwap(poolKey, zeroForOne, amountSpecified, sqrtPriceLimitX96, hookData),
zeroForOne
);
} else if (uint256(poolManagerId) == 1) {
poolKey.poolManager = BIN_MANAGER;
if (amountSpecified >> 127 != amountSpecified >> 128) {
Panic.panic(Panic.ARITHMETIC_OVERFLOW);
}
_pancakeInfinitySettleDelta(
state,
IPancakeInfinityBinPoolManager(address(poolKey.poolManager))
.unsafeSwap(poolKey, zeroForOne, int128(amountSpecified), hookData),
zeroForOne
);
} else {
assembly ("memory-safe") {
mstore(0x00, 0x0a9a7da6) // selector for `UnknownPoolManagerId(uint8)`
mstore(0x20, and(0xff, poolManagerId))
revert(0x1c, 0x24)
}
}
_pancakeInfinitySettleDelta(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The risk with this pattern is that it invites more flexibility in the mixin than in required to get this feature functional. Instead of allowing the mixin arbitrary flexibility in _pancakeInfinitySettleDelta to call back into the base's swapToClManager and swapToBinManager (inversion of control) having a virtual pure function that simply returns the address of the CL manager or Bin manager (or revert) removes the footgun and (potentially, should be measured) improves solc's ability to optimize.

I would expect that that change would also improve the amount of boilerplate required, potentially allowing us to move the vault/manager address selection into the chain-specific mixin. Of course, that totally breaks everything if there are 2 PancakeInfinity forks on the same chain

state,
_dispatchPancakeInfinity(
poolManagerId, poolKey, zeroForOne, amountSpecified, sqrtPriceLimitX96, hookData
),
zeroForOne
);
}
}

Expand Down Expand Up @@ -593,4 +573,48 @@ abstract contract PancakeInfinity is SettlerSwapAbstract {
return returndata;
}
}

// Each fork mixin in `pancakeInfinityForks/` defines its binding of pool manager IDs to pool
// managers by overriding this function with one that routes each recognized ID to
// `swapToClManager` or `swapToBinManager` with the appropriate pool manager's address, and
// calls `revertUnknownPoolManagerId` otherwise. A fork that lacks one of the pool manager
// types simply omits that route, which also omits the corresponding swap code from the
// compiled contract.
function _dispatchPancakeInfinity(
uint8 poolManagerId,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When possible, types should be widened to a full word to avoid solc's wasteful cleaning of short types

PoolKey memory poolKey,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
bytes calldata hookData
) internal virtual returns (BalanceDelta);

function swapToClManager(
address clManager,
PoolKey memory poolKey,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
bytes calldata hookData
) internal returns (BalanceDelta) {
poolKey.poolManager = IPancakeInfinityPoolManager(clManager);
return IPancakeInfinityCLPoolManager(clManager)
.unsafeSwap(poolKey, zeroForOne, amountSpecified, sqrtPriceLimitX96, hookData);
}

function swapToBinManager(
address binManager,
PoolKey memory poolKey,
bool zeroForOne,
int256 amountSpecified,
bytes calldata hookData
) internal returns (BalanceDelta) {
poolKey.poolManager = IPancakeInfinityPoolManager(binManager);
if (amountSpecified >> 127 != amountSpecified >> 128) {
Panic.panic(Panic.ARITHMETIC_OVERFLOW);
}
return
IPancakeInfinityBinPoolManager(binManager)
.unsafeSwap(poolKey, zeroForOne, int128(amountSpecified), hookData);
}
}
8 changes: 8 additions & 0 deletions src/core/SettlerErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ error NotConverged();
/// the list of recognized pool managers.
error UnknownPoolManagerId(uint8 poolManagerId);

function revertUnknownPoolManagerId(uint8 poolManagerId) pure {
assembly ("memory-safe") {
mstore(0x00, 0x0a9a7da6) // selector for `UnknownPoolManagerId(uint8)`
mstore(0x20, and(0xff, poolManagerId))
revert(0x1c, 0x24)
}
}

/// @notice Thrown when the `msg.value` is less than the minimum expected value.
error Underpayment(uint256 msgValueMin, uint256 msgValueActual);

Expand Down
30 changes: 30 additions & 0 deletions src/core/pancakeInfinityForks/OrvexCL.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

import {PancakeInfinityBase, PoolKey} from "../PancakeInfinity.sol";
import {BalanceDelta} from "../UniswapV4Types.sol";
import {revertUnknownPoolManagerId} from "../SettlerErrors.sol";

address constant orvexVault = 0xFe7E25dE55e5cBbEcCcb661F3679F873f72B9b0D;
address constant orvexClManager = 0xd01C774d4A66408326Bc65728Ac5Ae5aAf004032;

abstract contract OrvexCL is PancakeInfinityBase {
function _PANCAKE_INFINITY_VAULT() internal pure override returns (address) {
return orvexVault;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should check if solc is smart enough to optimize this constant or whether it needs this to be a literal in order to optimize.

}

function _dispatchPancakeInfinity(
uint8 poolManagerId,
PoolKey memory poolKey,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
bytes calldata hookData
) internal override returns (BalanceDelta) {
if (poolManagerId == 0) {
return swapToClManager(orvexClManager, poolKey, zeroForOne, amountSpecified, sqrtPriceLimitX96, hookData);
} else {
revertUnknownPoolManagerId(poolManagerId);
}
}
}
35 changes: 35 additions & 0 deletions src/core/pancakeInfinityForks/PancakeInfinity.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

import {PancakeInfinityBase, PoolKey} from "../PancakeInfinity.sol";
import {BalanceDelta} from "../UniswapV4Types.sol";
import {revertUnknownPoolManagerId} from "../SettlerErrors.sol";

address constant pancakeInfinityVault = 0x238a358808379702088667322f80aC48bAd5e6c4;
address constant pancakeInfinityClManager = 0xa0FfB9c1CE1Fe56963B0321B32E7A0302114058b;
address constant pancakeInfinityBinManager = 0xC697d2898e0D09264376196696c51D7aBbbAA4a9;

abstract contract PancakeInfinity is PancakeInfinityBase {
function _PANCAKE_INFINITY_VAULT() internal pure override returns (address) {
return pancakeInfinityVault;
}

function _dispatchPancakeInfinity(
uint8 poolManagerId,
PoolKey memory poolKey,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
bytes calldata hookData
) internal override returns (BalanceDelta) {
if (poolManagerId == 0) {
return swapToClManager(
pancakeInfinityClManager, poolKey, zeroForOne, amountSpecified, sqrtPriceLimitX96, hookData
);
} else if (poolManagerId == 1) {
return swapToBinManager(pancakeInfinityBinManager, poolKey, zeroForOne, amountSpecified, hookData);
} else {
revertUnknownPoolManagerId(poolManagerId);
}
}
}
Loading