Basic Access Control in Cadence

This is only for the admin to press. Don’t press it. Please. Pretty Please

We made it to week 2 without forgetting about the Blog! Take that, haters! 🙌

Hello! My name is Josh, and I am a smart contract engineer on the Flow Team at Dapper Labs.

If you’re new here, welcome! This is a weekly blog about Cadence, Flow’s new state-of-the-art language for smart contracts. I recommend starting out with my first post about beginner materials before reading this, because I’ll be assuming readers already have a basic understanding of Cadence, or just really enjoy my writing style. (I’m flattered!)

What is access control?

Access control is the restriction of fields, functions, and types to certain scopes and users. It is an extremely important to for developers to deeply consider to ensure their code is protected against potential vulnerabilities. Blockchain technology certainly has had its fair share of bugs and exploits and many of them could have been prevented with better-managed access control.

I believe every developer should have a thorough understanding of Cadence access control before even thinking about deploying their project on mainnet. High standards I know, but we’re dealing with things with real-value here! (and most of you probably won’t listen to me anyway and just copy the Top Shot code without thinking about it)

Unlike most languages, Cadence provides two different layers of access control that are both built into the language:

I would recommend checking those out before reading more of this post.

I’m not going to provide a detailed explanation of the different kinds of access control in this post, but I am going to talk about some of the reasoning for utilizing these, and a few examples of how they can be used effectively in smart contracts.

Two kinds of access control in Cadence

Keyword Access Control

The first type of access control in Cadence is the kind that most of you are probably familiar with: Using keywords to specify the level of access of a particular field.

Cadence provides four different keywords to specify access levels. Every field, function, and type is required to specify a level of access.

pub or access(all) means the declaration is accessible/visible in all scopes. They each do the same thing, but pub is shorter, so it is recommended to use.

For example, a public field in a type can be accessed using the access syntax (`object.field`) on an instance of the type in an outer scope. This does not allow the declaration to be publicly writable though.

So if I had this contract deployed to my account:

pub contract HelloWorld {    pub let greeting: String    pub let greetingsByLanguage: {String: String}    pub fun returnGreeting(): String {
return self.greeting
}
}

In a transaction or script, anyone who imports the contract from my account could read the greeting field, greetingsByLanguage dictionary, or call the returnGreeting function, but they can’t overwrite the whole value in those fields, with one exception.

If there is a public dictionary or array field, then even though it can’t be overwritten by anyone like this,

HelloWorld.greetingsByLanguage = {}    // Not possible

an element of it can still be modified by anyone like this.

HelloWorld.greetingsByLanguage["Español"] = "Hola"    // possible

Because of things like this, we strongly recommend to make all fields in contracts and composite types private, access(self), by default. You can then define setters and getters for your fields. Lets see what that looks like in the previous contract.

pub contract HelloWorld {    access(self) let greeting: String    access(self) let greetingsByLanguage: {String: String}    pub fun returnGreeting(): String {
return self.greeting
}
pub fun returnLanguageGreeting(_ language: String): String? {
return self.greetingsByLanguage[language]
}
}

This uses access(self), which means the declaration is only accessible/visible in the current and inner scopes. For example, an access(self) field can only be accessed by functions of the type is part of, not by code in an outer scope. This allows the developer to be able to explicitly define how their fields are accessed by getters and setters.

The other two access keywords are somewhat unique to Cadence.

access(contract) means the declaration is only accessible/visible in the scope of the contract that defined it. This means that other types and functions that are defined in the same contract can access it, but not other contracts in the same account.

You could have something like this:

pub contract HelloWorld {    pub struct Hello {        access(contract) var greeting: String    }    pub fun returnGreeting(_ hello: Hello): String {
return hello.greeting
}
}

In this situation, if you had an instance of the Hello struct, you would not be able to directly get the greeting field by using Hello.greeting, but since the returnGreeting function is defined in the same contract, you can give it your Hello struct and get the greeting field that way:

let helloObject: HelloWorld.Hello = Hello("hola")// invalid because the field is access contract
let greeting = helloObject.greeting
// Valid: The contract function is able to access the field and
// return it
let greeting = HelloWorld.returnGreeting(helloObject)
// greeting is "hola"

This is just a simple example, but I’m sure you can imagine more sophisticated utilization of this ability.

access(account) means the declaration is only accessible/visible in the scope of the entire account where it is defined. Flow accounts can have any number of Cadence contracts deployed to them, so this keyword means that any other contracts in the same account are able to access it.

With this field, you could declare fields in contracts as access(account) , knowing that you want them to be “private” for the time being, but keeping open the possibility that you could deploy a new contract later that can interact with the first contract in new ways. You also might want to keep your contracts simpler by only grouping similar functionality within a contract, but having related contracts still in the same account that can share some information with each other.

This keyword is the one that I have the least experience using, but recently I have been thinking about more ways to use it. If you have any examples where you have used it, please leave a comment and let me know!

Capability-based Access Control

The other main form of access control in Cadence utilizes Capability-based security, a security model that is not used very often. Most security models (and most other smart contract programming languages) restrict access based on who you are, but capability security is more about what you own.

Lets look at an analogy:

so many choices!

Image that you are designing a physical device for a government to use to manage access to important internal functionality, like printing money, declaring war, paying taxes, etc. Everyone in the country needs to have access to some subset of that functionality, but each person’s access specification is different than many of the other people. For example, everyone needs access to the “pay taxes” functionality, but only elected government officials need to have access to “declare war”, and so on and so forth.

If we were designing this system in the way that most smart contracts manage access, with a list that indicates who has access to what, then this physical device would resemble a giant control board in a public place with thousands of different buttons on it for each and every functionality that the government has some control over. Anyone on Earth would be able to walk up to this board and press any of the buttons, but the button would perform some sort of biometric scan (like an iris scan or fingerprint) to verify that this person has the authority to perform the action. If they pass the scan, then the functionality would happen. There is even a public button for changing the access rules!

This is a contrived example, but I am sure you can see the problems with this. If any one of the buttons wasn’t designed properly or is malfunctioning, then anyone on Earth would be able to exploit the broken button to do things they aren’t supposed to!

A more effective solution to this problem would be to give each citizen a device that only contains the buttons for functionality they are permitted to access. Then each citizen stores that device in a secure place in their home or safe that only they can access. Now, if there is a vulnerability in one of the buttons, an attacker has the difficult task of breaking into each participants house and safe individually, which is much harder and time-consuming.

This is what capabilities accomplish in Cadence. Instead of building a special access control list with opaque and complex rules, access control is built into the fundamentals of the Cadence type-system. Users get special resource objects that they store in their account that define which functionality they are allowed to access.

Using the example above, we’d create a HelloAdmin resource that allows the owner of it to update the greeting field:

pub contract HelloWorld {    // This needs to be access(contract) so the resource defined in
// the same contract can access it
access(contract) var greeting: String
pub fun returnGreeting(): String {
return self.greeting
}
// Only owner of this resource object can call this function
pub resource HelloAdmin {
pub fun modifyGreeting(_ newGreeting: String) {
HelloWorld.greeting = newGreeting
}
}
}

Attackers wouldn’t even have the opportunity to exploit many potential bugs because the code isn’t even accessible to them to even try to exploit in the first place!

Capabilities are a complex subject, and I am planning on doing a blog post in the future that is completely dedicated to them. For now, I would recommend checking out this blog post about generic capabilities and the Cadence Capability documentation.

Conclusion

Hopefully I’ve made some of the access control features of Cadence a little more clear for you.

Please share any interesting uses of Cadence’s access control features that you have done or seen others implement!

If you have any questions, the entire Flow team and community is here to support you! Please do not hesitate to reach out via our Discord server, the Flow Forum, or via an issue in the Flow Github repo.

Are there any other topics or interesting projects that you know would useful to newcomers or that you would like me to write a blog post about? Feel free to comment with your ideas and I might include them in a future post!

Flow Discord: https://discord.gg/flow

Flow Forum: https://forum.onflow.org

Flow Github: https://github.com/onflow/flow

See you next week! 👋

Smart Contract Engineer at Dapper Labs, working with Cadence, the best language for smart contracts!