Skip to content

Commit 2df53c9

Browse files
authored
feat(contract_manager): add Movement deployment (#1586)
1 parent b0cb32f commit 2df53c9

File tree

8 files changed

+223
-1
lines changed

8 files changed

+223
-1
lines changed

contract_manager/scripts/sync_wormhole_guardian_set.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
CosmWasmPriceFeedContract,
55
DefaultStore,
66
EvmPriceFeedContract,
7+
SuiWormholeContract,
78
toPrivateKey,
89
} from "../src";
910

@@ -27,6 +28,24 @@ async function main() {
2728
const privateKey = toPrivateKey(argv.privateKey);
2829
const chains = argv.chain;
2930

31+
for (const contract of Object.values(DefaultStore.wormhole_contracts)) {
32+
if (contract instanceof SuiWormholeContract) {
33+
if (chains && !chains.includes(contract.getChain().getId())) {
34+
continue;
35+
}
36+
37+
try {
38+
let index = await contract.getCurrentGuardianSetIndex();
39+
console.log("Guardian Index at Start:", index);
40+
await contract.syncMainnetGuardianSets(privateKey);
41+
index = await contract.getCurrentGuardianSetIndex();
42+
console.log("Guardian Index at End:", index);
43+
} catch (e) {
44+
console.error(`Error updating Guardianset for ${contract.getId()}`, e);
45+
}
46+
}
47+
}
48+
3049
for (const contract of Object.values(DefaultStore.contracts)) {
3150
// We are currently only managing wormhole receiver contracts in EVM and
3251
// CosmWasm and Solana-based networks. The rest of the networks are

contract_manager/src/contracts/sui.ts

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { Chain, SuiChain } from "../chains";
22
import { DataSource } from "@pythnetwork/xc-admin-common";
3+
import { WormholeContract } from "./wormhole";
34
import { PriceFeedContract, PrivateKey, TxResult } from "../base";
45
import { SuiPythClient } from "@pythnetwork/pyth-sui-js";
56
import { SUI_CLOCK_OBJECT_ID } from "@mysten/sui.js/utils";
67
import { Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519";
78
import { TransactionBlock } from "@mysten/sui.js/transactions";
9+
import { uint8ArrayToBCS } from "@certusone/wormhole-sdk/lib/cjs/sui";
810

911
type ObjectId = string;
1012

@@ -405,3 +407,168 @@ export class SuiPriceFeedContract extends PriceFeedContract {
405407
return result.data.content.fields;
406408
}
407409
}
410+
411+
export class SuiWormholeContract extends WormholeContract {
412+
public static type = "SuiWormholeContract";
413+
private client: SuiPythClient;
414+
415+
getId(): string {
416+
return `${this.chain.getId()}_${this.address}`;
417+
}
418+
419+
getType(): string {
420+
return SuiWormholeContract.type;
421+
}
422+
423+
toJson() {
424+
return {
425+
chain: this.chain.getId(),
426+
address: this.address,
427+
type: SuiWormholeContract.type,
428+
};
429+
}
430+
431+
static fromJson(
432+
chain: Chain,
433+
parsed: {
434+
type: string;
435+
address: string;
436+
stateId: string;
437+
}
438+
): SuiWormholeContract {
439+
if (parsed.type !== SuiWormholeContract.type)
440+
throw new Error("Invalid type");
441+
if (!(chain instanceof SuiChain))
442+
throw new Error(`Wrong chain type ${chain}`);
443+
return new SuiWormholeContract(chain, parsed.address, parsed.stateId);
444+
}
445+
446+
constructor(
447+
public chain: SuiChain,
448+
public address: string,
449+
public stateId: string
450+
) {
451+
super();
452+
this.client = new SuiPythClient(
453+
this.chain.getProvider(),
454+
// HACK:
455+
// We're using the SuiPythClient to work with the Wormhole contract
456+
// so there is no Pyth contract here, passing empty string to type-
457+
// check.
458+
"",
459+
this.stateId
460+
);
461+
}
462+
463+
async getCurrentGuardianSetIndex(): Promise<number> {
464+
const data = await this.getStateFields();
465+
return Number(data.guardian_set_index);
466+
}
467+
468+
// There doesn't seem to be a way to get a value out of any function call
469+
// via a Sui transaction due to the linear nature of the language, this is
470+
// enforced at the TransactionBlock level by only allowing you to receive
471+
// receipts.
472+
async getChainId(): Promise<number> {
473+
return this.chain.getWormholeChainId();
474+
}
475+
476+
// NOTE: There's no way to getChain() on the main interface, should update
477+
// that interface.
478+
public getChain(): SuiChain {
479+
return this.chain;
480+
}
481+
482+
async getGuardianSet(): Promise<string[]> {
483+
const data = await this.getStateFields();
484+
const guardian_sets = data.guardian_sets;
485+
return guardian_sets;
486+
}
487+
488+
async upgradeGuardianSets(
489+
senderPrivateKey: PrivateKey,
490+
vaa: Buffer
491+
): Promise<TxResult> {
492+
const tx = new TransactionBlock();
493+
const coreObjectId = this.stateId;
494+
const corePackageId = await this.client.getWormholePackageId();
495+
const [verifiedVaa] = tx.moveCall({
496+
target: `${corePackageId}::vaa::parse_and_verify`,
497+
arguments: [
498+
tx.object(coreObjectId),
499+
tx.pure(uint8ArrayToBCS(vaa)),
500+
tx.object(SUI_CLOCK_OBJECT_ID),
501+
],
502+
});
503+
504+
const [decreeTicket] = tx.moveCall({
505+
target: `${corePackageId}::update_guardian_set::authorize_governance`,
506+
arguments: [tx.object(coreObjectId)],
507+
});
508+
509+
const [decreeReceipt] = tx.moveCall({
510+
target: `${corePackageId}::governance_message::verify_vaa`,
511+
arguments: [tx.object(coreObjectId), verifiedVaa, decreeTicket],
512+
typeArguments: [
513+
`${corePackageId}::update_guardian_set::GovernanceWitness`,
514+
],
515+
});
516+
517+
tx.moveCall({
518+
target: `${corePackageId}::update_guardian_set::update_guardian_set`,
519+
arguments: [
520+
tx.object(coreObjectId),
521+
decreeReceipt,
522+
tx.object(SUI_CLOCK_OBJECT_ID),
523+
],
524+
});
525+
526+
const keypair = Ed25519Keypair.fromSecretKey(
527+
Buffer.from(senderPrivateKey, "hex")
528+
);
529+
const result = await this.executeTransaction(tx, keypair);
530+
return { id: result.digest, info: result };
531+
}
532+
533+
private async getStateFields(): Promise<any> {
534+
const provider = this.chain.getProvider();
535+
const result = await provider.getObject({
536+
id: this.stateId,
537+
options: { showContent: true },
538+
});
539+
if (
540+
!result.data ||
541+
!result.data.content ||
542+
result.data.content.dataType !== "moveObject"
543+
)
544+
throw new Error("Unable to fetch pyth state object");
545+
return result.data.content.fields;
546+
}
547+
548+
/**
549+
* Given a transaction block and a keypair, sign and execute it
550+
* Sets the gas budget to 2x the estimated gas cost
551+
* @param tx
552+
* @param keypair
553+
* @private
554+
*/
555+
private async executeTransaction(
556+
tx: TransactionBlock,
557+
keypair: Ed25519Keypair
558+
) {
559+
const provider = this.chain.getProvider();
560+
tx.setSender(keypair.toSuiAddress());
561+
const dryRun = await provider.dryRunTransactionBlock({
562+
transactionBlock: await tx.build({ client: provider }),
563+
});
564+
tx.setGasBudget(BigInt(dryRun.input.gasData.budget.toString()) * BigInt(2));
565+
return provider.signAndExecuteTransactionBlock({
566+
signer: keypair,
567+
transactionBlock: tx,
568+
options: {
569+
showEffects: true,
570+
showEvents: true,
571+
},
572+
});
573+
}
574+
}

contract_manager/src/store.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
EvmPriceFeedContract,
1616
EvmWormholeContract,
1717
SuiPriceFeedContract,
18+
SuiWormholeContract,
1819
WormholeContract,
1920
} from "./contracts";
2021
import { Token } from "./token";
@@ -122,6 +123,7 @@ export class Store {
122123
[CosmWasmPriceFeedContract.type]: CosmWasmPriceFeedContract,
123124
[CosmWasmWormholeContract.type]: CosmWasmWormholeContract,
124125
[SuiPriceFeedContract.type]: SuiPriceFeedContract,
126+
[SuiWormholeContract.type]: SuiWormholeContract,
125127
[EvmPriceFeedContract.type]: EvmPriceFeedContract,
126128
[AptosPriceFeedContract.type]: AptosPriceFeedContract,
127129
[AptosWormholeContract.type]: AptosWormholeContract,

contract_manager/store/chains/SuiChains.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,8 @@
1313
mainnet: true
1414
rpcUrl: https://fullnode.mainnet.sui.io:443
1515
type: SuiChain
16+
- id: movement_devnet_m2
17+
wormholeChainName: movement_devnet_m2
18+
mainnet: false
19+
rpcUrl: https://sui.devnet.m2.movementlabs.xyz:443
20+
type: SuiChain

contract_manager/store/contracts/SuiPriceFeedContracts.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@
66
stateId: "0x2d82612a354f0b7e52809fc2845642911c7190404620cec8688f68808f8800d8"
77
wormholeStateId: "0xebba4cc4d614f7a7cdbe883acc76d1cc767922bc96778e7b68be0d15fce27c02"
88
type: SuiPriceFeedContract
9+
- chain: movement_devnet_m2
10+
stateId: "0xa2b4997fe170d5d7d622d5f43e54880ccdf69716df4ac4d215a69c35a0a1831f"
11+
wormholeStateId: "0xcf185fbc1af3a437a600587e0b39e5fede163336ffbb7ff24dca9b6eb19d2656"
12+
type: SuiPriceFeedContract
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- chain: movement_devnet_m2
2+
type: SuiWormholeContract
3+
address: "0x23a373b70e6e23a39e4846fa6896fa12beb08da061b3d4ec856bc8ead54f1e22"
4+
stateId: "0xcf185fbc1af3a437a600587e0b39e5fede163336ffbb7ff24dca9b6eb19d2656"

governance/xc_admin/packages/xc_admin_common/src/chains.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ export const RECEIVER_CHAINS = {
151151
orange_testnet: 50073,
152152
polygon_amoy: 50074,
153153
starknet_sepolia: 50075,
154-
// sui_l2: 50076,
154+
movement_devnet_m2: 50076,
155155
taiko_mainnet: 50077,
156156
sei_evm_mainnet: 50078,
157157
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "Pyth"
3+
version = "0.0.2"
4+
5+
[dependencies.Sui]
6+
git = "https://github.com/MystenLabs/sui.git"
7+
subdir = "crates/sui-framework/packages/sui-framework"
8+
rev = "041c5f2bae2fe52079e44b70514333532d69f4e6"
9+
10+
[dependencies.Wormhole]
11+
git = "https://github.com/wormhole-foundation/wormhole.git"
12+
subdir = "sui/wormhole"
13+
rev = "sui-upgrade-testnet"
14+
15+
[addresses]
16+
pyth = "0x0"
17+
wormhole = "0x23a373b70e6e23a39e4846fa6896fa12beb08da061b3d4ec856bc8ead54f1e22"
18+
19+
[dev-addresses]
20+
pyth = "0x100"
21+
wormhole = "0x200"

0 commit comments

Comments
 (0)