Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions packages/ovault-evm/contracts/VaultComposerSyncNative.sol
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,31 @@ contract VaultComposerSyncNative is VaultComposerSync, IVaultComposerSyncNative
);
}

/**
* @dev Unwrap WETH and transfer native ETH directly to recipient on the same chain
* @dev Overrides base _sendLocal which would incorrectly transfer WETH (ERC20) instead of native ETH
* @dev The base VaultComposerSync._sendLocal does `IERC20(ASSET_ERC20).safeTransfer(recipient, amount)`
* which transfers WETH. For the native variant, the vault redeems WETH but the recipient
* expects native ETH, so we must call WETH.withdraw() first.
* @param _oft The OFT contract address to determine asset vs share path
* @param _sendParam The parameters for the send operation
*/
function _sendLocal(
address _oft,
SendParam memory _sendParam,
address _refundAddress,
uint256 _msgValue
) internal override virtual {
if (_oft == ASSET_OFT) {
/// @dev Vault redeems WETH; unwrap to native ETH before delivering to recipient
IWETH(ASSET_ERC20).withdraw(_sendParam.amountLD);
(bool success, ) = payable(address(uint160(uint256(_sendParam.to)))).call{ value: _sendParam.amountLD }("");
if (!success) revert NativeTransferFailed();
Comment on lines +105 to +107
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

_sendParam.to is decoded as a bytes32 “address” everywhere else via bytes32ToAddress() (see VaultComposerSync._sendLocal). Here it’s converted with address(uint160(uint256(_sendParam.to))), which is inconsistent and can be error-prone if the bytes32 encoding ever changes or includes non-address data. Prefer using the same bytes32ToAddress() helper for consistency (and add the needed using OFTComposeMsgCodec for bytes32; in this contract since using directives aren’t inherited).

Copilot uses AI. Check for mistakes.
} else {
super._sendLocal(_oft, _sendParam, _refundAddress, _msgValue);
}
Comment on lines +103 to +110
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

This new local-send branch (unwrap WETH + native ETH transfer) isn’t covered by existing tests: current native unit tests cover dstEid == VAULT_EID on the deposit/share path, but not the redeem/asset path where _oft == ASSET_OFT and _sendLocal is hit. Please add a unit test that redeems and sends with dstEid == VAULT_EID and asserts the recipient’s ETH balance increases (and that they do not receive WETH).

Copilot uses AI. Check for mistakes.
}

/**
* @dev Unwrap WETH when sending to Stargate PoolNative and send via OFT
* @dev Overriden to unwrap WETH when sending to Stargate PoolNative
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ interface IVaultComposerSyncNative {
error AssetOFTTokenNotNative(); // 0xd61c4b4a
error AmountExceedsMsgValue(); // 0x0f971d59
error ETHTransferNotFromAsset(); // 0x02cadbeb
error NativeTransferFailed(); // 0x26b2aba5

/**
* @notice Deposits Native token (ETH) from the caller into the vault and sends them to the recipient
Expand Down
Loading