Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
10 changes: 10 additions & 0 deletions governance/xc_admin/packages/xc_admin_common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,16 @@
"types": "./dist/cjs/governance_payload/SetWormholeAddress.d.ts"
}
},
"./governance_payload/SetWormholeAddressAndDataSources": {
"import": {
"default": "./dist/esm/governance_payload/SetWormholeAddressAndDataSources.mjs",
"types": "./dist/esm/governance_payload/SetWormholeAddressAndDataSources.d.ts"
},
"require": {
"default": "./dist/cjs/governance_payload/SetWormholeAddressAndDataSources.cjs",
"types": "./dist/cjs/governance_payload/SetWormholeAddressAndDataSources.d.ts"
}
},
"./governance_payload/StellarExecutorAction": {
"import": {
"default": "./dist/esm/governance_payload/StellarExecutorAction.mjs",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { SetDataSources } from "../governance_payload/SetDataSources";
import { SetFee, SetFeeInToken } from "../governance_payload/SetFee";
import { SetTransactionFee } from "../governance_payload/SetTransactionFee";
import { SetValidPeriod } from "../governance_payload/SetValidPeriod";
import { SetWormholeAddressAndDataSources } from "../governance_payload/SetWormholeAddressAndDataSources";
import {
CosmosUpgradeContract,
EvmUpgradeContract,
Expand Down Expand Up @@ -271,6 +272,90 @@ test("GovernancePayload ser/de", () => {
),
).toBeTruthy();

const setWormholeAddressAndDataSources =
new SetWormholeAddressAndDataSources(
"ethereum",
"0102030405060708090a0b0c0d0e0f1011121314",
[
{
emitterAddress:
"6bb14509a612f01fbbc4cffeebd4bbfb492a86df717ebe92eb6df432a3f00a25",
emitterChain: 1,
},
{
emitterAddress:
"000000000000000000000000000000000000000000000000000000000000012d",
emitterChain: 3,
},
],
42n,
8n,
);
const setWormholeAddressAndDataSourcesBuffer =
setWormholeAddressAndDataSources.encode();
console.log(setWormholeAddressAndDataSourcesBuffer.toJSON());
expect(
setWormholeAddressAndDataSourcesBuffer.equals(
Buffer.from([
// header: PTGM | module=1 | action=10 | ethereum chain id
80, 84, 71, 77, 1, 10, 0, 2,
// wormhole address (20 bytes)
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
// numSources = 2
2,
// data source 1
0, 1, 107, 177, 69, 9, 166, 18, 240, 31, 187, 196, 207, 254, 235, 212,
187, 251, 73, 42, 134, 223, 113, 126, 190, 146, 235, 109, 244, 50, 163,
240, 10, 37,
// data source 2
0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 45,
// fee value = 42, expo = 8
0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 8,
]),
),
).toBeTruthy();
const decodedSetWormholeAddressAndDataSources =
SetWormholeAddressAndDataSources.decode(
setWormholeAddressAndDataSourcesBuffer,
);
expect(decodedSetWormholeAddressAndDataSources?.targetChainId).toBe(
"ethereum",
);
expect(decodedSetWormholeAddressAndDataSources?.address).toBe(
"0102030405060708090a0b0c0d0e0f1011121314",
);
expect(decodedSetWormholeAddressAndDataSources?.dataSources).toEqual([
{
emitterAddress:
"6bb14509a612f01fbbc4cffeebd4bbfb492a86df717ebe92eb6df432a3f00a25",
emitterChain: 1,
},
{
emitterAddress:
"000000000000000000000000000000000000000000000000000000000000012d",
emitterChain: 3,
},
]);
expect(decodedSetWormholeAddressAndDataSources?.newFeeValue).toBe(42n);
expect(decodedSetWormholeAddressAndDataSources?.newFeeExpo).toBe(8n);
expect(
decodeGovernancePayload(setWormholeAddressAndDataSourcesBuffer),
).toBeInstanceOf(SetWormholeAddressAndDataSources);

// Fee fields are always present even when zero (migrate path).
const migratePayload = new SetWormholeAddressAndDataSources(
"ethereum",
"0102030405060708090a0b0c0d0e0f1011121314",
[],
0n,
0n,
);
const migrateBuffer = migratePayload.encode();
expect(migrateBuffer.length).toBe(8 + 20 + 1 + 16);
expect(migrateBuffer.subarray(-16).equals(Buffer.alloc(16))).toBeTruthy();
expect(migrateBuffer[5]).toBe(10);

const upgradeContract = new UpgradeContract256Bit(
"starknet",
"043d0ed8155263af0862372df3af9403c502358661f317f62fbdc026d03beaee",
Expand Down Expand Up @@ -483,6 +568,23 @@ function governanceActionArb(): Arbitrary<PythGovernanceAction> {
},
);
return fc.oneof(evmArb, starknetArb);
} else if (header.action === "SetWormholeAddressAndDataSources") {
return fc
.record({
address: hexBytesArb({ maxLength: 20, minLength: 20 }),
dataSources: fc.array(dataSourceArb()),
newFeeExpo: fc.bigUintN(64),
newFeeValue: fc.bigUintN(64),
})
.map(({ address, dataSources, newFeeValue, newFeeExpo }) => {
return new SetWormholeAddressAndDataSources(
header.targetChainId,
address,
dataSources,
newFeeValue,
newFeeExpo,
);
});
} else if (header.action === "Execute") {
return fc
.record({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const TargetAction = {
SetFeeInToken: 7,
SetTransactionFee: 8,
WithdrawFee: 9,
SetWormholeAddressAndDataSources: 10,
} as const;

export const EvmExecutorAction = {
Expand Down Expand Up @@ -74,6 +75,8 @@ export function toActionName(
return "SetTransactionFee";
case 9:
return "WithdrawFee";
case 10:
return "SetWormholeAddressAndDataSources";
}
} else if (
deserialized.moduleId == MODULE_EVM_EXECUTOR &&
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import * as BufferLayout from "@solana/buffer-layout";
import type { ChainName } from "../chains";
import { safeBufferConcat } from "../utils/buffer";
import * as BufferLayoutExt from "./BufferLayoutExt";
import type { ActionName, PythGovernanceAction } from "./PythGovernanceAction";
import { PythGovernanceHeader } from "./PythGovernanceAction";
import type { DataSource } from "./SetDataSources";

const DataSourceLayout: BufferLayout.Structure<DataSource> =
BufferLayout.struct([
BufferLayout.u16be("emitterChain"),
BufferLayoutExt.hexBytes(32, "emitterAddress"),
]);

const FeeLayout: BufferLayout.Structure<
Readonly<{ newFeeValue: bigint; newFeeExpo: bigint }>
> = BufferLayout.struct([
BufferLayoutExt.u64be("newFeeValue"),
BufferLayoutExt.u64be("newFeeExpo"),
]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Wire ordering (data-source chain/address, fee value/expo) not verifiable against sibling EVM handler

The wire format encodes each data source as emitterChain(u16be) then emitterAddress(32) and the fee trailer as newFeeValue(u64be) then newFeeExpo(u64be), matching the local SetDataSources/SetFee conventions. However the PR description states the format must match the Solidity parser in a sibling EVM PR, which is not present in this repo, so byte-for-byte compatibility with the on-chain handler could not be confirmed here. Worth confirming against the EVM handler PR before relying on cross-chain interop.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


/**
* Set the wormhole address, data sources, and fee on the target chain.
*
* Wire format after the governance header:
* newWormholeAddress(20) | numSources(u8) | dataSources* | newFeeValue(u64be) | newFeeExpo(u64be)
*/
export class SetWormholeAddressAndDataSources implements PythGovernanceAction {
readonly actionName: ActionName;

constructor(
readonly targetChainId: ChainName,
readonly address: string,
readonly dataSources: DataSource[],
readonly newFeeValue: bigint,
readonly newFeeExpo: bigint,
) {
this.actionName = "SetWormholeAddressAndDataSources";
}

static decode(data: Buffer): SetWormholeAddressAndDataSources | undefined {
const header = PythGovernanceHeader.decode(data);
if (!header || header.action !== "SetWormholeAddressAndDataSources") {
return undefined;
}

let index = PythGovernanceHeader.span;
const address = BufferLayoutExt.hexBytes(20).decode(data, index);
index += 20;

const numSources = BufferLayout.u8().decode(data, index);
index += 1;
const dataSources = [];
for (let i = 0; i < numSources; i++) {
dataSources.push(DataSourceLayout.decode(data, index));
index += DataSourceLayout.span;
}

const fee = FeeLayout.decode(data, index);

return new SetWormholeAddressAndDataSources(
header.targetChainId,
address,
dataSources,
fee.newFeeValue,
fee.newFeeExpo,
);
}

encode(): Buffer {
const headerBuffer = new PythGovernanceHeader(
this.targetChainId,
"SetWormholeAddressAndDataSources",
).encode();

const addressBuf = Buffer.alloc(20);
BufferLayoutExt.hexBytes(20).encode(this.address, addressBuf);

const numSourcesBuf = Buffer.alloc(1);
BufferLayout.u8().encode(this.dataSources.length, numSourcesBuf);

const dataSourceBufs = this.dataSources.map((source) => {
const buf = Buffer.alloc(DataSourceLayout.span);
DataSourceLayout.encode(source, buf);
return buf;
});

const feeBuf = Buffer.alloc(FeeLayout.span);
FeeLayout.encode(
{
newFeeExpo: this.newFeeExpo,
newFeeValue: this.newFeeValue,
},
feeBuf,
);

return safeBufferConcat([
headerBuffer,
addressBuf,
numSourcesBuf,
...dataSourceBufs,
feeBuf,
]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
EvmSetWormholeAddress,
StarknetSetWormholeAddress,
} from "./SetWormholeAddress";
import { SetWormholeAddressAndDataSources } from "./SetWormholeAddressAndDataSources";
import {
CallStellarExecutor,
UpgradeStellarExecutor,
Expand Down Expand Up @@ -84,6 +85,8 @@ export function decodeGovernancePayload(
return undefined;
}
}
case "SetWormholeAddressAndDataSources":
return SetWormholeAddressAndDataSources.decode(data);
case "Execute":
return EvmExecute.decode(data);
case "SetTransactionFee":
Expand Down Expand Up @@ -125,6 +128,7 @@ export * from "./SetFee";
export * from "./SetTransactionFee";
export * from "./SetValidPeriod";
export * from "./SetWormholeAddress";
export * from "./SetWormholeAddressAndDataSources";
export * from "./StellarExecutorAction";
export * from "./UpdateTrustedSigner";
export * from "./UpgradeContract";
Expand Down
Loading