diff --git a/packages/core/GOVERNANCE.md b/packages/core/GOVERNANCE.md
new file mode 100644
index 0000000000..adb0d0fab3
--- /dev/null
+++ b/packages/core/GOVERNANCE.md
@@ -0,0 +1,205 @@
+
+
+
+
+Human Protocol — Governance
+Cross-chain governance contracts (Hub + Spokes) and tooling.
+
+## Overview
+
+The Governance system is composed of a Hub Governor (MetaHumanGovernor) and one or more Spoke contracts (DAOSpokeContract) on other chains. Proposals are created on the Hub and broadcast via Wormhole Relayer to Spokes. Voting occurs on Hub and Spokes; results are collected back to the Hub before queueing/execution through a Timelock.
+
+ABIs for these contracts are exported under `packages/core/abis/governance` on compile.
+
+## Contracts
+
+### MetaHumanGovernor (Hub)
+
+Based on OpenZeppelin Governor with extensions:
+
+- GovernorSettings: voting delay/period, proposal threshold (in seconds, timestamp mode)
+- GovernorVotes & GovernorVotesQuorumFraction: ERC20Votes-based voting and quorum percent
+- GovernorTimelockControl: queue/execute through TimelockController
+- CrossChainGovernorCountingSimple: sums Hub + Spoke votes, maintains Spoke snapshots per proposal
+- Magistrate: privileged role that can create cross-chain proposals and trigger certain actions
+
+Key functions:
+
+- `crossChainPropose(targets, values, calldatas, description)` (onlyMagistrate, payable):
+ Creates a proposal on the Hub, snapshots current Spokes, and broadcasts to all Spokes via Wormhole.
+- `requestCollections(proposalId)` (payable):
+ After the vote period ends, requests each Spoke to send back its tallies.
+- `queue(targets, values, calldatas, descriptionHash)`: Queues a successful proposal in the Timelock; requires collection phase to be finished if Spokes are involved.
+- `crossChainCancel(...)` (payable): Cancels and broadcasts cancel to Spokes.
+- `updateSpokeContracts(spokes)`: Owner-only (Ownable) to set active Spokes. Typically owned by Timelock.
+
+Constructor (simplified):
+
+`MetaHumanGovernor(IVotes token, TimelockController timelock, CrossChainAddress[] spokes, uint16 hubWormholeChainId, address wormholeRelayer, address magistrate, uint256 secondsPerBlock, uint48 votingDelaySeconds, uint32 votingPeriodSeconds, uint256 proposalThreshold, uint256 quorumFraction)`
+
+### DAOSpokeContract (Spoke)
+
+Receives proposal metadata from Hub and opens a local voting window based on timestamps supplied by Hub.
+
+- `castVote(proposalId, support)`: Vote with ERC20Votes weight at the snapshot time
+- `receiveWormholeMessages(...)`: Handles incoming broadcasts from Hub
+- `sendVoteResultToHub(proposalId)` (onlyMagistrate, payable): Sends tallies to Hub (also triggered when Hub calls `requestCollections`)
+
+Constructor (simplified):
+
+`DAOSpokeContract(bytes32 hubAddress, uint16 hubChainId, IVotes voteToken, uint256 targetSecondsPerBlock, uint16 spokeChainId, address wormholeRelayer, address magistrate)`
+
+### VHMToken (Voting token)
+
+Wrapper token (ERC20Votes + ERC20Wrapper) for HMT used for voting. Timestamps are used for clock mode.
+
+- Deploy with `VHMToken(HMT_ADDRESS)` and self-delegate to activate voting power.
+
+### Magistrate
+
+Minimal Ownable-like role with no renounce. Controls proposal creation on Hub and result sending on Spokes. Can be transferred via `transferMagistrate(newMagistrate)`.
+
+### Wormhole Interfaces
+
+`IWormholeRelayer` and `IWormholeReceiver` are used to send/receive cross-chain messages via Wormhole Automatic Relayer.
+
+## Environment
+
+Create a `.env` in `packages/core` with at least the following variables (see `.env.example` for more)
+
+You also need the relevant explorer API keys if you plan to verify contracts.
+
+## Build
+
+```bash
+yarn install
+yarn compile
+```
+
+## Deployment
+
+All commands are run from `packages/core` unless noted. Use `--network ` for the target network (see `hardhat.config.ts`).
+
+### 1) Deploy vHMT (voting token)
+
+Prereq: `HMT_TOKEN_ADDRESS` set and funded deployer key.
+
+```bash
+npx hardhat run scripts/deploy-vhmt.ts --network
+```
+
+### 2) Deploy Hub (Governor + Timelock)
+
+Ensure Hub env vars are filled: `HUB_WORMHOLE_CHAIN_ID`, `HUB_AUTOMATIC_RELAYER_ADDRESS`, `MAGISTRATE_ADDRESS`, `HUB_SECONDS_PER_BLOCK`, voting params, and `HUB_VOTE_TOKEN_ADDRESS`.
+
+```bash
+yarn deploy:hub --network
+```
+
+Optionally, transfer ownership (Ownable on Governor for spoke updates) to the Timelock:
+
+```bash
+npx hardhat run scripts/dao-ownership.ts --network
+```
+
+### 3) Deploy Spokes (per Spoke chain)
+
+For each Spoke network, set:
+
+- `SPOKE_WORMHOLE_CHAIN_IDS` = Wormhole chainId of the Spoke
+- `SPOKE_AUTOMATIC_RELAYER_ADDRESS` = Wormhole Automatic Relayer on the Spoke
+- `SPOKE_VOTE_TOKEN_ADDRESS` = vHMT (or other IVotes) on the Spoke
+
+Then deploy:
+
+```bash
+yarn deploy:spokes --network
+```
+
+Collect all Spoke addresses and their Wormhole chain IDs for the update step.
+
+### 4) Register Spokes on the Hub
+
+Set `SPOKE_ADDRESSES` and `SPOKE_WORMHOLE_CHAIN_IDS` as comma-separated lists, then run on the Hub network:
+
+```bash
+yarn update:spokes --network
+```
+
+### 5) Self-delegate voting power (optional, for testing/quorum)
+
+Provide `SECOND_PRIVATE_KEY` and `THIRD_PRIVATE_KEY` and run:
+
+```bash
+yarn hub:selfdelegate:vote --network
+yarn spoke:selfdelegate:vote --network
+```
+
+## Proposal lifecycle
+
+1. Create (Hub): set `DESCRIPTION` and run:
+
+ ```bash
+ yarn create:proposal --network
+ ```
+
+2. Vote (Hub + Spokes):
+
+ - On Hub, use standard OZ Governor voting flows (e.g., cast votes via a UI or script if enabled).
+ - On each Spoke, call `castVote(proposalId, support)` where support = 0 (Against), 1 (For), 2 (Abstain). Window is enforced by timestamps provided by Hub.
+
+3. Collect Spoke tallies (Hub): after the main voting period ends, anyone can call on Hub:
+
+ - `requestCollections(proposalId)` (payable): triggers Spokes to send results back via Wormhole. This repository does not include a ready-made script; use Hardhat console or a block explorer to call it. Ensure enough ETH to cover relayer quotes.
+
+4. Queue (Hub): once `state(proposalId)` is `Succeeded`, queue in Timelock:
+
+ - Queue is used to schedule an approved proposal in the Timelock after voting succeeds. It sets an ETA (after the timelock delay) when the proposal can be executed.
+
+ ```bash
+ npx hardhat run scripts/queue-proposal.ts --network
+ ```
+
+5. Execute (Hub): after the Timelock delay, execute with `execute(targets, values, calldatas, descriptionHash)` using the same params used for queue. You can use a block explorer or a small script.
+
+Notes:
+
+- `propose(...)` on the Hub is intentionally disabled; use `crossChainPropose(...)`.
+- The Hub will return `Pending` while waiting for Spoke collection after the voting period if collection hasn’t finished.
+- Fees: cross-chain messaging uses Wormhole Relayer quotes; ensure the sender funds cover costs on create and collection.
+
+## Verification
+
+Verify contracts per network using Hardhat’s verify task (examples):
+
+```bash
+# Hub governor
+npx hardhat verify --network \
+ "[]" \
+ \
+
+
+# Timelock (if needed)
+npx hardhat verify --network 1 [] []
+
+# Spoke (use bytes32-padded governor address)
+npx hardhat verify --network \
+ \
+
+
+# vHMT
+npx hardhat verify --network
+```
+
+Adjust arguments/order if constructors change; consult the contract sources if verification fails.
+
+## Troubleshooting
+
+- Wrong Wormhole chain IDs or relayer addresses will cause messages to be dropped. Double-check per network.
+- Insufficient ETH for relayer fees: increase the value sent on `crossChainPropose` / `requestCollections` or fund the signer.
+- Not enough voting power: ensure holders self-delegate on Hub/Spokes.
+- Updating Spokes requires Governor ownership; if owned by Timelock, execute an ownership-protected transaction via Timelock.
+
+## License
+
+This project is licensed under the MIT License. See the [LICENSE](https://github.com/humanprotocol/human-protocol/blob/main/LICENSE) file for details.
diff --git a/packages/core/README.md b/packages/core/README.md
index 1a1d6cef77..180b21ca8d 100644
--- a/packages/core/README.md
+++ b/packages/core/README.md
@@ -218,6 +218,10 @@ npx hardhat verify --network [NETWORK_NAME] [CONTRACT_ADDRESS]
For detailed information about core, please refer to the [Human Protocol Tech Docs](https://human-protocol.gitbook.io/hub/human-tech-docs/architecture/components/smart-contracts).
+### Governance
+
+For the cross-chain Governance system (Hub + Spokes), deployment, and proposal lifecycle, see the root-level [GOVERNANCE.md](./GOVERNANCE.md). The in-source, detailed reference lives at [`contracts/governance/README.md`](contracts/governance/README.md).
+
## License
This project is licensed under the MIT License. See the [LICENSE](https://github.com/humanprotocol/human-protocol/blob/main/LICENSE) file for details.
diff --git a/packages/core/contracts/governance/DAOSpokeContract.sol b/packages/core/contracts/governance/DAOSpokeContract.sol
index b30ba32141..de9f6601cb 100644
--- a/packages/core/contracts/governance/DAOSpokeContract.sol
+++ b/packages/core/contracts/governance/DAOSpokeContract.sol
@@ -85,13 +85,6 @@ contract DAOSpokeContract is IWormholeReceiver, Magistrate {
hubContractChainId = _hubContractChainId;
}
- /**
- * @dev Allows the magistrate address to withdraw all funds from the contract
- */
- function withdrawFunds() public onlyMagistrate {
- payable(msg.sender).sendValue(address(this).balance);
- }
-
function hasVoted(
uint256 proposalId,
address account
@@ -199,14 +192,14 @@ contract DAOSpokeContract is IWormholeReceiver, Magistrate {
* @dev Receives messages from the Wormhole protocol's relay mechanism and processes them accordingly.
* This function is intended to be called only by the designated Wormhole relayer.
* @param payload The payload of the received message.
- * @param sourceAddress The address that initiated the message transmission (HelloWormhole contract address).
+ * @param sourceAddress The address that initiated the message transmission (Hub contract address).
* @param sourceChain The chain ID of the source contract.
* @param deliveryHash A unique hash representing the delivery of the message to prevent duplicate processing.
*/
function receiveWormholeMessages(
bytes memory payload,
bytes[] memory, // additionalVaas
- bytes32 sourceAddress, // address that called 'sendPayloadToEvm' (HelloWormhole contract address)
+ bytes32 sourceAddress, // address that called 'sendPayloadToEvm' (Hub contract address)
uint16 sourceChain,
bytes32 deliveryHash // this can be stored in a mapping deliveryHash => bool to prevent duplicate deliveries
) public payable override {
@@ -296,7 +289,7 @@ contract DAOSpokeContract is IWormholeReceiver, Magistrate {
0, // no receiver value needed
GAS_LIMIT,
hubContractChainId,
- address(uint160(uint256(hubContractAddress)))
+ magistrate()
);
}
}
@@ -336,7 +329,7 @@ contract DAOSpokeContract is IWormholeReceiver, Magistrate {
0, // no receiver value needed
GAS_LIMIT,
hubContractChainId,
- address(uint160(uint256(hubContractAddress)))
+ magistrate()
);
}
diff --git a/packages/core/contracts/governance/MetaHumanGovernor.sol b/packages/core/contracts/governance/MetaHumanGovernor.sol
index 9d4c57e637..57d4d9fb0c 100644
--- a/packages/core/contracts/governance/MetaHumanGovernor.sol
+++ b/packages/core/contracts/governance/MetaHumanGovernor.sol
@@ -89,77 +89,47 @@ contract MetaHumanGovernor is
secondsPerBlock = _secondsPerBlock;
}
- /**
- * @dev Allows the magistrate address to withdraw all funds from the contract
- */
- function withdrawFunds() public onlyMagistrate {
- payable(msg.sender).sendValue(address(this).balance);
- }
-
function cancel(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
- ) public virtual override(Governor) returns (uint256) {
- // First, perform the original cancellation logic.
- uint256 proposalId = super.cancel(
+ ) public override(Governor) returns (uint256) {
+ uint256 proposalId = hashProposal(
targets,
values,
calldatas,
descriptionHash
);
- //Notify all spoke chains about the cancellation
- _notifySpokeChainsOfCancellation(proposalId);
+ if (spokeContractsSnapshots[proposalId].length > 0) {
+ revert('Please use crossChainCancel for proposals with spokes.');
+ }
- return proposalId;
+ return super.cancel(targets, values, calldatas, descriptionHash);
}
- function _notifySpokeChainsOfCancellation(uint256 proposalId) internal {
- uint256 spokeContractsLength = spokeContractsSnapshots[proposalId]
- .length;
- for (uint16 i = 0; i < spokeContractsLength; i++) {
- bytes memory message = abi.encode(
- 2, // "2" is an inused function selector indicating cancellation
- proposalId
- );
-
- bytes memory payload = abi.encode(
- spokeContractsSnapshots[proposalId][i].contractAddress,
- spokeContractsSnapshots[proposalId][i].chainId,
- bytes32(uint256(uint160(address(this)))),
- message
- );
-
- uint256 cost = quoteCrossChainMessage(
- spokeContractsSnapshots[proposalId][i].chainId,
- 0
- );
-
- // Send cancellation message
- wormholeRelayer.sendPayloadToEvm{value: cost}(
- spokeContractsSnapshots[proposalId][i].chainId,
- address(
- uint160(
- uint256(
- spokeContractsSnapshots[proposalId][i]
- .contractAddress
- )
- )
- ),
- payload,
- 0,
- GAS_LIMIT
- );
- }
+ function crossChainCancel(
+ address[] calldata targets,
+ uint256[] calldata values,
+ bytes[] calldata calldatas,
+ bytes32 descriptionHash
+ ) external payable returns (uint256) {
+ uint256 proposalId = super.cancel(
+ targets,
+ values,
+ calldatas,
+ descriptionHash
+ );
+ bytes memory message = abi.encode(uint16(2), proposalId); // selector 2 = cancel
+ _sendCrossChainMessageToSpokes(proposalId, message, address(this), 2);
+ return proposalId;
}
/**
* @dev Receives messages from the Wormhole protocol's relay mechanism and processes them accordingly.
* This function is intended to be called only by the designated Wormhole relayer.
* @param payload The payload of the received message.
- * @param additionalVaas An array of additional data (not used in this function).
* @param sourceAddress The address that initiated the message transmission (HelloWormhole contract address).
* @param sourceChain The chain ID of the source contract.
* @param deliveryHash A unique hash representing the delivery of the message to prevent duplicate processing.
@@ -183,7 +153,6 @@ contract MetaHumanGovernor is
address intendedRecipient, //chainId
,
,
- //sender
bytes memory decodedMessage
) = abi.decode(payload, (address, uint16, address, bytes));
@@ -191,11 +160,6 @@ contract MetaHumanGovernor is
revert InvalidIntendedRecipient();
}
- // require(
- // intendedRecipient == address(this),
- // 'Message is not addressed for this contract'
- // );
-
processedMessages[deliveryHash] = true;
// Gets a function selector option
uint16 option;
@@ -305,48 +269,8 @@ contract MetaHumanGovernor is
_finishCollectionPhase(proposalId);
}
- // Get a price of sending the message back to hub
- uint256 sendMessageToHubCost = quoteCrossChainMessage(chainId, 0);
-
- // Sends an empty message to each of the aggregators.
- // If they receive a message, it is their cue to send data back
- for (uint16 i = 1; i <= spokeContractsLength; ++i) {
- // Using "1" as the function selector
- bytes memory message = abi.encode(1, proposalId);
-
- uint16 spokeChainId = spokeContractsSnapshots[proposalId][i - 1]
- .chainId;
- address spokeAddress = address(
- uint160(
- uint256(
- spokeContractsSnapshots[proposalId][i - 1]
- .contractAddress
- )
- )
- );
-
- bytes memory payload = abi.encode(
- spokeAddress,
- spokeChainId,
- msg.sender,
- message
- );
-
- uint256 cost = quoteCrossChainMessage(
- spokeChainId,
- sendMessageToHubCost
- );
-
- wormholeRelayer.sendPayloadToEvm{value: cost}(
- spokeChainId,
- spokeAddress,
- payload,
- sendMessageToHubCost, // send value to enable the spoke to send back vote result
- GAS_LIMIT,
- spokeChainId,
- spokeAddress
- );
- }
+ bytes memory message = abi.encode(uint16(1), proposalId); // selector 1 = requestCollections
+ _sendCrossChainMessageToSpokes(proposalId, message, msg.sender, 1);
}
/**
@@ -377,59 +301,72 @@ contract MetaHumanGovernor is
uint256 voteStartTimestamp = proposalSnapshot(proposalId);
uint256 voteEndTimestamp = proposalDeadline(proposalId);
- // Sends the proposal to all of the other spoke contracts
- if (spokeContractsSnapshots[proposalId].length > 0) {
- // Iterate over every spoke contract and send a message
- uint256 spokeContractsLength = spokeContractsSnapshots[proposalId]
- .length;
- for (uint16 i = 1; i <= spokeContractsLength; ++i) {
- bytes memory message = abi.encode(
- 0, // Function selector "0" for destination contract
- proposalId,
- block.timestamp, // proposal creation timestamp
- voteStartTimestamp, //vote start timestamp
- voteEndTimestamp //vote end timestamp
- );
-
- uint16 spokeChainId = spokeContractsSnapshots[proposalId][i - 1]
- .chainId;
- address spokeAddress = address(
- uint160(
- uint256(
- spokeContractsSnapshots[proposalId][i - 1]
- .contractAddress
- )
+ bytes memory message = abi.encode(
+ uint16(0), // selector 0 = propose
+ proposalId,
+ block.timestamp,
+ voteStartTimestamp,
+ voteEndTimestamp
+ );
+ _sendCrossChainMessageToSpokes(proposalId, message, address(this), 0);
+ return proposalId;
+ }
+
+ /**
+ * @dev Internal function to send a cross-chain message to all spoke contracts for a given proposal.
+ * @param proposalId The ID of the proposal.
+ * @param message The encoded message to send.
+ * @param sender The address to set as msg.sender in the payload (for requestCollections, otherwise address(this)).
+ * @param selector The function selector (0: propose, 1: requestCollections, 2: cancel).
+ */
+ function _sendCrossChainMessageToSpokes(
+ uint256 proposalId,
+ bytes memory message,
+ address sender,
+ uint16 selector
+ ) internal {
+ uint256 spokeContractsLength = spokeContractsSnapshots[proposalId]
+ .length;
+ uint256 sendMessageToHubCost = _quoteCrossChainMessage(chainId, 0);
+ bool isRequestCollectionsMessage = (selector == 1);
+ for (uint16 i = 1; i <= spokeContractsLength; ++i) {
+ uint16 spokeChainId = spokeContractsSnapshots[proposalId][i - 1]
+ .chainId;
+ address spokeAddress = address(
+ uint160(
+ uint256(
+ spokeContractsSnapshots[proposalId][i - 1]
+ .contractAddress
)
- );
-
- bytes memory payload = abi.encode(
- spokeAddress,
- spokeChainId,
- bytes32(uint256(uint160(address(this)))),
- message
- );
-
- uint256 cost = quoteCrossChainMessage(spokeChainId, 0);
-
- wormholeRelayer.sendPayloadToEvm{value: cost}(
- spokeChainId,
- spokeAddress,
- payload,
- 0, // no receiver value needed
- GAS_LIMIT,
- spokeChainId,
- spokeAddress
- );
- }
+ )
+ );
+ bytes memory payload = abi.encode(
+ spokeAddress,
+ spokeChainId,
+ sender,
+ message
+ );
+ uint256 cost = _quoteCrossChainMessage(
+ spokeChainId,
+ isRequestCollectionsMessage ? sendMessageToHubCost : 0
+ );
+ wormholeRelayer.sendPayloadToEvm{value: cost}(
+ spokeChainId,
+ spokeAddress,
+ payload,
+ isRequestCollectionsMessage ? sendMessageToHubCost : 0,
+ GAS_LIMIT,
+ spokeChainId,
+ magistrate()
+ );
}
- return proposalId;
}
/**
* @dev Retrieves the quote for cross chain message delivery.
* @return cost Price, in units of current chain currency, that the delivery provider charges to perform the relay
*/
- function quoteCrossChainMessage(
+ function _quoteCrossChainMessage(
uint16 targetChain,
uint256 valueToSend
) internal view returns (uint256 cost) {
@@ -470,18 +407,18 @@ contract MetaHumanGovernor is
/**
* @dev Retrieves the quorum required for voting.
- * @param blockNumber The block number to calculate the quorum for.
+ * @param snapshotTime The timestamp (snapshot) at which to calculate the quorum
* @return The required quorum percentage.
*/
function quorum(
- uint256 blockNumber
+ uint256 snapshotTime
)
public
view
override(Governor, GovernorVotesQuorumFraction)
returns (uint256)
{
- return super.quorum(blockNumber);
+ return super.quorum(snapshotTime);
}
/**
diff --git a/packages/core/scripts/create-proposal.ts b/packages/core/scripts/create-proposal.ts
index ce40586a0b..3c26dd815b 100644
--- a/packages/core/scripts/create-proposal.ts
+++ b/packages/core/scripts/create-proposal.ts
@@ -26,11 +26,11 @@ async function main() {
proposal.values,
proposal.calldatas,
proposal.description,
- { value: ethers.parseEther('0.01') }
+ { value: ethers.parseEther('0.015') }
);
await transactionResponse.wait();
- console.log('Proposal created:');
+ console.log('Proposal created:', transactionResponse.hash);
}
main()
diff --git a/packages/core/scripts/deploy-hub.ts b/packages/core/scripts/deploy-hub.ts
index 40588c5cd3..68020a2563 100644
--- a/packages/core/scripts/deploy-hub.ts
+++ b/packages/core/scripts/deploy-hub.ts
@@ -24,12 +24,16 @@ async function main() {
}
// vHMT Deployment
- const VHMToken = await ethers.getContractFactory(
- 'contracts/governance/vhm-token/VHMToken.sol:VHMToken'
- );
- const VHMTokenContract = await VHMToken.deploy(hmtTokenAddress);
- await VHMTokenContract.waitForDeployment();
- console.log('VHMToken deployed to:', await VHMTokenContract.getAddress());
+ // const VHMToken = await ethers.getContractFactory(
+ // 'contracts/governance/vhm-token/VHMToken.sol:VHMToken'
+ // );
+ // const VHMTokenContract = await VHMToken.deploy(hmtTokenAddress);
+ // await VHMTokenContract.waitForDeployment();
+ // console.log('VHMToken deployed to:', await VHMTokenContract.getAddress());
+ const vhmTokenAddress = process.env.VHM_TOKEN_ADDRESS || '';
+ if (!vhmTokenAddress) {
+ throw new Error('VHM Token Address is missing');
+ }
//DeployHUB
const chainId = process.env.HUB_WORMHOLE_CHAIN_ID;
@@ -68,7 +72,7 @@ async function main() {
'contracts/governance/MetaHumanGovernor.sol:MetaHumanGovernor'
);
const metaHumanGovernorContract = await MetaHumanGovernor.deploy(
- VHMTokenContract.getAddress(),
+ vhmTokenAddress,
TimelockControllerContract.getAddress(),
[],
chainId,
diff --git a/packages/core/scripts/proposal.ts b/packages/core/scripts/proposal.ts
index 372fc770a0..609285b15d 100644
--- a/packages/core/scripts/proposal.ts
+++ b/packages/core/scripts/proposal.ts
@@ -14,22 +14,27 @@ export const getProposal = async () => {
throw new Error('One or more required environment variables are missing.');
}
- const deployerSigner = new ethers.Wallet(deployerPrivateKey, ethers.provider);
- const governanceContract = await ethers.getContractAt(
- 'MetaHumanGovernor',
- governorAddress,
- deployerSigner
- );
-
- const encodedCall = governanceContract.interface.encodeFunctionData(
- 'setVotingPeriod',
- [86400]
- );
+ // Keep the following commented code as a reference for how to encode a contract
+ // function call within a governance proposal, useful for future proposals that
+ // need to call contract methods through governance
+
+ // const deployerSigner = new ethers.Wallet(deployerPrivateKey, ethers.provider);
+ // const governanceContract = await ethers.getContractAt(
+ // 'MetaHumanGovernor',
+ // governorAddress,
+ // deployerSigner
+ // );
+
+ // const encodedCall = governanceContract.interface.encodeFunctionData(
+ // 'setVotingPeriod',
+ // [3600]
+ // );
// Proposal data
- const targets = [governorAddress];
+ const targets = [ethers.ZeroAddress];
const values = [0];
- const calldatas = [encodedCall];
+ // const calldatas = [encodedCall];
+ const calldatas = ['0x'];
// Example inputs (replace with actual values)
const descriptionHash = ethers.id(description);
diff --git a/packages/core/scripts/queue-proposal.ts b/packages/core/scripts/queue-proposal.ts
index bfcd100902..d90ccdb09e 100644
--- a/packages/core/scripts/queue-proposal.ts
+++ b/packages/core/scripts/queue-proposal.ts
@@ -20,18 +20,15 @@ async function main() {
);
const proposal = await getProposal();
-
- const transactionResponse = await governanceContract.cancel(
+ const transactionResponse = await governanceContract.queue(
proposal.targets,
proposal.values,
proposal.calldatas,
proposal.descriptionHash
);
- console.log(transactionResponse);
-
await transactionResponse.wait();
- console.log('Proposal queued:');
+ console.log('Proposal queued:', transactionResponse.hash);
}
main()
diff --git a/packages/core/scripts/update-spokes.ts b/packages/core/scripts/update-spokes.ts
index 86b419e0a5..bd206a4ed7 100644
--- a/packages/core/scripts/update-spokes.ts
+++ b/packages/core/scripts/update-spokes.ts
@@ -24,14 +24,18 @@ async function main() {
);
const spokeContracts = spokeAddresses.map((address, index) => ({
- contractAddress: ethers.zeroPadBytes(address, 32),
+ contractAddress: ethers.zeroPadValue(address, 32),
chainId: spokeChainIds[index],
}));
console.log('Updating spoke contracts...');
// can only be called by the governor
- await governanceContract.updateSpokeContracts(spokeContracts);
- console.log('Spoke contracts updated successfully.');
+ const transaction =
+ await governanceContract.updateSpokeContracts(spokeContracts);
+ console.log(
+ 'Spoke contracts updated successfully. TxHash:',
+ transaction.hash
+ );
}
main().catch((error) => {
diff --git a/packages/core/test/DAOSpokeContract.ts b/packages/core/test/DAOSpokeContract.ts
index b0ea22a6d3..663e3e2c97 100644
--- a/packages/core/test/DAOSpokeContract.ts
+++ b/packages/core/test/DAOSpokeContract.ts
@@ -526,55 +526,6 @@ describe('DAOSpokeContract', function () {
});
});
- describe('withdraw', () => {
- it('should withdraw as magistrate', async () => {
- await createProposalOnSpoke(
- daoSpoke,
- wormholeMockForDaoSpoke,
- 1,
- await governor.getAddress()
- );
-
- const contractBalanceBefore = await ethers.provider.getBalance(
- await daoSpoke.getAddress()
- );
- const ownerBalanceBefore = await ethers.provider.getBalance(
- await owner.getAddress()
- );
-
- const txReceipt = await daoSpoke.connect(owner).withdrawFunds();
- const tx = await txReceipt.wait();
-
- if (!tx) {
- throw new Error('Failed to fetch the transaction receipt');
- }
-
- const contractBalanceAfter = await ethers.provider.getBalance(
- await daoSpoke.getAddress()
- );
- const ownerBalanceAfter = await ethers.provider.getBalance(
- await owner.getAddress()
- );
-
- expect(contractBalanceAfter).to.equal(0);
- expect(ownerBalanceAfter - ownerBalanceBefore).to.equal(
- contractBalanceBefore - tx.gasUsed * tx.gasPrice
- );
- });
-
- it('should revert when not magistrate', async () => {
- await createProposalOnSpoke(
- daoSpoke,
- wormholeMockForDaoSpoke,
- 1,
- await governor.getAddress()
- );
-
- await expect(daoSpoke.connect(user1).withdrawFunds()).to.be.revertedWith(
- 'Magistrate: caller is not the magistrate'
- );
- });
- });
describe('sendVoteResultToHub', async () => {
it('should revert when not finished', async () => {
const proposalId = await createProposalOnSpoke(
diff --git a/packages/core/test/MetaHumanGovernor.ts b/packages/core/test/MetaHumanGovernor.ts
index 02c685132f..3aa35ccaca 100644
--- a/packages/core/test/MetaHumanGovernor.ts
+++ b/packages/core/test/MetaHumanGovernor.ts
@@ -1359,19 +1359,4 @@ describe('MetaHumanGovernor', function () {
governor.transferMagistrate(ethers.ZeroAddress)
).to.be.revertedWith('Magistrate: new magistrate is the zero address');
});
-
- it('Should withdraw as Magistrate', async function () {
- const contractBalance = await token.balanceOf(await governor.getAddress());
- const beforeWithdraw = await token.balanceOf(await owner.getAddress());
- await governor.connect(owner).withdrawFunds();
- const afterWithdraw = await token.balanceOf(await owner.getAddress());
- const diffference = afterWithdraw - beforeWithdraw;
- expect(diffference).to.equal(contractBalance);
- });
-
- it('Should reverts if not magistrate tries to withdraw', async function () {
- await expect(governor.connect(user1).withdrawFunds()).to.be.revertedWith(
- 'Magistrate: caller is not the magistrate'
- );
- });
});
diff --git a/packages/core/test/MetaHumanGovernorHubOnly.ts b/packages/core/test/MetaHumanGovernorHubOnly.ts
index 189482f6cd..09e0353316 100644
--- a/packages/core/test/MetaHumanGovernorHubOnly.ts
+++ b/packages/core/test/MetaHumanGovernorHubOnly.ts
@@ -807,19 +807,4 @@ describe('MetaHumanGovernorHubOnly', function () {
governor.transferMagistrate(ethers.ZeroAddress)
).to.be.revertedWith('Magistrate: new magistrate is the zero address');
});
-
- it('Should withdraw as Magistrate', async function () {
- const contractBalance = await token.balanceOf(await governor.getAddress());
- const beforeWithdraw = await token.balanceOf(await owner.getAddress());
- await governor.connect(owner).withdrawFunds();
- const afterWithdraw = await token.balanceOf(await owner.getAddress());
- const diffference = afterWithdraw - beforeWithdraw;
- expect(diffference).to.equal(contractBalance);
- });
-
- it('Should reverts if not magistrate tries to withdraw', async function () {
- await expect(governor.connect(user1).withdrawFunds()).to.be.revertedWith(
- 'Magistrate: caller is not the magistrate'
- );
- });
});