Skip to content
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

FLIP 198: Authorized Call #198

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

SnowyField1906
Copy link

Objective

The objective of this FLIP is to introduce the "Authorized Call" feature, which allows functions to be marked as private unless they are called with a specific prefix.

This feature aims to enhance access control in Contracts, providing developers with more flexibility and fine-grained control over function visibility based on caller Contracts.

Motivation

Flow introduced Cadence, a Resource-Oriented Programming Language which works towards the Capability system, it replaced msg.sender and proved to be effective for small projects.
However, as projects grow in size and complexity, the efficiency of the Capability system decreases compared to the use of msg.sender.

Besides, The existing access control mechanisms is relatively simple, they have limitations when it comes to defining private functions that can only be accessed under specific circumstances. This can make it challenging for developers to enforce strict access control rules in complex projects.

@turbolent
Copy link
Member

turbolent commented Sep 19, 2023

Thank you for opening this FLIP Thuận!

From what I understand, the FLIP proposes to introduce a "capability-based msg.sender equivalent" – the auth value is basically a capability (in the traditional sense).

Such a mechanism was previously proposed in a slightly different form: onflow/cadence#423 proposed adding an AuthAccount.Identity value, which could only be obtained from an AuthAccount. Similar to this FLIP's auth value, a &AuthAccount.Identity reference (value) could be passed around to functions and used to perform access control.

The FLIP for that future, onflow/flow#945, was eventually rejected.
Have you had a look at the Cadence GitHub issue and the FLIP before?

@dsainati1
Copy link
Contributor

In addition to Bastian's feedback, any changes to access control need to account for the upcoming Entitlements feature, which is outline in https://github.com/onflow/flips/blob/main/cadence/20221214-auth-remodel.md and https://github.com/onflow/flips/blob/main/cadence/20230623-entitlement-improvements.md. The old access control model will be obsolete within a matter of months, so there is little point in changing it unless those changes also translate to the new entitlement-based model.

@SnowyField1906
Copy link
Author

Thank you for the responses.

Firstly, I think this FLIP is more powerful in some aspects. At least, one of the implementations mentioned in the FLIP would be helpful.
I totally understand that Cadence puts Capability on top and everything should work towards it for various reasons, but honestly, this should not for all. I mean, in some unnecessary cases, this is totally annoying and counterproductive.

To me, if a contract is dominated by Capabilities, it is also dominated (dependent) by accounts (because Capabilities and Resources are obtained from them). This is the main difference, while this mechanism aims to release part of the contract from such things (related to accounts).

Anyway, this is just an alternative option. Combining both would have given a good result, and developers will know which is most suitable for their projects.

I have read that FLIP just now, but I'm still confused that it was rejected because there are problems in that FLIP or Cadence is sensitive to this mechanism. After all, since my FLIP is quite different, I would love to know the reasons why this should not be done.

@turbolent
Copy link
Member

@SnowyField1906

Thank you for your thoughts!

First, I want to reply to some of them and go into "why capabilities" and "why not identity-based access control".

Firstly, I think this FLIP is more powerful in some aspects. [...] in some unnecessary cases, this is totally annoying and counterproductive.

In what aspects is the proposed feature more powerful than the current access control API based on capabilities and entitlements, and in what way are these APIs annoying or even counterproductive? Do you have some examples?

The capability-based access control API was just recently improved by introducing Capability Controllers, after a long discussion with lots of input from the community, and entitlements are also an improvement over the previous feature of refining access using interfaces based on restricted types.

It would be great to hear how these improvements are not working out for you, especially given you mentioned a "scalability issue" for larger / more complex contracts.

To me, if a contract is dominated by Capabilities, it is also dominated (dependent) by accounts (because Capabilities and Resources are obtained from them). This is the main difference, while this mechanism aims to release part of the contract from such things (related to accounts).

Can you elaborate on that? The motivating example, granting access to a function, seems to be about "account access" by nature. What do you mean with "release part of the contract"?

Anyway, this is just an alternative option. Combining both would have given a good result, and developers will know which is most suitable for their projects.

The goal of Cadence is to provide a language to developers that is useful and safe. By design, it omits many features that are known to be unsafe (e.g. raw memory access) and provides features that allow developers to write safe programs (e.g. static type system). For any feature, there is always a trade-off between safety and convenience. Given that access control is a critical part of a smart contract, adding a feature that is potentially more convenient, but also leads to more bugs, does not align with Cadence's values.

Often it is also the case that developers are not necessarily familiar with all security nuances of an aspect of smart contract development (e.g. here, access control, but also e.g. random number generation): They might be unaware of the short-comings of identity-based access control, or the issues with reverting random numbers.

But neither should they! Cadence's goal is to provide tools to write useful programs, but also safe developers from mistakes. By providing safe features and guiding developers into using them, even inexperienced developers are able to write useful and safe contracts.

Adding alternatives which appear more convenient, will attract developers, likely making the "power user" feature the first stop. To many developers the convenient solution may appear as the better solution, and they are likely unaware of the short-comings.

In addition, having multiple features to achieve the same goal adds complexity. It will be hard for developers to reason about how the proposed functionality interacts with the existing functionality.

I have read that FLIP just now, but I'm still confused that it was rejected because there are problems in that FLIP or Cadence is sensitive to this mechanism. After all, since my FLIP is quite different, I would love to know the reasons why this should not be done.

The FLIP was rejected, because the access-control model that the functionality in that FLIP and in this FLIP is based on, identity-based access control has many short comings and is known for many security issues for decades. For example, see:

Security aside, Cadence encourages composability, but identity-based access control kills composability.

Regarding the FLIP in particular:

  • In the motivation section you are giving some examples for how capability-based access control can be used to implement the example use-case. Could you please add how the use-case would be implemented using the feature proposed in the FLIP?

  • In the prior art section, maybe link to the previous GitHub feature request and rejected FLIP that I shared in my previous comment.

  • The prior art section states:

    This works similarly to the msg.sender in Solidity (not tx.origin)

    In the previous FLIP, the program/developers passes a token/value which represents the identity of one of the signers from one function to another.
    From my limited understanding, this is equivalent to the behaviur of tx.origin in Soldity.

    Does the behavior in this FLIP differ? It is not quite clear to me what the value of auth.address is in a contract function of a deeper call-chain.

    For example, let's assume transaction A, signed by 0x1 and 0x2, calls a function of contract B.
    Is auth.address inside of the B function 0x1 or 0x2, depending on how the function was called?

    Further, if contract B, e.g. deployed at 0x3, then performs another call of a function in contract C, e.g. deployed at 0x4.
    What is the value of auth.address in the function inside of contract C Is it still 0x1/0x2, or 0x3?

Thank you again for your contributions into making Cadence better 👍

@bluesign
Copy link
Collaborator

I think the question is; what is the practical use case to prefer identity to capability ?

@dete
Copy link

dete commented Oct 29, 2024

Hey @SnowyField1906!

I know it's been a while since you wrote this suggestion up, and the team was very busy with the existing work for Cadence 1.0 (which has now shipped!). I'd love to dig into better understand the proposal again.

For context:

Cadence (with the 1.0 upgrade) currently has five levels of access control for calling methods on resources:

access(self) is the most restrictive level and means "Only other methods on the type that defined this method can call this method." This is similar to the private keyword in most other languages.

access(contract) and access(account) are similar to the above, but allow calls from other types defined in the same contract or deployed to the same account. These are kind of like friend declarations in C++ and are intended to make it easy for a single implementor spread functionality across multiple files (in the same account) without having to worry about passing around entitlements.

access(all) is like the public keyword and basically means: If you have a reference to this object, you can call this method.

access(E) (for some named entitlement E) is new in Cadence 1.0, but it is an explicit replacement for the implicit protections that the interface restrictions used to proved. This method blocks some callers, but unlike the first three levels, it doesn't care where the caller's code was defined, but it cares if the reference that the caller has the entitlement E associated with it.

By default, the & operator effectively includes all entitlements, so the "owner" of the object has the ability to create a reference with any set of valid entitlements associated with it. It is worth noting that the "owner" in this case is not necessarily an account. It's whatever data structure that holds the object, which could be the account, or it could be another resource object.

What I take from your proposal is that you would like a mechanism for limiting access to who can call a method that is distinct from all of these, and which works like msg.sender. This combines two things:

  1. It allows you to give friend-like access (similar to access(contract) and access(account)) to other smart contracts that aren't defined in the same account as the object definition in question.
  2. It allows you to dynamically change which code is considered a friend using run-time data instead of compile-time references.

The reason I separate out the second from the first is that if your concern was just to have a more robust "friend" mechanism, we could introduce something like access(Typename) which would allow your code reference a specific type and allow shared implementation across contracts without having to worry about Entitlements. This might be a good idea, and could perhaps be improvements on access(contract) and access(account) and ultimately replace them.

If you want to change access control dynamically, I'm left trying to find a scenario where this would be desirable. I think it comes down to a disagreement about the appropriate balance of power between the issuer of an asset (the entity that deployed the smart contract that defined the object) and the owner of the asset (the entity that currently stores and controls the object).

Cadence was designed with the mindset that the issuer should have absolute control over deciding what an object "is" and how it behaves. However, once ownership of a specific instance is transferred to a user, we don't consider it desirable for the issuer to have the power to change how it can be used or transferred. If the issuer retains control, that doesn't feel like true ownership.

I fear your proposal would make all of the following true:

  • There is functionality that the issuer has included in the object that is not appropriate for the current owner to directly use; in other words, access to this functionality is intrinsic to what defines the object
  • The issuer is able to provide access to a third party to access this functionality; in other words, the issuer wants to allow a third party to control functionality intrinsic to the object that even the owner can't access
  • The issuer can change which third party can access this functionality without any consultation or objection from the owner; in other words, both the issuer and the third party have continuing control over the object which exceeds the control of the current so-called "owner"!

I apologize if I sound a little preachy, but a lot of time and effort has gone into Flow and Cadence in the name of true ownership, so I thought it would be helpful to be very explicit as to why myself and others might be skeptical about a proposal that might undermine that.

As an aside, access problems is easily solved with Entitlements, provided you are willing and able to give the owner of the object the power to decide who (if anyone) is allowed to access the protected functionality.

If you still see a compelling case for functionality similar to what you've proposed, please provide it below. I encourage you to focus on giving an example of where it would be useful and how Cadence currently blocks that use case, instead of describing how you think Cadence should work. It could be that there are (or should be) alternative mechanisms that can solve your particular problem that fit better with the rest of the language.

Thank you for your efforts on this suggestion, and my apologies again for the delayed response.

@SnowyField1906
Copy link
Author

SnowyField1906 commented Oct 30, 2024

@dete @turbolent

To everything I have thought and experienced, Resource is awesome, honestly, I really loved to utilize this, because it is simply powerful and safe.
But yet, everything has trade off, it will become very messy for very simple logics. Well it's been over a year since I stopped coding Cadence and I've forgotten all the reasons why Resource was sometimes a hassle for me.

You are right that my proposal is an extension of access(contract) and access(account), because in some cases these things are not enough.
You will ask me for an example or you will feel that it is unreasonable, but such problems are often unthinkable until we encounter.
You will also ask me why not use Resource. Well so then, access(contract) and access(account) also should not exist because it is blah blah blah. But in the end it exists, because it simplifies a lot of unnecessary things and becomes powerful in some special situations even though we can easily imagine that Resource can also do the same.

In my proposal, I also described the this accessibility behaves like Capabilities (Contracts will be like Resources) because the whole Contract can even be unaccessable for others, as long as they reveal their Address / Public Account for the Contract to work. And Capabilities between Contracts now are managed and indicated by the Cadence's sugar syntax itself, not messy by lines of codes anymore.


(As far as I can remember) the thing I hate the most is using Factory in Cadence.

Because I can't pass Resource from parent Contract into init scope of created Contract. And I can't send it via inbox because it can't be sent one way, but the recipient needs to accept it.
Meanwhile, Resource will not take effect before Contract is created, it is even deployable but will show "Resource does not exist" (I forget this part honestly, but I remember very clear that because of some specific reasons, this is impossible). And sending via inbox is impossible for fully automated on-chain logic, you cannot send and receive such things in same one transaction.

For simple logic, it destroys the integrity of the Contract, because the Contract now depends too much on the deployer accounts to send and receive Capabilities/Resources.


Besides Factory, DAO also has some examples for this problem, it's been too long since I remember what Cadence can do so I can't think of a clever example, but for example a DAO with dynamic Custodians indexed in an array. We can't create $n$ Resources in this case but $1$ Resource with $n$ Capabilities.

And what do we lose here? 1 global Resource variable, whenever there is a change in Custodian, we have to remove each Capabilities and add each new Capabilities. The code will be bigger and more complex when the pattern is more complex. (Can users also falsify the Contract's integrity by sending/manipulating these Capabilities to others by themselves in order to make it unmatched with Contract data? I forgot). \

With my proposal, we can bury the verification part deep under the Interface or somewhere, and this verification depends automatically on the contract, no longer depends on the user. This kind of proposal will be for some system-level code that cannot address by Resources or simply when Resource becomes rebundant.


I remember I encountered this problem when coding an OrderBook with many nested Contracts. The common point of all the problems I mentioned is the interaction and processing automatically and on-chain. Separate from the dependence on accounts and shorten a lot of unnecessary repetitive logic. And in some cases, using Resources becomes impossible, I think you will be able to find many better examples yourself.

But maybe I'm wrong, I think Flow won't tend to do such things and Cadence won't like such complex Contracts, my grant MAMAEx#165 has not received any response after more than a year and we don't even want to deploy them even though everything from FE to Contracts is done and ready.


Thank you very much for really working hard for Cadence and caring of proposals and developers. This is always my favourite chain, sadly I don't have chance to work on this anymore and I'm always looking forward to work on this chain again. And it is already too late for me to remember things for our discussion.

@dete
Copy link

dete commented Oct 30, 2024

@SnowyField1906: Thanks so much for taking the time to write this up, even though the specifics are hard for you to remember. We will be closing this proposal, but I want to assure you that the examples you've shared are very helpful, and I'll work through them to see if we can't make Cadence work better for those use cases.

It's worth noting that Flow did release EVM support this year, and the best part is that the EVM is embedded inside Cadence, so you can mix Cadence and EVM code as needed for your use case. You should be able to use Cadence for the parts its best at while dropping down into EVM whenever you need to.

I'm going to want to think about your examples more, but let me share my preliminary thoughts about how I'd handle the problems you presented, if only for your curiosity!

Because I can't pass Resource from parent Contract into init scope of created Contract. And I can't send it via inbox because it can't be sent one way, but the recipient needs to accept it.

I have to admit the Factory use case isn't one I think about a lot, but I think that there's a key feature of Flow that isn't available in EVM that would make this use case a lot easier to think about. When you create an new Account object in Flow, you aren't just "creating an Account over there somewhere", the object you are creating is the actual Account object, and the code that created that Account can act as the account owner in every way. A feature that was added to Flow before Cadence 1.0, but perhaps after you were working on Flow, is the ability to create Capabilities on Accounts themselves. So, the way I'd imagine the Factory use case working in Flow is as follows:

  1. The main Factory account creates a new Account for the new deployment.
  2. In the same transaction where the new Account is created, the Factory creates a Capability on that Account and stores it (protected by smart contract code, I assume!)
  3. In any future transaction, the Factory account can use that Capability to take control of the newly deployed account with all the authority of that deployed code. It can deploy more code, it can access any stored data, it can issue new Capabilities back to the Factory.
  4. Ideally, after the setup phase, the Factory would have a narrower set of Capabilities than the full Account Capability, but there is no requirement or limitation here. If you create an Account, you can give yourself the authority to do whatever you want with that Account (until you chose to give up that control).

A DAO with dynamic Custodians indexed in an array. We can't create 𝑛 Resources in this case but 1 Resource with 𝑛 Capabilities

Cadence 1.0 has definitely improved this with the introduction of Capability Controllers, which make it easier to manage additional Capability objects including revoking them.

However, I wouldn't suggest that in this case. Instead, I'd have two main classes for the DAO, a main DAOManagement singleton Resource, with all of the important logic in there, and then I'd have a array Custodian Resource objects, each which had a copy of a single Capability referencing the DAOManagement singleton. You'd ask the DAOManagement object to create new instances of Custodian, which would get a new copy of the Capability. The Custodian object would just have a bunch of wrapper functions (annoying to create, but relatively easily to generate automatically) that would call through the Capability to the main singleton that would contain the code.

If the Custodian object would first check to see if it had revoked for each call, you could even allow the Custodian objects to be moved into the user's accounts. They could even move the Custodian between accounts if needed for organizational or security reasons.

Separate from the dependence on accounts and shorten a lot of unnecessary repetitive logic. And in some cases, using Resources becomes impossible, I think you will be able to find many better examples yourself.

Well, the goal of Cadence is to make it easier to manage more complexity, not harder. Obviously, as someone with a lot of input into Cadence's design, I find it easiest to think of examples where Cadence provides improvements! If you think of more examples of scenarios where the Resource oriented design feels limiting, please let us know. Perhaps the language needs to change, or perhaps we can offer suggestions like the above for how Cadence can make it work.

I'm sorry to hear that you weren't able to keep working in the Flow ecosystem; it sucks that you put a lot of work into a proposal at the same time as we were reworking our grants program. I hope you'll get a chance to join us again!

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

Successfully merging this pull request may close these issues.

5 participants