Преглед изворни кода

feat(target_chains/ton): add get_update_fee (#1853)

* add get_update_fee

* remove unused error

* add comment

* address comments

* update tests names

* precommit
Daniel Chew пре 1 година
родитељ
комит
64c9a28a57

+ 39 - 9
target_chains/ton/contracts/contracts/Pyth.fc

@@ -1,10 +1,36 @@
 #include "imports/stdlib.fc";
 #include "common/errors.fc";
 #include "common/storage.fc";
+#include "common/utils.fc";
+#include "./Wormhole.fc";
+
+const int ACCUMULATOR_MAGIC = 0x504e4155; ;; "PNAU" (Pyth Network Accumulator Update)
+const int MAJOR_VERSION = 1;
+const int MINIMUM_ALLOWED_MINOR_VERSION = 0;
+
+slice verify_header(slice data) {
+    int magic = data~load_uint(32);
+    throw_unless(ERROR_INVALID_MAGIC, magic == ACCUMULATOR_MAGIC);
+    int major_version = data~load_uint(8);
+    throw_unless(ERROR_INVALID_MAJOR_VERSION, major_version == MAJOR_VERSION);
+    int minor_version = data~load_uint(8);
+    throw_if(ERROR_INVALID_MINOR_VERSION, minor_version < MINIMUM_ALLOWED_MINOR_VERSION);
+    int trailing_header_size = data~load_uint(8);
+    ;; skip trailing headers and update type (uint8)
+    data~skip_bits(trailing_header_size);
+    data~skip_bits(8);
+    return data;
+}
+
+(int) get_update_fee(slice data) method_id {
+    load_data();
+    slice cs = verify_header(data);
+    int wormhole_proof_size_bytes = cs~load_uint(16);
+    (cell wormhole_proof, slice cs) = read_and_store_large_data(cs, wormhole_proof_size_bytes * 8);
+    int num_updates = cs~load_uint(8);
+    return single_update_fee * num_updates;
+}
 
-;; Opcodes
-const int OP_UPDATE_GUARDIAN_SET = 1;
-const int OP_EXECUTE_GOVERNANCE_ACTION = 2;
 
 (int, int, int, int) parse_price(slice price_feed) {
     int price = price_feed~load_int(256);
@@ -14,7 +40,8 @@ const int OP_EXECUTE_GOVERNANCE_ACTION = 2;
     return (price, conf, expo, publish_time);
 }
 
-(int, int, int, int) price_unsafe(int price_feed_id) method_id {
+(int, int, int, int) get_price_unsafe(int price_feed_id) method_id {
+    load_data();
     (slice result, int success) = latest_price_feeds.udict_get?(256, price_feed_id);
     throw_unless(ERROR_PRICE_FEED_NOT_FOUND, success);
     slice price_feed = result~load_ref().begin_parse();
@@ -22,14 +49,16 @@ const int OP_EXECUTE_GOVERNANCE_ACTION = 2;
     return parse_price(price);
 }
 
-(int, int, int, int) price_no_older_than(int time_period, int price_feed_id) method_id {
-    (int price, int conf, int expo, int publish_time) = price_unsafe(price_feed_id);
+(int, int, int, int) get_price_no_older_than(int time_period, int price_feed_id) method_id {
+    load_data();
+    (int price, int conf, int expo, int publish_time) = get_price_unsafe(price_feed_id);
     int current_time = now();
     throw_if(ERROR_OUTDATED_PRICE, current_time - publish_time > time_period);
     return (price, conf, expo, publish_time);
 }
 
-(int, int, int, int) ema_price_unsafe(int price_feed_id) method_id {
+(int, int, int, int) get_ema_price_unsafe(int price_feed_id) method_id {
+    load_data();
     (slice result, int success) = latest_price_feeds.udict_get?(256, price_feed_id);
     throw_unless(ERROR_PRICE_FEED_NOT_FOUND, success);
     slice price_feed = result~load_ref().begin_parse();
@@ -38,8 +67,9 @@ const int OP_EXECUTE_GOVERNANCE_ACTION = 2;
     return parse_price(ema_price);
 }
 
-(int, int, int, int) ema_price_no_older_than(int time_period, int price_feed_id) method_id {
-    (int price, int conf, int expo, int publish_time) = ema_price_unsafe(price_feed_id);
+(int, int, int, int) get_ema_price_no_older_than(int time_period, int price_feed_id) method_id {
+    load_data();
+    (int price, int conf, int expo, int publish_time) = get_ema_price_unsafe(price_feed_id);
     int current_time = now();
     throw_if(ERROR_OUTDATED_PRICE, current_time - publish_time > time_period);
     return (price, conf, expo, publish_time);

+ 4 - 0
target_chains/ton/contracts/contracts/Wormhole.fc

@@ -58,10 +58,12 @@ const int UPGRADE_MODULE = 0x000000000000000000000000000000000000000000000000000
 
 ;; Get methods
 int get_current_guardian_set_index() method_id {
+    load_data();
     return current_guardian_set_index;
 }
 
 (int, cell, int) get_guardian_set(int index) method_id {
+    load_data();
     return get_guardian_set_internal(index);
 }
 
@@ -111,6 +113,7 @@ int governance_action_is_consumed(int hash) method_id {
 }
 
 (int, int, int, int, int, int, int, int, slice, int) parse_and_verify_wormhole_vm(slice in_msg_body) impure {
+    load_data();
     ;; Parse VM fields
     int version = in_msg_body~load_uint(8);
     throw_unless(ERROR_INVALID_VERSION, version == 1);
@@ -247,6 +250,7 @@ int governance_action_is_consumed(int hash) method_id {
 
     ;; Mark the governance action as consumed
     consumed_governance_actions~udict_set(256, hash, begin_cell().store_int(true, 1).end_cell().begin_parse());
+    store_data();
 }
 
 () execute_governance_action(slice in_msg_body) impure {

+ 3 - 0
target_chains/ton/contracts/contracts/common/errors.fc

@@ -24,3 +24,6 @@ const int ERROR_INVALID_GUARDIAN_ADDRESS = 1018;
 ;; Pyth
 const int ERROR_PRICE_FEED_NOT_FOUND = 1019;
 const int ERROR_OUTDATED_PRICE = 1020;
+const int ERROR_INVALID_MAGIC = 1021;
+const int ERROR_INVALID_MAJOR_VERSION = 1022;
+const int ERROR_INVALID_MINOR_VERSION = 1023;

+ 3 - 1
target_chains/ton/contracts/contracts/common/storage.fc

@@ -4,6 +4,7 @@
 ;; Price struct: {price: int, conf: int, expo: int, publish_time: int}
 ;; PriceFeed struct: {price: Price, ema_price: Price}
 global cell latest_price_feeds; ;; Dictionary of PriceFeed structs, keyed by price_feed_id (256-bit)
+global int single_update_fee;
 
 ;; Wormhole
 global int current_guardian_set_index;
@@ -14,7 +15,6 @@ global int current_guardian_set_index;
 global cell guardian_sets;
 global int chain_id;
 global int governance_chain_id;
-;; GovernanceContract struct: {chain_id: int, address: slice}
 global int governance_contract;
 global cell consumed_governance_actions;
 
@@ -22,6 +22,7 @@ global cell consumed_governance_actions;
 () store_data() impure inline_ref {
     begin_cell()
         .store_dict(latest_price_feeds)
+        .store_uint(single_update_fee, 256)
         .store_uint(current_guardian_set_index, 32)
         .store_dict(guardian_sets)
         .store_uint(chain_id, 16)
@@ -36,6 +37,7 @@ global cell consumed_governance_actions;
 () load_data() impure inline_ref {
     var ds = get_data().begin_parse();
     latest_price_feeds = ds~load_dict();
+    single_update_fee = ds~load_uint(256);
     current_guardian_set_index = ds~load_uint(32);
     guardian_sets = ds~load_dict();
     chain_id = ds~load_uint(16);

+ 12 - 12
target_chains/ton/contracts/contracts/tests/PythTest.fc

@@ -5,22 +5,22 @@
     ;; nop;
 }
 
-(int, int, int, int) test_price_unsafe(int price_feed_id) method_id {
-    load_data();
-    return price_unsafe(price_feed_id);
+(int, int, int, int) test_get_price_unsafe(int price_feed_id) method_id {
+    return get_price_unsafe(price_feed_id);
 }
 
-(int, int, int, int) test_price_no_older_than(int time_period, int price_feed_id) method_id {
-    load_data();
-    return price_no_older_than(time_period, price_feed_id);
+(int, int, int, int) test_get_price_no_older_than(int time_period, int price_feed_id) method_id {
+    return get_price_no_older_than(time_period, price_feed_id);
 }
 
-(int, int, int, int) test_ema_price_unsafe(int price_feed_id) method_id {
-    load_data();
-    return ema_price_unsafe(price_feed_id);
+(int, int, int, int) test_get_ema_price_unsafe(int price_feed_id) method_id {
+    return get_ema_price_unsafe(price_feed_id);
 }
 
-(int, int, int, int) test_ema_price_no_older_than(int time_period, int price_feed_id) method_id {
-    load_data();
-    return ema_price_no_older_than(time_period, price_feed_id);
+(int, int, int, int) test_get_ema_price_no_older_than(int time_period, int price_feed_id) method_id {
+    return get_ema_price_no_older_than(time_period, price_feed_id);
+}
+
+(int) test_get_update_fee(slice in_msg_body) method_id {
+    return get_update_fee(in_msg_body);
 }

+ 1 - 4
target_chains/ton/contracts/contracts/tests/WormholeTest.fc

@@ -10,12 +10,10 @@
 }
 
 (int, int, int, int, int, int, int, int, slice, int) test_parse_and_verify_wormhole_vm(slice in_msg_body) method_id {
-    load_data();
     return parse_and_verify_wormhole_vm(in_msg_body);
 }
 
 (int) test_update_guardian_set(slice in_msg_body) method_id {
-    load_data();
     int old_guardian_set_index = get_current_guardian_set_index();
     update_guardian_set(in_msg_body);
     int new_guardian_set_index = get_current_guardian_set_index();
@@ -23,8 +21,7 @@
 }
 
 (int) test_get_current_guardian_set_index() method_id {
-    load_data();
-    return current_guardian_set_index;
+    return get_current_guardian_set_index();
 }
 
 (int, cell, int) test_get_guardian_set(int index) method_id {

+ 20 - 3
target_chains/ton/contracts/tests/PythTest.spec.ts

@@ -2,8 +2,13 @@ import { Blockchain, SandboxContract, TreasuryContract } from "@ton/sandbox";
 import { Cell, toNano } from "@ton/core";
 import "@ton/test-utils";
 import { compile } from "@ton/blueprint";
-import { HexString, Price } from "@pythnetwork/price-service-sdk";
+import {
+  HexString,
+  parseAccumulatorUpdateData,
+  Price,
+} from "@pythnetwork/price-service-sdk";
 import { PythTest, PythTestConfig } from "../wrappers/PythTest";
+import { HERMES_BTC_ETH_UPDATE } from "./utils/pyth";
 
 const PRICE_FEED_ID =
   "0x0000000000000000000000000000000000000000000000000000000000000000";
@@ -20,6 +25,7 @@ const EMA_PRICE = new Price({
   expo: 7,
   publishTime: 8,
 });
+const SINGLE_UPDATE_FEE = 1;
 
 describe("PythTest", () => {
   let code: Cell;
@@ -41,13 +47,15 @@ describe("PythTest", () => {
     priceFeedId: HexString = PRICE_FEED_ID,
     timePeriod: number = TIME_PERIOD,
     price: Price = PRICE,
-    emaPrice: Price = EMA_PRICE
+    emaPrice: Price = EMA_PRICE,
+    singleUpdateFee: number = SINGLE_UPDATE_FEE
   ) {
     const config: PythTestConfig = {
       priceFeedId,
       timePeriod,
       price,
       emaPrice,
+      singleUpdateFee,
     };
 
     pythTest = blockchain.openContract(PythTest.createFromConfig(config, code));
@@ -69,7 +77,6 @@ describe("PythTest", () => {
     await deployContract();
 
     const result = await pythTest.getPriceUnsafe(PRICE_FEED_ID);
-
     expect(result.price).toBe(1);
     expect(result.conf).toBe(2);
     expect(result.expo).toBe(3);
@@ -144,4 +151,14 @@ describe("PythTest", () => {
     expect(result.expo).toBe(7);
     expect(result.publishTime).toBe(8);
   });
+
+  it("should correctly get update fee", async () => {
+    await deployContract();
+
+    const result = await pythTest.getUpdateFee(
+      Buffer.from(HERMES_BTC_ETH_UPDATE, "hex")
+    );
+
+    expect(result).toBe(2);
+  });
 });

+ 7 - 0
target_chains/ton/contracts/tests/WormholeTest.spec.ts

@@ -154,4 +154,11 @@ describe("WormholeTest", () => {
       )
     ).rejects.toThrow("Unable to execute get method. Got exit_code: 1001"); // ERROR_INVALID_VERSION = 1001
   });
+
+  it("should correctly get guardian set", async () => {
+    await deployContract();
+
+    const getGuardianSetResult = await wormholeTest.getGuardianSet(0);
+    expect(getGuardianSetResult.keys).toEqual(GUARDIAN_SET_0);
+  });
 });

Разлика између датотеке није приказан због своје велике величине
+ 34 - 0
target_chains/ton/contracts/tests/utils/pyth.ts


+ 19 - 6
target_chains/ton/contracts/wrappers/PythTest.ts

@@ -10,12 +10,14 @@ import {
   SendMode,
 } from "@ton/core";
 import { HexString, Price } from "@pythnetwork/price-service-sdk";
+import { createCellChain } from "../tests/utils";
 
 export type PythTestConfig = {
   priceFeedId: HexString;
   timePeriod: number;
   price: Price;
   emaPrice: Price;
+  singleUpdateFee: number;
 };
 
 export class PythTest implements Contract {
@@ -33,7 +35,8 @@ export class PythTest implements Contract {
       config.priceFeedId,
       config.timePeriod,
       config.price,
-      config.emaPrice
+      config.emaPrice,
+      config.singleUpdateFee
     );
     const init = { code, data };
     return new PythTest(contractAddress(workchain, init), init);
@@ -43,7 +46,8 @@ export class PythTest implements Contract {
     priceFeedId: HexString,
     timePeriod: number,
     price: Price,
-    emaPrice: Price
+    emaPrice: Price,
+    singleUpdateFee: number
   ): Cell {
     const priceDict = Dictionary.empty(
       Dictionary.Keys.BigUint(256),
@@ -77,6 +81,7 @@ export class PythTest implements Contract {
 
     return beginCell()
       .storeDict(priceDict) // latest_price_feeds
+      .storeUint(singleUpdateFee, 256) // single_update_fee
       .storeUint(0, 32)
       .storeDict(Dictionary.empty())
       .storeUint(0, 16)
@@ -100,7 +105,7 @@ export class PythTest implements Contract {
   }
 
   async getPriceUnsafe(provider: ContractProvider, priceFeedId: HexString) {
-    const result = await provider.get("test_price_unsafe", [
+    const result = await provider.get("test_get_price_unsafe", [
       { type: "int", value: BigInt(priceFeedId) },
     ]);
 
@@ -122,7 +127,7 @@ export class PythTest implements Contract {
     timePeriod: number,
     priceFeedId: HexString
   ) {
-    const result = await provider.get("test_price_no_older_than", [
+    const result = await provider.get("test_get_price_no_older_than", [
       { type: "int", value: BigInt(timePeriod) },
       { type: "int", value: BigInt(priceFeedId) },
     ]);
@@ -141,7 +146,7 @@ export class PythTest implements Contract {
   }
 
   async getEmaPriceUnsafe(provider: ContractProvider, priceFeedId: HexString) {
-    const result = await provider.get("test_ema_price_unsafe", [
+    const result = await provider.get("test_get_ema_price_unsafe", [
       { type: "int", value: BigInt(priceFeedId) },
     ]);
 
@@ -163,7 +168,7 @@ export class PythTest implements Contract {
     timePeriod: number,
     priceFeedId: HexString
   ) {
-    const result = await provider.get("test_ema_price_no_older_than", [
+    const result = await provider.get("test_get_ema_price_no_older_than", [
       { type: "int", value: BigInt(timePeriod) },
       { type: "int", value: BigInt(priceFeedId) },
     ]);
@@ -180,4 +185,12 @@ export class PythTest implements Contract {
       publishTime,
     };
   }
+
+  async getUpdateFee(provider: ContractProvider, vm: Buffer) {
+    const result = await provider.get("test_get_update_fee", [
+      { type: "slice", cell: createCellChain(vm) },
+    ]);
+
+    return result.stack.readNumber();
+  }
 }

+ 2 - 1
target_chains/ton/contracts/wrappers/WormholeTest.ts

@@ -72,6 +72,7 @@ export class WormholeTest implements Contract {
 
     return beginCell()
       .storeDict(Dictionary.empty()) // latest_price_feeds, empty for initial state
+      .storeUint(0, 256) // single_update_fee, set to 0 for testing
       .storeUint(guardianSetIndex, 32)
       .storeDict(guardianSets)
       .storeUint(chainId, 16)
@@ -172,7 +173,7 @@ export class WormholeTest implements Contract {
     return result.stack.readNumber();
   }
 
-  async getGetGuardianSet(provider: ContractProvider, index: number) {
+  async getGuardianSet(provider: ContractProvider, index: number) {
     const result = await provider.get("test_get_guardian_set", [
       { type: "int", value: BigInt(index) },
     ]);

Неке датотеке нису приказане због велике количине промена