Skip to content

[Contracts] Redemption helper#1164

Merged
danielattilasimon merged 12 commits intomainfrom
redemption-helper
Oct 27, 2025
Merged

[Contracts] Redemption helper#1164
danielattilasimon merged 12 commits intomainfrom
redemption-helper

Conversation

@danielattilasimon
Copy link
Collaborator

@danielattilasimon danielattilasimon commented Oct 22, 2025

Adds a helper contract that will be used by the frontend to implement #1065.

Features added

Slippage protection

Normally, a redemption is performed by a bot in a closed arbitrage transaction, usually through a private mempool and with the help of a flashloan. Thus, if there's slippage of the redemption fee, redemption proportions or the execution price of any of the trades involved in the transaction making the redemption unprofitable, the TX will simply fail or not be included at all.

This is not the case when redeeming directly through CollateralRegistry: the backend will only ensure that the redemption fee (as a percentage) doesn't exceed the maximum acceptable value passed by the redeemer, it doesn't care if the collateral prices (as reported by their oracles) or the proportions of unbacked debt between branches has changed compared to what the redeemer simulated before signing their transaction.

This helper contract implements a wrapper around redeemCollateral() that adds the new parameter _minCollRedeemed, ensuring that the redeemer gets at least as much collateral from each branch as was presented to them on the UI (minus a small slippage tolerance margin, to be calculated by the frontend).

Redemption optimization

The job of CollateralRegistry is to break a redemption down into multiple smaller redemptions (one per each branch) adding up to the total amount that is to be redeemed. Additionally, it is responsible for calculating the redemption fee (as a percentage). It does so at the beginning of the redemption sequence, assuming that each of the per-branch redemptions will be complete, i.e. that there will be no unredeemed BOLD left.

However, this is not always the case. For example, the redemption could terminate early on one or more branches due to hitting the user-configured iteration limit (_maxIterationsPerCollateral). When this happens, the user pays a higher fee than warranted by the actual amount of BOLD successfully redeemed, because of the aforementioned assumption made when calculating the fee.

The truncateRedemption() function lets the user avoid such (unlikely) scenarios by simulating the redemption, and calculating the maximum amount of BOLD that can be redeemed completely within the given iteration limit.

@danielattilasimon danielattilasimon changed the title [WIP] Redemption helper [WIP] [Contracts] Redemption helper Oct 23, 2025
@danielattilasimon danielattilasimon added this to the Release 1.9 milestone Oct 23, 2025
Copy link
Collaborator

@bingen bingen left a comment

Choose a reason for hiding this comment

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

It looks good in general. I still need to finish the review of truncateRedemption, but the important part in my opinion is that the wrapper redeemCollateral is fine. The frontend may show the amounts passed as collateral and their values in $, so even if one of the other helpers made a mistake the user would realize that is getting a bad deal.

RedemptionContext[] memory branch = new RedemptionContext[](numBranches);

for (uint256 i = 0; i < numBranches; ++i) {
branch[i].collToken = collateralRegistry.getToken(i);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not sure if it’s worth, but we may store the tokens, even immutably like we do it collateral registry, to save some gas.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I considered this too. The 3 calls to collateralRegistry.getToken() total about 1.35K gas, so that's the upper limit on how much we could save by switching to immutables. It makes the code a bit nasty though.

Or we could use storage, but unless we pass an access list with the TX, it would actually end up costing more, since cold slots take 2.1K to SLOAD.

In the end, I didn't think the 1.35K potential savings warranted adding the extra immutable optimization.

for (uint256 i = 0; i < numBranches; ++i) {
if (branch[i].redeemable && branch[i].proportion > 0) {
uint256 extrapolatedBold = branch[i].redeemedBold * totalProportions / branch[i].proportion;
if (extrapolatedBold < truncatedBold) truncatedBold = extrapolatedBold;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I’m having hard time understanding this part. It seems that truncatedBold can be overridden at any branch iteration. Why do we know the last one is the good one? I’ll get back to it later, but maybe a comment may help.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

For each branch, we determine how much the total BOLD redeemed across all branches would have been if the redemption was proportional (we call this extrapolatedBold). Then we take the smallest of those amounts.

Normally, this is no different from the input _bold amount. However, if any of the branches terminated because of the iteration limit, the redeemed amount on that branch will be less than proportional. Since such redemptions end up redeeming less BOLD than intended, they pay a higher fee than necessary (since the fee is fixed at the beginning of the TX, before the actually redeemed amounts are known).

The point of truncateRedemption() is to find the maximal amount of BOLD that can be redeemed within a given iteration limit without overpaying on the redemption fee.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added a comment that hopefully explains this.

@danielattilasimon danielattilasimon changed the title [WIP] [Contracts] Redemption helper [Contracts] Redemption helper Oct 27, 2025
@danielattilasimon danielattilasimon marked this pull request as ready for review October 27, 2025 02:35
@danielattilasimon danielattilasimon merged commit dbdbfc2 into main Oct 27, 2025
6 checks passed
@danielattilasimon danielattilasimon deleted the redemption-helper branch October 27, 2025 04:40
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.

2 participants