Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix IOTA-SUI folder mismatch and restore correct structure in 'target_chains' #2517

Open
wants to merge 1 commit into
base: iota-contract-testnet
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
9 changes: 9 additions & 0 deletions target_chains/iota/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Pyth on Sui

This directory contains the Pyth contract for Sui and utilities to deploy and use it.

- `cli` folder contains tools for deploying and upgrading the Pyth contract.
- `contracts` folder contains the Pyth contract source code in Move.
- `sdk` folder contains the Pyth javascript SDK for Sui that should be used by dApp developers.

For more information regarding Pyth integration on Sui please refer to our [docs](https://docs.pyth.network/documentation/pythnet-price-feeds/sui).
1 change: 1 addition & 0 deletions target_chains/iota/cli-iota/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
lib
69 changes: 69 additions & 0 deletions target_chains/iota/cli-iota/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Pre-requisites

Install move cli according to this [doc](../contracts/README.md)

# Deploying from scratch

Configure the `Move.toml` file accordingly. The wormhole address should be specified based on the target chain in the `Move.toml` and the pyth address should be `0x0`.
We can deploy the pyth oracle and initialize it with the following command:

```bash
npm run cli -- deploy --private-key <private-key> --chain [iota_sui|iota_sui]
```

You can then add your iota contract configs to the contract manager store.

You can also manually create all the price feeds available at the moment to make it easier for devs to test the oracle.

```bash
npm run cli -- create-all --private-key <private-key> --contract <contract-id>
```

# Updating price feeds:

You can use the `create` and `update-feeds` commands to create and update price feeds respectively.

```bash
npm run cli -- create --feed-id <feed-id> --private-key <private-key> --contract <contract-id>
```

```bash
npm run cli -- update-feeds --feed-id <feed-id> --private-key <private-key> --contract <contract-id>
```

# Upgrade process:

The following steps are needed to upgrade our iota contracts:

- Contract changes:
- Create a new struct for the new version and update `current_version` and `previous_version` functions in `version_control` module
- Implement any custom logic needed to migrate the data from the old struct to the new one in the `migrate` module
- Update dependency (e.g. wormhole) addresses if needed
- Generate the digest for the new contract build
- Create a governance proposal, proposing the iota package to be upgraded to this specific digest
- Approve and execute the governance proposal
- Run the upgrade transaction and publish the new package

## Generating the new contract hash:

Run the following command to generate the new hash, make sure the contract addresses are identical to the deployed ones:

```bash
npm run cli -- generate-digest
```

## Upgrading the contract

To upgrade the contract after the governance vaa was executed run:

```bash
npm run cli -- upgrade --private-key <private-key> --contract <contract-id> --vaa <upgrade-vaa>
```

The upgrade procedure consists of 2 transactions. The first one is to upgrade the contract (iota level) and the second one is to run the `migrate` function and upgrade the version (package level).
Since clients try to fetch the latest version of the package automatically, it's important to run the second transaction as soon as possible after the first one.

### FAQ:

- I'm seeing the error `Transaction has non recoverable errors from at least 1/3 of validators`. What should I do?
Make sure you have enough funding in the wallet and try again. Usually a more descriptive error message is available in the returned value of the transaction.
27 changes: 27 additions & 0 deletions target_chains/iota/cli-iota/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "pyth-iota-cli",
"version": "0.1.0",
"description": "Pyth IOTA Integration Cli tools",
"main": "index.js",
"license": "Apache-2.0",
"scripts": {
"cli": "ts-node src/cli.ts",
"build": "tsc"
},
"private": "true",
"dependencies": {
"@certusone/wormhole-sdk": "^0.9.12",
"@iota/iota-sdk": "^0.5.0",
"@pythnetwork/contract-manager": "workspace:*",
"@pythnetwork/price-service-client": "^1.4.0",
"@pythnetwork/price-service-sdk": "^1.2.0",
"@pythnetwork/xc-admin-common": "workspace:*",
"prettier": "^2.8.7",
"ts-node": "^10.9.1",
"typescript": "^5.0.4",
"yargs": "^17.7.2"
},
"devDependencies": {
"@types/yargs": "^17.0.32"
}
}
288 changes: 288 additions & 0 deletions target_chains/iota/cli-iota/src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import {
DefaultStore,
getDefaultDeploymentConfig,
IotaChain,
IotaPriceFeedContract,
} from "@pythnetwork/contract-manager";
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
import { execSync } from "child_process";
import { initPyth, publishPackage } from "./pyth_deploy";
import { Ed25519Keypair } from "@iota/iota-sdk/keypairs/ed25519";
import { resolve } from "path";
import {
buildForBytecodeAndDigest,
migratePyth,
upgradePyth,
} from "./upgrade_pyth";

const OPTIONS = {
"private-key": {
type: "string",
demandOption: true,
desc: "Private key to use to sign transaction",
},
contract: {
type: "string",
demandOption: true,
desc: "Contract to use for the command (e.g iota_0x68dda579251917b3db28e35c4df495c6e664ccc085ede867a9b773c8ebedc2c1)",
},
path: {
type: "string",
default: "../../contracts",
desc: "Path to the iota contracts, will use ../../contracts by default",
},
endpoint: {
type: "string",
default: "https://hermes.pyth.network",
desc: "Price service endpoint to use, defaults to https://hermes.pyth.network",
},
"feed-id": {
type: "array",
demandOption: true,
desc: "Price feed ids to create without the leading 0x (e.g f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b). Can be provided multiple times for multiple feed updates",
},
} as const;

function getContract(contractId: string): IotaPriceFeedContract {
const contract = DefaultStore.contracts[contractId] as IotaPriceFeedContract;
if (!contract) {
throw new Error(`Contract ${contractId} not found`);
}
return contract;
}

yargs(hideBin(process.argv))
.command(
"create",
"Create a new price feed",
(yargs) => {
return yargs
.options({
contract: OPTIONS.contract,
"feed-id": OPTIONS["feed-id"],
"private-key": OPTIONS["private-key"],
endpoint: OPTIONS.endpoint,
})
.usage(
"$0 create --contract <contract-id> --feed-id <feed-id> --private-key <private-key>"
);
},
async (argv) => {
const contract = getContract(argv.contract);
const priceService = new PriceServiceConnection(argv.endpoint);
const feedIds = argv["feed-id"] as string[];
const vaas = await priceService.getLatestVaas(feedIds);
const digest = await contract.executeCreatePriceFeed(
argv["private-key"],
vaas.map((vaa) => Buffer.from(vaa, "base64"))
);
console.log("Transaction successful. Digest:", digest);
}
)
.command(
"create-all",
"Create all price feeds for a contract",
(yargs) => {
return yargs
.options({
contract: OPTIONS.contract,
"private-key": OPTIONS["private-key"],
endpoint: OPTIONS.endpoint,
})
.usage(
"$0 create-all --contract <contract-id> --private-key <private-key>"
);
},
async (argv) => {
const contract = getContract(argv.contract);
const priceService = new PriceServiceConnection(argv.endpoint);
const feedIds = await priceService.getPriceFeedIds();
const BATCH_SIZE = 10;
for (let i = 0; i < feedIds.length; i += BATCH_SIZE) {
const batch = feedIds.slice(i, i + BATCH_SIZE);
const vaas = await priceService.getLatestVaas(batch);
const digest = await contract.executeCreatePriceFeed(
argv["private-key"],
vaas.map((vaa) => Buffer.from(vaa, "base64"))
);
console.log("Transaction successful. Digest:", digest);
console.log(`Progress: ${i + BATCH_SIZE}/${feedIds.length}`);
}
}
)
.command(
"generate-digest",
"Generate digest for a contract",
(yargs) => {
return yargs
.options({
path: OPTIONS.path,
})
.usage("$0 generate-digest --path <path-to-contracts>");
},
async (argv) => {
const buildOutput: {
modules: string[];
dependencies: string[];
digest: number[];
} = JSON.parse(
execSync(
`iota move build --dump-bytecode-as-base64 --path ${__dirname}/${argv.path} 2> /dev/null`,
{
encoding: "utf-8",
}
)
);
console.log("Contract digest:");
console.log(Buffer.from(buildOutput.digest).toString("hex"));
}
)
.command(
"deploy",
"Deploy a contract",
(yargs) => {
return yargs
.options({
"private-key": OPTIONS["private-key"],
chain: {
type: "string",
demandOption: true,
desc: "Chain to deploy the code to. Can be iota_mainnet or iota_testnet",
},
path: OPTIONS.path,
})
.usage(
"$0 deploy --private-key <private-key> --chain [iota_mainnet|iota_testnet] --path <path-to-contracts>"
);
},
async (argv) => {
const walletPrivateKey = argv["private-key"];
const chain = DefaultStore.chains[argv.chain] as IotaChain;
const keypair = Ed25519Keypair.fromSecretKey(
new Uint8Array(Buffer.from(walletPrivateKey, "hex"))
);
const result = await publishPackage(
keypair,
chain.getProvider(),
argv.path
);
const deploymentType = "stable";
const config = getDefaultDeploymentConfig(deploymentType);
await initPyth(
keypair,
chain.getProvider(),
result.packageId,
result.deployerCapId,
result.upgradeCapId,
config
);
}
)
.command(
"update-feeds",
"Update price feeds for a contract",
(yargs) => {
return yargs
.options({
contract: OPTIONS.contract,
"feed-id": OPTIONS["feed-id"],
"private-key": OPTIONS["private-key"],
endpoint: OPTIONS.endpoint,
})
.usage(
"$0 update-feeds --contract <contract-id> --feed-id <feed-id> --private-key <private-key>"
);
},
async (argv) => {
const contract = getContract(argv.contract);
const priceService = new PriceServiceConnection(argv.endpoint);
const feedIds = argv["feed-id"] as string[];
const vaas = await priceService.getLatestVaas(feedIds);
const digest = await contract.executeUpdatePriceFeedWithFeeds(
argv["private-key"],
vaas.map((vaa) => Buffer.from(vaa, "base64")),
feedIds
);
console.log("Transaction successful. Digest:", digest);
}
)
.command(
"upgrade",
"Upgrade a contract",
(yargs) => {
return yargs
.options({
"private-key": OPTIONS["private-key"],
contract: OPTIONS.contract,
vaa: {
type: "string",
demandOption: true,
desc: "Signed Vaa for upgrading the package in hex format",
},
path: OPTIONS.path,
})
.usage(
"$0 upgrade --private-key <private-key> --contract <contract-id> --vaa <upgrade-vaa>"
);
},
async (argv) => {
const contract = getContract(argv.contract);
const keypair = Ed25519Keypair.fromSecretKey(
new Uint8Array(Buffer.from(argv["private-key"], "hex"))
);

const pythContractsPath = resolve(`${__dirname}/${argv.path}`);

// Build for modules and dependencies
const { modules, dependencies, digest } =
buildForBytecodeAndDigest(pythContractsPath);
//Execute upgrade with signed governance VAA.
console.log("Digest is", digest.toString("hex"));
const pythPackageOld = await contract.getPackageId(contract.stateId);
console.log("Old package id:", pythPackageOld);
const signedVaa = Buffer.from(argv.vaa, "hex");
const upgradeResults = await upgradePyth(
keypair,
contract.chain.getProvider(),
modules,
dependencies,
signedVaa,
contract
);
console.log("Tx digest", upgradeResults.digest);
if (
!upgradeResults.effects ||
upgradeResults.effects.status.status !== "success"
) {
throw new Error("Upgrade failed");
}

console.log(
"Upgrade successful, Executing the migrate function in a separate transaction..."
);

// We can not do the migration in the same transaction since the newly published package is not found
// on chain at the beginning of the transaction.

const migrateResults = await migratePyth(
keypair,
contract.chain.getProvider(),
signedVaa,
contract,
pythPackageOld
);
console.log("Tx digest", migrateResults.digest);
if (
!migrateResults.effects ||
migrateResults.effects.status.status !== "success"
) {
throw new Error(
`Migrate failed. Old package id is ${pythPackageOld}. Please do the migration manually`
);
}
console.log("Migrate successful");
}
)
.demandCommand().argv;
Loading