Skip to content

Renegade v2#575

Open
jparklev wants to merge 10 commits into
masterfrom
jparklev/renegade-v2
Open

Renegade v2#575
jparklev wants to merge 10 commits into
masterfrom
jparklev/renegade-v2

Conversation

@jparklev

@jparklev jparklev commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Picked up from #567

Context from 567:
Renegade v2 is deployed to Base Mainnet and Arbitrum One. On both deployments, the function signature for settlement is the same: sponsorExternalMatch(uint256,address,(address,address,(uint256),uint256,uint256,uint256),(bool,uint8,bytes),(address,bool,uint256,uint256,bytes))

"The adapter contract exposes sellToRenegade() with the following signature. Unlike the v1 adapter contract, there is no bool baseForQuote parameter.

> function sellToRenegade(
    address target,
    IERC20 sellToken,
    bytes memory data,
    uint256 minBuyAmt
) internal returns (uint256 buyAmt)

@jparklev jparklev changed the title Renegade v2 [WIP] Renegade v2 Jun 1, 2026
@jparklev jparklev mentioned this pull request Jun 1, 2026

@duncancmt duncancmt left a comment

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.

General style in this repo is that constant arguments go on the left of commutative operations in assembly. E.g. add(0x40, data) ⭕ -- add(data, 0x40)

If this isn't already in AGENTS.md, it should be

Comment thread src/core/Renegade.sol Outdated
Comment thread src/core/Renegade.sol Outdated
assembly ("memory-safe") {
recipient := mload(add(data, 0x40))
}
require((recipient == address(0)) || (recipient == address(this)), "Renegade: bad recipient");

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.

What does address(0) indicate?

Comment thread src/core/Renegade.sol Outdated
Comment on lines +94 to +96
// newBuyAmt = floor(newSellAmt / price), matching
// FixedPointLib.divIntegerByFixedPoint.
uint256 newBuyAmt = (newSellAmt << 63) / priceRepr;

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.

If we need to handle overflow, left shift should be avoided. If we don't need to handle overflow / should become unsafeDiv

Comment thread src/core/Renegade.sol Outdated
Comment on lines +100 to +101
require(newBuyAmt >= minInternalPartyAmountIn, "Renegade: newBuyAmt < minInternalPartyAmountIn");
require(newBuyAmt <= maxInternalPartyAmountIn, "Renegade: newBuyAmt > maxInternalPartyAmountIn");

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.

Presumably Renegade checks this internally? If it doesn't, please replace this with the pattern:

if (!condition) {
    revert MyCustomErrorSelector(...);
}

or, even better, throw the error in assembly. This is for contract size. If this isn't already documented as a stylistic dictum in AGENTS.md, please add it.

Comment thread src/core/Renegade.sol Outdated
Comment on lines +105 to +107
// Snapshot the buyToken balance before the call so we can measure the
// actual amount transferred after.
uint256 buyTokenBalanceBefore = buyToken.fastBalanceOf(address(this));

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.

This is an antipattern for slippage checking https://kebabsec.xyz/posts/critical_vulnerability_in_uniswapx/

Comment thread src/core/Renegade.sol Outdated
Comment on lines +145 to +147
// Check the balance delta.
buyAmt = buyToken.fastBalanceOf(address(this)) - buyTokenBalanceBefore;
if (buyAmt < minBuyAmt) revertTooMuchSlippage(buyToken, minBuyAmt, buyAmt);

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.

Shouldn't this be handled by _checkSlippageAndTransfer? Also is custody optimization not possible with RenegadeV2?

Comment thread src/core/Renegade.sol Outdated
* 0x140 uint256 <offset> GasSponsorOptions: (address refundAddress, bool refundNativeEth, uint256 refundAmount, uint256 nonce, bytes signature)
* )
*/
uint32 internal constant RENEGADE_SELECTOR = uint32(

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.

If you define this constant as a literal, then you'll be able to access it in assembly. Even though the current expression is compile-time constant, because it isn't literal, it can't be accessed from Yul. Also should probably be private

@jparklev jparklev changed the title [WIP] Renegade v2 Renegade v2 Jun 17, 2026
@jparklev jparklev marked this pull request as ready for review June 17, 2026 22:07
@immunefi-magnus

Copy link
Copy Markdown

🛡️ Immunefi PR Reviews

We noticed that your project isn't set up for automatic code reviews. If you'd like this PR reviewed by the Immunefi team, you can request it manually using the link below:

🔗 Send this PR in for review

Once submitted, we'll take care of assigning a reviewer and follow up here.

Comment thread src/chains/Base/Common.sol Outdated
Comment on lines +108 to +122
} else { // if (action == uint32(ISettlerActions.PANCAKE_INFINITY.selector))
} else {
// if (action == uint32(ISettlerActions.PANCAKE_INFINITY.selector))
sellToPancakeInfinity(recipient, sellToken, bps, feeOnTransfer, hashMul, hashMod, fills, amountOutMin);
}
/*
} else if (action == uint32(ISettlerActions.EULERSWAP.selector)) {
(address recipient, IERC20 sellToken, uint256 bps, IEulerSwap pool, bool zeroForOne, uint256 amountOutMin) =
abi.decode(data, (address, IERC20, uint256, IEulerSwap, bool, uint256));
/*
} else if (action == uint32(ISettlerActions.EULERSWAP.selector)) {
(address recipient, IERC20 sellToken, uint256 bps, IEulerSwap pool, bool zeroForOne, uint256 amountOutMin) =
abi.decode(data, (address, IERC20, uint256, IEulerSwap, bool, uint256));

sellToEulerSwap(recipient, sellToken, bps, pool, zeroForOne, amountOutMin);
*/
sellToEulerSwap(recipient, sellToken, bps, pool, zeroForOne, amountOutMin);
*/

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.

please fix bad formatting

Comment thread src/core/Renegade.sol Outdated
Comment on lines +54 to +56
if ((recipient != address(0)) && (recipient != address(this))) revertInvalidRenegadeData();
if (sellToken != internalPartyOutputToken) revertInvalidRenegadeData();
if (priceRepr == 0) revertInvalidRenegadeData();

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.

Join these together with FastLogic.or into a single branch to save bytecode size.

Comment thread src/core/Renegade.sol Outdated
Comment on lines +65 to +66
if (newBuyAmt < minInternalPartyAmountIn) revertInvalidRenegadeData();
if (newBuyAmt > maxInternalPartyAmountIn) revertInvalidRenegadeData();

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.

combine these with the above checks as well, presuming that these checks are meaningful and not redundant, which I suspect they are

Comment thread src/core/Renegade.sol Outdated
revertTooMuchSlippage(_extractBuyToken(data, baseForQuote), minBuyAmount, buyAmount);
}
address target = _renegadeGasSponsorV2();
uint256 buyTokenBalanceBefore = buyToken.fastBalanceOf(address(this));

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.

This is still the forbidden way of checking slippage. Please fix.

Comment thread src/core/Renegade.sol
let len := mload(data)
// temporarily clobber `data` size memory area
mstore(data, selector)
// Allowed selectors don't clash with any relevant function of restricted targets so we can skip checking `target`

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.

Please restore this comment

Comment thread src/core/Renegade.sol Outdated
}

buyAmt = buyToken.fastBalanceOf(address(this)) - buyTokenBalanceBefore;
if (buyAmt < minBuyAmt) revertTooMuchSlippage(buyToken, minBuyAmt, buyAmt);

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.

Forbidden

Comment thread src/ISettlerActions.sol
/// VIP actions should always start with `recipient` address and the `permit` from the taker
/// followed by all the other parameters to ensure compatibility with `executeWithPermit` entrypoint.
/// `minBuyAmount`/`amountOutMin` should always be the last parameter.

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.

Please fix bad formatting

@duncancmt duncancmt left a comment

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.

This is looking pretty good. I think I still don't all the way understand how we're expecting Renegade to interact with slippage checking and custody optimization. The previous iteration seemed like it was amenable to custody optimization (and consequently slippage checking), but the current iteration removes that functionality entirely. Is this because we don't know whether we'll be applying that optimization at the time we request the firm quote from Renegade and the recipient is cryptographically bound to the price?

Even if custody optimization isn't possible, it's still desirable to be able to slippage-check the Renegade leg. Per-leg slippage checking is an infrequent, but still-used idiom.

Lastly, if we're going to be destructuring/interpreting many of the fields of the arguments to sponsorExternalMatch, it would make sense for those fields to be arguments of the action rather than taking it as opaque bytes and implicitly extracting them. It's less error-prone and (sometimes) more gas efficient. If we're not reading any of the BoundedMatchResult fields, then that can still be opaque.

Feel free to DM me if you want to iterate faster.

Comment thread src/core/Renegade.sol Outdated
// FixedPointLib.divIntegerByFixedPoint.
uint256 newBuyAmt = (newSellAmt << 63).unsafeDiv(priceRepr);

if (newBuyAmt < minBuyAmt) revertTooMuchSlippage(buyToken, minBuyAmt, newBuyAmt);

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.

Why was this removed? Does Renegade not support custody optimization?

Comment thread src/core/Renegade.sol Outdated
Comment on lines -65 to -66
if (newBuyAmt < minInternalPartyAmountIn) revertInvalidRenegadeData();
if (newBuyAmt > maxInternalPartyAmountIn) revertInvalidRenegadeData();

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.

just an idle thought here: should we clamp? if there's a maximum notional, if the swap encounters slippage, we should clamp to avoid a revert, no?

Comment thread src/core/Renegade.sol
// `data` is the args to `sponsorExternalMatch` minus the 4-byte selector; its payload begins at
// `data + 0x20`. The head is static (`BoundedMatchResult` is inlined), so we index fixed offsets:
// 0x20 externalPartyAmountIn, 0x40 recipient, 0x80 internalPartyOutputToken.
uint32 private constant RENEGADE_SELECTOR = 0x54ea46d4;

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.

it doesn't necessarily need to be here, but somewhere there should be a more readable signature from which this selector was computed

Comment thread src/core/Renegade.sol Outdated
}

buyAmt = buyToken.fastBalanceOf(address(this)) - buyTokenBalanceBefore;
if (buyAmt < minBuyAmt) revertTooMuchSlippage(buyToken, minBuyAmt, buyAmt);

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.

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants