main.ts 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import yargs from "yargs";
  2. const {hideBin} = require('yargs/helpers')
  3. import * as web3 from '@solana/web3.js';
  4. import {PublicKey, Transaction, TransactionInstruction, AccountMeta, Keypair, Connection} from "@solana/web3.js";
  5. import {setDefaultWasm, importCoreWasm, ixFromRust} from '@certusone/wormhole-sdk'
  6. setDefaultWasm("node")
  7. yargs(hideBin(process.argv))
  8. .command('post_message [nonce] [message] [consistency]', 'post a message', (yargs) => {
  9. return yargs
  10. .positional('nonce', {
  11. describe: 'nonce of the message',
  12. type: "number",
  13. required: true
  14. })
  15. .positional('message', {
  16. describe: 'message to post',
  17. type: "string",
  18. required: true
  19. })
  20. .positional('consistency', {
  21. describe: 'confirmation level that this message requires <CONFIRMED|FINALIZED>',
  22. type: "string",
  23. required: true
  24. })
  25. }, async (argv: any) => {
  26. const bridge = await importCoreWasm()
  27. let connection = setupConnection(argv);
  28. let bridge_id = new PublicKey(argv.bridge);
  29. // Generate a new random public key
  30. let from = web3.Keypair.generate();
  31. let emitter = web3.Keypair.generate();
  32. let message = web3.Keypair.generate();
  33. let airdropSignature = await connection.requestAirdrop(
  34. from.publicKey,
  35. web3.LAMPORTS_PER_SOL,
  36. );
  37. await connection.confirmTransaction(airdropSignature);
  38. let fee_acc = await bridge.fee_collector_address(bridge_id.toString());
  39. let bridge_state = await get_bridge_state(connection, bridge_id);
  40. let transferIx = web3.SystemProgram.transfer({
  41. fromPubkey: from.publicKey,
  42. toPubkey: new PublicKey(fee_acc),
  43. lamports: bridge_state.config.fee,
  44. });
  45. if (argv.consistency !== "CONFIRMED" && argv.consistency !== "FINALIZED") {
  46. throw new Error("invalid consistency level")
  47. }
  48. let ix = ixFromRust(bridge.post_message_ix(bridge_id.toString(), from.publicKey.toString(), emitter.publicKey.toString(), message.publicKey.toString(), argv.nonce, Buffer.from(argv.message, "hex"), argv.consistency));
  49. // Add transfer instruction to transaction
  50. let transaction = new web3.Transaction().add(transferIx, ix);
  51. // Sign transaction, broadcast, and confirm
  52. let signature = await web3.sendAndConfirmTransaction(
  53. connection,
  54. transaction,
  55. [from, emitter, message],
  56. {
  57. skipPreflight: true
  58. }
  59. );
  60. console.log('SIGNATURE', signature);
  61. })
  62. .command('post_vaa [vaa]', 'post a VAA on Solana', (yargs) => {
  63. return yargs
  64. .positional('vaa', {
  65. describe: 'vaa to post',
  66. type: "string",
  67. required: true
  68. })
  69. }, async (argv: any) => {
  70. let connection = setupConnection(argv);
  71. let bridge_id = new PublicKey(argv.bridge);
  72. // Generate a new random public key
  73. let from = web3.Keypair.generate();
  74. let airdropSignature = await connection.requestAirdrop(
  75. from.publicKey,
  76. web3.LAMPORTS_PER_SOL,
  77. );
  78. await connection.confirmTransaction(airdropSignature);
  79. let vaa = Buffer.from(argv.vaa, "hex");
  80. await post_vaa(connection, bridge_id, from, vaa);
  81. })
  82. .command('execute_governance_vaa [vaa]', 'execute a governance VAA on Solana', (yargs) => {
  83. return yargs
  84. .positional('vaa', {
  85. describe: 'vaa to post',
  86. type: "string",
  87. required: true
  88. })
  89. }, async (argv: any) => {
  90. const bridge = await importCoreWasm()
  91. let connection = setupConnection(argv);
  92. let bridge_id = new PublicKey(argv.bridge);
  93. // Generate a new random public key
  94. let from = web3.Keypair.generate();
  95. let airdropSignature = await connection.requestAirdrop(
  96. from.publicKey,
  97. web3.LAMPORTS_PER_SOL,
  98. );
  99. await connection.confirmTransaction(airdropSignature);
  100. let vaa = Buffer.from(argv.vaa, "hex");
  101. await post_vaa(connection, bridge_id, from, vaa);
  102. let parsed_vaa = await bridge.parse_vaa(vaa);
  103. let ix: TransactionInstruction;
  104. switch (parsed_vaa.payload[32]) {
  105. case 1:
  106. console.log("Upgrading contract")
  107. ix = bridge.upgrade_contract_ix(bridge_id.toString(), from.publicKey.toString(), from.publicKey.toString(), vaa);
  108. break
  109. case 2:
  110. console.log("Updating guardian set")
  111. ix = bridge.update_guardian_set_ix(bridge_id.toString(), from.publicKey.toString(), vaa);
  112. break
  113. case 3:
  114. console.log("Setting fees")
  115. ix = bridge.set_fees_ix(bridge_id.toString(), from.publicKey.toString(), vaa);
  116. break
  117. case 4:
  118. console.log("Transferring fees")
  119. ix = bridge.transfer_fees_ix(bridge_id.toString(), from.publicKey.toString(), vaa);
  120. break
  121. default:
  122. throw new Error("unknown governance action")
  123. }
  124. let transaction = new web3.Transaction().add(ixFromRust(ix));
  125. // Sign transaction, broadcast, and confirm
  126. let signature = await web3.sendAndConfirmTransaction(
  127. connection,
  128. transaction,
  129. [from],
  130. {
  131. skipPreflight: true
  132. }
  133. );
  134. console.log('SIGNATURE', signature);
  135. })
  136. .option('rpc', {
  137. alias: 'u',
  138. type: 'string',
  139. description: 'URL of the Solana RPC',
  140. default: "http://localhost:8899"
  141. })
  142. .option('bridge', {
  143. alias: 'b',
  144. type: 'string',
  145. description: 'Bridge address',
  146. default: "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"
  147. })
  148. .argv;
  149. async function post_vaa(connection: Connection, bridge_id: PublicKey, payer: Keypair, vaa: Buffer) {
  150. const bridge = await importCoreWasm()
  151. let bridge_state = await get_bridge_state(connection, bridge_id);
  152. let guardian_addr = new PublicKey(bridge.guardian_set_address(bridge_id.toString(), bridge_state.guardian_set_index));
  153. let acc = await connection.getAccountInfo(guardian_addr);
  154. if (acc?.data === undefined) {
  155. return
  156. }
  157. let guardian_data = bridge.parse_guardian_set(new Uint8Array(acc?.data));
  158. let signature_set = Keypair.generate();
  159. let txs = bridge.verify_signatures_ix(bridge_id.toString(), payer.publicKey.toString(), bridge_state.guardian_set_index, guardian_data, signature_set.publicKey.toString(), vaa)
  160. // Add transfer instruction to transaction
  161. for (let tx of txs) {
  162. let ixs: Array<TransactionInstruction> = tx.map((v: any) => {
  163. return ixFromRust(v)
  164. })
  165. let transaction = new web3.Transaction().add(ixs[0], ixs[1]);
  166. // Sign transaction, broadcast, and confirm
  167. await web3.sendAndConfirmTransaction(
  168. connection,
  169. transaction,
  170. [payer, signature_set],
  171. {
  172. skipPreflight: true
  173. }
  174. );
  175. }
  176. let ix = ixFromRust(bridge.post_vaa_ix(bridge_id.toString(), payer.publicKey.toString(), signature_set.publicKey.toString(), vaa));
  177. let transaction = new web3.Transaction().add(ix);
  178. // Sign transaction, broadcast, and confirm
  179. let signature = await web3.sendAndConfirmTransaction(
  180. connection,
  181. transaction,
  182. [payer],
  183. {
  184. skipPreflight: true
  185. }
  186. );
  187. console.log('SIGNATURE', signature);
  188. }
  189. async function get_bridge_state(connection: Connection, bridge_id: PublicKey): Promise<BridgeState> {
  190. const bridge = await importCoreWasm()
  191. let bridge_state = new PublicKey(bridge.state_address(bridge_id.toString()));
  192. let acc = await connection.getAccountInfo(bridge_state);
  193. if (acc?.data === undefined) {
  194. throw new Error("bridge state not found")
  195. }
  196. return bridge.parse_state(new Uint8Array(acc?.data));
  197. }
  198. function setupConnection(argv: yargs.Arguments): web3.Connection {
  199. return new web3.Connection(
  200. argv.rpc as string,
  201. 'confirmed',
  202. );
  203. }
  204. interface BridgeState {
  205. // The current guardian set index, used to decide which signature sets to accept.
  206. guardian_set_index: number,
  207. // Lamports in the collection account
  208. last_lamports: number,
  209. // Bridge configuration, which is set once upon initialization.
  210. config: BridgeConfig,
  211. }
  212. interface BridgeConfig {
  213. // Period for how long a guardian set is valid after it has been replaced by a new one. This
  214. // guarantees that VAAs issued by that set can still be submitted for a certain period. In
  215. // this period we still trust the old guardian set.
  216. guardian_set_expiration_time: number,
  217. // Amount of lamports that needs to be paid to the protocol to post a message
  218. fee: number,
  219. }