-
Notifications
You must be signed in to change notification settings - Fork 96
[WIP] Cross-chain Strategy #2715
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
base: master
Are you sure you want to change the base?
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #2715 +/- ##
==========================================
+ Coverage 41.10% 41.75% +0.64%
==========================================
Files 126 133 +7
Lines 5778 6129 +351
Branches 1537 1633 +96
==========================================
+ Hits 2375 2559 +184
- Misses 3401 3567 +166
- Partials 2 3 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| pragma solidity ^0.8.0; | ||
|
|
||
| /** | ||
| * @title OUSD Yearn V3 Remote Strategy Mock - the Mainnet part |
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.
"- the Mainnet part" should probably be "- the L2 chain part"
contracts/contracts/mocks/crosschain/CrossChainRemoteStrategyMock.sol
Outdated
Show resolved
Hide resolved
| address public immutable cctpHookWrapper; | ||
|
|
||
| // USDC address on local chain | ||
| address public immutable baseToken; |
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.
we can rename this to usdcToken?
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.
That's a good point. Since we would only be dealing with USDC with CCTP
contracts/contracts/strategies/crosschain/AbstractCCTPIntegrator.sol
Outdated
Show resolved
Hide resolved
* fix deploy files * minor rename * add calls to Morpho Vault into a try catch * refactor hook wrapper * don't revert if withdraw from underlying fails * use checkBalance for deposit/withdrawal acknowledgment * update message in remote strategy * remove unneeded functions
* Fix race condition * Transfer everything on wtihdrawal * Move destination domain one step above
* add cross chain unit test basic files * add basic unit test setup * add header encoding * more tests
* more unit test integration * more tying up ends * fix bug * cleanup * add full round-trip test * cleanup * Fix approve all and prettify --------- Co-authored-by: Shahul Hameed <10547529+shahthepro@users.noreply.github.com>
naddison36
left a comment
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.
This is my first pass of the contracts
| using SafeERC20 for IERC20; | ||
| using CrossChainStrategyHelper for bytes; | ||
|
|
||
| // Remote strategy balance |
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: lets make this Natspec with /// @notice
| // Remote strategy balance | ||
| uint256 public remoteStrategyBalance; | ||
|
|
||
| // Amount that's bridged but not yet received on the destination chain |
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: can be Natspec with /// @notice
| // USDC address on local chain | ||
| address public immutable baseToken; | ||
|
|
||
| // Domain ID of the chain from which messages are accepted |
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: best to make all these comments on public variables a Natspec with /// @notice
| // Domain ID of the chain from which messages are accepted | ||
| uint32 public immutable peerDomainID; | ||
|
|
||
| // Strategy address on other chain |
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: I'd add the example values. eg Ethereum 0, Base 6
| // Strategy address on other chain | ||
| address public immutable peerStrategy; | ||
|
|
||
| // CCTP params |
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: a link to the CCTP
Finality thresholds would be good here https://developers.circle.com/cctp/technical-guide#finality-thresholds
| } | ||
| } | ||
|
|
||
| /// @inheritdoc InitializableAbstractStrategy |
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: I think its better to have CrossChainMasterStrategy specific Natspec. eg _recipient must be the vault and _asset must be USDC.
| */ | ||
| function _deposit(address _asset, uint256 depositAmount) internal virtual { | ||
| require(_asset == baseToken, "Unsupported asset"); | ||
| require(!isTransferPending(), "Transfer already pending"); |
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: it feels safer to have this check in _getNextNonce
| require(_asset == baseToken, "Unsupported asset"); | ||
| require(!isTransferPending(), "Transfer already pending"); | ||
| require(pendingAmount == 0, "Unexpected pending amount"); | ||
| require(depositAmount > 0, "Deposit amount must be greater than 0"); |
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: can be shortened to Must deposit something which is what a lot of other strategies use
| require(depositAmount > 0, "Deposit amount must be greater than 0"); | ||
| require( | ||
| depositAmount <= MAX_TRANSFER_AMOUNT, | ||
| "Deposit amount exceeds max transfer amount" |
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: can be shortened to Deposit amount exceeds max so it fits in an EVM word
| } | ||
|
|
||
| /// @inheritdoc Generalized4626Strategy | ||
| function deposit(address _asset, uint256 _amount) |
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.
Are the deposit, depositAll, withdraw and withdrawAll functions required on CrossChainRemoteStrategy? Won't all deposits and withdrawals come from the Ethereum via CCTP?
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.
Copy-pasting my reply from Discord:
Yeah, that was one of the design decisions. There could be cases where the Guardian might wanna do withdrawals and deposits. Like let's say there're problems with underlying 4626 Vault and we wanna remove all our funds from that Vault instantly, the guradian can call withdraw all directly. Also, if the deposit or withdrawal fails, the guardian can do it manually without it having to be a CCTP message
|
|
||
| /** | ||
| * @notice Set address of Strategist. | ||
| * This is important to have the function name same as IVault. |
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.
Is this saying the Strategist needs to be the vaultAddress?
The constructor states
// Vault address must always be the proxy address
// so that IVault(vaultAddress).strategistAddr() works
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.
Nope. I think I could've worded this better, I'll change it.
So, the problem is that the Generalized4626Strategy uses IVault.strategistAddr(). However the remote strategy has no Vault, it only communicates with the Master strategy. So, whenever IVault.strategistAddr() is used in the abstract strategy, it'll revert. So, what I did is add a function with same signature as strategistAddr(), so whenever IVault.strategistAddr() is called, it'll be same as calling address(this).strategistAddr().
Strategist should always be the 2/8 multisig. But for Remote strategy, the Vault should be its own proxy addresss
clement-ux
left a comment
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.
First surface pass
| // Operator address: Can relay CCTP messages | ||
| address public operator; |
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.
Global comment about slot arrangement, maybe we can:
- Move
MAX_TRANSFER_AMOUNTabovecctpMessageTransmitterto keep order: constant -> immutable -> variable - cast
minFinalityThresholdandfeePremiumBpsintouint16. - move
nonceProcessedbellowoperator - pack
minFinalityThreshold,feePremiumBpsandlastTransferNonceall together in a single slot.
| require( | ||
| minFinalityThreshold == 1000, | ||
| "Unfinalized messages are not supported" | ||
| ); |
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.
Quick question, what's the reason for only supporting fast transfer and not standard transfer?
| // CCTP Message Header fields | ||
| // Ref: https://developers.circle.com/cctp/technical-guide#message-header | ||
| uint8 constant VERSION_INDEX = 0; | ||
| uint8 constant SOURCE_DOMAIN_INDEX = 4; | ||
| uint8 constant SENDER_INDEX = 44; | ||
| uint8 constant RECIPIENT_INDEX = 76; | ||
| uint8 constant MESSAGE_BODY_INDEX = 148; | ||
|
|
||
| // Message body V2 fields | ||
| // Ref: https://developers.circle.com/cctp/technical-guide#message-body | ||
| // Ref: https://github.com/circlefin/evm-cctp-contracts/blob/master/src/messages/v2/BurnMessageV2.sol | ||
| uint8 constant BURN_MESSAGE_V2_VERSION_INDEX = 0; | ||
| uint8 constant BURN_MESSAGE_V2_RECIPIENT_INDEX = 36; | ||
| uint8 constant BURN_MESSAGE_V2_AMOUNT_INDEX = 68; | ||
| uint8 constant BURN_MESSAGE_V2_MESSAGE_SENDER_INDEX = 100; | ||
| uint8 constant BURN_MESSAGE_V2_FEE_EXECUTED_INDEX = 164; | ||
| uint8 constant BURN_MESSAGE_V2_HOOK_DATA_INDEX = 228; |
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: Shouldn't we create a Library for these constants?
| enum TransferType { | ||
| None, // To avoid using 0 | ||
| Deposit, | ||
| Withdrawal | ||
| } |
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.
I think we should have None, Deposit, Withdrawal in capital letter.
| uint256 undepositedUSDC = IERC20(baseToken).balanceOf(address(this)); | ||
| return undepositedUSDC + pendingAmount + remoteStrategyBalance; |
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: I don't think it is necessary to cache undepositedUSDC, we can directly have:
return IERC20(baseToken).balanceOf(address(this)) + pendingAmount + remoteStrategyBalance;| _markNonceAsProcessed(nonce); | ||
|
|
||
| // Effect of confirming a deposit, reset pending amount | ||
| pendingAmount = 0; |
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.
gas: delete pendingAmount should be cheaper than pendingAmount = 0;
| /// @inheritdoc Generalized4626Strategy | ||
| function withdrawAll() external virtual override onlyGovernorOrStrategist { | ||
| uint256 contractBalance = IERC20(baseToken).balanceOf(address(this)); | ||
| uint256 balance = checkBalance(baseToken) - contractBalance; | ||
| _withdraw(address(this), baseToken, balance); | ||
| } |
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.
It fetches IERC20(baseToken).balanceOf(address(this)) two times, one at line 139, once again in checkBalance.
Maybe we can have something like this:
function withdrawAll() external virtual override onlyGovernorOrStrategist {
_withdraw(address(this), baseToken, IERC4626(platformAddress).previewRedeem(platform.balanceOf(address(this))));
}
Code Changes
Check readme: https://github.com/OriginProtocol/origin-dollar/blob/shah/cross-chain-strategy-cctpv2/contracts/contracts/strategies/crosschain/crosschain-strategy.md