Skip to content

Commit a73a924

Browse files
Refactor msg value to be passed down vs. direct reference (#1749)
Co-authored-by: Tino Martínez Molina <[email protected]>
1 parent eea4ee1 commit a73a924

File tree

2 files changed

+99
-54
lines changed

2 files changed

+99
-54
lines changed

packages/ovault-evm/contracts/VaultComposerSync.sol

Lines changed: 75 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -56,25 +56,14 @@ contract VaultComposerSync is IVaultComposerSync, ReentrancyGuard {
5656
constructor(address _vault, address _assetOFT, address _shareOFT) {
5757
VAULT = IERC4626(_vault);
5858

59-
ASSET_OFT = _assetOFT;
6059
SHARE_OFT = _shareOFT;
61-
SHARE_ERC20 = IOFT(SHARE_OFT).token();
60+
ASSET_OFT = _assetOFT;
61+
62+
SHARE_ERC20 = _initializeShareToken();
63+
ASSET_ERC20 = _initializeAssetToken();
6264

6365
ENDPOINT = address(IOAppCore(ASSET_OFT).endpoint());
6466
VAULT_EID = ILayerZeroEndpointV2(ENDPOINT).eid();
65-
66-
if (SHARE_ERC20 != address(VAULT)) {
67-
revert ShareTokenNotVault(SHARE_ERC20, address(VAULT));
68-
}
69-
70-
/// @dev ShareOFT must be an OFT adapter. We can infer this by checking 'approvalRequired()'.
71-
/// @dev burn() on tokens when a user sends changes totalSupply() which the asset:share ratio depends on.
72-
if (!IOFT(SHARE_OFT).approvalRequired()) revert ShareOFTNotAdapter(SHARE_OFT);
73-
74-
/// @dev Approve the share adapter with the share tokens held by this contract
75-
IERC20(SHARE_ERC20).approve(_shareOFT, type(uint256).max);
76-
77-
ASSET_ERC20 = _initializeAssetToken(_assetOFT, VAULT);
7867
}
7968

8069
/**
@@ -111,7 +100,7 @@ contract VaultComposerSync is IVaultComposerSync, ReentrancyGuard {
111100
}
112101
}
113102

114-
_refund(_composeSender, _message, amount, tx.origin);
103+
_refund(_composeSender, _message, amount, tx.origin, msg.value);
115104
emit Refunded(_guid);
116105
}
117106
}
@@ -141,9 +130,9 @@ contract VaultComposerSync is IVaultComposerSync, ReentrancyGuard {
141130
if (msg.value < minMsgValue) revert InsufficientMsgValue(minMsgValue, msg.value);
142131

143132
if (_oftIn == ASSET_OFT) {
144-
_depositAndSend(_composeFrom, _amount, sendParam, tx.origin);
133+
_depositAndSend(_composeFrom, _amount, sendParam, tx.origin, msg.value);
145134
} else {
146-
_redeemAndSend(_composeFrom, _amount, sendParam, tx.origin);
135+
_redeemAndSend(_composeFrom, _amount, sendParam, tx.origin, msg.value);
147136
}
148137
}
149138

@@ -159,7 +148,13 @@ contract VaultComposerSync is IVaultComposerSync, ReentrancyGuard {
159148
address _refundAddress
160149
) external payable virtual nonReentrant {
161150
IERC20(ASSET_ERC20).safeTransferFrom(msg.sender, address(this), _assetAmount);
162-
_depositAndSend(OFTComposeMsgCodec.addressToBytes32(msg.sender), _assetAmount, _sendParam, _refundAddress);
151+
_depositAndSend(
152+
OFTComposeMsgCodec.addressToBytes32(msg.sender),
153+
_assetAmount,
154+
_sendParam,
155+
_refundAddress,
156+
msg.value
157+
);
163158
}
164159

165160
/**
@@ -168,6 +163,7 @@ contract VaultComposerSync is IVaultComposerSync, ReentrancyGuard {
168163
* @param _assetAmount The number of assets to deposit
169164
* @param _sendParam Parameter that defines how to send the shares
170165
* @param _refundAddress Address to receive excess payment of the LZ fees
166+
* @param _msgValue The amount of native tokens sent with the transaction
171167
* @notice This function first deposits the assets to mint shares, validates the shares meet minimum slippage requirements,
172168
* then sends the minted shares cross-chain using the OFT (Omnichain Fungible Token) protocol
173169
* @notice _sendParam.amountLD is set to the share amount minted, and minAmountLD is reset to 0 for send operation
@@ -176,15 +172,16 @@ contract VaultComposerSync is IVaultComposerSync, ReentrancyGuard {
176172
bytes32 _depositor,
177173
uint256 _assetAmount,
178174
SendParam memory _sendParam,
179-
address _refundAddress
175+
address _refundAddress,
176+
uint256 _msgValue
180177
) internal virtual {
181178
uint256 shareAmount = _deposit(_depositor, _assetAmount);
182179
_assertSlippage(shareAmount, _sendParam.minAmountLD);
183180

184181
_sendParam.amountLD = shareAmount;
185182
_sendParam.minAmountLD = 0;
186183

187-
_send(SHARE_OFT, _sendParam, _refundAddress);
184+
_send(SHARE_OFT, _sendParam, _refundAddress, _msgValue);
188185
emit Deposited(_depositor, _sendParam.to, _sendParam.dstEid, _assetAmount, shareAmount);
189186
}
190187

@@ -210,7 +207,13 @@ contract VaultComposerSync is IVaultComposerSync, ReentrancyGuard {
210207
address _refundAddress
211208
) external payable virtual nonReentrant {
212209
IERC20(SHARE_ERC20).safeTransferFrom(msg.sender, address(this), _shareAmount);
213-
_redeemAndSend(OFTComposeMsgCodec.addressToBytes32(msg.sender), _shareAmount, _sendParam, _refundAddress);
210+
_redeemAndSend(
211+
OFTComposeMsgCodec.addressToBytes32(msg.sender),
212+
_shareAmount,
213+
_sendParam,
214+
_refundAddress,
215+
msg.value
216+
);
214217
}
215218

216219
/**
@@ -219,6 +222,7 @@ contract VaultComposerSync is IVaultComposerSync, ReentrancyGuard {
219222
* @param _shareAmount The number of shares to redeem
220223
* @param _sendParam Parameter that defines how to send the assets
221224
* @param _refundAddress Address to receive excess payment of the LZ fees
225+
* @param _msgValue The amount of native tokens sent with the transaction
222226
* @notice This function first redeems the specified share amount for the underlying asset,
223227
* validates the received amount against slippage protection, then initiates a cross-chain
224228
* transfer of the redeemed assets using the OFT (Omnichain Fungible Token) protocol
@@ -229,15 +233,16 @@ contract VaultComposerSync is IVaultComposerSync, ReentrancyGuard {
229233
bytes32 _redeemer,
230234
uint256 _shareAmount,
231235
SendParam memory _sendParam,
232-
address _refundAddress
236+
address _refundAddress,
237+
uint256 _msgValue
233238
) internal virtual {
234239
uint256 assetAmount = _redeem(_redeemer, _shareAmount);
235240
_assertSlippage(assetAmount, _sendParam.minAmountLD);
236241

237242
_sendParam.amountLD = assetAmount;
238243
_sendParam.minAmountLD = 0;
239244

240-
_send(ASSET_OFT, _sendParam, _refundAddress);
245+
_send(ASSET_OFT, _sendParam, _refundAddress, _msgValue);
241246
emit Redeemed(_redeemer, _sendParam.to, _sendParam.dstEid, _shareAmount, assetAmount);
242247
}
243248

@@ -303,12 +308,13 @@ contract VaultComposerSync is IVaultComposerSync, ReentrancyGuard {
303308
* @param _oft The OFT contract address to use for sending
304309
* @param _sendParam The parameters for the send operation
305310
* @param _refundAddress Address to receive excess payment of the LZ fees
311+
* @param _msgValue The amount of native tokens sent with the transaction
306312
*/
307-
function _send(address _oft, SendParam memory _sendParam, address _refundAddress) internal virtual {
313+
function _send(address _oft, SendParam memory _sendParam, address _refundAddress, uint256 _msgValue) internal virtual {
308314
if (_sendParam.dstEid == VAULT_EID) {
309-
_sendLocal(_oft, _sendParam, _refundAddress);
315+
_sendLocal(_oft, _sendParam, _refundAddress, _msgValue);
310316
} else {
311-
_sendRemote(_oft, _sendParam, _refundAddress);
317+
_sendRemote(_oft, _sendParam, _refundAddress, _msgValue);
312318
}
313319
}
314320

@@ -317,9 +323,11 @@ contract VaultComposerSync is IVaultComposerSync, ReentrancyGuard {
317323
* @dev Transfers tokens directly without LayerZero messaging
318324
* @param _oft The OFT contract address to determine which token to transfer
319325
* @param _sendParam The parameters for the send operation
326+
* @dev _refundAddress Address to receive excess payment of the LZ fees (unused for local transfers)
327+
* @param _msgValue The amount of native tokens sent with the transaction (must be 0 for local transfers)
320328
*/
321-
function _sendLocal(address _oft, SendParam memory _sendParam, address /*_refundAddress*/) internal virtual {
322-
if (msg.value > 0) revert NoMsgValueExpected();
329+
function _sendLocal(address _oft, SendParam memory _sendParam, address /*_refundAddress*/, uint256 _msgValue) internal virtual {
330+
if (_msgValue > 0) revert NoMsgValueExpected();
323331

324332
/// @dev Can do this because _oft is validated before this function is called
325333
address erc20 = _oft == ASSET_OFT ? ASSET_ERC20 : SHARE_ERC20;
@@ -332,9 +340,10 @@ contract VaultComposerSync is IVaultComposerSync, ReentrancyGuard {
332340
* @param _oft The OFT contract address to use for sending
333341
* @param _sendParam The parameters for the send operation
334342
* @param _refundAddress Address to receive excess payment of the LZ fees
343+
* @param _msgValue The amount of native tokens sent with the transaction
335344
*/
336-
function _sendRemote(address _oft, SendParam memory _sendParam, address _refundAddress) internal virtual {
337-
IOFT(_oft).send{ value: msg.value }(_sendParam, MessagingFee(msg.value, 0), _refundAddress);
345+
function _sendRemote(address _oft, SendParam memory _sendParam, address _refundAddress, uint256 _msgValue) internal virtual {
346+
IOFT(_oft).send{ value: _msgValue}(_sendParam, MessagingFee(_msgValue, 0), _refundAddress);
338347
}
339348

340349
/**
@@ -343,36 +352,59 @@ contract VaultComposerSync is IVaultComposerSync, ReentrancyGuard {
343352
* @param _message The original message that was sent
344353
* @param _amount The amount of tokens to refund
345354
* @param _refundAddress Address to receive the refund
355+
* @param _msgValue The amount of native tokens sent with the transaction
346356
*/
347-
function _refund(address _oft, bytes calldata _message, uint256 _amount, address _refundAddress) internal virtual {
357+
function _refund(address _oft, bytes calldata _message, uint256 _amount, address _refundAddress, uint256 _msgValue) internal virtual {
348358
/// @dev Extracted from the _message header. Will always be part of the _message since it is created by lzReceive
349359
SendParam memory refundSendParam;
350360
refundSendParam.dstEid = OFTComposeMsgCodec.srcEid(_message);
351361
refundSendParam.to = OFTComposeMsgCodec.composeFrom(_message);
352362
refundSendParam.amountLD = _amount;
353363

354-
_sendRemote(_oft, refundSendParam, _refundAddress);
364+
_sendRemote(_oft, refundSendParam, _refundAddress, _msgValue);
365+
}
366+
367+
/**
368+
* @dev Internal function to validate the share token compatibility
369+
* @dev Validate part of the constructor in an overridable function due to differences in asset and OFT token
370+
* @return shareERC20 The address of the share ERC20 token
371+
* @notice Share token must be the vault itself
372+
* @notice Share OFT must be an adapter (approvalRequired() returns true)
373+
*/
374+
function _initializeShareToken() internal virtual returns (address shareERC20) {
375+
shareERC20 = IOFT(SHARE_OFT).token();
376+
377+
if (shareERC20 != address(VAULT)) {
378+
revert ShareTokenNotVault(shareERC20, address(VAULT));
379+
}
380+
381+
/// @dev ShareOFT must be an OFT adapter. We can infer this by checking 'approvalRequired()'.
382+
/// @dev burn() on tokens when a user sends changes totalSupply() which the asset:share ratio depends on.
383+
if (!IOFT(SHARE_OFT).approvalRequired()) revert ShareOFTNotAdapter(SHARE_OFT);
384+
385+
/// @dev Approve the share adapter with the share tokens held by this contract
386+
IERC20(shareERC20).approve(SHARE_OFT, type(uint256).max);
355387
}
356388

357389
/**
358390
* @dev Internal function to validate the asset token compatibility
359391
* @dev Validate part of the constructor in an overridable function due to differences in asset and OFT token
360392
* @dev For example, in the case of VaultComposerSyncPoolNative, the asset token is WETH but the OFT token is native
361-
* @param _assetOFT The address of the asset OFT
362-
* @param _vault The address of the vault
393+
* @return assetERC20 The address of the asset ERC20 token
394+
* @notice Asset token should match the vault's underlying asset (overridable behavior)
363395
*/
364-
function _initializeAssetToken(address _assetOFT, IERC4626 _vault) internal virtual returns (address assetERC20) {
365-
assetERC20 = IOFT(_assetOFT).token();
396+
function _initializeAssetToken() internal virtual returns (address assetERC20) {
397+
assetERC20 = IOFT(ASSET_OFT).token();
366398

367-
if (assetERC20 != address(_vault.asset())) {
368-
revert AssetTokenNotVaultAsset(assetERC20, address(_vault.asset()));
399+
if (assetERC20 != address(VAULT.asset())) {
400+
revert AssetTokenNotVaultAsset(assetERC20, address(VAULT.asset()));
369401
}
370402

371-
/// @dev Approve the vault to spend the asset tokens held by this contract
372-
IERC20(assetERC20).approve(address(_vault), type(uint256).max);
373-
374403
/// @dev If the asset OFT is an adapter, approve it as well
375-
if (IOFT(_assetOFT).approvalRequired()) IERC20(assetERC20).approve(_assetOFT, type(uint256).max);
404+
if (IOFT(ASSET_OFT).approvalRequired()) IERC20(assetERC20).approve(ASSET_OFT, type(uint256).max);
405+
406+
/// @dev Approve the vault to spend the asset tokens held by this contract
407+
IERC20(assetERC20).approve(address(VAULT), type(uint256).max);
376408
}
377409

378410
receive() external payable virtual {}

packages/ovault-evm/contracts/VaultComposerSyncNative.sol

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pragma solidity ^0.8.22;
44
import { IERC4626 } from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
55

66
import { IOFT, SendParam, MessagingFee } from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol";
7+
import { OFTComposeMsgCodec } from "@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.sol";
78

89
import { IVaultComposerSyncNative } from "./interfaces/IVaultComposerSyncNative.sol";
910
import { IWETH } from "./interfaces/IWETH.sol";
@@ -20,6 +21,8 @@ import { VaultComposerSync } from "./VaultComposerSync.sol";
2021
* @dev Compatible with ERC4626 vaults and requires Share OFT to be an adapter
2122
*/
2223
contract VaultComposerSyncNative is VaultComposerSync, IVaultComposerSyncNative {
24+
using OFTComposeMsgCodec for bytes32;
25+
2326
/**
2427
* @notice Initializes the VaultComposerSyncPoolNative contract with vault and OFT token addresses
2528
* @param _vault The address of the ERC4626 vault contract
@@ -48,8 +51,15 @@ contract VaultComposerSyncNative is VaultComposerSync, IVaultComposerSyncNative
4851
if (msg.value < _assetAmount) revert AmountExceedsMsgValue();
4952

5053
IWETH(ASSET_ERC20).deposit{ value: _assetAmount }();
54+
5155
/// @dev Reduce msg.value to the amount used as Fee for the lzSend operation
52-
this.depositAndSend{ value: msg.value - _assetAmount }(_assetAmount, _sendParam, _refundAddress);
56+
_depositAndSend(
57+
OFTComposeMsgCodec.addressToBytes32(msg.sender),
58+
_assetAmount,
59+
_sendParam,
60+
_refundAddress,
61+
msg.value - _assetAmount
62+
);
5363
}
5464

5565
/**
@@ -59,9 +69,9 @@ contract VaultComposerSyncNative is VaultComposerSync, IVaultComposerSyncNative
5969
* @param _sendParam The parameters for the send operation
6070
* @param _refundAddress Address to receive tokens and native on Pool failure
6171
*/
62-
function _sendRemote(address _oft, SendParam memory _sendParam, address _refundAddress) internal override {
63-
/// @dev msg.value passed in this call is used as LayerZero fee
64-
uint256 msgValue = msg.value;
72+
function _sendRemote(address _oft, SendParam memory _sendParam, address _refundAddress, uint256 _msgValue) internal override {
73+
/// @dev _msgValue passed in this call is used as LayerZero fee
74+
uint256 msgValue = _msgValue;
6575

6676
/// @dev Safe because this is the only function in VaultComposerSync that calls oft.send()
6777
if (_oft == ASSET_OFT) {
@@ -73,20 +83,23 @@ contract VaultComposerSyncNative is VaultComposerSync, IVaultComposerSyncNative
7383
msgValue += _sendParam.amountLD;
7484
}
7585

76-
IOFT(_oft).send{ value: msgValue }(_sendParam, MessagingFee(msg.value, 0), _refundAddress);
86+
IOFT(_oft).send{ value: msgValue }(_sendParam, MessagingFee(_msgValue, 0), _refundAddress);
7787
}
7888

7989
/**
8090
* @dev Internal function to validate the asset token compatibility
8191
* @dev In VaultComposerSyncNative, the asset token is WETH but the OFT token is native (ETH)
82-
* @param _assetOFT The address of the asset OFT (Omnichain Fungible Token) contract
83-
* @param _vault The address of the vault contract
8492
* @return assetERC20 The address of the asset ERC20 token
8593
*/
86-
function _initializeAssetToken(address _assetOFT, IERC4626 _vault) internal override returns (address assetERC20) {
87-
if (IOFT(_assetOFT).token() != address(0)) revert AssetOFTTokenNotNative();
88-
assetERC20 = _vault.asset();
89-
IWETH(assetERC20).approve(address(_vault), type(uint256).max);
94+
function _initializeAssetToken() internal override returns (address assetERC20) {
95+
assetERC20 = VAULT.asset();
96+
97+
if (IOFT(ASSET_OFT).token() != address(0)) revert AssetOFTTokenNotNative();
98+
99+
// @dev The asset OFT does NOT need approval since it operates in native ETH.
100+
// if (IOFT(ASSET_OFT).approvalRequired()) IERC20(assetERC20).approve(ASSET_OFT, type(uint256).max);
101+
102+
IWETH(assetERC20).approve(address(VAULT), type(uint256).max);
90103
}
91104

92105
/**

0 commit comments

Comments
 (0)