Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.19;
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
Expand All @@ -8,9 +8,10 @@ import "@example-vaa-executor/libraries/ExecutorMessages.sol";
import "@native-token-transfers/interfaces/INttManager.sol";

import "./interfaces/INttManagerWithTokenPaymentExecutor.sol";
import "./interfaces/INttManagerWethUnwrap.sol";
import "./interfaces/ITokenPaymentExecutor.sol";

string constant nttManagerWithExecutorVersion = "NttManagerWithTokenPaymentExecutor-0.0.1";
string constant nttManagerWithExecutorVersion = "NttManagerWithTokenPaymentExecutor-0.0.2";

/// @title NttManagerWithTokenPaymentExecutor
/// @notice The NttManagerWithTokenPaymentExecutor contract is a shim contract that initiates
Expand Down Expand Up @@ -56,8 +57,10 @@ contract NttManagerWithTokenPaymentExecutor is INttManagerWithTokenPaymentExecut
// Transfer the fee to the referrer.
amount = payFee(token, amount, feeArgs, nttm, recipientChain);

// Approve the bridge to spend the tokens.
_maxApproveIfNeeded(token, nttManager, amount);

// Initiate the transfer.
SafeERC20.forceApprove(IERC20(token), nttManager, amount);
msgId = nttm.transfer{ value: msg.value }(
amount,
recipientChain,
Expand All @@ -67,21 +70,96 @@ contract NttManagerWithTokenPaymentExecutor is INttManagerWithTokenPaymentExecut
encodedInstructions
);

uint256 executorFee = estimatedCost; // Avoid stack too deep error
{
// Approve custom token fee for executor.
bytes32 universalTokenAddress;
assembly {
universalTokenAddress := calldataload(add(add(executorArgs, calldataload(add(executorArgs, 32))), 132))
}
IERC20 tokenAddress = IERC20(address(uint160(uint256(universalTokenAddress))));
SafeERC20.safeTransferFrom(tokenAddress, msg.sender, address(this), executorFee);
SafeERC20.forceApprove(tokenAddress, address(tokenPaymentExecutor), executorFee);
SafeERC20.safeTransferFrom(tokenAddress, msg.sender, address(this), estimatedCost);
SafeERC20.forceApprove(tokenAddress, address(tokenPaymentExecutor), estimatedCost);
}

// Generate the executor event.
tokenPaymentExecutor.requestExecutionWithTokenPayment(
executorFee,
estimatedCost,
recipientChain,
nttm.getPeer(recipientChain).peerAddress,
executorArgs.refundAddress,
executorArgs.signedQuote,
ExecutorMessages.makeNTTv1Request(
chainId,
bytes32(uint256(uint160(address(nttm)))),
bytes32(uint256(msgId))
),
executorArgs.instructions
);

// Refund any excess value.
uint256 currentBalance = address(this).balance;
if (currentBalance > 0) {
(bool refundSuccessful, ) = payable(executorArgs.refundAddress).call{ value: currentBalance }("");
if (!refundSuccessful) {
revert RefundFailed(currentBalance);
}
}
}

function transferETH(
uint256 estimatedCost,
address nttManager,
uint256 amount,
uint16 recipientChain,
bytes32 recipientAddress,
bytes32 refundAddress,
bytes memory encodedInstructions,
ExecutorArgs calldata executorArgs,
FeeArgs calldata feeArgs
) external payable returns (uint64 msgId) {
INttManagerWethUnwrap nttm = INttManagerWethUnwrap(nttManager);
IWETH weth = nttm.weth();
address token = address(weth);
require(token != address(0), "WETH does not exist");

// This requires the amount + executionAmount to exactly equal msg.value
// because `transferTokensWithRelay` will revert if there is any extra.
require(msg.value >= amount, "Not enough msg value");
uint256 remainingValue = msg.value - amount;

// Deposit the amount to be transferred into WETH.
weth.deposit{ value: amount }();

// Transfer the fee to the referrer.
amount = payFee(token, amount, feeArgs, nttm, recipientChain);

// Approve the bridge to spend the tokens.
_maxApproveIfNeeded(token, nttManager, amount);

// Initiate the transfer.
msgId = nttm.transfer{ value: remainingValue }(
amount,
recipientChain,
recipientAddress,
refundAddress,
false,
encodedInstructions
);

{
// Approve custom token fee for executor.
bytes32 universalTokenAddress;
assembly {
universalTokenAddress := calldataload(add(add(executorArgs, calldataload(add(executorArgs, 32))), 132))
}
IERC20 tokenAddress = IERC20(address(uint160(uint256(universalTokenAddress))));
SafeERC20.safeTransferFrom(tokenAddress, msg.sender, address(this), estimatedCost);
SafeERC20.forceApprove(tokenAddress, address(tokenPaymentExecutor), estimatedCost);
}

// Generate the executor event.
tokenPaymentExecutor.requestExecutionWithTokenPayment(
estimatedCost,
recipientChain,
nttm.getPeer(recipientChain).peerAddress,
executorArgs.refundAddress,
Expand Down Expand Up @@ -165,4 +243,12 @@ contract NttManagerWithTokenPaymentExecutor is INttManagerWithTokenPaymentExecut
TrimmedAmount trimmedAmount = amount.trim(fromDecimals, toDecimals);
newFee = trimmedAmount.untrim(fromDecimals);
}

function _maxApproveIfNeeded(address tokenAddr, address spender, uint256 amount) internal {
IERC20 token = IERC20(tokenAddr);
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < amount) {
SafeERC20.forceApprove(token, spender, type(uint256).max);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "native-token-transfers/evm/src/interfaces/INttManager.sol";
import "./IWETH.sol";

interface INttManagerWethUnwrap is INttManager {
function weth() external returns (IWETH);
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,27 @@ interface INttManagerWithTokenPaymentExecutor {
ExecutorArgs calldata executorArgs,
FeeArgs calldata feeArgs
) external payable returns (uint64 msgId);

/// @notice Transfer a given amount to a recipient on a given chain using the Executor for relaying.
/// @param estimatedCost Delivery cost in custom token.
/// @param nttManager The NTT manager used for the transfer.
/// @param amount The amount to transfer.
/// @param recipientChain The Wormhole chain ID for the destination.
/// @param recipientAddress The recipient address.
/// @param refundAddress The address to which a refund for unussed gas is issued on the recipient chain.
/// @param encodedInstructions Additional instructions to be forwarded to the recipient chain.
/// @param executorArgs The arguments to be passed into the Executor.
/// @param feeArgs The arguments used to compute and pay the referrer fee.
/// @return msgId The resulting message ID of the transfer
function transferETH(
uint256 estimatedCost,
address nttManager,
uint256 amount,
uint16 recipientChain,
bytes32 recipientAddress,
bytes32 refundAddress,
bytes memory encodedInstructions,
ExecutorArgs calldata executorArgs,
FeeArgs calldata feeArgs
) external payable returns (uint64 msgId);
}
12 changes: 12 additions & 0 deletions executor_contracts/evm/executor/interfaces/IWETH.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IWETH is IERC20 {
function deposit() external payable;

function transfer(address to, uint256 value) external returns (bool);

function withdraw(uint256) external;
}
18 changes: 18 additions & 0 deletions executor_contracts/evm/executor/test/MockNttManagerWethUnwrap.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "@native-token-transfers/NttManager/NttManagerWethUnwrap.sol";
import "@native-token-transfers/interfaces/IManagerBase.sol";

contract MockNttManagerWethUnwrap is NttManagerWethUnwrap {
constructor(
address _token,
IManagerBase.Mode _mode,
uint16 _chainId,
uint64 _rateLimitDuration,
bool _skipRateLimiting,
address owner
) NttManagerWethUnwrap(_token, _mode, _chainId, _rateLimitDuration, _skipRateLimiting) {
_transferOwnership(owner);
}
}
24 changes: 24 additions & 0 deletions executor_contracts/evm/executor/test/MockWETHToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockWETHToken is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}

function mint(address account, uint256 value) external {
_mint(account, value);
}

function burn(address account, uint256 value) external {
_burn(account, value);
}

function deposit() public payable {
_update(address(0), msg.sender, msg.value);
}
function withdraw(uint wad) public {
_update(msg.sender, address(0), wad);
payable(msg.sender).transfer(wad);
}
}
14 changes: 14 additions & 0 deletions foundry.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"lib/example-messaging-executor": {
"rev": "575069616477efbec961bcfb77d7baf44e9f3baa"
},
"lib/example-ntt-with-executor-evm": {
"rev": "e1de88385c7913c2e5df65c0889bc9a099558314"
},
"lib/native-token-transfers": {
"rev": "25a5c3f89446be499a89d1c453db996f29de9290"
},
"lib/wormhole-solidity-sdk": {
"rev": "b9e129e65d34827d92fceeed8c87d3ecdfc801d0"
}
}
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ cache_path = 'cache_forge'
evm_version = 'paris'
optimizer = true
optimizer_runs = 200
via_ir = true
ignored_error_codes = []
1 change: 1 addition & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default defineConfig({
enabled: true,
runs: 200,
},
viaIR: true,
},
},
paths: {
Expand Down
2 changes: 1 addition & 1 deletion lib/native-token-transfers
Loading
Loading