-
Notifications
You must be signed in to change notification settings - Fork 113
Description
Summary: The sendMessage function in WormholeBridgeForColony cannot successfully publish messages to Wormhole on chains with non-zero message fees because it fails to pay the required fee. This completely disables outbound cross-chain communication on all production networks.
Vulnerability Description:
The Wormhole protocol requires a fee to publish messages, as documented in the publishMessage function (https://wormhole.com/docs/products/messaging/reference/core-contract-evm/#publishmessage). Looking at the implementation contract (https://github.com/wormhole-foundation/wormhole/blob/main/ethereum/contracts/Implementation.sol) referenced in the doc, the function enforces this requirement strictly:
function publishMessage(
uint32 nonce,
bytes memory payload,
uint8 consistencyLevel
) public payable returns (uint64 sequence) {
// check fee
require(msg.value == messageFee(), "invalid fee");
sequence = useSequence(msg.sender);
emit LogMessagePublished(msg.sender, sequence, nonce, payload, consistencyLevel);
}The require(msg.value == messageFee(), "invalid fee"); statement mandates that the exact fee amount must be sent with the transaction. The fee amount is determined by calling messageFee() on the Wormhole contract, which returns the required fee in wei.
However, in WormholeBridgeForColony.sol, the sendMessage function has two critical flaws:
function sendMessage(
uint256 _evmChainId,
bytes memory _payload
) public onlyColonyNetwork returns (bool) {
require(supportedEvmChainId(_evmChainId), "colony-bridge-not-known-chain");
// This returns a sequence, but we don't care about it
// The first sequence ID is, I believe 0, so all return values are potentially valid
// slither-disable-next-line unused-return
try wormhole.publishMessage(0, _payload, 0) {
return true;
} catch {
return false;
}
}Flaw 1: The sendMessage function is not declared as payable. This means it cannot receive ETH and msg.value will always be 0 when this function executes.
Flaw 2: Even if the function were payable, it doesn't forward any value when calling wormhole.publishMessage(0, _payload, 0). The call syntax doesn't include {value: someAmount}, so msg.value received by the Wormhole contract will be 0.
When sendMessage attempts to call wormhole.publishMessage, the Wormhole contract checks if msg.value == messageFee(). On production chains, messageFee() typically returns a non-zero value to prevent spam. Since msg.value is 0 but messageFee() is greater than 0, the equality check fails and the transaction reverts with "invalid fee".
The try/catch block catches this revert and returns false instead of propagating the error. While this prevents the entire transaction from failing, it silently disables all outbound bridging functionality.
Impact:
-
Complete Loss of Outbound Bridging: On any chain where Wormhole charges a non-zero message fee (which includes all major production networks), the bridge cannot send any messages. Every call to
sendMessagewill fail and returnfalse. -
Silent Failure: Because the function uses
try/catchand returnsfalseinstead of reverting, the calling code (colonyNetwork) may not properly detect or handle the failure. This could lead to inconsistent state where the local chain believes a cross-chain action was initiated, but no message was actually sent. -
Broken Cross-Chain Operations: Any Colony Network functionality that depends on cross-chain communication (governance decisions, token transfers, state synchronization, etc.) will be completely non-functional.
-
Economic Lock-In: If users or the protocol have already committed resources expecting cross-chain functionality to work, those resources could be effectively locked or stranded.
-
No Testnet Detection: This bug may not appear on testnets where
messageFee()returns 0, creating a false sense of security during testing that doesn't reflect production behavior.
Fix:
The fix requires two changes to the sendMessage function:
function sendMessage(
uint256 _evmChainId,
bytes memory _payload
) public payable onlyColonyNetwork returns (bool) { // Add 'payable' modifier
require(supportedEvmChainId(_evmChainId), "colony-bridge-not-known-chain");
// Get the required fee from Wormhole
uint256 wormholeFee = wormhole.messageFee();
// Ensure sufficient ETH was provided
require(msg.value >= wormholeFee, "colony-bridge-insufficient-fee");
// slither-disable-next-line unused-return
try wormhole.publishMessage{value: wormholeFee}(0, _payload, 0) {
// Refund excess ETH if any
if (msg.value > wormholeFee) {
(bool refundSuccess, ) = msg.sender.call{value: msg.value - wormholeFee}("");
require(refundSuccess, "colony-bridge-refund-failed");
}
return true;
} catch {
// Refund all ETH on failure
(bool refundSuccess, ) = msg.sender.call{value: msg.value}("");
require(refundSuccess, "colony-bridge-refund-failed");
return false;
}
}