cosmwasm.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. import { Chain, CosmWasmChain } from "../chains";
  2. import { readFileSync } from "fs";
  3. import {
  4. ContractInfoResponse,
  5. CosmwasmQuerier,
  6. Price,
  7. PythWrapperExecutor,
  8. PythWrapperQuerier,
  9. } from "@pythnetwork/cosmwasm-deploy-tools";
  10. import { Coin } from "@cosmjs/stargate";
  11. import { DataSource } from "xc_admin_common";
  12. import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
  13. import {
  14. Contract,
  15. getDefaultDeploymentConfig,
  16. PrivateKey,
  17. TxResult,
  18. } from "../base";
  19. import { WormholeContract } from "./wormhole";
  20. /**
  21. * Variables here need to be snake case to match the on-chain contract configs
  22. */
  23. export interface WormholeSource {
  24. emitter: string;
  25. chain_id: number;
  26. }
  27. export interface DeploymentConfig {
  28. data_sources: WormholeSource[];
  29. governance_source: WormholeSource;
  30. wormhole_contract: string;
  31. governance_source_index: number;
  32. governance_sequence_number: number;
  33. chain_id: number;
  34. valid_time_period_secs: number;
  35. fee: { amount: string; denom: string };
  36. }
  37. export class WormholeCosmWasmContract extends WormholeContract {
  38. constructor(public chain: CosmWasmChain, public address: string) {
  39. super();
  40. }
  41. async getConfig() {
  42. const chainQuerier = await CosmwasmQuerier.connect(this.chain.endpoint);
  43. return (await chainQuerier.getAllContractState({
  44. contractAddr: this.address,
  45. })) as Record<string, string>;
  46. }
  47. async getCurrentGuardianSetIndex(): Promise<number> {
  48. const config = await this.getConfig();
  49. return JSON.parse(config["\x00\x06config"])["guardian_set_index"];
  50. }
  51. async getGuardianSet(): Promise<string[]> {
  52. const config = await this.getConfig();
  53. const guardianSetIndex = JSON.parse(config["\x00\x06config"])[
  54. "guardian_set_index"
  55. ];
  56. let key = "\x00\fguardian_set";
  57. //append guardianSetIndex as 4 bytes to key string
  58. key += Buffer.from(guardianSetIndex.toString(16).padStart(8, "0"), "hex");
  59. const guardianSet = JSON.parse(config[key])["addresses"];
  60. return guardianSet.map((entry: { bytes: string }) =>
  61. Buffer.from(entry.bytes, "base64").toString("hex")
  62. );
  63. }
  64. async upgradeGuardianSets(
  65. senderPrivateKey: PrivateKey,
  66. vaa: Buffer
  67. ): Promise<TxResult> {
  68. const executor = await this.chain.getExecutor(senderPrivateKey);
  69. const result = await executor.executeContract({
  70. contractAddr: this.address,
  71. msg: {
  72. submit_v_a_a: { vaa: vaa.toString("base64") },
  73. },
  74. });
  75. return { id: result.txHash, info: result };
  76. }
  77. }
  78. export class CosmWasmContract extends Contract {
  79. async getDataSources(): Promise<DataSource[]> {
  80. const config = await this.getConfig();
  81. return config.config_v1.data_sources.map(
  82. ({ emitter, chain_id }: WormholeSource) => {
  83. return {
  84. emitterChain: Number(chain_id),
  85. emitterAddress: Buffer.from(emitter, "base64").toString("hex"),
  86. };
  87. }
  88. );
  89. }
  90. async getGovernanceDataSource(): Promise<DataSource> {
  91. const config = await this.getConfig();
  92. const { emitter: emitterAddress, chain_id: chainId } =
  93. config.config_v1.governance_source;
  94. return {
  95. emitterChain: Number(chainId),
  96. emitterAddress: Buffer.from(emitterAddress, "base64").toString("hex"),
  97. };
  98. }
  99. static type = "CosmWasmContract";
  100. constructor(public chain: CosmWasmChain, public address: string) {
  101. super();
  102. }
  103. static fromJson(
  104. chain: Chain,
  105. parsed: { type: string; address: string }
  106. ): CosmWasmContract {
  107. if (parsed.type !== CosmWasmContract.type) throw new Error("Invalid type");
  108. if (!(chain instanceof CosmWasmChain))
  109. throw new Error(`Wrong chain type ${chain}`);
  110. return new CosmWasmContract(chain, parsed.address);
  111. }
  112. getType(): string {
  113. return CosmWasmContract.type;
  114. }
  115. /**
  116. * Stores the wasm code on the specified chain using the provided private key as the signer
  117. * You can find the wasm artifacts from the repo releases
  118. * @param chain chain to store the code on
  119. * @param privateKey private key to use for signing the transaction in hex format without 0x prefix
  120. * @param wasmPath path in your local filesystem to the wasm artifact
  121. */
  122. static async storeCode(
  123. chain: CosmWasmChain,
  124. privateKey: PrivateKey,
  125. wasmPath: string
  126. ) {
  127. const contractBytes = readFileSync(wasmPath);
  128. const executor = await chain.getExecutor(privateKey);
  129. return executor.storeCode({ contractBytes });
  130. }
  131. /**
  132. * Deploys a new contract to the specified chain using the uploaded wasm code codeId
  133. * @param chain chain to deploy to
  134. * @param codeId codeId of the uploaded wasm code. You can get this from the storeCode result
  135. * @param config deployment config for initializing the contract (data sources, governance source, etc)
  136. * @param privateKey private key to use for signing the transaction in hex format without 0x prefix
  137. */
  138. static async initialize(
  139. chain: CosmWasmChain,
  140. codeId: number,
  141. config: DeploymentConfig,
  142. privateKey: PrivateKey
  143. ): Promise<CosmWasmContract> {
  144. const executor = await chain.getExecutor(privateKey);
  145. const result = await executor.instantiateContract({
  146. codeId: codeId,
  147. instMsg: config,
  148. label: "pyth",
  149. });
  150. await executor.updateContractAdmin({
  151. newAdminAddr: result.contractAddr,
  152. contractAddr: result.contractAddr,
  153. });
  154. return new CosmWasmContract(chain, result.contractAddr);
  155. }
  156. getId(): string {
  157. return `${this.chain.getId()}_${this.address}`;
  158. }
  159. toJson() {
  160. return {
  161. chain: this.chain.getId(),
  162. address: this.address,
  163. type: CosmWasmContract.type,
  164. };
  165. }
  166. async getQuerier(): Promise<PythWrapperQuerier> {
  167. const chainQuerier = await CosmwasmQuerier.connect(this.chain.endpoint);
  168. const pythQuerier = new PythWrapperQuerier(chainQuerier);
  169. return pythQuerier;
  170. }
  171. async getCodeId(): Promise<number> {
  172. const result = await this.getWasmContractInfo();
  173. return result.codeId;
  174. }
  175. async getWasmContractInfo(): Promise<ContractInfoResponse> {
  176. const chainQuerier = await CosmwasmQuerier.connect(this.chain.endpoint);
  177. return chainQuerier.getContractInfo({ contractAddr: this.address });
  178. }
  179. async getConfig() {
  180. const chainQuerier = await CosmwasmQuerier.connect(this.chain.endpoint);
  181. const allStates = (await chainQuerier.getAllContractState({
  182. contractAddr: this.address,
  183. })) as Record<string, string>;
  184. const config = {
  185. config_v1: JSON.parse(allStates["\x00\tconfig_v1"]),
  186. contract_version: JSON.parse(allStates["\x00\x10contract_version"]),
  187. };
  188. return config;
  189. }
  190. async getLastExecutedGovernanceSequence() {
  191. const config = await this.getConfig();
  192. return Number(config.config_v1.governance_sequence_number);
  193. }
  194. private parsePrice(priceInfo: Price) {
  195. return {
  196. conf: priceInfo.conf.toString(),
  197. publishTime: priceInfo.publish_time.toString(),
  198. expo: priceInfo.expo.toString(),
  199. price: priceInfo.price.toString(),
  200. };
  201. }
  202. async getPriceFeed(feedId: string) {
  203. const querier = await this.getQuerier();
  204. try {
  205. const response = await querier.getPriceFeed(this.address, feedId);
  206. return {
  207. price: this.parsePrice(response.price),
  208. emaPrice: this.parsePrice(response.ema_price),
  209. };
  210. } catch (e) {
  211. return undefined;
  212. }
  213. }
  214. equalDataSources(
  215. dataSources1: WormholeSource[],
  216. dataSources2: WormholeSource[]
  217. ): boolean {
  218. if (dataSources1.length !== dataSources2.length) return false;
  219. for (let i = 0; i < dataSources1.length; i++) {
  220. let found = false;
  221. for (let j = 0; j < dataSources2.length; j++) {
  222. if (
  223. dataSources1[i].emitter === dataSources2[j].emitter &&
  224. dataSources1[i].chain_id === dataSources2[j].chain_id
  225. ) {
  226. found = true;
  227. break;
  228. }
  229. }
  230. if (!found) return false;
  231. }
  232. return true;
  233. }
  234. async getDeploymentType(): Promise<string> {
  235. const config = await this.getConfig();
  236. const convertDataSource = (source: DataSource) => {
  237. return {
  238. emitter: Buffer.from(source.emitterAddress, "hex").toString("base64"),
  239. chain_id: source.emitterChain,
  240. };
  241. };
  242. const stableDataSources =
  243. getDefaultDeploymentConfig("stable").dataSources.map(convertDataSource);
  244. const betaDataSources =
  245. getDefaultDeploymentConfig("beta").dataSources.map(convertDataSource);
  246. if (this.equalDataSources(config.config_v1.data_sources, stableDataSources))
  247. return "stable";
  248. else if (
  249. this.equalDataSources(config.config_v1.data_sources, betaDataSources)
  250. )
  251. return "beta";
  252. else return "unknown";
  253. }
  254. async executeUpdatePriceFeed(senderPrivateKey: PrivateKey, vaas: Buffer[]) {
  255. const base64Vaas = vaas.map((v) => v.toString("base64"));
  256. const fund = await this.getUpdateFee(base64Vaas);
  257. const executor = await this.chain.getExecutor(senderPrivateKey);
  258. const pythExecutor = new PythWrapperExecutor(executor);
  259. const result = await pythExecutor.executeUpdatePriceFeeds({
  260. contractAddr: this.address,
  261. vaas: base64Vaas,
  262. fund,
  263. });
  264. return { id: result.txHash, info: result };
  265. }
  266. async executeGovernanceInstruction(privateKey: PrivateKey, vaa: Buffer) {
  267. const executor = await this.chain.getExecutor(privateKey);
  268. const pythExecutor = new PythWrapperExecutor(executor);
  269. const result = await pythExecutor.executeGovernanceInstruction({
  270. contractAddr: this.address,
  271. vaa: vaa.toString("base64"),
  272. });
  273. return { id: result.txHash, info: result };
  274. }
  275. async getWormholeContract(): Promise<WormholeCosmWasmContract> {
  276. const config = await this.getConfig();
  277. const wormholeAddress = config.config_v1.wormhole_contract;
  278. return new WormholeCosmWasmContract(this.chain, wormholeAddress);
  279. }
  280. async getUpdateFee(msgs: string[]): Promise<Coin> {
  281. const querier = await this.getQuerier();
  282. return querier.getUpdateFee(this.address, msgs);
  283. }
  284. async getBaseUpdateFee(): Promise<{ amount: string; denom: string }> {
  285. const config = await this.getConfig();
  286. return config.config_v1.fee;
  287. }
  288. async getVersion(): Promise<string> {
  289. const config = await this.getConfig();
  290. return config.contract_version;
  291. }
  292. getChain(): CosmWasmChain {
  293. return this.chain;
  294. }
  295. async getTotalFee(): Promise<bigint> {
  296. const client = await CosmWasmClient.connect(this.chain.endpoint);
  297. const coin = await client.getBalance(
  298. this.address,
  299. this.getChain().feeDenom
  300. );
  301. return BigInt(coin.amount);
  302. }
  303. async getValidTimePeriod() {
  304. const client = await CosmWasmClient.connect(this.chain.endpoint);
  305. const result = await client.queryContractSmart(
  306. this.address,
  307. "get_valid_time_period"
  308. );
  309. return Number(result.secs + result.nanos * 1e-9);
  310. }
  311. }