cosmwasm.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. import { Chains, CosmWasmChain } from "./chains";
  2. import { readFileSync } from "fs";
  3. import { getPythConfig } from "@pythnetwork/cosmwasm-deploy-tools/lib/configs";
  4. import {
  5. CHAINS,
  6. DataSource,
  7. HexString32Bytes,
  8. SetFeeInstruction,
  9. } from "@pythnetwork/xc-governance-sdk";
  10. import { DeploymentType } from "@pythnetwork/cosmwasm-deploy-tools/lib/helper";
  11. import {
  12. CosmwasmExecutor,
  13. PythWrapperExecutor,
  14. PythWrapperQuerier,
  15. } from "@pythnetwork/cosmwasm-deploy-tools";
  16. import {
  17. ContractInfoResponse,
  18. CosmwasmQuerier,
  19. } from "@pythnetwork/cosmwasm-deploy-tools/lib/chains-manager/chain-querier";
  20. import { PriceServiceConnection } from "@pythnetwork/price-service-client";
  21. import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
  22. import { Contract } from "./base";
  23. /**
  24. * Variables here need to be snake case to match the on-chain contract configs
  25. */
  26. namespace CosmWasmContract {
  27. export interface WormholeSource {
  28. emitter: string;
  29. chain_id: number;
  30. }
  31. export interface DeploymentConfig {
  32. data_sources: WormholeSource[];
  33. governance_source: WormholeSource;
  34. wormhole_contract: string;
  35. governance_source_index: number;
  36. governance_sequence_number: number;
  37. chain_id: number;
  38. valid_time_period_secs: number;
  39. fee: { amount: string; denom: string };
  40. }
  41. }
  42. export class CosmWasmContract extends Contract {
  43. async getDataSources(): Promise<DataSource[]> {
  44. const config = await this.getConfig();
  45. return config.config_v1.data_sources.map(({ emitter, chain_id }: any) => {
  46. return new DataSource(
  47. Number(chain_id),
  48. new HexString32Bytes(Buffer.from(emitter, "base64").toString("hex"))
  49. );
  50. });
  51. }
  52. async getGovernanceDataSource(): Promise<DataSource> {
  53. const config = await this.getConfig();
  54. const { emitter: emitterAddress, chain_id: chainId } =
  55. config.config_v1.governance_source;
  56. return new DataSource(
  57. Number(chainId),
  58. new HexString32Bytes(
  59. Buffer.from(emitterAddress, "base64").toString("hex")
  60. )
  61. );
  62. }
  63. static type = "CosmWasmContract";
  64. constructor(public chain: CosmWasmChain, public address: string) {
  65. super();
  66. }
  67. static fromJson(parsed: any): CosmWasmContract {
  68. if (parsed.type !== CosmWasmContract.type) throw new Error("Invalid type");
  69. if (!Chains[parsed.chain])
  70. throw new Error(`Chain ${parsed.chain} not found`);
  71. return new CosmWasmContract(
  72. Chains[parsed.chain] as CosmWasmChain,
  73. parsed.address
  74. );
  75. }
  76. getType(): string {
  77. return CosmWasmContract.type;
  78. }
  79. //TODO : make deploymentType enum stable | edge
  80. static getDeploymentConfig(
  81. chain: CosmWasmChain,
  82. deploymentType: string,
  83. wormholeContract: string
  84. ): CosmWasmContract.DeploymentConfig {
  85. return getPythConfig({
  86. feeDenom: chain.feeDenom,
  87. wormholeChainId: CHAINS[chain.getId() as keyof typeof CHAINS],
  88. wormholeContract,
  89. deploymentType: deploymentType as DeploymentType,
  90. });
  91. }
  92. /**
  93. * Stores the wasm code on the specified chain using the provided mnemonic as the signer
  94. * You can find the wasm artifacts from the repo releases
  95. * @param chain chain to store the code on
  96. * @param mnemonic mnemonic to use for signing the transaction
  97. * @param wasmPath path in your local filesystem to the wasm artifact
  98. */
  99. static async storeCode(
  100. chain: CosmWasmChain,
  101. mnemonic: string,
  102. wasmPath: string
  103. ) {
  104. const contractBytes = readFileSync(wasmPath);
  105. let executor = this.getExecutor(chain, mnemonic);
  106. return executor.storeCode({ contractBytes });
  107. }
  108. /**
  109. * Deploys a new contract to the specified chain using the uploaded wasm code codeId
  110. * @param chain chain to deploy to
  111. * @param codeId codeId of the uploaded wasm code. You can get this from the storeCode result
  112. * @param config deployment config for initializing the contract (data sources, governance source, etc)
  113. * @param mnemonic mnemonic to use for signing the transaction
  114. */
  115. static async initialize(
  116. chain: CosmWasmChain,
  117. codeId: number,
  118. config: CosmWasmContract.DeploymentConfig,
  119. mnemonic: string
  120. ): Promise<CosmWasmContract> {
  121. let executor = this.getExecutor(chain, mnemonic);
  122. let result = await executor.instantiateContract({
  123. codeId: codeId,
  124. instMsg: config,
  125. label: "pyth",
  126. });
  127. await executor.updateContractAdmin({
  128. newAdminAddr: result.contractAddr,
  129. contractAddr: result.contractAddr,
  130. });
  131. return new CosmWasmContract(chain, result.contractAddr);
  132. }
  133. /**
  134. * Uploads the wasm code and initializes a new contract to the specified chain.
  135. * Use this method if you are deploying to a new chain, or you want a fresh contract in
  136. * a testnet environment. Uses the default deployment configurations for governance, data sources,
  137. * valid time period, etc. You can manually run the storeCode and initialize methods if you want
  138. * more control over the deployment process.
  139. * @param chain
  140. * @param wormholeContract
  141. * @param mnemonic
  142. * @param wasmPath
  143. */
  144. static async deploy(
  145. chain: CosmWasmChain,
  146. wormholeContract: string,
  147. mnemonic: string,
  148. wasmPath: string
  149. ): Promise<CosmWasmContract> {
  150. let config = this.getDeploymentConfig(chain, "edge", wormholeContract);
  151. const { codeId } = await this.storeCode(chain, mnemonic, wasmPath);
  152. return this.initialize(chain, codeId, config, mnemonic);
  153. }
  154. private static getExecutor(chain: CosmWasmChain, mnemonic: string) {
  155. // TODO: logic for injective
  156. return new CosmwasmExecutor(
  157. chain.executorEndpoint,
  158. mnemonic,
  159. chain.prefix,
  160. chain.gasPrice + chain.feeDenom
  161. );
  162. }
  163. getId(): string {
  164. return `${this.chain.getId()}_${this.address}`;
  165. }
  166. toJson() {
  167. return {
  168. chain: this.chain.id,
  169. address: this.address,
  170. type: CosmWasmContract.type,
  171. };
  172. }
  173. async getQuerier(): Promise<PythWrapperQuerier> {
  174. const chainQuerier = await CosmwasmQuerier.connect(
  175. this.chain.querierEndpoint
  176. );
  177. const pythQuerier = new PythWrapperQuerier(chainQuerier);
  178. return pythQuerier;
  179. }
  180. async getCodeId(): Promise<number> {
  181. let result = await this.getWasmContractInfo();
  182. return result.codeId;
  183. }
  184. async getWasmContractInfo(): Promise<ContractInfoResponse> {
  185. const chainQuerier = await CosmwasmQuerier.connect(
  186. this.chain.querierEndpoint
  187. );
  188. return chainQuerier.getContractInfo({ contractAddr: this.address });
  189. }
  190. async getConfig() {
  191. const chainQuerier = await CosmwasmQuerier.connect(
  192. this.chain.querierEndpoint
  193. );
  194. let allStates = (await chainQuerier.getAllContractState({
  195. contractAddr: this.address,
  196. })) as any;
  197. let config = {
  198. config_v1: JSON.parse(allStates["\x00\tconfig_v1"]),
  199. contract_version: JSON.parse(allStates["\x00\x10contract_version"]),
  200. };
  201. return config;
  202. }
  203. // TODO: function for uploading the code and getting the code id
  204. // TODO: function for upgrading the contract
  205. // TODO: Cleanup and more strict linter to convert let to const
  206. async getPriceFeed(feedId: string): Promise<any> {
  207. let querier = await this.getQuerier();
  208. return querier.getPriceFeed(this.address, feedId);
  209. }
  210. equalDataSources(
  211. dataSources1: CosmWasmContract.WormholeSource[],
  212. dataSources2: CosmWasmContract.WormholeSource[]
  213. ): boolean {
  214. if (dataSources1.length !== dataSources2.length) return false;
  215. for (let i = 0; i < dataSources1.length; i++) {
  216. let found = false;
  217. for (let j = 0; j < dataSources2.length; j++) {
  218. if (
  219. dataSources1[i].emitter === dataSources2[j].emitter &&
  220. dataSources1[i].chain_id === dataSources2[j].chain_id
  221. ) {
  222. found = true;
  223. break;
  224. }
  225. }
  226. if (!found) return false;
  227. }
  228. return true;
  229. }
  230. async getDeploymentType(): Promise<string> {
  231. let config = await this.getConfig();
  232. let wormholeContract = config.config_v1.wormhole_contract;
  233. let stableConfig = getPythConfig({
  234. feeDenom: this.chain.feeDenom,
  235. wormholeChainId: CHAINS[this.chain.getId() as keyof typeof CHAINS],
  236. wormholeContract,
  237. deploymentType: "stable",
  238. });
  239. let edgeConfig = getPythConfig({
  240. feeDenom: this.chain.feeDenom,
  241. wormholeChainId: CHAINS[this.chain.getId() as keyof typeof CHAINS],
  242. wormholeContract,
  243. deploymentType: "edge",
  244. });
  245. if (
  246. this.equalDataSources(
  247. config.config_v1.data_sources,
  248. stableConfig.data_sources
  249. )
  250. )
  251. return "stable";
  252. else if (
  253. this.equalDataSources(
  254. config.config_v1.data_sources,
  255. edgeConfig.data_sources
  256. )
  257. )
  258. return "edge";
  259. else return "unknown";
  260. }
  261. async executeUpdatePriceFeed(feedId: string, mnemonic: string) {
  262. const deploymentType = await this.getDeploymentType();
  263. const priceServiceConnection = new PriceServiceConnection(
  264. deploymentType === "stable"
  265. ? "https://xc-mainnet.pyth.network"
  266. : "https://xc-testnet.pyth.network"
  267. );
  268. const vaas = await priceServiceConnection.getLatestVaas([feedId]);
  269. const fund = await this.getUpdateFee(vaas);
  270. let executor = new CosmwasmExecutor(
  271. this.chain.executorEndpoint,
  272. mnemonic,
  273. this.chain.prefix,
  274. this.chain.gasPrice + this.chain.feeDenom
  275. );
  276. let pythExecutor = new PythWrapperExecutor(executor);
  277. return pythExecutor.executeUpdatePriceFeeds({
  278. contractAddr: this.address,
  279. vaas,
  280. fund,
  281. });
  282. }
  283. async executeGovernanceInstruction(mnemonic: string, vaa: string) {
  284. let executor = new CosmwasmExecutor(
  285. this.chain.executorEndpoint,
  286. mnemonic,
  287. this.chain.prefix,
  288. this.chain.gasPrice + this.chain.feeDenom
  289. );
  290. let pythExecutor = new PythWrapperExecutor(executor);
  291. return pythExecutor.executeGovernanceInstruction({
  292. contractAddr: this.address,
  293. vaa,
  294. });
  295. }
  296. async getUpdateFee(msgs: string[]): Promise<any> {
  297. let querier = await this.getQuerier();
  298. return querier.getUpdateFee(this.address, msgs);
  299. }
  300. getSetUpdateFeePayload(fee: number): Buffer {
  301. return new SetFeeInstruction(
  302. CHAINS[this.chain.getId() as keyof typeof CHAINS],
  303. BigInt(fee),
  304. BigInt(0)
  305. ).serialize();
  306. }
  307. async getValidTimePeriod() {
  308. let client = await CosmWasmClient.connect(this.chain.querierEndpoint);
  309. let result = await client.queryContractSmart(
  310. this.address,
  311. "get_valid_time_period"
  312. );
  313. return Number(result.secs + result.nanos * 1e-9);
  314. }
  315. }