diff --git a/packages/ovault-evm/contracts/VaultComposerSyncNative.sol b/packages/ovault-evm/contracts/VaultComposerSyncNative.sol index b9842b3fd2..5ff2c28d89 100644 --- a/packages/ovault-evm/contracts/VaultComposerSyncNative.sol +++ b/packages/ovault-evm/contracts/VaultComposerSyncNative.sol @@ -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(); + } else { + super._sendLocal(_oft, _sendParam, _refundAddress, _msgValue); + } + } + /** * @dev Unwrap WETH when sending to Stargate PoolNative and send via OFT * @dev Overriden to unwrap WETH when sending to Stargate PoolNative diff --git a/packages/ovault-evm/contracts/interfaces/IVaultComposerSyncNative.sol b/packages/ovault-evm/contracts/interfaces/IVaultComposerSyncNative.sol index d12d65efb2..a68231b1d0 100644 --- a/packages/ovault-evm/contracts/interfaces/IVaultComposerSyncNative.sol +++ b/packages/ovault-evm/contracts/interfaces/IVaultComposerSyncNative.sol @@ -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