Skip to content

Commit

Permalink
add unhappy path for update price feeds with insufficient fee
Browse files Browse the repository at this point in the history
  • Loading branch information
cctdaniel committed Sep 12, 2024
1 parent 32dfaa2 commit d0ac2a6
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 48 deletions.
10 changes: 7 additions & 3 deletions target_chains/ton/contracts/contracts/Pyth.fc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "common/constants.fc";
#include "common/merkle_tree.fc";
#include "common/governance_actions.fc";
#include "common/gas.fc";
#include "./Wormhole.fc";

cell store_price(int price, int conf, int expo, int publish_time) {
Expand Down Expand Up @@ -165,10 +166,13 @@ int parse_pyth_payload_in_wormhole_vm(slice payload) impure {
cs = new_cs;

int num_updates = cs~load_uint(8);
int fee = single_update_fee * num_updates;
int update_fee = single_update_fee * num_updates;
int compute_fee = get_compute_fee(WORKCHAIN, UPDATE_PRICE_FEEDS_GAS);
throw_unless(ERROR_INSUFFICIENT_GAS, msg_value >= compute_fee);
int remaining_msg_value = msg_value - compute_fee;

;; Check if the sender has sent enough TON to cover the fee
throw_unless(ERROR_INSUFFICIENT_FEE, msg_value >= fee);
;; Check if the sender has sent enough TON to cover the update_fee
throw_unless(ERROR_INSUFFICIENT_FEE, remaining_msg_value >= update_fee);

(_, _, _, _, int emitter_chain_id, int emitter_address, _, _, slice payload, _) = parse_and_verify_wormhole_vm(wormhole_proof.begin_parse());

Expand Down
2 changes: 2 additions & 0 deletions target_chains/ton/contracts/contracts/common/constants.fc
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ const int GUARDIAN_SET_EXPIRY = 86400; ;; 1 day in seconds
const int UPGRADE_MODULE = 0x0000000000000000000000000000000000000000000000000000000000436f7265; ;; "Core" (left-padded to 256 bits) in hex

const int WORMHOLE_MERKLE_UPDATE_TYPE = 0;

const int WORKCHAIN = 0;
3 changes: 3 additions & 0 deletions target_chains/ton/contracts/contracts/common/errors.fc
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,6 @@ const int ERROR_OLD_GOVERNANCE_MESSAGE = 1033;
const int ERROR_INVALID_GOVERNANCE_TARGET = 1034;
const int ERROR_INVALID_GOVERNANCE_MAGIC = 1035;
const int ERROR_INVALID_GOVERNANCE_MODULE = 1036;

;; Common
const int ERROR_INSUFFICIENT_GAS = 1037;
4 changes: 4 additions & 0 deletions target_chains/ton/contracts/contracts/common/gas.fc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
int get_compute_fee(int workchain, int gas_used) asm(gas_used workchain) "GETGASFEE";

;; The actual gas used for the transaction is 350166 but we add ~10% (385182.6) and round up (390000) to be on the safe side because the amount of gas used can vary based on the current state of the blockchain
const int UPDATE_PRICE_FEEDS_GAS = 390000;
88 changes: 44 additions & 44 deletions target_chains/ton/contracts/tests/PythTest.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,23 @@ describe("PythTest", () => {
});
}

async function updateGuardianSets(
pythTest: SandboxContract<PythTest>,
deployer: SandboxContract<TreasuryContract>
) {
for (const vaa of MAINNET_UPGRADE_VAAS) {
const result = await pythTest.sendUpdateGuardianSet(
deployer.getSender(),
Buffer.from(vaa, "hex")
);
expect(result.transactions).toHaveTransaction({
from: deployer.address,
to: pythTest.address,
success: true,
});
}
}

it("should correctly get price unsafe", async () => {
await deployContract();

Expand Down Expand Up @@ -204,69 +221,53 @@ describe("PythTest", () => {
await deployContract();
let result;

const mainnet_upgrade_vaa_1 = MAINNET_UPGRADE_VAAS[0];
result = await pythTest.sendUpdateGuardianSet(
deployer.getSender(),
Buffer.from(mainnet_upgrade_vaa_1, "hex")
);
expect(result.transactions).toHaveTransaction({
from: deployer.address,
to: pythTest.address,
success: true,
});
await updateGuardianSets(pythTest, deployer);

const mainnet_upgrade_vaa_2 = MAINNET_UPGRADE_VAAS[1];
result = await pythTest.sendUpdateGuardianSet(
deployer.getSender(),
Buffer.from(mainnet_upgrade_vaa_2, "hex")
);
expect(result.transactions).toHaveTransaction({
from: deployer.address,
to: pythTest.address,
success: true,
});
const updateData = Buffer.from(HERMES_BTC_ETH_UPDATE, "hex");
const updateFee = await pythTest.getUpdateFee(updateData);

const mainnet_upgrade_vaa_3 = MAINNET_UPGRADE_VAAS[2];
result = await pythTest.sendUpdateGuardianSet(
result = await pythTest.sendUpdatePriceFeeds(
deployer.getSender(),
Buffer.from(mainnet_upgrade_vaa_3, "hex")
updateData,
toNano(updateFee)
);
expect(result.transactions).toHaveTransaction({
from: deployer.address,
to: pythTest.address,
success: true,
});

const mainnet_upgrade_vaa_4 = MAINNET_UPGRADE_VAAS[3];
result = await pythTest.sendUpdateGuardianSet(
deployer.getSender(),
Buffer.from(mainnet_upgrade_vaa_4, "hex")
);
expect(result.transactions).toHaveTransaction({
from: deployer.address,
to: pythTest.address,
success: true,
});

// Check if the price has been updated correctly
const updatedPrice = await pythTest.getPriceUnsafe(BTC_PRICE_FEED_ID);
expect(updatedPrice.price).not.toBe(Number(PRICE.price)); // Since we updated the price, it should not be the same as the initial price
expect(updatedPrice.publishTime).toBeGreaterThan(PRICE.publishTime);
});

it("should fail to update price feeds with insufficient fee", async () => {
await deployContract();

await updateGuardianSets(pythTest, deployer);

const updateData = Buffer.from(HERMES_BTC_ETH_UPDATE, "hex");
const updateFee = await pythTest.getUpdateFee(updateData);

result = await pythTest.sendUpdatePriceFeeds(
// Send less than the required fee
const insufficientFee = updateFee - 1;

const result = await pythTest.sendUpdatePriceFeeds(
deployer.getSender(),
updateData,
toNano(updateFee)
156000000n + BigInt(insufficientFee) // 156000000 = 390000 (estimated gas used for the transaction, this is defined in contracts/common/gas.fc as UPDATE_PRICE_FEEDS_GAS) * 400 (current settings in basechain are as follows: 1 unit of gas costs 400 nanotons)
);

// Check that the transaction did not succeed
expect(result.transactions).toHaveTransaction({
from: deployer.address,
to: pythTest.address,
success: true,
success: false,
exitCode: 1030, // ERROR_INSUFFICIENT_FEE = 1030
});

// Check if the price has been updated correctly
const updatedPrice = await pythTest.getPriceUnsafe(BTC_PRICE_FEED_ID);
expect(updatedPrice.price).not.toBe(Number(PRICE.price)); // Since we updated the price, it should not be the same as the initial price
expect(updatedPrice.publishTime).toBeGreaterThan(PRICE.publishTime);
});

it("should return the correct chain ID", async () => {
Expand Down Expand Up @@ -399,9 +400,8 @@ describe("PythTest", () => {

// Verify that the old data source is no longer valid
const oldDataSource = DATA_SOURCES[0];
const oldDataSourceIsValid = await pythTest.getIsValidDataSource(
oldDataSource
);
const oldDataSourceIsValid =
await pythTest.getIsValidDataSource(oldDataSource);
expect(oldDataSourceIsValid).toBe(false);
});

Expand Down
17 changes: 16 additions & 1 deletion target_chains/ton/contracts/tests/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DataSource } from "@pythnetwork/xc-admin-common";
import { Cell, beginCell } from "@ton/core";
import { Cell, Transaction, beginCell } from "@ton/core";

export function createCellChain(buffer: Buffer): Cell {
let chunks = bufferToChunks(buffer, 127);
Expand Down Expand Up @@ -52,3 +52,18 @@ export function parseDataSource(cell: Cell): DataSource {
const emitterAddress = slice.loadUint(256).toString(16).padStart(64, "0");
return { emitterChain, emitterAddress };
}

function computedGeneric(transaction: Transaction) {
if (transaction.description.type !== "generic")
throw "Expected generic transactionaction";
if (transaction.description.computePhase.type !== "vm")
throw "Compute phase expected";
return transaction.description.computePhase;
}

export function printTxGasStats(name: string, transaction: Transaction) {
const txComputed = computedGeneric(transaction);
console.log(`${name} used ${txComputed.gasUsed} gas`);
console.log(`${name} gas cost: ${txComputed.gasFees}`);
return txComputed.gasFees;
}

0 comments on commit d0ac2a6

Please sign in to comment.