Skip to content

Commit 43573b1

Browse files
authored
Merge pull request #5 from buttonwood-protocol/adding-fulfillment-vault-to-router
feat(router): adding fulfillment-vault to router
2 parents da1d2a2 + 0df3091 commit 43573b1

12 files changed

Lines changed: 629 additions & 308 deletions

File tree

addresses/addresses-999.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"fulfillmentVaultAddress": "0x8Bc1F48Ce0D241BB2B7EC05AF202B7dA9aaBd1d9",
33
"rolloverVaultAddress": "0x5e4A85D1BaD334A2Fdd07856201136bC6eE2e302",
4-
"routerAddress": "0xeE958d427aFEd8443F89209693EE6b7B989239fF"
4+
"routerAddress": "0x58B6943Ed3a48981eA58c0918830eFd4febbe5a1"
55
}

broadcast/DeployRouter.s.sol/999/run-1766029502056.json

Lines changed: 253 additions & 0 deletions
Large diffs are not rendered by default.

broadcast/DeployRouter.s.sol/999/run-latest.json

Lines changed: 102 additions & 100 deletions
Large diffs are not rendered by default.

pull_request_template.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
## Changes
2+
<!--- description of changes and screenshots --->
3+
4+
## Testing :
5+
<!--- How changes were tested --->
6+
7+
## Reviewers:
8+
<!--- Please tag your reviewers --->

script/DeployRouter.s.sol

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ pragma solidity ^0.8.20;
44
import {DeployFulfillmentVaultScript} from "./DeployFulfillmentVault.s.sol";
55
import {console} from "forge-std/console.sol";
66
import {Router} from "../src/Router.sol";
7+
import {RolloverVault} from "../src/RolloverVault.sol";
8+
import {FulfillmentVault} from "../src/FulfillmentVault.sol";
79

810
contract DeployRouterScript is DeployFulfillmentVaultScript {
911
Router public router;
@@ -13,7 +15,11 @@ contract DeployRouterScript is DeployFulfillmentVaultScript {
1315
}
1416

1517
function run() public virtual override {
16-
vm.startBroadcast(deployerPrivateKey);
18+
rolloverVault = RolloverVault(payable(vm.envAddress("ROLLOVER_VAULT_ADDRESS")));
19+
console.log("Rollover vault address: %s", address(rolloverVault));
20+
fulfillmentVault = FulfillmentVault(payable(vm.envAddress("FULFILLMENT_VAULT_ADDRESS")));
21+
console.log("Fulfillment vault address: %s", address(fulfillmentVault));
22+
vm.startBroadcast();
1723
deployRouter();
1824
// logAddresses();
1925
vm.stopBroadcast();
@@ -23,7 +29,12 @@ contract DeployRouterScript is DeployFulfillmentVaultScript {
2329
if (address(rolloverVault) == address(0)) {
2430
revert("Rollover vault not deployed");
2531
}
26-
router = new Router(wrappedNativeTokenAddress, generalManagerAddress, address(rolloverVault), pythAddress);
32+
if (address(fulfillmentVault) == address(0)) {
33+
revert("Fulfillment vault not deployed");
34+
}
35+
router = new Router(
36+
wrappedNativeTokenAddress, generalManagerAddress, address(rolloverVault), address(fulfillmentVault), pythAddress
37+
);
2738
router.approveCollaterals();
2839
router.approveUsdTokens();
2940
}

src/Router.sol

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {IPyth} from "@pythnetwork/IPyth.sol";
2828
import {PythErrors} from "@pythnetwork/PythErrors.sol";
2929
import {MortgageMath} from "@core/libraries/MortgageMath.sol";
3030
import {MortgagePosition} from "@core/types/MortgagePosition.sol";
31-
import {IRolloverVault} from "./interfaces/IRolloverVault/IRolloverVault.sol";
31+
import {ILiquidityVault} from "./interfaces/ILiquidityVault/ILiquidityVault.sol";
3232

3333
/**
3434
* @title Router
@@ -53,6 +53,8 @@ contract Router is
5353
/// @inheritdoc IRouter
5454
address public immutable rolloverVault;
5555
/// @inheritdoc IRouter
56+
address public immutable fulfillmentVault;
57+
/// @inheritdoc IRouter
5658
address public immutable pyth;
5759
/// @inheritdoc IRouter
5860
address public immutable wrappedNativeToken;
@@ -67,12 +69,20 @@ contract Router is
6769
* @param _wrappedNativeToken The address of the wrapped native token (i.e., whype: 0x555...)
6870
* @param _generalManager The address of the general manager contract
6971
* @param _rolloverVault The address of the rollover vault contract
72+
* @param _fulfillmentVault The address of the fulfillment vault contract
7073
* @param _pyth The address of the Pyth contract
7174
*/
72-
constructor(address _wrappedNativeToken, address _generalManager, address _rolloverVault, address _pyth) {
75+
constructor(
76+
address _wrappedNativeToken,
77+
address _generalManager,
78+
address _rolloverVault,
79+
address _fulfillmentVault,
80+
address _pyth
81+
) {
7382
wrappedNativeToken = _wrappedNativeToken;
7483
generalManager = _generalManager;
7584
rolloverVault = _rolloverVault;
85+
fulfillmentVault = _fulfillmentVault;
7686
pyth = _pyth;
7787
usdx = IGeneralManager(_generalManager).usdx();
7888
consol = IGeneralManager(_generalManager).consol();
@@ -450,20 +460,37 @@ contract Router is
450460
}
451461

452462
/**
453-
* @inheritdoc IRouter
463+
* @dev Internal function to deposit into a liquidity vault
464+
* @param vault The address of the liquidity vault to deposit into
465+
* @param usdToken The address of the usdToken to pull in
466+
* @param usdTokenAmount The amount of usdToken to pull in
454467
*/
455-
function rolloverVaultDeposit(address usdToken, uint256 usdTokenAmount) external {
468+
function _vaultDeposit(address vault, address usdToken, uint256 usdTokenAmount) internal {
456469
// Convert the usdTokenAmount to USDX
457470
uint256 usdxAmount = convert(usdToken, usdx, usdTokenAmount);
458471

459472
// Pull in the usdToken from the user
460473
_pullUsdToken(usdToken, usdxAmount);
461474

462475
// Deposit the USDX into the rollover vault
463-
IUSDX(usdx).approve(rolloverVault, usdxAmount);
464-
IRolloverVault(rolloverVault).deposit(usdx, usdxAmount);
476+
IUSDX(usdx).approve(vault, usdxAmount);
477+
ILiquidityVault(vault).deposit(usdx, usdxAmount);
465478

466-
// Transfer the rollover vault share tokens to the user
467-
IRolloverVault(rolloverVault).transfer(msg.sender, IRolloverVault(rolloverVault).balanceOf(address(this)));
479+
// Transfer the vault share tokens to the user
480+
ILiquidityVault(vault).transfer(msg.sender, ILiquidityVault(vault).balanceOf(address(this)));
481+
}
482+
483+
/**
484+
* @inheritdoc IRouter
485+
*/
486+
function rolloverVaultDeposit(address usdToken, uint256 usdTokenAmount) external {
487+
_vaultDeposit(rolloverVault, usdToken, usdTokenAmount);
488+
}
489+
490+
/**
491+
* @inheritdoc IRouter
492+
*/
493+
function fulfillmentVaultDeposit(address usdToken, uint256 usdTokenAmount) external {
494+
_vaultDeposit(fulfillmentVault, usdToken, usdTokenAmount);
468495
}
469496
}

src/interfaces/IRouter/IRouter.sol

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ interface IRouter is IRouterErrors {
2323
*/
2424
function rolloverVault() external view returns (address);
2525

26+
/**
27+
* @notice The address of the fulfillment vault contract
28+
* @return The address of the fulfillment vault contract
29+
*/
30+
function fulfillmentVault() external view returns (address);
31+
2632
/**
2733
* @notice The address of the Pyth contract
2834
* @return The address of the Pyth contract
@@ -178,4 +184,11 @@ interface IRouter is IRouterErrors {
178184
* @param usdTokenAmount The amount of usdToken to pull in
179185
*/
180186
function rolloverVaultDeposit(address usdToken, uint256 usdTokenAmount) external;
187+
188+
/**
189+
* @notice Deposit into the fulfillment vault
190+
* @param usdToken The address of the usdToken to pull in
191+
* @param usdTokenAmount The amount of usdToken to pull in
192+
*/
193+
function fulfillmentVaultDeposit(address usdToken, uint256 usdTokenAmount) external;
181194
}

test/BaseTest.t.sol

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,17 @@ import {IPyth} from "@pythnetwork/IPyth.sol";
4242
import {MockPyth} from "@pythnetwork/MockPyth.sol";
4343
import {MockERC20} from "./mocks/MockERC20.sol";
4444
import {RolloverVault} from "../src/RolloverVault.sol";
45+
import {FulfillmentVault} from "../src/FulfillmentVault.sol";
46+
import {CoreSimulatorLib} from "@hyper-evm-lib/test/simulation/CoreSimulatorLib.sol";
47+
import {PrecompileLib} from "@hyper-evm-lib/src/PrecompileLib.sol";
48+
import {HyperCore} from "@hyper-evm-lib/test/simulation/HyperCore.sol";
49+
import {TokenRegistry} from "@hyper-evm-lib/src/registry/TokenRegistry.sol";
50+
import {HLConversions} from "@hyper-evm-lib/src/common/HLConversions.sol";
51+
import {HLConstants} from "@hyper-evm-lib/src/common/HLConstants.sol";
4552

4653
contract BaseTest is Test {
4754
using OPoolConfigIdLibrary for OPoolConfigId;
55+
using PrecompileLib for address;
4856

4957
// Actors
5058
address public admin = makeAddr("admin");
@@ -112,6 +120,27 @@ contract BaseTest is Test {
112120
uint8 ROLLLOVER_VAULT_DECIMALS = 24; // Make this 6 + usdx decimals
113121
uint8 ROLLLOVER_VAULT_DECIMALS_OFFSET = 6;
114122
RolloverVault public rolloverVault;
123+
// Hyper-EVM-Lib Values
124+
HyperCore public hyperCore;
125+
TokenRegistry public tokenRegistry;
126+
address public HYPER_CORE_ADDRESS = 0x9999999999999999999999999999999999999999;
127+
address TOKEN_INFO_PRECOMPILE_ADDRESS = 0x000000000000000000000000000000000000080C;
128+
address SPOT_INFO_PRECOMPILE_ADDRESS = 0x000000000000000000000000000000000000080b;
129+
address SPOT_PX_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000808;
130+
address public TOKEN_REGISTRY_ADDRESS = 0x0b51d1A9098cf8a72C325003F44C194D41d7A85B;
131+
address public UBTC_SYSTEM_ADDRESS = 0x20000000000000000000000000000000000000c5;
132+
uint32 public HYPE_TOKEN_INDEX = uint32(HLConstants.hypeTokenIndex());
133+
uint32 public USDC_TOKEN_INDEX = 0;
134+
uint32 public USDT_TOKEN_INDEX = 268;
135+
uint32 public USDH_TOKEN_INDEX = 360;
136+
uint32 public UBTC_TOKEN_INDEX = 197;
137+
// FulfillmentVault Args
138+
FulfillmentVault public fulfillmentVault;
139+
string FULFILLMENT_VAULT_NAME = "Test Fulfillment Vault";
140+
string FULFILLMENT_VAULT_SYMBOL = "tFT";
141+
uint8 FULFILLMENT_VAULT_DECIMALS = 24;
142+
uint8 FULFILLMENT_VAULT_DECIMALS_OFFSET = 6;
143+
// Vault Priming Amount
115144
uint256 public PRIME_AMOUNT = 1e18; // The initial amount of depositableAsset to prime the liquidityVault with (in depositableAsset decimals)
116145

117146
function _deployWHype() internal {
@@ -428,5 +457,154 @@ contract BaseTest is Test {
428457
vm.stopPrank();
429458
}
430459

460+
function mockTokenInfo(
461+
uint32 tokenIndex,
462+
address evmContract,
463+
string memory name,
464+
uint8 szDecimals,
465+
uint8 weiDecimals,
466+
int8 evmExtraWeiDecimals
467+
) internal {
468+
PrecompileLib.TokenInfo memory info = PrecompileLib.TokenInfo({
469+
name: name,
470+
spots: new uint64[](0),
471+
deployerTradingFeeShare: 0,
472+
deployer: address(0),
473+
evmContract: evmContract,
474+
szDecimals: szDecimals,
475+
weiDecimals: weiDecimals,
476+
evmExtraWeiDecimals: evmExtraWeiDecimals
477+
});
478+
479+
vm.mockCall(TOKEN_INFO_PRECOMPILE_ADDRESS, abi.encode(tokenIndex), abi.encode(info));
480+
if (tokenIndex != HYPE_TOKEN_INDEX && tokenIndex != USDC_TOKEN_INDEX) {
481+
tokenRegistry.setTokenInfo(tokenIndex);
482+
}
483+
}
484+
485+
function mockSpotInfo(uint32 spotIndex, string memory name, uint64[2] memory tokens) internal {
486+
PrecompileLib.SpotInfo memory info = PrecompileLib.SpotInfo({name: name, tokens: tokens});
487+
vm.mockCall(SPOT_INFO_PRECOMPILE_ADDRESS, abi.encode(spotIndex), abi.encode(info));
488+
}
489+
490+
function mockSpotPx(uint32 spotIndex, uint64 px) internal {
491+
vm.mockCall(SPOT_PX_PRECOMPILE_ADDRESS, abi.encode(spotIndex), abi.encode(px));
492+
}
493+
494+
function setupHyperCore() internal {
495+
hyperCore = new HyperCore();
496+
vm.etch(HYPER_CORE_ADDRESS, address(hyperCore).code);
497+
vm.label(HYPER_CORE_ADDRESS, "HyperCore");
498+
hyperCore = CoreSimulatorLib.init();
499+
hyperCore.setUseRealL1Read(false);
500+
}
501+
502+
function setupTokenRegistry() internal {
503+
tokenRegistry = new TokenRegistry();
504+
vm.etch(TOKEN_REGISTRY_ADDRESS, address(tokenRegistry).code);
505+
vm.label(TOKEN_REGISTRY_ADDRESS, "TokenRegistry");
506+
tokenRegistry = TokenRegistry(TOKEN_REGISTRY_ADDRESS);
507+
mockTokenInfo(USDT_TOKEN_INDEX, address(usdt), "USDT", 2, 8, -2);
508+
mockTokenInfo(USDH_TOKEN_INDEX, address(usdh), "USDH", 2, 8, -2);
509+
mockTokenInfo(HYPE_TOKEN_INDEX, address(0), "HYPE", 2, 8, 0);
510+
mockTokenInfo(USDC_TOKEN_INDEX, address(0), "USDC", 8, 8, 0);
511+
mockTokenInfo(UBTC_TOKEN_INDEX, address(ubtc), "UBTC", 5, 10, -2);
512+
}
513+
514+
function setupSpotInfo() internal {
515+
uint64[2] memory tokens = [uint64(USDT_TOKEN_INDEX), uint64(USDC_TOKEN_INDEX)];
516+
mockSpotInfo(USDT_TOKEN_INDEX, "@166", tokens);
517+
tokens = [uint64(USDH_TOKEN_INDEX), uint64(USDC_TOKEN_INDEX)];
518+
mockSpotInfo(USDH_TOKEN_INDEX, "@230", tokens);
519+
tokens = [uint64(HYPE_TOKEN_INDEX), uint64(USDC_TOKEN_INDEX)];
520+
mockSpotInfo(HYPE_TOKEN_INDEX, "@107", tokens);
521+
tokens = [uint64(UBTC_TOKEN_INDEX), uint64(USDC_TOKEN_INDEX)];
522+
mockSpotInfo(UBTC_TOKEN_INDEX, "@142", tokens);
523+
}
524+
525+
function primeFulfillmentVault() public {
526+
// Mint 0.5 PRIME_AMOUNT of usdt0 and usdh to the admin
527+
vm.startPrank(admin);
528+
uint256 usdtAmount = usdx.convertUnderlying(address(usdt), PRIME_AMOUNT / 2);
529+
uint256 usdhAmount = usdx.convertUnderlying(address(usdh), PRIME_AMOUNT / 2);
530+
deal(address(usdt), admin, usdtAmount);
531+
deal(address(usdh), admin, usdhAmount);
532+
vm.stopPrank();
533+
534+
// Admin primes the fulfillmentVault with PRIME_AMOUNT of usdx
535+
vm.startPrank(admin);
536+
usdt.approve(address(usdx), usdtAmount);
537+
usdh.approve(address(usdx), usdhAmount);
538+
usdx.deposit(address(usdt), usdtAmount);
539+
usdx.deposit(address(usdh), usdhAmount);
540+
usdx.approve(address(fulfillmentVault), PRIME_AMOUNT);
541+
fulfillmentVault.deposit(address(usdx), PRIME_AMOUNT);
542+
vm.stopPrank();
543+
544+
// Transfer the fulfillmentVault balance to the fulfillmentVault itself
545+
vm.startPrank(admin);
546+
fulfillmentVault.transfer(address(fulfillmentVault), fulfillmentVault.balanceOf(admin));
547+
vm.stopPrank();
548+
}
549+
550+
function setUpFulfillmentVault() public {
551+
// Initialize the HyperCore simulator
552+
// vm.createSelectFork(vm.rpcUrl("hyperliquid"), 17133085);
553+
setupHyperCore();
554+
555+
// Setup core
556+
setUpCore();
557+
558+
// Setup the mock token registry
559+
setupTokenRegistry();
560+
561+
// Setup the mock spot info
562+
setupSpotInfo();
563+
564+
// Deploy the fulfillmentVault
565+
FulfillmentVault fulfillmentVaultImplementation = new FulfillmentVault();
566+
bytes memory initializerData = abi.encodeWithSelector(
567+
FulfillmentVault.initialize.selector,
568+
FULFILLMENT_VAULT_NAME,
569+
FULFILLMENT_VAULT_SYMBOL,
570+
FULFILLMENT_VAULT_DECIMALS,
571+
FULFILLMENT_VAULT_DECIMALS_OFFSET,
572+
address(whype),
573+
address(generalManager),
574+
address(admin)
575+
);
576+
ERC1967Proxy proxy = new ERC1967Proxy(address(fulfillmentVaultImplementation), initializerData);
577+
fulfillmentVault = FulfillmentVault(payable(address(proxy)));
578+
579+
// Prime the fulfillmentVault
580+
primeFulfillmentVault();
581+
582+
// Grant the keeper the KEEPER_ROLE
583+
vm.startPrank(admin);
584+
fulfillmentVault.grantRole(fulfillmentVault.KEEPER_ROLE(), keeper);
585+
vm.stopPrank();
586+
587+
// Force the fulfillmentVault to be activated on hypercore
588+
vm.mockCall(
589+
HLConstants.CORE_USER_EXISTS_PRECOMPILE_ADDRESS, abi.encode(address(fulfillmentVault)), abi.encode(true)
590+
);
591+
CoreSimulatorLib.forceAccountActivation(address(fulfillmentVault));
592+
593+
// Force activate the HYPE system address so bridging works
594+
vm.mockCall(
595+
HLConstants.CORE_USER_EXISTS_PRECOMPILE_ADDRESS, abi.encode(HLConstants.HYPE_SYSTEM_ADDRESS), abi.encode(true)
596+
);
597+
CoreSimulatorLib.forceAccountActivation(HLConstants.HYPE_SYSTEM_ADDRESS);
598+
599+
// Force activate the UBTC system address so bridging works
600+
vm.mockCall(HLConstants.CORE_USER_EXISTS_PRECOMPILE_ADDRESS, abi.encode(UBTC_SYSTEM_ADDRESS), abi.encode(true));
601+
CoreSimulatorLib.forceAccountActivation(UBTC_SYSTEM_ADDRESS);
602+
603+
// Grant the fulfillmentVault the orderPool's FULFILLMENT_VAULT_ROLE
604+
vm.startPrank(admin);
605+
IAccessControl(address(orderPool)).grantRole(Roles.FULFILLMENT_ROLE, address(fulfillmentVault));
606+
vm.stopPrank();
607+
}
608+
431609
function test_constructor() public view virtual {}
432610
}

0 commit comments

Comments
 (0)