aptos.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. import { AptosAccount, TxnBuilderTypes, AptosClient, BCS } from "aptos";
  2. import { NETWORKS } from "./networks";
  3. import { impossible, Payload } from "./vaa";
  4. import { sha3_256 } from "js-sha3";
  5. import { ethers } from "ethers";
  6. import { assertChain, ChainId, CONTRACTS } from "@certusone/wormhole-sdk/lib/cjs/utils/consts";
  7. export async function execute_aptos(
  8. payload: Payload,
  9. vaa: Buffer,
  10. network: "MAINNET" | "TESTNET" | "DEVNET",
  11. contract: string | undefined,
  12. rpc: string | undefined
  13. ) {
  14. const chain = "aptos";
  15. // turn VAA bytes into BCS format. That is, add a length prefix
  16. const serializer = new BCS.Serializer();
  17. serializer.serializeBytes(vaa);
  18. const bcsVAA = serializer.getBytes();
  19. switch (payload.module) {
  20. case "Core":
  21. contract = contract ?? CONTRACTS[network][chain]["core"];
  22. if (contract === undefined) {
  23. throw Error("core bridge contract is undefined")
  24. }
  25. switch (payload.type) {
  26. case "GuardianSetUpgrade":
  27. console.log("Submitting new guardian set")
  28. await callEntryFunc(network, rpc, `${contract}::guardian_set_upgrade`, "submit_vaa_entry", [], [bcsVAA]);
  29. break
  30. case "ContractUpgrade":
  31. console.log("Upgrading core contract")
  32. await callEntryFunc(network, rpc, `${contract}::contract_upgrade`, "submit_vaa_entry", [], [bcsVAA]);
  33. break
  34. default:
  35. impossible(payload)
  36. }
  37. break
  38. case "NFTBridge":
  39. contract = contract ?? CONTRACTS[network][chain]["nft_bridge"];
  40. if (contract === undefined) {
  41. throw Error("nft bridge contract is undefined")
  42. }
  43. switch (payload.type) {
  44. case "ContractUpgrade":
  45. console.log("Upgrading contract")
  46. await callEntryFunc(network, rpc, `${contract}::contract_upgrade`, "submit_vaa_entry", [], [bcsVAA]);
  47. break
  48. case "RegisterChain":
  49. console.log("Registering chain")
  50. await callEntryFunc(network, rpc, `${contract}::register_chain`, "submit_vaa_entry", [], [bcsVAA]);
  51. break
  52. case "Transfer": {
  53. console.log("Completing transfer")
  54. await callEntryFunc(network, rpc, `${contract}::complete_transfer`, "submit_vaa_entry", [], [bcsVAA]);
  55. break
  56. }
  57. }
  58. break
  59. case "TokenBridge":
  60. contract = contract ?? CONTRACTS[network][chain]["token_bridge"];
  61. if (contract === undefined) {
  62. throw Error("token bridge contract is undefined")
  63. }
  64. switch (payload.type) {
  65. case "ContractUpgrade":
  66. console.log("Upgrading contract")
  67. await callEntryFunc(network, rpc, `${contract}::contract_upgrade`, "submit_vaa_entry", [], [bcsVAA]);
  68. break
  69. case "RegisterChain":
  70. console.log("Registering chain")
  71. await callEntryFunc(network, rpc, `${contract}::register_chain`, "submit_vaa_entry", [], [bcsVAA]);
  72. break
  73. case "AttestMeta": {
  74. console.log("Creating wrapped token")
  75. // Deploying a wrapped asset requires two transactions:
  76. // 1. Publish a new module under a resource account that defines a type T
  77. // 2. Initialise a new coin with that type T
  78. // These need to be done in separate transactions, becasue a
  79. // transaction that deploys a module cannot use that module
  80. //
  81. // Tx 1.
  82. try {
  83. await callEntryFunc(network, rpc, `${contract}::wrapped`, "create_wrapped_coin_type", [], [bcsVAA]);
  84. } catch (e) {
  85. console.log("this one already happened (probably)")
  86. }
  87. // We just deployed the module (notice the "wait" argument which makes
  88. // the previous step block until finality).
  89. // Now we're ready to do Tx 2. The module above got deployed to a new
  90. // resource account, which is seeded by the token bridge's address and
  91. // the origin information of the token. We can recompute this address
  92. // offline:
  93. const tokenAddress = payload.tokenAddress;
  94. const tokenChain = payload.tokenChain;
  95. assertChain(tokenChain);
  96. let wrappedContract = deriveWrappedAssetAddress(hex(contract), tokenChain, hex(tokenAddress));
  97. // Tx 2.
  98. console.log(`Deploying resource account ${wrappedContract}`);
  99. let token = new TxnBuilderTypes.TypeTagStruct(TxnBuilderTypes.StructTag.fromString(`${wrappedContract}::coin::T`));
  100. await callEntryFunc(network, rpc, `${contract}::wrapped`, "create_wrapped_coin", [token], [bcsVAA]);
  101. break
  102. }
  103. case "Transfer": {
  104. console.log("Completing transfer")
  105. // TODO: only handles wrapped assets for now
  106. const tokenAddress = payload.tokenAddress;
  107. const tokenChain = payload.tokenChain;
  108. assertChain(tokenChain);
  109. let wrappedContract = deriveWrappedAssetAddress(hex(contract), tokenChain, hex(tokenAddress));
  110. const token = new TxnBuilderTypes.TypeTagStruct(TxnBuilderTypes.StructTag.fromString(`${wrappedContract}::coin::T`));
  111. await callEntryFunc(network, rpc, `${contract}::complete_transfer`, "submit_vaa_and_register_entry", [token], [bcsVAA]);
  112. break
  113. }
  114. case "TransferWithPayload":
  115. throw Error("Can't complete payload 3 transfer from CLI")
  116. default:
  117. impossible(payload)
  118. break
  119. }
  120. break
  121. default:
  122. impossible(payload)
  123. }
  124. }
  125. export function deriveWrappedAssetAddress(
  126. token_bridge_address: Uint8Array, // 32 bytes
  127. origin_chain: ChainId,
  128. origin_address: Uint8Array, // 32 bytes
  129. ): string {
  130. let chain: Buffer = Buffer.alloc(2);
  131. chain.writeUInt16BE(origin_chain);
  132. if (origin_address.length != 32) {
  133. throw new Error(`${origin_address}`)
  134. }
  135. // from https://github.com/aptos-labs/aptos-core/blob/25696fd266498d81d346fe86e01c330705a71465/aptos-move/framework/aptos-framework/sources/account.move#L90-L95
  136. let DERIVE_RESOURCE_ACCOUNT_SCHEME = Buffer.alloc(1);
  137. DERIVE_RESOURCE_ACCOUNT_SCHEME.writeUInt8(255);
  138. return sha3_256(Buffer.concat([token_bridge_address, chain, Buffer.from("::", "ascii"), origin_address, DERIVE_RESOURCE_ACCOUNT_SCHEME]));
  139. }
  140. export function deriveResourceAccount(
  141. deployer: Uint8Array, // 32 bytes
  142. seed: string,
  143. ) {
  144. // from https://github.com/aptos-labs/aptos-core/blob/25696fd266498d81d346fe86e01c330705a71465/aptos-move/framework/aptos-framework/sources/account.move#L90-L95
  145. let DERIVE_RESOURCE_ACCOUNT_SCHEME = Buffer.alloc(1);
  146. DERIVE_RESOURCE_ACCOUNT_SCHEME.writeUInt8(255);
  147. return sha3_256(Buffer.concat([deployer, Buffer.from(seed, "ascii"), DERIVE_RESOURCE_ACCOUNT_SCHEME]))
  148. }
  149. export async function callEntryFunc(
  150. network: "MAINNET" | "TESTNET" | "DEVNET",
  151. rpc: string | undefined,
  152. module: string,
  153. func: string,
  154. ty_args: BCS.Seq<TxnBuilderTypes.TypeTag>,
  155. args: BCS.Seq<BCS.Bytes>,
  156. ) {
  157. let key: string | undefined = NETWORKS[network]["aptos"].key;
  158. if (key === undefined) {
  159. throw new Error("No key for aptos");
  160. }
  161. const accountFrom = new AptosAccount(new Uint8Array(Buffer.from(key, "hex")));
  162. let client: AptosClient;
  163. // if rpc arg is passed in, then override default rpc value for that network
  164. if (typeof rpc != 'undefined') {
  165. client = new AptosClient(rpc);
  166. } else {
  167. client = new AptosClient(NETWORKS[network]["aptos"].rpc);
  168. }
  169. const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
  170. client.getAccount(accountFrom.address()),
  171. client.getChainId(),
  172. ]);
  173. const txPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
  174. TxnBuilderTypes.EntryFunction.natural(
  175. module,
  176. func,
  177. ty_args,
  178. args
  179. )
  180. );
  181. const rawTxn = new TxnBuilderTypes.RawTransaction(
  182. TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()),
  183. BigInt(sequenceNumber),
  184. txPayload,
  185. BigInt(100000), //max gas to be used. TODO(csongor): we could compute this from the simulation below...
  186. BigInt(100), //price per unit gas TODO(csongor): we should get this dynamically
  187. BigInt(Math.floor(Date.now() / 1000) + 10),
  188. new TxnBuilderTypes.ChainId(chainId),
  189. );
  190. // simulate transaction before submitting
  191. const sim = await client.simulateTransaction(accountFrom, rawTxn);
  192. sim.forEach((tx) => {
  193. if (!tx.success) {
  194. console.error(JSON.stringify(tx, null, 2));
  195. throw new Error(`Transaction failed: ${tx.vm_status}`);
  196. }
  197. });
  198. // simulation successful... let's do it
  199. const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn);
  200. const transactionRes = await client.submitSignedBCSTransaction(bcsTxn);
  201. await client.waitForTransaction(transactionRes.hash);
  202. return transactionRes.hash;
  203. }
  204. // strip the 0x prefix from a hex string
  205. function hex(x: string): Buffer {
  206. return Buffer.from(ethers.utils.hexlify(x, { allowMissingPrefix: true }).substring(2), "hex");
  207. }