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
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/MigrateGovernanceAndWormhole": {
"import": {
"default": "./dist/esm/governance_payload/MigrateGovernanceAndWormhole.mjs",
"types": "./dist/esm/governance_payload/MigrateGovernanceAndWormhole.d.ts"
},
"require": {
"default": "./dist/cjs/governance_payload/MigrateGovernanceAndWormhole.cjs",
"types": "./dist/cjs/governance_payload/MigrateGovernanceAndWormhole.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 { MigrateGovernanceAndWormhole } from "../governance_payload/MigrateGovernanceAndWormhole";
import {
CosmosUpgradeContract,
EvmUpgradeContract,
Expand Down Expand Up @@ -271,6 +272,103 @@ test("GovernancePayload ser/de", () => {
),
).toBeTruthy();

const migrateGovernanceAndWormhole = new MigrateGovernanceAndWormhole(
"ethereum",
"0102030405060708090a0b0c0d0e0f1011121314",
[
{
emitterAddress:
"6bb14509a612f01fbbc4cffeebd4bbfb492a86df717ebe92eb6df432a3f00a25",
emitterChain: 1,
},
{
emitterAddress:
"000000000000000000000000000000000000000000000000000000000000012d",
emitterChain: 3,
},
],
{
emitterAddress:
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
emitterChain: 1,
},
2,
);
const migrateGovernanceAndWormholeBuffer =
migrateGovernanceAndWormhole.encode();
console.log(migrateGovernanceAndWormholeBuffer.toJSON());
expect(
migrateGovernanceAndWormholeBuffer.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,
// governance emitter
0, 1, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
170, 170, 170, 170, 170,
// governanceDataSourceIndex = 2
0, 0, 0, 2,
]),
),
).toBeTruthy();
const decodedMigrateGovernanceAndWormhole =
MigrateGovernanceAndWormhole.decode(migrateGovernanceAndWormholeBuffer);
expect(decodedMigrateGovernanceAndWormhole?.targetChainId).toBe("ethereum");
expect(decodedMigrateGovernanceAndWormhole?.address).toBe(
"0102030405060708090a0b0c0d0e0f1011121314",
);
expect(decodedMigrateGovernanceAndWormhole?.dataSources).toEqual([
{
emitterAddress:
"6bb14509a612f01fbbc4cffeebd4bbfb492a86df717ebe92eb6df432a3f00a25",
emitterChain: 1,
},
{
emitterAddress:
"000000000000000000000000000000000000000000000000000000000000012d",
emitterChain: 3,
},
]);
expect(decodedMigrateGovernanceAndWormhole?.governanceDataSource).toEqual({
emitterAddress:
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
emitterChain: 1,
});
expect(decodedMigrateGovernanceAndWormhole?.governanceDataSourceIndex).toBe(
2,
);
expect(
decodeGovernancePayload(migrateGovernanceAndWormholeBuffer),
).toBeInstanceOf(MigrateGovernanceAndWormhole);

// Empty data sources is a valid payload
// (header + address + numSources=0 + governance emitter + index).
const migratePayload = new MigrateGovernanceAndWormhole(
"ethereum",
"0102030405060708090a0b0c0d0e0f1011121314",
[],
{
emitterAddress:
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
emitterChain: 1,
},
2,
);
const migrateBuffer = migratePayload.encode();
expect(migrateBuffer.length).toBe(8 + 20 + 1 + 34 + 4);
expect(migrateBuffer[5]).toBe(10);

const upgradeContract = new UpgradeContract256Bit(
"starknet",
"043d0ed8155263af0862372df3af9403c502358661f317f62fbdc026d03beaee",
Expand Down Expand Up @@ -483,6 +581,30 @@ function governanceActionArb(): Arbitrary<PythGovernanceAction> {
},
);
return fc.oneof(evmArb, starknetArb);
} else if (header.action === "MigrateGovernanceAndWormhole") {
return fc
.record({
address: hexBytesArb({ maxLength: 20, minLength: 20 }),
dataSources: fc.array(dataSourceArb()),
governanceDataSource: dataSourceArb(),
governanceDataSourceIndex: fc.nat({ max: 0xffff_ffff }),
})
.map(
({
address,
dataSources,
governanceDataSource,
governanceDataSourceIndex,
}) => {
return new MigrateGovernanceAndWormhole(
header.targetChainId,
address,
dataSources,
governanceDataSource,
governanceDataSourceIndex,
);
},
);
} else if (header.action === "Execute") {
return fc
.record({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
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"),
]);

/**
* Migrate the wormhole address, price data sources, and governance emitter
* on the target chain.
*
* Wire format after the governance header:
* newWormholeAddress(20) |
* numSources(u8) | [emitterChain(u16be) | emitterAddress(32)]* |
* governanceEmitterChain(u16be) | governanceEmitterAddress(32) |
* governanceDataSourceIndex(u32be)
*
* Fee is not included; set fee separately via SetFee before migration.
*/
export class MigrateGovernanceAndWormhole implements PythGovernanceAction {
readonly actionName: ActionName;

constructor(
readonly targetChainId: ChainName,
readonly address: string,
readonly dataSources: DataSource[],
readonly governanceDataSource: DataSource,
readonly governanceDataSourceIndex: number,
) {
this.actionName = "MigrateGovernanceAndWormhole";
}

static decode(data: Buffer): MigrateGovernanceAndWormhole | undefined {
const header = PythGovernanceHeader.decode(data);
if (!header || header.action !== "MigrateGovernanceAndWormhole") {
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 governanceDataSource = DataSourceLayout.decode(data, index);
index += DataSourceLayout.span;

const governanceDataSourceIndex = BufferLayout.u32be().decode(data, index);
index += 4;

if (index !== data.length) {
return undefined;
}

return new MigrateGovernanceAndWormhole(
header.targetChainId,
address,
dataSources,
governanceDataSource,
governanceDataSourceIndex,
);
}

encode(): Buffer {
const headerBuffer = new PythGovernanceHeader(
this.targetChainId,
"MigrateGovernanceAndWormhole",
).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 governanceDataSourceBuf = Buffer.alloc(DataSourceLayout.span);
DataSourceLayout.encode(
this.governanceDataSource,
governanceDataSourceBuf,
);

const governanceDataSourceIndexBuf = Buffer.alloc(4);
BufferLayout.u32be().encode(
this.governanceDataSourceIndex,
governanceDataSourceIndexBuf,
);

return safeBufferConcat([
headerBuffer,
addressBuf,
numSourcesBuf,
...dataSourceBufs,
governanceDataSourceBuf,
governanceDataSourceIndexBuf,
]);
}
}
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,
MigrateGovernanceAndWormhole: 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 "MigrateGovernanceAndWormhole";
}
} else if (
deserialized.moduleId == MODULE_EVM_EXECUTOR &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
EvmSetWormholeAddress,
StarknetSetWormholeAddress,
} from "./SetWormholeAddress";
import { MigrateGovernanceAndWormhole } from "./MigrateGovernanceAndWormhole";
import {
CallStellarExecutor,
UpgradeStellarExecutor,
Expand Down Expand Up @@ -84,6 +85,8 @@ export function decodeGovernancePayload(
return undefined;
}
}
case "MigrateGovernanceAndWormhole":
return MigrateGovernanceAndWormhole.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 "./MigrateGovernanceAndWormhole";
export * from "./StellarExecutorAction";
export * from "./UpdateTrustedSigner";
export * from "./UpgradeContract";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
MultisigParser,
PythGovernanceActionImpl,
SetDataSources,
MigrateGovernanceAndWormhole,
WormholeMultisigInstruction,
} from "@pythnetwork/xc-admin-common";
import type { PublicKey } from "@solana/web3.js";
Expand Down Expand Up @@ -88,7 +89,10 @@ const getInstructionSummary = (
);
} else if (governanceAction instanceof PythGovernanceActionImpl) {
return [{ name: governanceAction.action } as const];
} else if (governanceAction instanceof SetDataSources) {
} else if (
governanceAction instanceof SetDataSources ||
governanceAction instanceof MigrateGovernanceAndWormhole
) {
return [{ name: governanceAction.actionName } as const];
} else {
return [{ name: "unknown" } as const];
Expand Down
Loading