cadence icon indicating copy to clipboard operation
cadence copied to clipboard

Feature Request: Get Account object that can only access specific storage and public paths

Open joshuahannan opened this issue 4 months ago • 11 comments

Issue to be solved

Currently we can get account objects that are entitled to only access certain functionality like Storage, Capabilities, etc, and this is definitely nice, but it doesn't really allow the kind of restriction of functionality that would be the most useful. Even if the transaction is restricted to Storage, it can still access every storage path and modify any storage path.

Currently, it is an antipattern to pass account objects to utility functions to set up account storage, capabilities, etc, but this restriction is starting to get really out of hand. There is so much boilerplate in Cadence transactions for initializing storage and capabilities now, it makes transactions really complicated and error prone.

For example, just look at this transaction to schedule a COA transaction: https://github.com/onflow/flow-core-contracts/blob/josh/coa-handler/transactions/transactionScheduler/schedule_coa_transaction.cdc

90% of the transaction is just boilerplate to make sure the storage and capabilities are set up properly, which is so unnecessary.

It would be really nice if we were able to get authorized account objects that could only access certain paths so we can put this boilerplate in utility functions in the contract that defined them so we can really simplify these transactions.

Suggested Solution

Have functions on the account object to get an account object or reference that can only access the specified paths. If other paths are attempted to be accessed, then the transaction would fail checking or execution (not sure where this can be verified)

something like this:

account.getPathRestrictedAccount(storagePaths: [/storage/coa, /storage/coaScheduledTransactionHandler, /storage/flowTransactionSchedulerManager], publicPaths: [/public/coa, /public/coaScheduledTransactionHandler, /public/flowTransactionSchedulerManager])

joshuahannan avatar Oct 27 '25 15:10 joshuahannan

I like this idea in general, but also I am not sure what is the latest vision on this. ( maybe @dete can chime in )

Because this is a bit shift from wallet to dapp ( with paths etc ) ( long ago we had some transaction format changes discussions for example, a bit conflicts with that )

Once we starting passing &Account, it becomes a bit like : FunctionName(acc: &Account) and all logic would be encapsulated on the dapp side. ( a bit pessimistic view point as usual ) On the other hand this is how EVM basically works.

bluesign avatar Oct 29 '25 17:10 bluesign

yeah, I think it is good to encapsulate most of the logic on the dapp side. Cadence transactions are getting more and more cumbersome and so much of it is boilerplate. It just looks bad and is really confusing

joshuahannan avatar Oct 29 '25 19:10 joshuahannan

I like the idea of attenuating storage access, it's a good continuation of the effort to attenuate access to an account.

Maybe the API could be defined on Storage, as a function that returns a reference to an attenuated Storage object:

fun limited(paths: [Path]): &Storage

Example:

prepare(signer: auth(Storage) &Account) {
    let valueOnlyStorage = signer.storage.limited(paths: [/storage/value])
    SomeContract.doSomething(valueOnlyStorage)
}

The returned storage object would then dynamically enforce that operations can only be performed on one of the paths.

We could later add similar APIs to other account "components", like Capabilities, Keys, etc.

turbolent avatar Nov 07 '25 21:11 turbolent

yeah that would be great. You're implying that you could include public paths in that also, right?

joshuahannan avatar Nov 07 '25 21:11 joshuahannan

Yeah, the returned limited storage reference would have the same functionality as an unlimited one. The only difference would be that all operations would go through a path allow list.

Good question regarding public paths. It might be useful to express something like "only allow access to these storage paths, but still allow any public path."

turbolent avatar Nov 10 '25 16:11 turbolent

That would be fine from a read perspective, but I'm thinking about the cases where apps publish capabilties for an account. It is fine if they read any public path, but I only want to give them the ability to publish a capability at a certain set of public paths, or not allowed to publish capabilities at any public paths at all.

joshuahannan avatar Nov 10 '25 17:11 joshuahannan

[...] I'm thinking about the cases where apps publish capabilties for an account. It is fine if they read any public path, but I only want to give them the ability to publish a capability at a certain set of public paths, or not allowed to publish capabilities at any public paths at all.

That could be achieved by applying the same idea to Capabilities.

Having thought about it a bit more: Maybe the API should not just be a allow list, and for all operations, but rather a function, per operation. The function/per-operation approach could be more fine-grained (e.g. have allow lists for individual operations like publishing and unpublishing), and more flexible (e.g. keep state)

With the function/per-operation approach, your use case could be implemented with e.g.

let onlyPublishAtFooCaps = account.capabilities.limitPublish(
    fun (_ capability: Capability, at path: PublicPath): Bool {
        return path == /public/foo
    }
)
onlyPublishAtFooCaps.publish(cap, at: /public/bar) // panics

turbolent avatar Nov 10 '25 17:11 turbolent

That could be useful, but I worry about the desired for it to be fine-grained and flexible getting in the way of ease of use.

I think it starts to fall apart a bit the more complex the transaction gets, right? Like the one I linked above would have to create one for all the storage operations, one for the capability issuing, and one for the capability publishing. Seems like more of a hassle than just having a single account object that was created with a list of public and storage paths that are allowed and the contract can do everything it wants with that single object and if it ever tried to write or issue a capability for any path that isn't in the list, it fails. That seems really easy to understand and use

joshuahannan avatar Nov 10 '25 18:11 joshuahannan

Maybe we can make it part of entitlement somehow; something like :

BorrowValue([/storage/x, storage/y]) or BorrowValue</storage/x> not sure about feasibility technically, but at least wallets will have easier time parsing and showing to users.

bluesign avatar Nov 13 '25 13:11 bluesign

Any way this can be prioritized higher? I'm happy to participate in a call or something to discuss the specifics. I think this would be really amazing to have

joshuahannan avatar Dec 09 '25 18:12 joshuahannan

Adding features is not a current priority, but we can plan it in for the next OKR and start with some research & design

turbolent avatar Dec 09 '25 21:12 turbolent