The Path to “Stable Cadence”

The Path to “Stable Cadence”

One of the most limiting aspects to building on Flow today is the requirement that all smart contracts undergo a security review before deployment on mainnet. Our goal is, and always has been, for mainnet deployment to work like testnet deployment: When you are ready, go for it!

Of course, Cadence is new, and not yet battle-hardened, so to avoid creating a bad experience for users, we have worked with the community to make sure that all smart contracts that are deployed on mainnet stick with the “tried-and-true” features of Cadence that are known to work well. (And to make sure no-one sneaks nasty code on chain in an attempt to exploit possible edge-cases!)

This post is to announce that the core Cadence team is making “Stable Cadence” its highest priority. Stable Cadence is a version of Cadence that is tested and verified to the point where we can recommend that mainnet allows unrestricted smart contract deployment, just like testnet.

There are three main steps involved in getting Stable Cadence released:

  1. A period of code-hardening, where most or all of the development effort on Cadence is focused on finding and properly handling various edge cases and possible exploits.
  2. A full, professional code audit of the Cadence code base (audit team to be finalized, but it will be well-established, independent, and the results will be published publicly).
  3. A period of “incentivized stress testing”, where we use an enhanced bug bounty program to attract as many independent security researchers as possible to further scrutinize the Cadence implementation.

An Opportunity

As we were preparing to turn our focus to Stable Cadence, we realized that there are a small number of improvements to Cadence that are not easy to introduce without breaking existing code. Once Stable Cadence is live and anyone can deploy code anonymously to mainnet, breaking changes will simply not be acceptable. But maybe, just maybe, some breaking changes are acceptable today since we have a relationship with most of the developers that are currently live, and can work with them to update the on-chain code. The pain might be worth it, provided the improvements are significant enough.

Each of the changes listed below has a chance to make future Cadence development significantly more powerful and/or pleasant. Each of them addresses a major stumbling block that developers are seeing today. And each of them requires significant changes to existing code on mainnet; virtually every contract on mainnet would need to be updated if we adopted all of them.

We have written up each of these issues in their own thread, linked below, to encourage discussion and debate. For each issue, if the community overwhelmingly thinks that the improvement is not worth the complexity of updating existing code, we will move forward with the current structure. And, of course, if the community sees the value of any of these changes, we will work with each and every team that is currently live on mainnet to make sure their code works properly under the new implementation.

These discussions should not be seen as a simple up-down vote. We have identified problems and proposed concrete solutions to them, but alternate suggestions are welcome. We just want to create the best language for smart contract development!

The Changes Under Consideration

Here is a list of some of the changes that the team is going to propose to address problems in the current version of Cadence:

  1. Streamlined token standards – The current fungible and non-fungible token standards for Flow can only support a single FT or NFT type per contract. It would be beneficial if it was possible to define multiple FT/NFT types per contract. Currently, these standards use type requirements, a language feature which was initially intended to assist with the definition of such standards. However, since the release of Cadence and using these standards, it has become apparent that the standards are limited due to type requirements. Removing type requirements would require changes to the NFT and FT interfaces, which in turn would require code upgrades for all NFT and FT contracts, though the changes would be minimal, and as a result, the standards would be much more flexible and powerful.

  2. Improve capabilities – Capabilities are one of the most powerful aspects of Cadence. At the moment we think they are also awkward to learn, frustrating to use, and the code that interacts with them is overly verbose. It would be beneficial to make capabilities much easier for developers to use, by replacing the current API with an alternative, but this would be a fundamental change to the current mechanism for handling capabilities. For example, if the notion of “links” would be removed from Cadence, both code and state updates () would be required.

  3. Remove reentrancy “foot-guns” – Cadence makes it very easy to avoid reentrancy attacks: do not allow shared access to global mutable state (i.e. avoid contract variables), ensure your code never has two references to the same instance object live at the same time. Both of these facilities are “bad code smells”, and the kind of thing that an auditor would suggest to strenuously avoid. It would be beneficial to find ways to prevent reentrancy bugs, which would likely require a modification of the language specification.

  4. Remove security “foot-guns” – Since the initial release of Cadence, accidental mutability through public fields has been identified as a potential for security issues: Accessing public fields of resources directly can have some surprising side effects. Since resources are overwhelmingly intended to represent assets of value or a mechanism for managing access control, there is a strong argument for preventing developers from accidentally introducing bugs into their contracts.

    Potential solutions to this problem have already been proposed in FLIPs 703 and 739, which are open for community feedback.

  5. Clarify and fix the behaviour of certain features and functions – For example, references to optionals, storage functions, etc.

    Potential improvements have already been proposed in FLIPs 729 and 722, which are open for community feedback.

Other Changes

Stable Cadence will likely include some other minor changes. However, Stable Cadence should not be seen as a feature release, nor should anyone assume we do not care about any key features just because they are not planned as part of Stable Cadence.

Yes, there are many ways that Cadence can (and will!) improve beyond Stable Cadence, and we are as anxious as anyone to see them go live. But the vast majority of these features are purely additive and can be released in future versions without breaking existing code. As such, we do not want to let those features delay the release of Stable Cadence.

Please Weigh In!

If you have any thoughts or questions about the overall idea of “Stable Cadence”, or if you have ideas for key features that should be considered for Stable Cadence since they might break existing code, please comment below.

Please follow the links to the individual proposals above or find all Cadence FLIPs on GitHub, and give feedback. The issues listed above will be explained in much more detail in their own posts, and we are happy to join in the discussion there about the specifics.

––

Much thanks to the whole community for being patient with us as we got to this point! We are all looking forward to hearing your thoughts about how we can make sure Cadence keeps improving.

Thanks also to the Cadence team for helping with this effort and this announcement :pray:

6 Likes

Think about: https://github.com/onflow/cadence/issues/537
(Make Crypto contract compatible with Ethereum)

Now that we have entered 2022 the year of multi-chain I think.
we need better language level support to allow third-party teams to bring more cross-chain possibilities to Flow ecosystem. This is the biggest charm of Web3 and it should not be a closed system.

3 Likes

Can you please give some examples to showcase the exact meaning of this, and the potential impact ?

We’d propose to include this feature: https://github.com/onflow/cadence/issues/1171. Probably @bluesign is already working on this one? :pray:

Great move. Much appreciated.

We are in the process of Smart Contract review with the Flow team. Would you recommend to wait for the “Stable Cadence” so that we won’t have to upgrade our contracts during initial phase after launch? If so, can you provide a rough estimate on when Stable Cadence will be out?

Our contracts references and imports both FT and NFT contracts on Mainnet. So if these standard contracts will be upgraded, their contract addresses will change right? If so, we’ll have to modify our contracts with new import addresses for FT and NFT contracts. Then we’ll have to redeploy our modify contracts while importing our old contracts to access it’s state right?

Congrats Flow team, this a great news and a significant milestone to make Flow permissionless and fulfill its promise as a blockchain.
I believe stable cadence should include something around cross chain as mentioned before and ease the integration with the other L1 blockchains.
There is a huge number of developers who would be pleased to join Flow if this exists. On top of that Flow could benefit from other L1s ecosystems in return.

Hi.

I’m Japanese developer. And I have some opinion, and at first I commented on general channel, as below…

Since I couldn’t login to stable cadence comments page, I suggest new idea here,
I think the feature newly planned implemented are having annoying things.
FT and NFT things are also annoying since we are investing too much money.
The pub accessible changes are solved by AI solutions which detects such vulnerabilities. I know the top engineer of cadence has degree of AI. It’s not so difficult, and if you ask, I also can create this AI model.
And we are having educational products especially in YouTube. Since we are having education products already, the planned features, in my opinion, are not so significantly important, and from investor perspective, it should be forked in the future.
I love current Cadence, so please listen to my opinion,
thanks.

So I’m concerned about changing FT thing as it might bear new problem.

Hey this is great news.

Below are some stuff I though from my notes:

  • +1 for Improve capabilities: Revoke would be great here. I think after these improvements and storage query API, we can get rid of Path also. ( at least I hope )

  • Contract Updates vs Composability: This is also another area I think can see improvement. Now upgradeability vs composability are against each other. Currently you need to be very brave to use someoneelse’s contract.

  • Deploy Contract: As opening up contract deployment to everyone, this should need big warning etc from Wallet side, as it opens up big security hole in general. Contract deploy = giving access to account

  • Get Resource Reference with uuid ( some immutable reference, or in script context ) : This can be very useful, if we can query any resource on chain by uuid. ( This is much better than Type + id )

  • Transactions should be simplified: Now transactions are a bit mess, I think better action is to somehow move transaction logic into the contract. Now reading a transaction is too verbose. Mostly current problem is accessing to storage over AuthAccount and linking. If capabilities are fixed, I think a lot of code can be removed here.

  • Transaction Arguments and Post conditions are not utilized enough: Actually Post conditions on transactions are very strong security tools. Maybe they can be incentivized more to use.

  • Dynamic import: I think this can be very nice to have, import from a variable or conditional import depending on the network.

  • Stack depth to events: Currently event index is single dimensional and not enough to track events, stack depth parameter there can help a lot.

  • FT contracts update: I feel current withdraw/deposit is a bit weird tbh (creating temporary vault in the process) It somehow doubles the number of events, some addresses are lost ( from , to etc depending on use case ( if I withdraw from storage vault, or withdraw from vault that I gained by withdraw ) Some transfer method can make sense here.

  • Events: I think events needs at least some part of onchain access. Everything currently requires querying events from the chain, but query system is too limited. I think there should be some kind of way to query recent events at least in Cadence.

  • Backward compatibility: I think slowly Sporks will become history, As cadence development will continue, I think there is a need for multiple versions of Cadence on same network at the same time. I think currently we can expect people to upgrade contracts so we can do backwards compatibility breaking changes is a bit flawed. In this context maybe another level of language ( like cadence VM ) maybe helpful. But this is the most tricky area.

  • stdlib: This is not totally related for Stable Cadence, but it would be nice to have a bigger stdlib.

  • and some more ideas: Multi Chain Crypto, Oracles, Onchain Random, Onchain multisig management

Also I am a bit undecided but as a feeling I think interfaces are a bit weak currently, I can claim For example to support Provider interface in NonFungibleToken, but I can update contract anytime to panic on withdrawal.

Another one I am undecided is ‘container’ property for resources and exposing signers for current transaction. They are too powerful but also can be abused maybe.

Great news that this will be a focus!

I posted a FLIP about initializing a contract in a users account. I guess sort of like a way to make it easier to create and link things properly without exposing the nitty gritty details. It is still very early but I am interested on your opionions on it. https://github.com/onflow/flow/pull/748

From my personal experience right now I am quite scared for users if we release Stable Cadence to early. These are the things I think would need to be worked on more:

  • Make it possible to upgrade contracts a bit more freely then now, put in some efforts to be able to remove some of the restrictions that are there currently. If we remove the gate to deploying to mainnet people will do it, make mistakes and whine when they cannot change it later on. Users will not be happy.

  • Have a way of messuring code coverage of cadence in tests. If we do not have a review process having some way of knowing if you have covered all the edge cases wold make me sleep better atleast.

  • an AuthPointer that can be used in storefronts and similar to have the capabilityt o withdraw only a single item, not your entire collection. With open contract updates I will have a very large problem trusting a contract that requires a Provider capability to my entire NFT collection. The pointer could be created from a method in cadence and it could even not be able to be created in cadence for security reasons.

    pub struct AuthNFTPointer {
     pub let collection: Capability<&{MetadataViews.ResolverCollection, NonFungibleToken.Provider}>
     pub let id: UInt64
    
     init(collection: Capability<&{MetadataViews.ResolverCollection, NonFungibleToken.Provider}>, id: UInt64) {
       self.collection=collection
       self.id=id
      }
    
     pub fun resolveView(_ type: Type) : AnyStruct? {
       return self.collection.borrow()!.borrowViewResolver(id: self.id).resolveView(type)
     }
    
     pub fun getViews() : [Type]{
       return self.collection.borrow()!.borrowViewResolver(id: self.id).getViews()
     }
    
     pub fun transfer(_ cap: Capability<&{NonFungibleToken.Receiver}>) {
       let resource <- self.collection.borrow()!.withdraw(withdrawID: self.id)
       //TODO: emit event
       cap.borrow()!.deposit(token: <- resource)
     }
    

}

  • Implement this feature https://github.com/onflow/cadence/issues/423, Identity would be very usefull here since you can send it into contracts to prove your identity, especially if it cannot be created in cadence. It could actually be used in things like deposit to prove the owner of the one it is deposited from. It should then maybe also be in the AuthPointer i wrote about above.

  • audited/checkmared transactions. Right now portto/blocto has this https://github.com/portto/flow-transactions/. Granted it does not work for non-custodial accounts, they get to see the entire transaction on their cell. Having some way to explain users in a standard way across wallet what a transactions does would be benefitical. This could just be the hash of the tx that you can lookup in some webpage and the community could contribute to it. Or even you can have some markers in the code that allow you to comment each block individually if you have on-the-fly-generated-transactions.

General comments

  • would it be possible to be able to access the owner of a resource/reference when the transactions started in some way? I guess my issue is with deposity/withdraw patterns. The fact that we need to invest lots of time in tooling to visualize withdraw/despoit correctly is not good.

  • some way to get a Pointer to a given resource in a collection, both a read pointer and and pointer that can withdraw it. A read pointer could look something like this:

    pub struct ViewReadPointer{
      pub let collection: Capability<&{MetadataViews.ResolverCollection}>
      pub let id: UInt64
    
      init(collection: Capability<&{MetadataViews.ResolverCollection}>, id: UInt64) {
        self.collection=collection
        self.id=id
      }
    
      pub fun resolveView(_ type: Type) : AnyStruct? {
        return self.collection.borrow()!.borrowViewResolver(id: self.id).resolveView(type)
      }
    
      pub fun getViews() : [Type]{
        return self.collection.borrow()!.borrowViewResolver(id: self.id).getViews()
      }
    }
    
  • Merging/finishing this is a must https://github.com/onflow/cadence/pull/1076 so that we can defaultly implement a lot of boilerplate code everywhere.

Streamline FT token standard

  • The events emitted are not the best, finding a way to have a single event that can have both sender and receiver would be great. One way is to use Identity and require that as input. The best would be if the Identity could be fetched from the Vault resource itself.

  • Make FT implement MetadataViews when that is out and the Display View.

Streamline NFT token standards

100% agree on this one. We have already had a meeting about nftv2 standard. The current one even with the new MetadataViews has some limitations.

  • uuid should be the identifier, not a seperate id. Having the ability to references resources with a single global identitifer is huge. Especially with the emergence of MetadataViews that spans across standards.

  • deposit/withdraw → transfer: Send in the capability to a collection and put it in the collection. Emit an event that contains both sender and receiver. Or alternatly allow a way of knowing the owner of the resource that it had when the transaction began and then use that in the emitted events.

  • Right now the INTF interface of NFT does not really do anything. Either remove it or make it be used for something.

  • finding a way to default implement part of deposit and withdraw, maybe put the line of code that needs to be there for every NFT into its own method? Creating your NFT should not require a lot of boilerplate if you ask me.

Lots more information here in the notes that i belive Josh made after the nftv2 meeting.

  • Prioritize work on Metadata standard and related views. The core here is already merged, not released, still there is lots to do. Agreeing on Display, Rarity and Listing (for marketplaces) is very important if you ask me.

FLOW was born as a specialized chain for NFTs and I find that fact great and I support FLOW since I bought in the public sale more than a year ago on Coinlist.
However, I ask from my ignorance, will this type of proposal to facilitate the creation of SC and open up to more devs, will it also facilitate the creation of projects such as l&b, farming, etc, as it’s developed in other chains; or is that not what is intended to achieve with this proposal?
Those types of projects, it is true that they attract investment, devs and users to the chain. But it is true, that the meaning of the FLOW chain is the NFTs …
So I ask what is the direction of FLOW as a mainnet and as a token.

I think storage and public paths should be dictated by the mint contract. The path should be deterministic, immutable and unique in the same way “identifier” is for cadence “Type”.
Currently you can store any NFT in any storage path. The problem with that, besides potential collissions, is a single third party can break functionality for everyone.

Example:

Legendary TopShot moments are now non-custodial. TopShot originally stored moments owned by an address in “/storage/MomentCollection”. If you puchased a TopShot moment on a third party marketplace and
that marketplace decided to store the purchased TopShot moment in “/storage/RandomPath” instead of the expected “/storage/MomentCollection” then that user would be unable to list on any other third party marketplace because
neither the user nor marketplace would know that the TopShot moment they expect is in an unexpected storage path.

The NFT standard has two main public interfaces, CollectionPublic and Receiver which both have the deposit function. I believe the CollectionPublic interface should not have the deposit function, and sort of related, the Cadence storage system may need some extra features.

With the current NFT interfaces, if an uncapped collection exists, an account may then mint many NFTs and clog the storage of all accounts that carry that resource and expose it with a public capability. This is more prevalent because there is no toggle for a user to turn this off (Unlinking the Receiver part of the NFT, because CollectionPublic also has the deposit function).

By uncapped collection, I’m referring to one that can have an infinite amount of NFTs in it (TopShot, Gaia, SportsIcon, Sturdy, many more).

This makes what is a seemingly harmless transaction (setting up a collection to receive an airdrop) in the stable cadence world actually have the ability to brick a flow account, granted that the collection creator or minter is willing to spend a little bit of gas fees.

Some other possible solutions:

  1. Have a max locked flow balance that’s configurable at Flow port or somewhere, and prevents storage from being added to an account if we’ve reached the configured max. Alternatively allow for a minimum unlocked flow balance set aside for an account. This is mainly to make it so if someone is being clogged, it may be a bit easier to deal with, and they can still run any transactions that do not require more storage on their account (can afford gas fees assuming not subsidized by Blocto) until they’re able to fix the problem that’s clogging the storage.

  2. Change to how storage is payed. I’d love to see a way for a collection to cover the locked flow storage on behalf of another user. IMO that should be the standard coming from a collection that is minting these NFTs/creating this storage, rather than who is holding the resource at that time.

  3. Allow accounts to not allow deposits into their account for a collection. Add a toggle as part of the standard NFT interface. This is very NFT specific though, and doesn’t help with other similar scenarios.

I think addressing this storage problem will also be needed if there are any plans to standardize having NFT collections that hold many different types of NFTs

I actually started on a FLIP to address this https://github.com/onflow/flow/pull/748, I have not had time to look at it more after the initial writeup so it is not done yet.

Basically the owner of a contract should be able to say how storage/linking for that contract should be handled and everybody else can just refer to that. This would in practice means that in most cases you would not even need Capabilities and Paths anymore. Especially if you combine that with the storageQuery Api.

Great point! As mentioned in the announcement’s goals, we will focus on adding more features after Stable Cadence

We will post a separate topic discussing the problems and ideas for improving the standards by improving the language soon

There is no estimate at the moment, as the goal depends on community feedback. If many proposals are rejected, it will take less time, if they are accepted, they need to be implemented, so the estimate will increase.

Good point! As mentioned in the announcement’s goals, we will focus on adding more features after the release of Stable Cadence

Thank you for the suggestion! This would be a new feature and is out of scope for Stable Cadence.
Also, this is unfortunately not trivial, as it would require an index of ID -> location in storage, which is easy for Execution Nodes to maintain, but must be somehow be part of the the execution state, so it is verified and there are proofs. The index (part of it) would also need to be somehow transferred to Verification Nodes

I agree with @bjartek and I had some additional thoughts along the lines of this.

I think we can make Flow Transactions more digestible by more people if we can prohibit “path” instantiations within the transactions themselves and instead ask contracts to handle that logic on their side. We are supposed to be asking users to “review your transactions before sending to make sure it’s actually safe!” - as most users might not be as comfortable reading and understanding an entire transaction code block, we should work on making that code as easy to read as possible.

The new change I’d like to suggest is that each resource (or maybe each contract) should have it’s own filesystem. For example, right now I might save my TopShot NFT to “/storage/MomentCollection”. From a programming viewpoint, that logic should not look any different. In the background, it should get saved to “/0b2a3299cc857e29/storage/MomentCollection” or something similar (The prefix is the mainnet TopShot contract address). This way, paths between contracts will never have to conflict with each other. It’s so much easier to re-use pre-made contracts without having to worry if names will conflict. Perhaps the path could have just been /storage/collection instead of /storage/MomentCollection, and have the address prefix at the beginning do the work of telling us what kind of collection we should expect.

Good idea, and this had come up before: https://github.com/onflow/cadence/issues/58
Agree this is something we should consider improving :+1: