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
55 changes: 55 additions & 0 deletions target_chains/ethereum/contracts/contracts/pyth/PythGovernance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,14 @@ abstract contract PythGovernance is
setTransactionFee(parseSetTransactionFeePayload(gi.payload));
} else if (gi.action == GovernanceAction.WithdrawFee) {
withdrawFee(parseWithdrawFeePayload(gi.payload));
} else if (
gi.action == GovernanceAction.MigrateGovernanceAndWormhole
) {
if (gi.targetChainId == 0)
revert PythErrors.InvalidGovernanceTarget();
migrateGovernanceAndWormhole(
parseMigrateGovernanceAndWormholePayload(gi.payload)
);
} else {
revert PythErrors.InvalidGovernanceMessage();
}
Expand Down Expand Up @@ -252,6 +260,53 @@ abstract contract PythGovernance is
emit WormholeAddressSet(oldWormholeAddress, address(wormhole()));
}

/// @dev Atomically migrates wormhole address, valid data sources, and
/// governance emitter without dual-VAA re-verify. Resets the last executed
/// governance sequence so the new emitter can start from sequence 0.
/// Fee is set separately via SetFee before migration.
function migrateGovernanceAndWormhole(
MigrateGovernanceAndWormholePayload memory payload
) internal {
if (payload.newWormholeAddress == address(0))
revert PythErrors.InvalidWormholeAddressToSet();

uint256 codeSize;
address newWormholeAddress = payload.newWormholeAddress;
assembly {
codeSize := extcodesize(newWormholeAddress)
}
if (codeSize == 0) revert PythErrors.InvalidWormholeAddressToSet();

if (payload.governanceDataSource.emitterAddress == bytes32(0))
revert PythErrors.InvalidGovernanceDataSource();

address oldWormholeAddress = address(wormhole());
setWormhole(payload.newWormholeAddress);
emit WormholeAddressSet(oldWormholeAddress, address(wormhole()));

setDataSources(
SetDataSourcesPayload({dataSources: payload.dataSources})
);

// Governance data source index must strictly increase to prevent replay.
if (
payload.governanceDataSourceIndex <= governanceDataSourceIndex()
) revert PythErrors.OldGovernanceMessage();

PythInternalStructs.DataSource
memory oldGovernanceDataSource = governanceDataSource();

setGovernanceDataSourceIndex(payload.governanceDataSourceIndex);
setGovernanceDataSource(payload.governanceDataSource);
setLastExecutedGovernanceSequence(0);

emit GovernanceDataSourceSet(
oldGovernanceDataSource,
governanceDataSource(),
lastExecutedGovernanceSequence()
);
}

function setTransactionFee(
SetTransactionFeePayload memory payload
) internal {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ contract PythGovernanceInstructions {
SetWormholeAddress, // 6
SetFeeInToken, // 7 - No-op for EVM chains
SetTransactionFee, // 8
WithdrawFee // 9
WithdrawFee, // 9
MigrateGovernanceAndWormhole // 10
}

struct GovernanceInstruction {
Expand Down Expand Up @@ -94,6 +95,13 @@ contract PythGovernanceInstructions {
uint256 fee;
}

struct MigrateGovernanceAndWormholePayload {
address newWormholeAddress;
PythInternalStructs.DataSource[] dataSources;
PythInternalStructs.DataSource governanceDataSource;
uint32 governanceDataSourceIndex;
}

/// @dev Parse a GovernanceInstruction
function parseGovernanceInstruction(
bytes memory encodedInstruction
Expand Down Expand Up @@ -276,4 +284,49 @@ contract PythGovernanceInstructions {
if (encodedPayload.length != index)
revert PythErrors.InvalidGovernanceMessage();
}

/// @dev Parse a MigrateGovernanceAndWormholePayload (action 10) with minimal validation
function parseMigrateGovernanceAndWormholePayload(
bytes memory encodedPayload
)
public
pure
returns (MigrateGovernanceAndWormholePayload memory payload)
{
uint index = 0;

payload.newWormholeAddress = address(encodedPayload.toAddress(index));
index += 20;

uint8 dataSourcesLength = encodedPayload.toUint8(index);
index += 1;

payload.dataSources = new PythInternalStructs.DataSource[](
dataSourcesLength
);

for (uint i = 0; i < dataSourcesLength; i++) {
payload.dataSources[i].chainId = encodedPayload.toUint16(index);
index += 2;

payload.dataSources[i].emitterAddress = encodedPayload.toBytes32(
index
);
index += 32;
}

payload.governanceDataSource.chainId = encodedPayload.toUint16(index);
index += 2;

payload.governanceDataSource.emitterAddress = encodedPayload.toBytes32(
index
);
index += 32;

payload.governanceDataSourceIndex = encodedPayload.toUint32(index);
index += 4;

if (encodedPayload.length != index)
revert PythErrors.InvalidGovernanceMessage();
}
}
Loading
Loading