Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
48 changes: 48 additions & 0 deletions evm/tests/foundry/HyperFungibleTokenTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol

import {HyperFungibleToken} from "@hyperbridge/core/apps/HyperFungibleToken.sol";
import {WrappedHyperFungibleToken} from "@hyperbridge/core/apps/WrappedHyperFungibleToken.sol";
import {MainnetForkBaseTest} from "./MainnetForkBaseTest.sol";
import {UniV3UniswapV2Wrapper} from "../../src/utils/uniswapv2/UniV3UniswapV2Wrapper.sol";
import {HostParams} from "../../src/core/EvmHost.sol";

// Concrete HyperFungibleToken for testing
contract TestHFT is HyperFungibleToken {
Expand Down Expand Up @@ -723,3 +726,48 @@ contract WrappedHyperFungibleTokenTest is BaseTest {

receive() external payable {}
}

/// @notice Forks mainnet and prices a cross-chain send through `HyperApp.quote()` against a host
/// whose fee oracle is the real `UniV3UniswapV2Wrapper`.
///
/// The V3 quoter prices `getAmountsIn` by executing a swap and reverting to read the result, so it
/// writes state. `quote()` must therefore reach it via a `CALL`; a `view` `quote()` emits a
/// `STATICCALL`, which rejects the state write and reverts before the fee can be priced.
contract HyperFungibleTokenQuoteForkTest is MainnetForkBaseTest {
function testQuotePricesNativeFeeThroughV3Wrapper() public {
UniV3UniswapV2Wrapper wrapper = new UniV3UniswapV2Wrapper(address(this));
wrapper.init(
UniV3UniswapV2Wrapper.Params({
WETH: _uniswapV2Router.WETH(),
swapRouter: 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45,
quoter: 0x61fFE014bA17989E743c5F6cB21bF9697530B21e,
maxFee: 500
})
);

// Point the host's fee oracle at the V3 wrapper, as it is configured on mainnet.
HostParams memory params = host.hostParams();
params.uniswapV2 = address(wrapper);
vm.prank(address(manager));
host.updateHostParams(params);

bytes memory destChain = StateMachine.evm(42161); // Arbitrum
bytes memory remoteContract = abi.encodePacked(address(0xBEEF));

TestHFT hft = new TestHFT();
hft.configure(HyperFungibleToken.ConfigOptions({host: address(host), dispatcher: address(dispatcher)}));
hft.addChain(destChain, remoteContract);

HyperFungibleToken.SendParams memory sendParams = HyperFungibleToken.SendParams({
dest: destChain,
to: remoteContract,
amount: 1 ether,
timeout: 3600,
relayerFee: 10 * 1e18, // 10 DAI
data: ""
});

uint256 nativeFee = hft.quote(sendParams);
assertGt(nativeFee, 0, "quote should price the relayer fee in native through the V3 wrapper");
}
}
6 changes: 3 additions & 3 deletions sdk/packages/core/contracts/apps/HyperApp.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
interface IUniswapV2Router02 {
function WETH() external pure returns (address);

function getAmountsIn(uint256, address[] calldata) external pure returns (uint256[] memory);
function getAmountsIn(uint256, address[] calldata) external returns (uint256[] memory);
}

/**
Expand Down Expand Up @@ -70,7 +70,7 @@ abstract contract HyperApp is IApp {
/**
* @dev returns the quoted fee in the native token for dispatching a POST request
*/
function quote(DispatchPost memory request) public view returns (uint256) {
function quote(DispatchPost memory request) public returns (uint256) {
address _host = host();
address _uniswap = IDispatcher(_host).uniswapV2Router();
address[] memory path = new address[](2);
Expand All @@ -82,7 +82,7 @@ abstract contract HyperApp is IApp {
/**
* @dev returns the quoted fee in the native token for dispatching a GET request
*/
function quote(DispatchGet memory request) public view returns (uint256) {
function quote(DispatchGet memory request) public returns (uint256) {
address _host = host();
address _uniswap = IDispatcher(_host).uniswapV2Router();
address[] memory path = new address[](2);
Expand Down
2 changes: 1 addition & 1 deletion sdk/packages/core/contracts/apps/HyperFungibleToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ contract HyperFungibleToken is ERC20, ERC165, HyperApp, Ownable, Pausable {
* @param params The send parameters
* @return The fee amount in native currency
*/
function quote(SendParams calldata params) public view returns (uint256) {
function quote(SendParams calldata params) public returns (uint256) {
return quote(_buildDispatchPost(params));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ contract HyperFungibleTokenUpgradeable is
* @param params The send parameters
* @return The fee amount in native currency
*/
function quote(SendParams calldata params) public view returns (uint256) {
function quote(SendParams calldata params) public returns (uint256) {
return quote(_buildDispatchPost(params));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ contract WrappedHyperFungibleToken is ERC165, HyperApp, Ownable, Pausable {
* @param params The send parameters
* @return The fee amount in native currency
*/
function quote(HyperFungibleToken.SendParams calldata params) public view returns (uint256) {
function quote(HyperFungibleToken.SendParams calldata params) public returns (uint256) {
return quote(_buildDispatchPost(params));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ contract WrappedHyperFungibleTokenUpgradeable is
* @param params The send parameters
* @return The fee amount in native currency
*/
function quote(HyperFungibleTokenUpgradeable.SendParams calldata params) public view returns (uint256) {
function quote(HyperFungibleTokenUpgradeable.SendParams calldata params) public returns (uint256) {
return quote(_buildDispatchPost(params));
}

Expand Down
12 changes: 6 additions & 6 deletions sdk/packages/sdk/src/abis/hyperFungibleToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ export const HyperFungibleTokenABI = [
internalType: "uint256",
},
],
stateMutability: "view",
stateMutability: "nonpayable",
},
{
type: "function",
Expand Down Expand Up @@ -599,7 +599,7 @@ export const HyperFungibleTokenABI = [
internalType: "uint256",
},
],
stateMutability: "view",
stateMutability: "nonpayable",
},
{
type: "function",
Expand Down Expand Up @@ -650,7 +650,7 @@ export const HyperFungibleTokenABI = [
internalType: "uint256",
},
],
stateMutability: "view",
stateMutability: "nonpayable",
},
{
type: "function",
Expand Down Expand Up @@ -1670,7 +1670,7 @@ export const WrappedHyperFungibleTokenABI = [
internalType: "uint256",
},
],
stateMutability: "view",
stateMutability: "nonpayable",
},
{
type: "function",
Expand Down Expand Up @@ -1721,7 +1721,7 @@ export const WrappedHyperFungibleTokenABI = [
internalType: "uint256",
},
],
stateMutability: "view",
stateMutability: "nonpayable",
},
{
type: "function",
Expand Down Expand Up @@ -1772,7 +1772,7 @@ export const WrappedHyperFungibleTokenABI = [
internalType: "uint256",
},
],
stateMutability: "view",
stateMutability: "nonpayable",
},
{
type: "function",
Expand Down
9 changes: 6 additions & 3 deletions sdk/packages/sdk/src/protocols/hyperFungibleToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,13 +204,16 @@ export class HyperFungibleToken {

let totalNativeCost = 0n
try {
totalNativeCost = (await this.source.client.readContract({
// `quote` is non-view: the host's fee oracle is a Uniswap V3/V4 quoter that
// writes state and reverts to read the result, so it can't run under STATICCALL.
// simulateContract runs it via eth_call and returns the value without a STATICCALL.
const { result } = await this.source.client.simulateContract({
address: params.token,
abi: HyperFungibleTokenABI,
functionName: "quote",
args: [sendParams],
})) as bigint
totalNativeCost = (totalNativeCost * 101n) / 100n // 1% buffer
})
totalNativeCost = ((result as bigint) * 101n) / 100n // 1% buffer
} catch {
// host may not be set up to price native fees on this chain yet
}
Expand Down
Loading