-
Notifications
You must be signed in to change notification settings - Fork 0
Add create scripts, domain and PTB #80
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
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,204 @@ | ||
| /** | ||
| * Creates a new shared AMM config for the target network. | ||
| */ | ||
| import yargs from "yargs" | ||
|
|
||
| import { | ||
| DEFAULT_BASE_SPREAD_BPS, | ||
| DEFAULT_VOLATILITY_MULTIPLIER_BPS, | ||
| getAmmConfigOverview, | ||
| resolveAmmConfigInputs | ||
| } from "@sui-amm/domain-core/models/amm" | ||
| import { buildCreateAmmConfigTransaction } from "@sui-amm/domain-core/ptb/amm" | ||
| import { resolveAmmPackageId } from "@sui-amm/domain-node/amm" | ||
| import { emitJsonOutput } from "@sui-amm/tooling-node/json" | ||
| import type { Tooling } from "@sui-amm/tooling-node/factory" | ||
| import { runSuiScript } from "@sui-amm/tooling-node/process" | ||
| import { findCreatedArtifactBySuffix } from "@sui-amm/tooling-node/transactions" | ||
| import { | ||
| logAmmConfigOverview, | ||
| resolveAmmAdminCapIdFromArtifacts, | ||
| resolvePythPriceFeedIdHex | ||
| } from "../../utils/amm.ts" | ||
|
|
||
| type CreateAmmArguments = { | ||
| adminCapId?: string | ||
| baseSpreadBps?: string | ||
| volatilityMultiplierBps?: string | ||
| useLaser?: boolean | ||
| pythPriceFeedId?: string | ||
| pythPriceFeedLabel?: string | ||
| ammPackageId?: string | ||
| devInspect?: boolean | ||
| dryRun?: boolean | ||
| json?: boolean | ||
| } | ||
|
|
||
| const resolveAdminCapId = async ({ | ||
| tooling, | ||
| cliArguments, | ||
| ammPackageId | ||
| }: { | ||
| tooling: Pick<Tooling, "network" | "suiClient"> | ||
| cliArguments: CreateAmmArguments | ||
| ammPackageId: string | ||
| }): Promise<string> => { | ||
| const explicitAdminCapId = cliArguments.adminCapId?.trim() | ||
| if (explicitAdminCapId) { | ||
| return explicitAdminCapId | ||
| } | ||
|
|
||
| return resolveAmmAdminCapIdFromArtifacts({ | ||
| tooling, | ||
| ammPackageId | ||
| }) | ||
| } | ||
|
|
||
| runSuiScript( | ||
| async (tooling, cliArguments: CreateAmmArguments) => { | ||
| const ammPackageId = await resolveAmmPackageId({ | ||
| networkName: tooling.network.networkName, | ||
| ammPackageId: cliArguments.ammPackageId | ||
| }) | ||
| const adminCapId = await resolveAdminCapId({ | ||
| tooling, | ||
| cliArguments, | ||
| ammPackageId | ||
| }) | ||
|
|
||
| const ammConfigInputs = resolveAmmConfigInputs({ | ||
| pythPriceFeedIdHex: await resolvePythPriceFeedIdHex({ | ||
| networkName: tooling.network.networkName, | ||
| pythPriceFeedId: cliArguments.pythPriceFeedId, | ||
| pythPriceFeedLabel: cliArguments.pythPriceFeedLabel | ||
| }), | ||
| volatilityMultiplierBps: cliArguments.volatilityMultiplierBps, | ||
| baseSpreadBps: cliArguments.baseSpreadBps, | ||
| useLaser: cliArguments.useLaser | ||
| }) | ||
|
|
||
| const createAmmTransaction = buildCreateAmmConfigTransaction({ | ||
| packageId: ammPackageId, | ||
| adminCapId, | ||
| baseSpreadBps: ammConfigInputs.baseSpreadBps, | ||
| volatilityMultiplierBps: ammConfigInputs.volatilityMultiplierBps, | ||
| useLaser: ammConfigInputs.useLaser, | ||
| pythPriceFeedIdBytes: ammConfigInputs.pythPriceFeedIdBytes | ||
| }) | ||
|
|
||
| const { execution, summary } = await tooling.executeTransactionWithSummary({ | ||
| transaction: createAmmTransaction, | ||
| signer: tooling.loadedEd25519KeyPair, | ||
| summaryLabel: "create-amm", | ||
| devInspect: cliArguments.devInspect, | ||
| dryRun: cliArguments.dryRun | ||
| }) | ||
|
|
||
| if (!execution) { | ||
| return | ||
| } | ||
|
|
||
| const createdArtifacts = execution.objectArtifacts.created | ||
| const createdAmmConfig = findCreatedArtifactBySuffix( | ||
| createdArtifacts, | ||
| "::manager::AMMConfig" | ||
| ) | ||
|
|
||
| if (!createdAmmConfig) { | ||
| throw new Error( | ||
| "Expected an AMM config object to be created, but it was not found in transaction artifacts." | ||
| ) | ||
| } | ||
|
|
||
| const ammConfigOverview = await getAmmConfigOverview( | ||
| createdAmmConfig.objectId, | ||
| tooling.suiClient | ||
| ) | ||
|
|
||
| if ( | ||
| emitJsonOutput( | ||
| { | ||
| ammConfig: ammConfigOverview, | ||
| adminCapId, | ||
| digest: createdAmmConfig.digest, | ||
| initialSharedVersion: createdAmmConfig.initialSharedVersion, | ||
| pythPriceFeedIdHex: ammConfigInputs.pythPriceFeedIdHex, | ||
| transactionSummary: summary | ||
| }, | ||
| cliArguments.json | ||
| ) | ||
| ) { | ||
| return | ||
| } | ||
|
|
||
| logAmmConfigOverview(ammConfigOverview, { | ||
| initialSharedVersion: createdAmmConfig.initialSharedVersion | ||
| }) | ||
| }, | ||
| yargs() | ||
| .option("adminCapId", { | ||
| alias: ["admin-cap-id"], | ||
| type: "string", | ||
| description: | ||
| "Admin cap object id for AMM config creation; inferred from the selected AMM publish when omitted.", | ||
| demandOption: false | ||
| }) | ||
| .option("baseSpreadBps", { | ||
| alias: ["base-spread-bps"], | ||
| type: "string", | ||
| description: "Base spread in basis points (u64).", | ||
| default: DEFAULT_BASE_SPREAD_BPS, | ||
| demandOption: false | ||
| }) | ||
| .option("volatilityMultiplierBps", { | ||
| alias: ["volatility-multiplier-bps"], | ||
| type: "string", | ||
| description: "Volatility multiplier in basis points (u64).", | ||
| default: DEFAULT_VOLATILITY_MULTIPLIER_BPS, | ||
| demandOption: false | ||
| }) | ||
| .option("useLaser", { | ||
| alias: ["use-laser"], | ||
| type: "boolean", | ||
| default: false, | ||
| description: "Enable the laser pricing path for the AMM." | ||
| }) | ||
| .option("pythPriceFeedId", { | ||
| alias: ["pyth-price-feed-id", "pyth-feed-id"], | ||
| type: "string", | ||
| description: "Pyth price feed id (32 bytes hex).", | ||
| demandOption: false | ||
| }) | ||
| .option("pythPriceFeedLabel", { | ||
| alias: ["pyth-price-feed-label", "pyth-feed-label"], | ||
| type: "string", | ||
| description: | ||
| "Localnet artifact feed label to resolve the feed id when --pyth-price-feed-id is omitted.", | ||
| demandOption: false | ||
| }) | ||
| .option("ammPackageId", { | ||
| alias: ["amm-package-id"], | ||
| type: "string", | ||
| description: | ||
| "Package ID for the AMM Move package; inferred from the latest publish entry in deployments/deployment.<network>.json when omitted.", | ||
| demandOption: false | ||
| }) | ||
| .option("devInspect", { | ||
| alias: ["dev-inspect", "debug"], | ||
| type: "boolean", | ||
| default: false, | ||
| description: "Run a dev-inspect and log VM error details." | ||
| }) | ||
| .option("dryRun", { | ||
| alias: ["dry-run"], | ||
| type: "boolean", | ||
| default: false, | ||
| description: "Run dev-inspect and exit without executing the transaction." | ||
| }) | ||
| .option("json", { | ||
| type: "boolean", | ||
| default: false, | ||
| description: "Output results as JSON." | ||
| }) | ||
| .strict() | ||
| ) |
160 changes: 160 additions & 0 deletions
160
packages/dapp/src/scripts/owner/test-integration/amm-create.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| import { readFile } from "node:fs/promises" | ||
| import path from "node:path" | ||
| import { describe, expect, it } from "vitest" | ||
|
|
||
| import { | ||
| AMM_CONFIG_TYPE_SUFFIX, | ||
| type AmmConfigOverview | ||
| } from "@sui-amm/domain-core/models/amm" | ||
| import { normalizeHex } from "@sui-amm/tooling-core/hex" | ||
| import { extractInitialSharedVersion } from "@sui-amm/tooling-core/shared-object" | ||
| import { createSuiLocalnetTestEnv } from "@sui-amm/tooling-node/testing/env" | ||
| import { | ||
| resolveDappMoveRoot, | ||
| resolveDappRoot | ||
| } from "@sui-amm/tooling-node/testing/paths" | ||
| import { | ||
| createSuiScriptRunner, | ||
| parseJsonFromScriptOutput | ||
| } from "@sui-amm/tooling-node/testing/scripts" | ||
| import { DEFAULT_LOCALNET_PYTH_PRICE_FEED_ID } from "../../../utils/amm.ts" | ||
|
|
||
| type AmmCreateOutput = { | ||
| adminCapId?: string | ||
| ammConfig?: AmmConfigOverview | ||
| digest?: string | ||
| initialSharedVersion?: string | ||
| pythPriceFeedIdHex?: string | ||
| transactionSummary?: { label?: string } | ||
| } | ||
|
|
||
| type ObjectArtifact = { | ||
| objectId?: string | ||
| objectType?: string | ||
| initialSharedVersion?: string | ||
| } | ||
|
|
||
| const resolveKeepTemp = () => process.env.SUI_IT_KEEP_TEMP === "1" | ||
|
|
||
| const resolveWithFaucet = () => process.env.SUI_IT_WITH_FAUCET !== "0" | ||
|
|
||
| const resolveOwnerScriptPath = (scriptName: string) => | ||
| path.join( | ||
| resolveDappRoot(), | ||
| "src", | ||
| "scripts", | ||
| "owner", | ||
| scriptName.endsWith(".ts") ? scriptName : `${scriptName}.ts` | ||
| ) | ||
|
|
||
| const resolveObjectArtifactsPath = (artifactsDir: string) => | ||
| path.join(artifactsDir, "objects.localnet.json") | ||
|
|
||
| const readObjectArtifacts = async (artifactsDir: string) => { | ||
| const contents = await readFile( | ||
| resolveObjectArtifactsPath(artifactsDir), | ||
| "utf8" | ||
| ) | ||
|
|
||
| return JSON.parse(contents) as ObjectArtifact[] | ||
| } | ||
|
|
||
| const findObjectArtifactById = ( | ||
| artifacts: ObjectArtifact[], | ||
| objectId: string | ||
| ) => artifacts.find((artifact) => artifact.objectId === objectId) | ||
|
|
||
| const testEnv = createSuiLocalnetTestEnv({ | ||
| mode: "test", | ||
| keepTemp: resolveKeepTemp(), | ||
| withFaucet: resolveWithFaucet(), | ||
| moveSourceRootPath: resolveDappMoveRoot() | ||
| }) | ||
|
|
||
| describe("owner amm-create integration", () => { | ||
| it("creates a shared AMM config and records artifacts", async () => { | ||
| await testEnv.withTestContext("owner-amm-create", async (context) => { | ||
| const publisher = context.createAccount("publisher") | ||
| await context.fundAccount(publisher, { minimumCoinObjects: 2 }) | ||
|
|
||
| await context.publishPackage("prop-amm", publisher, { | ||
| withUnpublishedDependencies: true | ||
| }) | ||
|
|
||
| const baseSpreadBps = "37" | ||
| const volatilityMultiplierBps = "420" | ||
| const useLaser = true | ||
| const pythPriceFeedId = DEFAULT_LOCALNET_PYTH_PRICE_FEED_ID | ||
|
|
||
| const scriptRunner = createSuiScriptRunner(context) | ||
| const result = await scriptRunner.runScript( | ||
| resolveOwnerScriptPath("amm-create"), | ||
| { | ||
| account: publisher, | ||
| args: { | ||
| json: true, | ||
| baseSpreadBps, | ||
| volatilityMultiplierBps, | ||
| useLaser | ||
| } | ||
| } | ||
| ) | ||
|
|
||
| expect(result.exitCode).toBe(0) | ||
|
|
||
| const output = parseJsonFromScriptOutput<AmmCreateOutput>( | ||
| result.stdout, | ||
| "amm-create output" | ||
| ) | ||
| if (!output.ammConfig) { | ||
| throw new Error("amm-create output did not include ammConfig.") | ||
| } | ||
| if (!output.initialSharedVersion) { | ||
| throw new Error("amm-create output did not include shared version.") | ||
| } | ||
| if (!output.adminCapId) { | ||
| throw new Error("amm-create output did not include adminCapId.") | ||
| } | ||
|
|
||
| expect(output.digest).toBeTruthy() | ||
| expect(output.transactionSummary?.label).toBe("create-amm") | ||
| expect(output.ammConfig.baseSpreadBps).toBe(baseSpreadBps) | ||
| expect(output.ammConfig.volatilityMultiplierBps).toBe( | ||
| volatilityMultiplierBps | ||
| ) | ||
| expect(output.ammConfig.useLaser).toBe(useLaser) | ||
| expect(output.ammConfig.tradingPaused).toBe(false) | ||
| expect(normalizeHex(output.ammConfig.pythPriceFeedIdHex)).toBe( | ||
| normalizeHex(pythPriceFeedId) | ||
| ) | ||
| expect(normalizeHex(output.pythPriceFeedIdHex ?? "")).toBe( | ||
| normalizeHex(pythPriceFeedId) | ||
| ) | ||
|
|
||
| const objectResponse = await context.suiClient.getObject({ | ||
| id: output.ammConfig.configId, | ||
| options: { showOwner: true } | ||
| }) | ||
| if (!objectResponse.data) { | ||
| throw new Error("AMM config object could not be loaded from localnet.") | ||
| } | ||
| const onChainSharedVersion = extractInitialSharedVersion( | ||
| objectResponse.data | ||
| ) | ||
|
|
||
| expect(onChainSharedVersion).toBe(output.initialSharedVersion) | ||
|
|
||
| const objectArtifacts = await readObjectArtifacts(context.artifactsDir) | ||
| const createdArtifact = findObjectArtifactById( | ||
| objectArtifacts, | ||
| output.ammConfig.configId | ||
| ) | ||
| expect( | ||
| createdArtifact?.objectType?.endsWith(AMM_CONFIG_TYPE_SUFFIX) | ||
| ).toBe(true) | ||
| expect(createdArtifact?.initialSharedVersion).toBe( | ||
| output.initialSharedVersion | ||
| ) | ||
| }) | ||
| }) | ||
| }) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assert
adminCapIdagainst the package published in this test.Lines 80-82 discard the publish metadata, and Lines 115-117 only check that
adminCapIdis non-empty. A resolver or serialization bug can return a stale/unrelated cap ID and this test would still pass. Capture the publish digest and compare the output to the admin cap derived from that digest.Also applies to: 115-117
🤖 Prompt for AI Agents