Skip to content

Pyth Lazer for Cardano#3499

Open
matej-douro wants to merge 55 commits intomainfrom
matej/cardano-governance
Open

Pyth Lazer for Cardano#3499
matej-douro wants to merge 55 commits intomainfrom
matej/cardano-governance

Conversation

@matej-douro
Copy link
Contributor

@matej-douro matej-douro commented Feb 24, 2026

Summary

This is a big PR, merging Cardano work that was previously in a separate branch to main. There's more to do when it comes to SDK and building transactions off-chain, but contract itself should be in a place where the design is set and implemented.

How has this been tested?

  • Current tests cover my changes
  • Added new tests
  • Manually tested the code

Open with Devin

matej-douro and others added 23 commits February 13, 2026 19:17
@matej-douro matej-douro requested a review from a team as a code owner February 24, 2026 08:55
@vercel
Copy link

vercel bot commented Feb 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
api-reference Error Error Mar 7, 2026 5:27am
component-library Error Error Mar 7, 2026 5:27am
developer-hub Error Error Mar 7, 2026 5:27am
4 Skipped Deployments
Project Deployment Actions Updated (UTC)
entropy-explorer Skipped Skipped Mar 7, 2026 5:27am
insights Skipped Skipped Mar 7, 2026 5:27am
proposals Skipped Skipped Mar 7, 2026 5:27am
staking Skipped Skipped Mar 7, 2026 5:27am

Request Review

chatgpt-codex-connector[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Comment on lines +127 to +134
// The output contains state NFT and nothing else, except ADA. We can't
// check policy ID (minting script hash) here without introducing circular
// dependency, so malicious entity could send here a token from a different
// policy, but as long as we only trust state tied to a well known policy
// ID, this spending script will always ensure that updates to it are valid.
expect [(id, asset, 1)] =
assets.without_lovelace(input.value) |> assets.flatten()
expect (asset == config.state_nft)?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: State NFT policy ID cannot be validated in spend handler (documented design limitation)

The state.spend function at state.ak:132-134 extracts the single non-ADA asset from the input and uses its policy ID to locate the output, but cannot verify the policy ID matches the expected minting script hash without creating a circular dependency. The code documents this limitation clearly at lines 127-131. Security relies on the fact that only the legitimate minting script can produce tokens with the correct policy ID (since the origin UTxO is consumed during minting). This is a known Cardano pattern for avoiding circular script references.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

devin-ai-integration[bot]

This comment was marked as resolved.

Comment on lines +26 to +39
redeemers: Pairs<ScriptPurpose, Redeemer>,
) -> List<PriceUpdate> {
expect Some(redeemer) = pairs.get_first(redeemers, Withdraw(Script(script)))
expect updates: List<ByteArray> = redeemer
list.map(
updates,
fn(message) {
let message = message.parse_without_verification(message)
parse_update(message.payload)
},
)
}

fn parse_update(payload: ByteArray) -> PriceUpdate {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: get_updates intentionally skips signature verification

The get_updates function at lazer/contracts/cardano/lib/pyth.ak:26-39 calls message.parse_without_verification rather than message.parse_and_verify. This is intentional: it's an SDK helper for consumer contracts to read price updates from a transaction's redeemers after the pyth_price withdraw validator (lazer/contracts/cardano/validators/pyth_price.ak:15-33) has already verified each update's signature and signer trust. Duplicating verification on the consumer side would waste execution budget. The variable shadowing on line 35 (let message = message.parse_without_verification(message)) is valid Aiken — the module message is used for the function call, and the parameter message is used as the argument.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@matej-douro
Copy link
Contributor Author

Moved shared logic to https://github.com/pyth-network/pyth-lazer-cardano repo - we can merge its PR first (pyth-network/pyth-lazer-cardano#1).

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Co-authored-by: devin-ai-integration[bot]
<158243242+devin-ai-integration[bot]@users.noreply.github.com>
utxo: OutputReference,
self: Transaction,
) {
let old, _ <- state.spend(config, old, utxo, self)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Wormhole guardian set upgrade doesn't require owner NFT

In lazer/contracts/cardano/validators/wormhole_state.ak:36, the is_owner boolean from state.spend is discarded with _. This means anyone can submit a guardian set upgrade VAA. This is intentionally correct — guardian set upgrades are authenticated by the VAA itself (signed by the current guardian set quorum), and the contract at wormhole/governance.ak:31 enforces old.set_index + 1 == new.set_index. Not flagged as a bug because the security comes from VAA verification, not owner NFT.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +104 to +109
const state = spender.receive(ctx.parameters, stateNFT, {
deprecated_withdraw_scripts: new Map(),
governance: { ...initial, seen_sequence: 0n },
trusted_signers: new Map(),
withdraw_script: new Uint8Array(),
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Pyth state initialized with empty withdraw_script

In lazer/contracts/cardano/sdk/js/src/transactions.ts:108, the initial Pyth state is created with withdraw_script: new Uint8Array() (empty bytes). This means no withdraw script is active at deployment time, so price verification via the zero-withdraw trick won't work until a governance action sets one. The is_active_withdraw_script function at lazer/contracts/cardano/validators/pyth_state.ak:26-35 would fail to match any credential against an empty hash. This appears intentional (deploy first, configure via governance), but operators should be aware that the contract is non-functional for price verification immediately after deployment.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants