Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ If a sender contract is already deployed, you should not need to deploy a new on

During the initial deployment, you will need to additionally provide the Wormhole Core Bridge contract address from Ethereum as an argument. The Wormhole Core Bridge contract address information can be found [here](https://book.wormhole.com/reference/contracts.html).

If you are deploying the receiver to a new chain, you will also need to supply a construction argument to define which chain ID you are deploying on. You can find specific information on Wormhole chain IDs [here](https://github.com/wormhole-foundation/wormhole/blob/main/sdk/js/src/utils/consts.ts#L1). Additionally, at construction time, you will need to supply both the Wormhole Core Bridge contract address for the chain you are deploying on and the current contract address for the sender you want to trust.
If you are deploying the receiver to a new chain, you will also need to supply a construction argument to define which chain ID you are deploying on. You can find specific information on Wormhole chain IDs [here](https://github.com/wormhole-foundation/wormhole/blob/645ea79d064ae1b96744171a55a71d4ec2ac640c/sdk/vaa/structs.go#L213). Additionally, at construction time, you will need to supply both the Wormhole Core Bridge contract address for the chain you are deploying on and the current contract address for the sender you want to trust.

If you are redeploying the receiver to an existing chain, you will want to deploy the latest copy of the receiver contract for that chain with any changes desired. Similarly, the construction addresses for the Wormhole Core Contracts and sender will need to be supplied. Additionally, you will need to use the current receiver implementation to send a governance message to the Uniswap V3 deployment on that chain to update it's trusted receiver to point to the new implementation.

Expand Down
17 changes: 17 additions & 0 deletions foundry.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"lib/forge-std": {
"branch": {
"name": "v1.5.0",
"rev": "c2236853aadb8e2d9909bbecdc490099519b70a4"
}
},
"lib/openzeppelin-contracts": {
"branch": {
"name": "v4.8.2",
"rev": "eedca5d873a559140d79cc7ec674d0e28b2b6ebd"
}
},
"lib/wormhole": {
"rev": "36c34bec1cb87e87c9e6264d3d70796f2ace11f7"
}
}
46 changes: 46 additions & 0 deletions script/DeployUniswapWormholeMessageReceiver.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.7;

import "forge-std/Script.sol";
import {UniswapWormholeMessageReceiver} from "../src/UniswapWormholeMessageReceiver.sol";

/**
* @title DeployUniswapWormholeMessageReceiver
* @notice Deploys UniswapWormholeMessageReceiver. Uses Tempo defaults below; override with env vars.
*
* Tempo deployment defaults:
* Wormhole Core Bridge (local): 0xbebdb6C8ddC678FfA9f8748f85C815C556Dd8ac6
* Sender contract (Ethereum): 0xf5F4496219F31CDCBa6130B5402873624585615a
* Tempo chain identifier: 68
*
* Optional env overrides:
* WORMHOLE_ADDRESS - Wormhole core on this chain (default: Tempo Wormhole Core Bridge)
* MESSAGE_SENDER_ADDRESS - UniswapWormholeMessageSender on Ethereum (default: above)
* CHAIN_ID - Wormhole chain ID of this chain (default: 68)
*
* Example:
* forge script script/DeployUniswapWormholeMessageReceiver.s.sol --rpc-url <RPC> --broadcast
*/
contract DeployUniswapWormholeMessageReceiver is Script {
address constant DEFAULT_WORMHOLE = 0xbebdb6C8ddC678FfA9f8748f85C815C556Dd8ac6;
address constant DEFAULT_MESSAGE_SENDER = 0xf5F4496219F31CDCBa6130B5402873624585615a;
uint256 constant DEFAULT_CHAIN_ID = 68;

function run() external returns (UniswapWormholeMessageReceiver receiver) {
address wormholeAddress = vm.envOr("WORMHOLE_ADDRESS", DEFAULT_WORMHOLE);
address messageSenderAddress = vm.envOr("MESSAGE_SENDER_ADDRESS", DEFAULT_MESSAGE_SENDER);
uint16 chainId = uint16(vm.envOr("CHAIN_ID", DEFAULT_CHAIN_ID));

// Wormhole format: 12 zero bytes followed by 20-byte Ethereum address
bytes32 messageSender = bytes32(uint256(uint160(messageSenderAddress)));

vm.startBroadcast();
receiver = new UniswapWormholeMessageReceiver(wormholeAddress, messageSender, chainId);
vm.stopBroadcast();

console.log("UniswapWormholeMessageReceiver deployed at", address(receiver));
console.log(" wormhole: ", wormholeAddress);
console.log(" messageSender:", vm.toString(messageSender));
console.log(" chainId: ", chainId);
}
}
153 changes: 153 additions & 0 deletions script/ValidateUniswapWormholeMessageReceiver.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.7;

import "forge-std/Script.sol";
import {UniswapWormholeMessageReceiver} from "../src/UniswapWormholeMessageReceiver.sol";

interface IWormholeGetters {
function chainId() external view returns (uint16);
}

/**
* @title ValidateUniswapWormholeMessageReceiver
* @notice Validates a deployed UniswapWormholeMessageReceiver: constants, config, and initial state.
*
* Required env:
* RECEIVER_ADDRESS - Deployed UniswapWormholeMessageReceiver contract address.
*
* Tempo deployment expected values (used as defaults; override with env if needed):
* Sender contract (Ethereum): 0xf5F4496219F31CDCBa6130B5402873624585615a
* Tempo chain identifier: 68
* Wormhole Core (local): 0xbebdb6C8ddC678FfA9f8748f85C556Dd8ac6 (verified on-chain via chainId())
*
* Optional env overrides:
* EXPECTED_WORMHOLE_ADDRESS - Default: Tempo Wormhole core above (verified on-chain).
* EXPECTED_MESSAGE_SENDER_ADDRESS - Default: Tempo sender above.
* EXPECTED_CHAIN_ID - Default: 68.
*
* Example:
* RECEIVER_ADDRESS=0x... forge script script/ValidateUniswapWormholeMessageReceiver.s.sol --rpc-url <RPC>
*/
contract ValidateUniswapWormholeMessageReceiver is Script {
address constant DEFAULT_EXPECTED_MESSAGE_SENDER = 0xf5F4496219F31CDCBa6130B5402873624585615a;
address constant DEFAULT_EXPECTED_WORMHOLE = 0xbebdb6C8ddC678FfA9f8748f85C815C556Dd8ac6;
uint256 constant DEFAULT_EXPECTED_CHAIN_ID = 68;

// Must match UniswapWormholeMessageReceiver.EXPECTED_MESSAGE_PAYLOAD_VERSION
bytes32 constant EXPECTED_PAYLOAD_VERSION = keccak256(
abi.encode(
"UniswapWormholeMessageSenderV1 (bytes32 receivedMessagePayloadVersion, address[] memory targets, uint256[] memory values, bytes[] memory datas, address messageReceiver, uint16 receiverChainId)"
)
);

function run() external {
address receiverAddress = vm.envAddress("RECEIVER_ADDRESS");
UniswapWormholeMessageReceiver receiver = UniswapWormholeMessageReceiver(receiverAddress);

console.log("Validating UniswapWormholeMessageReceiver at", receiverAddress);
console.log("");

uint256 failures = 0;

// --- Wormhole Core (on-chain verification via chainId()) ---
uint256 expectedChainIdRaw = vm.envOr("EXPECTED_CHAIN_ID", DEFAULT_EXPECTED_CHAIN_ID);
require(expectedChainIdRaw <= type(uint16).max, "EXPECTED_CHAIN_ID out of range");
uint16 expectedChainId = uint16(expectedChainIdRaw);

// Wormhole Core (on-chain verification via chainId()). Use --fork-url so the script sees chain state.
address expectedWormhole = vm.envOr("EXPECTED_WORMHOLE_ADDRESS", DEFAULT_EXPECTED_WORMHOLE);
try IWormholeGetters(expectedWormhole).chainId() returns (uint16 wormholeChainId) {
if (wormholeChainId != expectedChainId) {
console.log("[FAIL] Wormhole chainId mismatch");
console.log(" actual: ", uint256(wormholeChainId));
console.log(" expected:", uint256(expectedChainId));
failures++;
} else {
console.log("[OK] Wormhole verified at", expectedWormhole);
console.log(" chainId:", uint256(expectedChainId));
}
} catch {
console.log("[FAIL] Wormhole not reachable at", expectedWormhole);
console.log(" (Use --fork-url <TEMPO_RPC> so the script runs against chain state)");
failures++;
}

// --- Constants ---
if (keccak256(bytes(receiver.NAME())) != keccak256(bytes("Uniswap Wormhole Message Receiver"))) {
console.log("[FAIL] NAME");
failures++;
} else {
console.log("[OK] NAME");
}

if (receiver.EXPECTED_MESSAGE_PAYLOAD_VERSION() != EXPECTED_PAYLOAD_VERSION) {
console.log("[FAIL] EXPECTED_MESSAGE_PAYLOAD_VERSION");
failures++;
} else {
console.log("[OK] EXPECTED_MESSAGE_PAYLOAD_VERSION");
}

// ETHEREUM_CHAIN_ID is the contract constant for source chain (Ethereum = 2), not this chain
if (receiver.ETHEREUM_CHAIN_ID() != 2) {
console.log("[FAIL] ETHEREUM_CHAIN_ID constant (must be 2 = Ethereum source chain)");
failures++;
} else {
console.log("[OK] ETHEREUM_CHAIN_ID (Ethereum = 2)");
}

if (receiver.MESSAGE_TIME_OUT_SECONDS() != 2 days) {
console.log("[FAIL] MESSAGE_TIME_OUT_SECONDS (expected 2 days)");
failures++;
} else {
console.log("[OK] MESSAGE_TIME_OUT_SECONDS");
}

// --- Config (immutables) ---
bytes32 messageSender = receiver.messageSender();
uint16 chainId = receiver.chainId();
console.log("[INFO] messageSender (bytes32):", vm.toString(messageSender));
console.log("[INFO] chainId:", chainId);

// forge-lint: disable-next-line unsafe-typecast -- high 12 bytes must be zero (Wormhole format)
if (bytes12(messageSender) != bytes12(0)) {
console.log("[FAIL] messageSender must have 12 leading zero bytes (Wormhole format)");
failures++;
} else {
console.log("[OK] messageSender Wormhole format (12 zero bytes + address)");
}

// Assert against expected config (Tempo defaults; override with env)
address expectedSender = vm.envOr("EXPECTED_MESSAGE_SENDER_ADDRESS", DEFAULT_EXPECTED_MESSAGE_SENDER);
bytes32 expectedMessageSender = bytes32(uint256(uint160(expectedSender)));
if (messageSender != expectedMessageSender) {
console.log("[FAIL] messageSender does not match expected (Tempo sender or EXPECTED_MESSAGE_SENDER_ADDRESS)");
failures++;
} else {
console.log("[OK] messageSender matches expected");
}

if (chainId != expectedChainId) {
console.log("[FAIL] chainId mismatch (expected Tempo = 68)");
console.log(" actual: ", uint256(chainId));
console.log(" expected:", uint256(expectedChainId));
failures++;
} else {
console.log("[OK] chainId ==", expectedChainId);
}

// --- Initial / current state ---
uint64 nextSeq = receiver.nextMinimumSequence();
console.log("[INFO] nextMinimumSequence:", nextSeq);
if (nextSeq != 0) {
console.log("[INFO] (nextMinimumSequence != 0: deployment may have already processed messages)");
}

console.log("");
if (failures > 0) {
console.log("Validation FAILED with", failures);
console.log("failure(s)");
revert("Validation failed");
}
console.log("Validation PASSED");
}
}