Skip to content

Conversation

tejasbadadare
Copy link
Contributor

@tejasbadadare tejasbadadare commented Aug 20, 2025

Summary

  • Add a contract state module. The singleton State consists of the current set of trusted signers.

    • Add getters
    • Add setter - state::update_trusted_signer can be used to upsert trusted signer info
  • Add initializers to pyth_lazer and admin modules. Initializers are called automatically once and only once at module publish time.

    • admin::init

      • Mints the singleton AdminCap and transfers ownership to the deployer. We guarantee this can only be called once by using the One-Time Witness pattern. Although init will only be called once, it's possible for contract upgrades to introduce bugs that allow minting extra AdminCaps. The OTW pattern prevents this from happening, as the OTW struct will be consumed and dropped by the first init.
      • Only the admin cap holder can call state::update_trusted_signer. This is done by specifying &AdminCap as the first parameter. The type system guarantees that only the admin cap owner can obtain a reference (borrow) to it, ensuring only the admin is capable of calling the function. By naming the parameter _, the cap reference is immediately consumed.
      • In the future, the admin cap should be transferred by the deployer to a custodian controlled by governance.
    • pyth_lazer::init

      • Creates the singleton State and shares it publicly. This moves the object into global storage and allows anyone to reference and read it. Updating it is only possible via the state:update_trusted_signer function.

Next up:

  • Upgradeability & versioning
  • Error handling

How has this been tested?

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

tejasbadadare and others added 16 commits August 19, 2025 14:17
Co-Authored-By: Tejas Badadare <[email protected]>
…and tests; verified with Sui CLI 1.53.2

Co-Authored-By: Tejas Badadare <[email protected]>
…d gate updates with admin cap

Co-Authored-By: Tejas Badadare <[email protected]>
Copy link

vercel bot commented Aug 20, 2025

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

Project Deployment Preview Comments Updated (UTC)
api-reference Ready Ready Preview Comment Aug 22, 2025 11:20pm
component-library Ready Ready Preview Comment Aug 22, 2025 11:20pm
developer-hub Ready Ready Preview Comment Aug 22, 2025 11:20pm
entropy-explorer Ready Ready Preview Comment Aug 22, 2025 11:20pm
insights Ready Ready Preview Comment Aug 22, 2025 11:20pm
proposals Ready Ready Preview Comment Aug 22, 2025 11:20pm
staking Ready Ready Preview Comment Aug 22, 2025 11:20pm

…_lazer::init; share State; add OTW doc comments

Co-Authored-By: Tejas Badadare <[email protected]>
Copy link
Contributor

@merolish merolish left a comment

Choose a reason for hiding this comment

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

Nice, seems straightforward.

Copy link
Collaborator

@ali-behjati ali-behjati left a comment

Choose a reason for hiding this comment

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

I left some comments. I'll approve after you help me understand it slightly better and this is complicated.

Re: admin cap ownership. The DAO should control everything in the end and in the Pyth Core contract, by some tricks we let the contract own it's own admin cap.


/// Initializes the module. Called at publish time.
/// Creates and shares the singular State object.
/// AdminCap is created and transferred in admin::init via a One-Time Witness.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmmm it's not here right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah it's happening in the admin module's init, but it should happen within the same publish PTB

/// Only the AdminCap owner can update the trusted signers.
fun init(otw: ADMIN, ctx: &mut TxContext) {
assert!(types::is_one_time_witness(&otw), 1);
let cap = AdminCap { id: object::new(ctx) };
Copy link
Collaborator

Choose a reason for hiding this comment

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

AdminCap can be created in other places by mistake no? If you want the admin cap to be only made by one, you need to enforce it via a type (like the cap retaining the witness, or having a phantom data or something).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

FWIW, the existing pyth contract also can't guard against new logic adding the ability to edit state. Any friend of state can call assert_latest_only and obtain a LatestOnly cap that lets them edit state. Currently governance and the "create price feeds" codepaths call it but others could be introduced in the future.

If the AdminCap retains the witness, doesn't the vuln just move to the witness? i.e. some codepath can be introduced in the future that can instantiate the witness, allowing instantiation of the AdminCap

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmmm you can't get the witness out of AdminCap without deconstructing it, right?

Regardless, I was thinking about the problem you are trying to solve (not being able to instantiate AdminCap twice) and I'm not sure we really need to solve it. We generally trust ourselves and not the others and in the future might have good reasons to have two or divide it in two capabilities.

The usage of witness also seems to be where you want external people to behave in a certain way and not us. (like someone is creating a Coin and we want to impose some certain characteristic there)

@tejasbadadare

This comment was marked as outdated.

@tejasbadadare
Copy link
Contributor Author

tejasbadadare commented Aug 22, 2025

@ali-behjati Okay today's convo on the future plan help elucidate this for me.

How the Pyth contract works is:

  1. When the package is deployed for the first time, it issues a DeployerCap to the tx sender. This DeployerCap and the framework-provided UpgradeCap are "traded in" to initialize the state, ensuring the state is created once and only once, even across contract upgrades.
  2. During the state initialization, the DeployerCap is destroyed and the UpgradeCap is transferred to the contract itself (gets stored in the singular State.) This means that only the contract can upgrade itself. The only codepath to trigger that is through governance.

For this iteration of the Lazer contract:

  • We don't have a remote executor to transfer the ownership to
  • We don't have the governance messages defined for signer rotations or upgrades.

Thus, I currently can't have the contract store its own UpgradeCap since then we'd have no way of upgrading it after deploying.

The implementation in this PR basically gives UpgradeCap and AdminCap to the deployment tx sender, giving it full control.
I think that's kind of all we can do until we define and implement governance messages for Lazer. We can transfer the AdminCap to some DAO wallet until we get there?
Once we do have governance, we can upgrade the contract to have the proper system from the Pyth contract. Or, we simply don't deploy until we have governance set up, but idk what the deadline is here... Wdyt?

@ali-behjati
Copy link
Collaborator

We can transfer the AdminCap to some DAO wallet until we get there?

It's practically impossible. The DAO lives on Solana, it can't have a non-Solana account. We can setup a multi-sig or something on SUI and ask the Pythian council to own to form a lose connection to the DAO but it's much more work.

To be an official Pyth contract, we need the governance to be there and that can't be done without using a bridge. That being said, the two governance methods you need are updating the signers and upgrade and shouldn't be difficult.

Copy link
Collaborator

@ali-behjati ali-behjati left a comment

Choose a reason for hiding this comment

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

I'm approving this to unblock you, we'll likely need to revisit it again in the future.

@tejasbadadare
Copy link
Contributor Author

I'm approving this to unblock you, we'll likely need to revisit it again in the future.

Sounds good. Once we add the governance messages i'll add the wormhole governance support.

@tejasbadadare tejasbadadare merged commit 3aed1cc into main Aug 25, 2025
11 checks passed
@tejasbadadare tejasbadadare deleted the tb/lazer/sui-storage branch August 25, 2025 19:23
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.

3 participants