-
Notifications
You must be signed in to change notification settings - Fork 33
add bridge validation hook #537
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
6de5cdd
7fe82b4
80f4dd3
4b149c4
adcdfb6
d1ca802
82433cd
b3661da
0066259
c792c08
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,167 @@ | ||||||||||||||||||||||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||||||||||||||||||||||
| pragma solidity 0.8.19; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import {xWELLRouter} from "@protocol/xWELL/xWELLRouter.sol"; | ||||||||||||||||||||||
| import {ProposalAction} from "@proposals/proposalTypes/IProposal.sol"; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /// @title BridgeValidationHook | ||||||||||||||||||||||
| /// @notice Hook to validate bridgeToRecipient calls in proposals | ||||||||||||||||||||||
| /// @dev Ensures that the native value sent with bridge calls is between 5x and 10x | ||||||||||||||||||||||
| /// the actual bridge cost returned by router.bridgeCost(destinationChain) | ||||||||||||||||||||||
| abstract contract BridgeValidationHook { | ||||||||||||||||||||||
| /// @notice Function selector for bridgeToRecipient(address,uint256,uint16) | ||||||||||||||||||||||
| bytes4 private constant BRIDGE_TO_RECIPIENT_SELECTOR = | ||||||||||||||||||||||
| xWELLRouter.bridgeToRecipient.selector; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /// @notice Minimum multiplier for bridge cost (5x) | ||||||||||||||||||||||
| uint256 private constant MIN_BRIDGE_COST_MULTIPLIER = 5; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /// @notice Maximum multiplier for bridge cost (10x) | ||||||||||||||||||||||
| uint256 private constant MAX_BRIDGE_COST_MULTIPLIER = 10; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /// @notice Verify bridge-related proposal actions before execution | ||||||||||||||||||||||
| /// @dev Called by inheriting contracts to validate bridge cost parameters | ||||||||||||||||||||||
| /// @param proposal Array of proposal actions to validate | ||||||||||||||||||||||
| function _verifyBridgeActions( | ||||||||||||||||||||||
| ProposalAction[] memory proposal | ||||||||||||||||||||||
| ) internal view { | ||||||||||||||||||||||
| uint256 proposalLength = proposal.length; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| for (uint256 i = 0; i < proposalLength; i++) { | ||||||||||||||||||||||
| bytes4 selector = bytesToBytes4(proposal[i].data); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Check if this action is a bridgeToRecipient call | ||||||||||||||||||||||
| if (selector == BRIDGE_TO_RECIPIENT_SELECTOR) { | ||||||||||||||||||||||
| address router = proposal[i].target; | ||||||||||||||||||||||
| uint256 actionValue = proposal[i].value; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Validate router is a contract | ||||||||||||||||||||||
| _validateRouterIsContract(router); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Extract wormholeChainId from calldata | ||||||||||||||||||||||
| // Calldata structure: | ||||||||||||||||||||||
| // 0-3: function selector | ||||||||||||||||||||||
| // 4-35: address to (32 bytes) | ||||||||||||||||||||||
| // 36-67: uint256 amount (32 bytes) | ||||||||||||||||||||||
| // 68-99: uint16 wormholeChainId (32 bytes, right-padded) | ||||||||||||||||||||||
| uint16 wormholeChainId = extractUint16FromCalldata( | ||||||||||||||||||||||
| proposal[i].data | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Get the actual bridge cost from the router with validation | ||||||||||||||||||||||
| uint256 bridgeCost = _getBridgeCost(router, wormholeChainId); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Validate that action value is between 5x and 10x the bridge cost | ||||||||||||||||||||||
| uint256 minValue = bridgeCost * MIN_BRIDGE_COST_MULTIPLIER; | ||||||||||||||||||||||
| uint256 maxValue = bridgeCost * MAX_BRIDGE_COST_MULTIPLIER; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| require( | ||||||||||||||||||||||
| actionValue >= minValue, | ||||||||||||||||||||||
| string.concat( | ||||||||||||||||||||||
| "BridgeValidationHook: bridge value too low. Expected >= ", | ||||||||||||||||||||||
| _toString(minValue), | ||||||||||||||||||||||
| ", got ", | ||||||||||||||||||||||
| _toString(actionValue) | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| require( | ||||||||||||||||||||||
| actionValue <= maxValue, | ||||||||||||||||||||||
| string.concat( | ||||||||||||||||||||||
| "BridgeValidationHook: bridge value too high. Expected <= ", | ||||||||||||||||||||||
| _toString(maxValue), | ||||||||||||||||||||||
| ", got ", | ||||||||||||||||||||||
| _toString(actionValue) | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /// @notice Validates that the router address is a contract | ||||||||||||||||||||||
| /// @param router The router address to validate | ||||||||||||||||||||||
| function _validateRouterIsContract(address router) private view { | ||||||||||||||||||||||
| require( | ||||||||||||||||||||||
| router.code.length > 0, | ||||||||||||||||||||||
| "BridgeValidationHook: router must be a contract" | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /// @notice Gets bridge cost from router and validates it's non-zero | ||||||||||||||||||||||
| /// @param router The router contract address | ||||||||||||||||||||||
| /// @param wormholeChainId The destination chain ID | ||||||||||||||||||||||
| /// @return bridgeCost The validated bridge cost | ||||||||||||||||||||||
| function _getBridgeCost( | ||||||||||||||||||||||
| address router, | ||||||||||||||||||||||
| uint16 wormholeChainId | ||||||||||||||||||||||
| ) private view returns (uint256 bridgeCost) { | ||||||||||||||||||||||
| bridgeCost = xWELLRouter(router).bridgeCost(wormholeChainId); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| require( | ||||||||||||||||||||||
| bridgeCost > 0, | ||||||||||||||||||||||
| "BridgeValidationHook: bridge cost must be greater than zero" | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /// @notice Extract uint16 value from calldata at the third parameter position | ||||||||||||||||||||||
| /// @param input The calldata to extract from | ||||||||||||||||||||||
| /// @return result The extracted uint16 value | ||||||||||||||||||||||
| function extractUint16FromCalldata( | ||||||||||||||||||||||
| bytes memory input | ||||||||||||||||||||||
| ) public pure returns (uint16 result) { | ||||||||||||||||||||||
| require( | ||||||||||||||||||||||
| input.length >= 100, | ||||||||||||||||||||||
| "BridgeValidationHook: invalid calldata length" | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // The uint16 wormholeChainId is the third parameter, starting at byte 68 | ||||||||||||||||||||||
| // It's stored in the last 2 bytes of a 32-byte word | ||||||||||||||||||||||
| bytes32 rawBytes; | ||||||||||||||||||||||
| assembly { | ||||||||||||||||||||||
| // Skip 32 bytes (array length) + 4 bytes (selector) + 64 bytes (first two params) | ||||||||||||||||||||||
| // = 100 bytes total, so we load from position 68 after the length prefix | ||||||||||||||||||||||
| let dataPointer := add(add(input, 0x20), 0x44) // 0x20 (32) + 0x44 (68) = 100 | ||||||||||||||||||||||
|
Comment on lines
+121
to
+123
|
||||||||||||||||||||||
| // Skip 32 bytes (array length) + 4 bytes (selector) + 64 bytes (first two params) | |
| // = 100 bytes total, so we load from position 68 after the length prefix | |
| let dataPointer := add(add(input, 0x20), 0x44) // 0x20 (32) + 0x44 (68) = 100 | |
| // To reach the third parameter (uint16 wormholeChainId), skip: | |
| // - 0x20 (32 bytes) for the length prefix | |
| // - 0x04 (4 bytes) for the function selector | |
| // - 0x40 (64 bytes) for the first two parameters | |
| // Total offset: 0x20 + 0x04 + 0x40 = 0x64 (100 decimal) bytes from the start of the bytes array in memory. | |
| // Since input points to the start of the bytes array, add 0x20 for the length prefix, then 0x44 (68 decimal) to reach the third parameter. | |
| let dataPointer := add(add(input, 0x20), 0x44) // 0x20 (32) + 0x44 (68) = 100 (decimal) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing SPDX license identifier. Add
// SPDX-License-Identifier: GPL-3.0-or-laterat the top of the file to match the project's license convention.