-
Notifications
You must be signed in to change notification settings - Fork 54
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
[lock/unlock]: support multi-stage finalization in rebalancer contract #613
Changes from 36 commits
b8e1fe7
8775026
529bfa8
e09bcf8
f416505
c36ec5e
2d482d5
10dde74
7828125
41886ec
e6ed0b0
2981558
ab12d13
8d6418c
ecd85b6
d537233
e5b0099
cf5e440
7f87ed7
3df4bd8
07da337
4dfec13
c35c5d1
8d440bc
c0c93e2
4a8052a
c533064
bad59da
f4c242d
11ed79a
8ce9141
774f941
2663f74
e7e90c7
6677ca6
1c1d3fe
63e0cc9
4bcefeb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,15 +4,15 @@ pragma solidity 0.8.19; | |
import {IBridgeAdapter} from "./interfaces/IBridge.sol"; | ||
import {IRebalancer} from "./interfaces/IRebalancer.sol"; | ||
import {ILiquidityContainer} from "./interfaces/ILiquidityContainer.sol"; | ||
|
||
import {IWrappedNative} from "../ccip/interfaces/IWrappedNative.sol"; | ||
import {OCR3Base} from "./ocr/OCR3Base.sol"; | ||
|
||
import {IERC20} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; | ||
import {SafeERC20} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
|
||
/// @notice Rebalancer for a single token over multiple chains. | ||
/// @dev This contract is designed to be used with the LockReleaseTokenPool contract but | ||
/// isn't constraint to it. It can be used with any contract that implements the ILiquidityContainer | ||
/// isn't constrained to it. It can be used with any contract that implements the ILiquidityContainer | ||
/// interface. | ||
/// @dev The OCR3 DON should only be able to transfer funds to other pre-approved contracts | ||
/// on other chains. Under no circumstances should it be able to transfer funds to arbitrary | ||
|
@@ -30,6 +30,11 @@ contract Rebalancer is IRebalancer, OCR3Base { | |
error InsufficientLiquidity(uint256 requested, uint256 available); | ||
error EmptyReport(); | ||
|
||
event FinalizationStepCompleted( | ||
uint64 indexed ocrSeqNum, | ||
uint64 indexed remoteChainSelector, | ||
bytes bridgeSpecificData | ||
); | ||
event LiquidityTransferred( | ||
uint64 indexed ocrSeqNum, | ||
uint64 indexed fromChainSelector, | ||
|
@@ -165,9 +170,10 @@ contract Rebalancer is IRebalancer, OCR3Base { | |
function receiveLiquidity( | ||
uint64 remoteChainSelector, | ||
uint256 amount, | ||
bool shouldWrapNative, | ||
bytes calldata bridgeSpecificPayload | ||
) external onlyOwner { | ||
_receiveLiquidity(remoteChainSelector, amount, bridgeSpecificPayload, type(uint64).max); | ||
_receiveLiquidity(remoteChainSelector, amount, bridgeSpecificPayload, shouldWrapNative, type(uint64).max); | ||
} | ||
|
||
/// @notice Transfers liquidity to another chain. | ||
|
@@ -217,6 +223,7 @@ contract Rebalancer is IRebalancer, OCR3Base { | |
uint64 remoteChainSelector, | ||
uint256 amount, | ||
bytes memory bridgeSpecificPayload, | ||
bool shouldWrapNative, | ||
uint64 ocrSeqNum | ||
) internal { | ||
// check if the remote chain is supported | ||
|
@@ -232,8 +239,20 @@ contract Rebalancer is IRebalancer, OCR3Base { | |
address(this), // localReceiver: this contract | ||
bridgeSpecificPayload | ||
) | ||
{ | ||
// successfully finalized the withdrawal | ||
returns (bool fundsAvailable) { | ||
if (fundsAvailable) { | ||
// finalization was successful and we can inject the liquidity into the container. | ||
// approve and liquidity container should transferFrom. | ||
_injectLiquidity(amount, ocrSeqNum, remoteChainSelector, bridgeSpecificPayload, shouldWrapNative); | ||
} else { | ||
// a finalization step was completed, but funds are not available. | ||
// hence, we cannot inject any liquidity yet. | ||
emit FinalizationStepCompleted(ocrSeqNum, remoteChainSelector, bridgeSpecificPayload); | ||
} | ||
|
||
// return here on the happy path. | ||
// sad path is when finalizeWithdrawERC20 reverts, which is handled after the catch block. | ||
return; | ||
} catch (bytes memory lowLevelData) { | ||
// failed to finalize the withdrawal. | ||
// this could mean that the withdrawal was already finalized | ||
|
@@ -242,8 +261,29 @@ contract Rebalancer is IRebalancer, OCR3Base { | |
emit FinalizationFailed(ocrSeqNum, remoteChainSelector, bridgeSpecificPayload, lowLevelData); | ||
} | ||
|
||
// inject liquidity into the liquidity container | ||
// approve and liquidity container should transferFrom | ||
// if we reach this point, the finalization failed. | ||
// since we don't have enough information to know why it failed, | ||
// we assume that it failed because the withdrawal was already finalized, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. instead of assuming, let's explain why this will always be safe to call injectLiquidity here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This logic will change in upcoming PRs after recent reviews, so stay tuned. |
||
// and that the funds are available. | ||
_injectLiquidity(amount, ocrSeqNum, remoteChainSelector, bridgeSpecificPayload, shouldWrapNative); | ||
} | ||
|
||
function _injectLiquidity( | ||
uint256 amount, | ||
uint64 ocrSeqNum, | ||
uint64 remoteChainSelector, | ||
bytes memory bridgeSpecificPayload, | ||
bool shouldWrapNative | ||
) private { | ||
// We trust the DON or the owner (the only two actors who can end up calling this function) | ||
// to correctly set the shouldWrapNative flag. | ||
// Some bridges only bridge native and not wrapped native. | ||
// In such a case we need to re-wrap the native in order to inject it into the liquidity container. | ||
// TODO: escape hatch in case of bug? | ||
if (shouldWrapNative) { | ||
_wrapNative(amount); | ||
} | ||
|
||
i_localToken.safeIncreaseAllowance(address(s_localLiquidityContainer), amount); | ||
s_localLiquidityContainer.provideLiquidity(amount); | ||
|
||
|
@@ -258,6 +298,11 @@ contract Rebalancer is IRebalancer, OCR3Base { | |
); | ||
} | ||
|
||
function _wrapNative(uint256 amount) private { | ||
IWrappedNative weth = IWrappedNative(address(i_localToken)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: remove assignment and inline. Might as well remove the function and just inline the single line at that point? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inlined 👍 |
||
weth.deposit{value: amount}(); | ||
} | ||
|
||
function _report(bytes calldata report, uint64 ocrSeqNum) internal override { | ||
IRebalancer.LiquidityInstructions memory instructions = abi.decode(report, (IRebalancer.LiquidityInstructions)); | ||
|
||
|
@@ -285,6 +330,7 @@ contract Rebalancer is IRebalancer, OCR3Base { | |
instructions.receiveLiquidityParams[i].remoteChainSelector, | ||
instructions.receiveLiquidityParams[i].amount, | ||
instructions.receiveLiquidityParams[i].bridgeData, | ||
instructions.receiveLiquidityParams[i].shouldWrapNative, | ||
ocrSeqNum | ||
); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit newline after interfaces
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed 👍