From d951f8f67f4e48922e3391bb1f87ba73d1ae35c4 Mon Sep 17 00:00:00 2001 From: tac0turtle Date: Thu, 4 Dec 2025 17:26:39 +0100 Subject: [PATCH 1/4] update FeeVault constructor to accept additional parameters for deployment configuration --- contracts/script/DeployFeeVault.s.sol | 94 +++++++++++++-------------- contracts/src/FeeVault.sol | 20 +++++- contracts/test/FeeVault.t.sol | 31 +++++---- 3 files changed, 83 insertions(+), 62 deletions(-) diff --git a/contracts/script/DeployFeeVault.s.sol b/contracts/script/DeployFeeVault.s.sol index 6d9b986..a8f0d91 100644 --- a/contracts/script/DeployFeeVault.s.sol +++ b/contracts/script/DeployFeeVault.s.sol @@ -10,81 +10,79 @@ contract DeployFeeVault is Script { address owner = vm.envAddress("OWNER"); bytes32 salt = vm.envOr("SALT", bytes32(0)); - // Optional: Post-deployment configuration uint32 destinationDomain = uint32(vm.envOr("DESTINATION_DOMAIN", uint256(0))); bytes32 recipientAddress = vm.envOr("RECIPIENT_ADDRESS", bytes32(0)); uint256 minimumAmount = vm.envOr("MINIMUM_AMOUNT", uint256(0)); uint256 callFee = vm.envOr("CALL_FEE", uint256(0)); - uint256 bridgeShareBps = vm.envOr("BRIDGE_SHARE_BPS", uint256(10000)); + uint256 bridgeShareBps = vm.envOr("BRIDGE_SHARE_BPS", uint256(0)); // 0 defaults to 10000 in constructor address otherRecipient = vm.envOr("OTHER_RECIPIENT", address(0)); // =================================== - // Compute address before deployment - address predicted = computeAddress(salt, owner); - console.log("Predicted FeeVault address:", predicted); - vm.startBroadcast(); // Deploy FeeVault with CREATE2 - FeeVault feeVault = new FeeVault{salt: salt}(owner); - console.log("FeeVault deployed at:", address(feeVault)); - require(address(feeVault) == predicted, "Address mismatch"); - - // Configure if values provided - if (destinationDomain != 0 && recipientAddress != bytes32(0)) { - feeVault.setRecipient(destinationDomain, recipientAddress); - console.log("Recipient set - domain:", destinationDomain); - } - - if (minimumAmount > 0) { - feeVault.setMinimumAmount(minimumAmount); - console.log("Minimum amount set:", minimumAmount); - } - - if (callFee > 0) { - feeVault.setCallFee(callFee); - console.log("Call fee set:", callFee); - } - - if (bridgeShareBps != 10000) { - feeVault.setBridgeShare(bridgeShareBps); - console.log("Bridge share set:", bridgeShareBps, "bps"); - } - - if (otherRecipient != address(0)) { - feeVault.setOtherRecipient(otherRecipient); - console.log("Other recipient set:", otherRecipient); - } + FeeVault feeVault = new FeeVault{salt: salt}( + owner, + destinationDomain, + recipientAddress, + minimumAmount, + callFee, + bridgeShareBps, + otherRecipient + ); vm.stopBroadcast(); + console.log("FeeVault deployed at:", address(feeVault)); + console.log("Owner:", owner); + console.log("Destination domain:", destinationDomain); + console.log("Minimum amount:", minimumAmount); + console.log("Call fee:", callFee); + console.log("Bridge share bps:", feeVault.bridgeShareBps()); console.log(""); console.log("NOTE: Call setHypNativeMinter() after deploying HypNativeMinter"); } - - /// @notice Compute the CREATE2 address for FeeVault deployment - function computeAddress(bytes32 salt, address owner) public view returns (address) { - bytes32 bytecodeHash = keccak256(abi.encodePacked(type(FeeVault).creationCode, abi.encode(owner))); - return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, bytecodeHash))))); - } } -/// @notice Standalone script to compute FeeVault address without deploying +/// @notice Compute FeeVault CREATE2 address off-chain +/// @dev Use this to predict the address before deploying +/// Requires env vars: DEPLOYER (EOA), OWNER, SALT (optional), and all constructor args contract ComputeFeeVaultAddress is Script { function run() external view { - address owner = vm.envAddress("OWNER"); - bytes32 salt = vm.envOr("SALT", bytes32(0)); address deployer = vm.envAddress("DEPLOYER"); + bytes32 salt = vm.envOr("SALT", bytes32(0)); - bytes32 bytecodeHash = keccak256(abi.encodePacked(type(FeeVault).creationCode, abi.encode(owner))); + address owner = vm.envAddress("OWNER"); + uint32 destinationDomain = uint32(vm.envOr("DESTINATION_DOMAIN", uint256(0))); + bytes32 recipientAddress = vm.envOr("RECIPIENT_ADDRESS", bytes32(0)); + uint256 minimumAmount = vm.envOr("MINIMUM_AMOUNT", uint256(0)); + uint256 callFee = vm.envOr("CALL_FEE", uint256(0)); + uint256 bridgeShareBps = vm.envOr("BRIDGE_SHARE_BPS", uint256(0)); + address otherRecipient = vm.envOr("OTHER_RECIPIENT", address(0)); - address predicted = - address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, bytecodeHash))))); + bytes32 initCodeHash = keccak256( + abi.encodePacked( + type(FeeVault).creationCode, + abi.encode( + owner, + destinationDomain, + recipientAddress, + minimumAmount, + callFee, + bridgeShareBps, + otherRecipient + ) + ) + ); + + address predicted = address( + uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, initCodeHash)))) + ); console.log("========== FeeVault Address Computation =========="); + console.log("Deployer (EOA):", deployer); console.log("Owner:", owner); console.log("Salt:", vm.toString(salt)); - console.log("Deployer:", deployer); console.log("Predicted address:", predicted); console.log("=================================================="); } diff --git a/contracts/src/FeeVault.sol b/contracts/src/FeeVault.sol index 296d33d..202c900 100644 --- a/contracts/src/FeeVault.sol +++ b/contracts/src/FeeVault.sol @@ -36,9 +36,25 @@ contract FeeVault { _; } - constructor(address _owner) { + constructor( + address _owner, + uint32 _destinationDomain, + bytes32 _recipientAddress, + uint256 _minimumAmount, + uint256 _callFee, + uint256 _bridgeShareBps, + address _otherRecipient + ) { + require(_bridgeShareBps <= 10000, "FeeVault: invalid bps"); + owner = _owner; - bridgeShareBps = 10000; // Default to 100% bridge + destinationDomain = _destinationDomain; + recipientAddress = _recipientAddress; + minimumAmount = _minimumAmount; + callFee = _callFee; + bridgeShareBps = _bridgeShareBps == 0 ? 10000 : _bridgeShareBps; + otherRecipient = _otherRecipient; + emit OwnershipTransferred(address(0), _owner); } diff --git a/contracts/test/FeeVault.t.sol b/contracts/test/FeeVault.t.sol index 0af644c..25be73b 100644 --- a/contracts/test/FeeVault.t.sol +++ b/contracts/test/FeeVault.t.sol @@ -35,15 +35,18 @@ contract FeeVaultTest is Test { user = address(0x1); otherRecipient = address(0x99); mockMinter = new MockHypNativeMinter(); - feeVault = new FeeVault(owner); - // Configure contract + feeVault = new FeeVault( + owner, + destination, + recipient, + minAmount, + fee, + 10000, // 100% bridge share + otherRecipient + ); + feeVault.setHypNativeMinter(address(mockMinter)); - feeVault.setRecipient(destination, recipient); - feeVault.setMinimumAmount(minAmount); - feeVault.setCallFee(fee); - feeVault.setOtherRecipient(otherRecipient); - // Default bridge share is 10000 (100%) } function test_Receive() public { @@ -207,11 +210,15 @@ contract FeeVaultTest is Test { function test_SendToCelestia_MinterNotSet() public { // Deploy fresh vault without minter - FeeVault freshVault = new FeeVault(owner); - freshVault.setRecipient(destination, recipient); - freshVault.setMinimumAmount(minAmount); - freshVault.setCallFee(fee); - freshVault.setOtherRecipient(otherRecipient); + FeeVault freshVault = new FeeVault( + owner, + destination, + recipient, + minAmount, + fee, + 10000, + otherRecipient + ); (bool success,) = address(freshVault).call{value: minAmount}(""); require(success); From cfbd87e3e67bed719c8c5a844fbb49075d302057 Mon Sep 17 00:00:00 2001 From: tac0turtle Date: Thu, 4 Dec 2025 17:28:25 +0100 Subject: [PATCH 2/4] lint --- contracts/script/DeployFeeVault.s.sol | 21 ++++----------------- contracts/test/FeeVault.t.sol | 10 +--------- 2 files changed, 5 insertions(+), 26 deletions(-) diff --git a/contracts/script/DeployFeeVault.s.sol b/contracts/script/DeployFeeVault.s.sol index a8f0d91..54b7e0f 100644 --- a/contracts/script/DeployFeeVault.s.sol +++ b/contracts/script/DeployFeeVault.s.sol @@ -22,13 +22,7 @@ contract DeployFeeVault is Script { // Deploy FeeVault with CREATE2 FeeVault feeVault = new FeeVault{salt: salt}( - owner, - destinationDomain, - recipientAddress, - minimumAmount, - callFee, - bridgeShareBps, - otherRecipient + owner, destinationDomain, recipientAddress, minimumAmount, callFee, bridgeShareBps, otherRecipient ); vm.stopBroadcast(); @@ -64,20 +58,13 @@ contract ComputeFeeVaultAddress is Script { abi.encodePacked( type(FeeVault).creationCode, abi.encode( - owner, - destinationDomain, - recipientAddress, - minimumAmount, - callFee, - bridgeShareBps, - otherRecipient + owner, destinationDomain, recipientAddress, minimumAmount, callFee, bridgeShareBps, otherRecipient ) ) ); - address predicted = address( - uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, initCodeHash)))) - ); + address predicted = + address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, initCodeHash))))); console.log("========== FeeVault Address Computation =========="); console.log("Deployer (EOA):", deployer); diff --git a/contracts/test/FeeVault.t.sol b/contracts/test/FeeVault.t.sol index 25be73b..dbb4301 100644 --- a/contracts/test/FeeVault.t.sol +++ b/contracts/test/FeeVault.t.sol @@ -210,15 +210,7 @@ contract FeeVaultTest is Test { function test_SendToCelestia_MinterNotSet() public { // Deploy fresh vault without minter - FeeVault freshVault = new FeeVault( - owner, - destination, - recipient, - minAmount, - fee, - 10000, - otherRecipient - ); + FeeVault freshVault = new FeeVault(owner, destination, recipient, minAmount, fee, 10000, otherRecipient); (bool success,) = address(freshVault).call{value: minAmount}(""); require(success); From 688323ab84391d9f0c894c16f4a4619774488b0f Mon Sep 17 00:00:00 2001 From: tac0turtle Date: Thu, 4 Dec 2025 19:24:44 +0100 Subject: [PATCH 3/4] updates --- contracts/README.md | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/contracts/README.md b/contracts/README.md index 70e91a4..9465e1c 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -33,18 +33,21 @@ The FeeVault uses CREATE2 for deterministic addresses across chains. ### Environment Variables -| Variable | Deploy | Operational | Description | -|----------|--------|-------------|-------------| -| `OWNER` | Required | - | Owner address (can configure the vault) | -| `SALT` | Optional | - | CREATE2 salt (default: `0x0`). Use any bytes32 value | -| `DESTINATION_DOMAIN` | Optional | Required | Hyperlane destination chain ID | -| `RECIPIENT_ADDRESS` | Optional | Required | Recipient on destination chain (bytes32, left-padded) | -| `MINIMUM_AMOUNT` | Optional | Optional | Minimum wei to bridge | -| `CALL_FEE` | Optional | Optional | Fee in wei for calling `sendToCelestia()` | -| `BRIDGE_SHARE_BPS` | Optional | Optional | Basis points to bridge (default: 10000 = 100%) | -| `OTHER_RECIPIENT` | Optional | Required* | Address to receive non-bridged portion | - -*`OTHER_RECIPIENT` is required only if `BRIDGE_SHARE_BPS` < 10000 +All configuration is set via constructor arguments at deploy time: + +| Variable | Required | Description | +|----------|----------|-------------| +| `OWNER` | Yes | Owner address (can configure the vault post-deployment) | +| `SALT` | No | CREATE2 salt (default: `0x0`). Use any bytes32 value | +| `DESTINATION_DOMAIN` | Yes* | Hyperlane destination chain ID | +| `RECIPIENT_ADDRESS` | Yes* | Recipient on destination chain (bytes32, left-padded) | +| `MINIMUM_AMOUNT` | No | Minimum wei to bridge (default: 0) | +| `CALL_FEE` | No | Fee in wei for calling `sendToCelestia()` (default: 0) | +| `BRIDGE_SHARE_BPS` | No | Basis points to bridge (default: 10000 = 100%) | +| `OTHER_RECIPIENT` | No** | Address to receive non-bridged portion | + +*Required for the vault to be operational (can be set to 0 at deploy and configured later via setters) +**Required if `BRIDGE_SHARE_BPS` < 10000 **Note:** `HYP_NATIVE_MINTER` must be set via `setHypNativeMinter()` after deployment for the vault to be operational. From beccaa2d9f24e52a6adac89077e845505562ba45 Mon Sep 17 00:00:00 2001 From: tac0turtle Date: Mon, 8 Dec 2025 14:04:11 +0100 Subject: [PATCH 4/4] ++ --- contracts/src/FeeVault.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/src/FeeVault.sol b/contracts/src/FeeVault.sol index 202c900..932538b 100644 --- a/contracts/src/FeeVault.sol +++ b/contracts/src/FeeVault.sol @@ -45,6 +45,7 @@ contract FeeVault { uint256 _bridgeShareBps, address _otherRecipient ) { + require(_owner != address(0), "FeeVault: owner is the zero address"); require(_bridgeShareBps <= 10000, "FeeVault: invalid bps"); owner = _owner;