Skip to content

Conversation

@shahthepro
Copy link
Collaborator

@shahthepro shahthepro commented Dec 11, 2025

@codecov
Copy link

codecov bot commented Dec 12, 2025

Codecov Report

❌ Patch coverage is 84.45748% with 53 lines in your changes missing coverage. Please review.
✅ Project coverage is 41.75%. Comparing base (172f9c4) to head (b16c7fd).
⚠️ Report is 2 commits behind head on master.

Files with missing lines Patch % Lines
...strategies/crosschain/CrossChainRemoteStrategy.sol 73.62% 24 Missing ⚠️
...strategies/crosschain/CrossChainMasterStrategy.sol 81.60% 16 Missing ⚠️
...s/strategies/crosschain/AbstractCCTPIntegrator.sol 90.65% 9 Missing and 1 partial ⚠️
...s/contracts/strategies/Generalized4626Strategy.sol 0.00% 3 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

pragma solidity ^0.8.0;

/**
* @title OUSD Yearn V3 Remote Strategy Mock - the Mainnet part
Copy link
Member

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"

address public immutable cctpHookWrapper;

// USDC address on local chain
address public immutable baseToken;
Copy link
Member

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?

Copy link
Collaborator Author

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

sparrowDom and others added 11 commits December 16, 2025 16:50
* 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
Copy link
Collaborator

@naddison36 naddison36 left a 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
Copy link
Collaborator

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
Copy link
Collaborator

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
Copy link
Collaborator

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
Copy link
Collaborator

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
Copy link
Collaborator

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
Copy link
Collaborator

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");
Copy link
Collaborator

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");
Copy link
Collaborator

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"
Copy link
Collaborator

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)
Copy link
Collaborator

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?

Copy link
Collaborator Author

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.
Copy link
Collaborator

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

Copy link
Collaborator Author

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

Copy link
Collaborator

@clement-ux clement-ux left a comment

Choose a reason for hiding this comment

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

First surface pass

Comment on lines +76 to +77
// Operator address: Can relay CCTP messages
address public operator;
Copy link
Collaborator

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_AMOUNT above cctpMessageTransmitter to keep order: constant -> immutable -> variable
  • cast minFinalityThreshold and feePremiumBps into uint16.
  • move nonceProcessed bellow operator
  • pack minFinalityThreshold, feePremiumBps and lastTransferNonce all together in a single slot.

Comment on lines +255 to +258
require(
minFinalityThreshold == 1000,
"Unfinalized messages are not supported"
);
Copy link
Collaborator

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?

Comment on lines +21 to +37
// 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;
Copy link
Collaborator

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?

Comment on lines +30 to +34
enum TransferType {
None, // To avoid using 0
Deposit,
Withdrawal
}
Copy link
Collaborator

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.

Comment on lines +123 to +124
uint256 undepositedUSDC = IERC20(baseToken).balanceOf(address(this));
return undepositedUSDC + pendingAmount + remoteStrategyBalance;
Copy link
Collaborator

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;
Copy link
Collaborator

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;

Comment on lines +137 to +142
/// @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);
}
Copy link
Collaborator

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))));
}

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.

5 participants