third_party/move/changes/1-friend-visibility.md
Friend visibility is a new feature in Move version 1.2 to give more control about where a function can be used. Previously a function had either public or private visibility, where a public function is accessible anywhere but a private function can be called only from within the module where it is defined. Friend-visible functions can be called only from explicitly allowed modules.
The simple public/private visibility scheme required using public visibility for “limited-access” functions, i.e., functions that:
Take all the initialize functions in the Diem framework as an example. In theory, the initialize functions should only be used by the Genesis module and never be exposed to any other modules nor scripts. However, due to the limitation in the current visibility model, these initialize functions have to be made public (in order for the Genesis module to call them) and both runtime capability checks and static verification are enforced to make sure that these functions will abort if not called from a genesis state.
Public functions have very restricted updating rules: a public function can never be deleted or renamed, and its function signature can never be altered. The rationale behind this restriction is that a public function is a contract made to the whole world and once the contract is made, the API should not be easily altered, as altering the API might break code that tries to invoke the public function. The owner of the public function has no control on who can invoke the function. In fact, knowing all the call sites requires a global scan of all code published in storage, which is not always possible nor scalable in a blockchain network with an open-publishing model.
In contrast, a friend function is only a contract made to the friends of a module, and furthermore, the module owner controls the membership of the friend list. That is, the module owner has complete knowledge of which modules may access a friend function. As a result, it is much easier to update a friend function because only the modules in the friend list needs to be coordinated for the change. This is especially true when a friend function and all its friend modules are maintained by the same owner.
Friend visibility can help simplify specification writing and verification with the Move prover. For example, given a friend function and its host module’s friend list, we can easily and exhaustively find all callsites of the friend function. With this information, we could, as an option, completely skip the specification of a friend function and inline the implementation into the caller. This may lead to further simplification in the verification techniques and allows stronger properties to be proved. In contrast, for a public function, it is necessary to write complete and accurate specifications for the function.
Friend visibility expands the set of possible visibility levels:
public(friend)public(script), andpublic.These respectively correspond to Private, Friend, Script, and Public in the Move bytecode file format. Script visibility addresses an orthogonal problem in the Diem Framework and more details can be found in the Script Visibility change description.
Beside the new public(friend) modifier, each module is allowed to have a friend list, specifiable as zero or more
friend <address::name> statements that list the modules trusted by the host module. Modules in the friend list are permitted to call a public(friend) function defined in the host module, but a non-friend module is prohibited from accessing a public(friend) function.
public(friend) is a new visibility modifier that can be applied to any function definition in a module. A public(friend) function can be invoked by any other functions in the same module (say module M), or any function defined in one of the modules in the friend list of module M.
Besides this visibility rule, public(friend) functions follow the same rules as any other module function, meaning they can invoke other functions in the same module (except public(script) functions), create new struct instances, access the global storage (of types declared in that module), etc.
A module can declare other modules as friends via friend declaration statements, in the format of
friend <address::name> — friend declaration using fully qualified module namefriend <module-name-alias> — friend declaration using a module name alias, where the module alias is introduced via the use statement.A module may have multiple friend declarations, and the union of all the friend modules is recorded in the friend list, which is a new section in the bytecode file format. For readability, friend declarations should generally be placed near the beginning of the module definition. Note that Move scripts cannot declare friend modules as the concept of friend functions does not even exist in scripts.
Friend declarations are subject to the following rules:
0x2::M cannot declare 0x2::M as a friend.0x2::M cannot declare 0x3::N as a friend.0x2::A friends 0x2::B friends 0x2::C friends 0x2::A is not allowed.0x2::A friends 0x2::B and 0x2::A also calls a function 0x2::B::foo().0x2::M cannot declare 0x2::X as a friend if 0x2::X cannot be resolved by the loader.A typical module with public(friend) functions and its friend modules is shown in the following example:
address 0x2 {
module A {
// friend declaration via fully qualified module name
friend 0x2::B;
// friend declaration via module alias
use 0x2::C;
friend C;
public(friend) fun foo() {
// a friend function can call other non-script functions in the same module
i_am_private();
i_am_public();
bar();
}
public(friend) fun bar() {}
fun i_am_private() {
// other functions in the same module can also call friend functions
bar();
}
public fun i_am_public() {
// other functions in the same module can also call friend functions
bar();
}
}
module B {
use 0x2::A;
public fun foo() {
// as a friend of 0x2::A, functions in B can call friend functions in A
A::foo();
}
public fun bar() {
0x2::A::bar();
}
}
}
B is a friend of module A — any function in module B can access any friend function in module AB is a friend of function foo() — any function in module B can call the friend function foo()A) is a friend of every friend function and friend A needs to be specified repeatedly for each friend function.foo() is a friend of module A — function foo() can call any friend function in module A0x3::B::foo() but not function 0x3::B::bar(), especially given that both bar() and foo() reside in the same module by 0x3::B. We could not contemplate a valid use case for this scenario.foo() is a friend of function bar() — function foo() can call friend function bar()foo() is a private function in module B, and bar() is a friend function in module A. This scheme requires that when the private function foo is renamed, something in module A needs to be updated as well! Function foo() is no longer “private” to module B under this scheme.B wants to access some friend functions in module A, then, the friendship with module A will be declared in the source code of module B (while the callee-side declaration requires that the friendship is declared in the source code of module A).FriendList entity in users’ accounts. More importantly, by looking at the source code of a module, the developers have no clue who can access the friend function and how the interaction may happen.Cross-module references complicate the process for publishing those modules. The issue can be illustrated in the following:
address 0x2 {
module M {
friend 0x2::N;
public(friend) fun foo() {}
}
module N {
use 0x2::M;
fun bar() { M::foo(); }
}
}
Suppose we define two modules M and N like the above:
N depends on M because N contains use 0x2::MM refers to N because M specifies friend 0x2::NNow think about how we should publish them on-chain....
M has to be published first. Otherwise, publishing module N first will make N::bar() fail miserably, while publishing M first should have no bad effects because no one can call M::foo() anyway.M, the bytecode verifier sees the visibility constraint, and it is a pointer to a nonexistent function N::bar(). The bytecode verifier will not try to resolve this function handle. It must tolerate that this visibility constraint is a forward declaration.N. Suppose both Alice and Eve can publish to 0x2. When M is published, both Alice and Eve see that M declares N as a friend. Eve may race against Alice to publish module N first, with a bad bar() function, to exploit the trust the developer of module M placed on Alice.N as a place holderMN that uses the friend function MM and N atomically in one transaction, there would be no risk of a race condition and there is no need to go through the three-step module publishing flow.public(address) — or just internal — that would allow for cross module visibility, but just under that address. The owner of that address would control all publishing into that address. This kind of visibility would be easy to enforce by a verifier given the address constraints. That is, the target function when linking would have to be in a module published under the same address.