Skip to content

Commit 359ba71

Browse files
committed
Addressing reviewer comments
1 parent 4adfb2e commit 359ba71

File tree

2 files changed

+44
-22
lines changed

2 files changed

+44
-22
lines changed

apps/nextra/pages/en/build/smart-contracts/book/functions.mdx

+43-21
Original file line numberDiff line numberDiff line change
@@ -817,16 +817,19 @@ let f: |u64|bool has copy+drop+store = is_even;
817817
let g: |u64|bool has copy+drop = is_odd;
818818
```
819819

820-
A public function is required to build a storable function value because it needs to be guaranteed that the underlying function persists so the function value can be safely restored from storage at any point in the future. However, code upgrade may change the underlying implementation of the function, while its signature is persistent.
820+
A _persistent_ function is required to build a storable function value because it needs to be guaranteed that the underlying function exists and can be safely restored from storage at any point in the future. However, code upgrade may change the underlying implementation of the function, while its signature is persistent.
821821

822-
If a function should storable which is non-public, an attribute can be used to mark this function as persistent, with the same upgrade behavior than public functions. This avoids exposing such a function outside of a package which can be a security risk:
822+
While `public` and `entry` functions are persistent by default, a none-public function needs to be marked with the attribute `#[persistent]` to become storable:
823823

824824
```move
825825
#[persistent] fun is_odd(x: u64): bool { x % 2 == 1 }
826826
...
827827
let g: |u64|bool has copy+drop+store = is_odd;
828828
```
829829

830+
Using the `#[persistent]` attribute is preferred if the only objective is to make a function storable, avoiding security implications with public or entry visibility.
831+
832+
830833
### Lambda Expressions and Closures
831834

832835
Function values can be denoted by _lambda expressions_ (as also available as parameters for [inline functions](#function-parameters-and-lambda-expressions)). Lambda expressions can capture context variables _by value_: those values are moved (or copied) into a _closure_, from where they are produced when the function is evaluated. Examples:
@@ -839,16 +842,9 @@ let add = |y| { let S(x) = s; x + y }; // s will be moved into the closure
839842
assert!(add(2) == 3)
840843
```
841844

842-
Notice it is not possible to capture reference values at this point of time in Move. Similarly, it is not possible to mutate any locals in the context of a lambda. Specifically, the following pattern as known from lambdas in inline functions, is not supported:
843-
844-
```move
845-
let x = 0;
846-
collection.for_each(|e| x += e) // DOES NOT COMPILE
847-
```
848-
849-
The type of the closure constructed by a lambda expression is inferred from the expression in the usual way (for example, the type of `add` in the example above is inferred as `|u64|u64`). The abilities of this function type are derived as follows. By default, the function underlying a closure is a private function, so the function itself is `copy+drop` (and not `store`). This is intersected with the abilities of all the captured context variables.
845+
Closures with captured values are lexicographical ordered using first the name of the underlying function (which maybe generated from lambda lifting), and then the captured values.
850846

851-
A closure can have the `store` ability under the condition that the underlying function is persistent. This is the case if the lambda denoting the closure is based on solely delaying some of the parameters of an underlying persistent function. Example:
847+
The type of the closure constructed by a lambda expression is inferred from the expression (for example, the type of `add` in the example above is inferred as `|u64|u64`). The abilities of this function type are derived as follows. By default, the function underlying a closure is a private function, so the function itself is `copy+drop` (and not `store`). This is intersected with the abilities of all the captured context variables. However, A closure created from a lambda can have the `store` ability under the condition that the underlying function is persistent. This is the case if the lambda denoting the closure is based on solely delaying some of the parameters of an underlying persistent function. Example:
852848

853849
```move
854850
#[persistent] fun add(x: u64, y: u64) { x + y }
@@ -860,15 +856,36 @@ let f: |u64|u64 has copy+drop+store = |y| add(y, x)
860856

861857
For reference, this mechanism is also called 'currying' of `add` in functional programming languages.
862858

859+
Notice it is not possible to _capture_ reference values at this point of time in Move. Thus, the following code does not compile:
860+
861+
```move
862+
let x = &22;
863+
let f = |y| add(*x, y) // DOES NOT COMPILE
864+
```
865+
866+
Related, it is not possible to mutate any locals in the context of a lambda. Specifically, the following pattern as known from lambdas with inline functions, is not supported:
867+
868+
```move
869+
let x = 0;
870+
collection.for_each(|e| x += e) // DOES NOT COMPILE
871+
```
872+
873+
However, the actual parameters of lambdas can be references, only captured values are restricted. For example:
874+
875+
```move
876+
let x = 22;
877+
let f : |&u64|u64 = |y| add(x, *y)
878+
```
879+
863880
### Reentrancy Check
864881

865-
Via dynamic dispatch of function values, reentrancy of modules in a chain of function calls is possible. If module `m1` uses module `m2`, and `m1` calls `m2::f` passing a function value to it, this function value can callback into `m1`. This situation is called _reentrancy_, and is not possible in Move without function values, since the module usage relation is acyclic.
882+
Via dynamic dispatch of function values, reentrancy of modules in a chain of function calls is possible. If module `m1` uses module `m2`, and `m1` calls `m2::f` passing a function value to it, this function value can callback into `m1`. This situation is called _reentrancy_, and is not possible in Move without function values, since the module usage relation is acyclic.
866883

867-
The Move VM dynamically detects reentrancy for a module, and _locks_ all resources declared in this module from being accessed. Thus during reentrancy, calling the functions `borrow_global`, `borrow_global_mut`, and `move_from` lead to an abort. Here is an example:
884+
The Move VM dynamically detects reentrancy for a module, and _locks_ all resources declared in this module from being accessed. Thus during reentrancy, calling resource operations like `&R[addr]`, `&mut R[addr]`, and `move_from` lead to an abort. Here is an example:
868885

869886
```move
870-
module caller {
871-
use addr::callee;
887+
module 0x42::caller {
888+
use 0x42::callee;
872889
struct R{ count: u64 } has key;
873890
fun calling() acquires R {
874891
let r = &mut R[@addr];
@@ -881,7 +898,7 @@ module caller {
881898
fun do_something(r: &mut R) { .. }
882899
}
883900
884-
module callee {
901+
module 0x42::callee {
885902
fun call_me<T(x: &mut T, action: |&mut T|) {
886903
action(x)
887904
}
@@ -890,16 +907,21 @@ module callee {
890907

891908
Notice that dispatching a function value to a concrete function in the same module is also considered to be reentrancy. If the function `callee::call_me` would be moved into the module `caller`, the same semantics is in effect.
892909

893-
The default reentrancy check ensures consistency of Move's reference semantics and suppresses side-effects for the resources in a reentered module. But it does not protect from side-effects on other state of the app. For this purposes, the `#[module_lock]` attribute can be attached to a function. In a calling context where this attribute is active, reentrancy will be fully suppressed, causing an abort at the moment a module is reentered. In the example below, the state effected is owned by another module `account` instead of the module `caller` for which the can potentially happen via the `notify` callback:
910+
The default reentrancy check ensures consistency of Move's reference semantics and suppresses side effects of reentrancy for the resources owned by the re-entered module. However, re-entered code is allowed to still access resource state managed by modules outside the reentrancy path. Such state accesses can be considered bad design, but they exist.
911+
For these purposes, the `#[module_lock]` attribute can be attached to a function:
894912

895913
```move
896-
module account { ... }
897-
module caller {
898-
#[module_lock] // without this lock, the notify call could withdraw unlimited amount
914+
module 0x42::account { ... }
915+
module 0x42::caller {
916+
#[module_lock] // without this lock, the notify call could withdraw more than intended.
899917
fun transfer(from: address, to: address, amount: u64, notify: |u64|) {
918+
// Oops. This should be really differently designed, using `Coin` type and moving it.
919+
assert!(account::balance() - MIN_BALANCE >= amount);
900920
account::deposit(to, amount)
901-
notify(amount); // attempt to reentrer `transfer` is blocked
921+
notify(amount); // attempt to re-entrer `transfer` is blocked
902922
account::withtdraw(from, amout);
903923
}
904924
}
905925
```
926+
927+
While a function with this attribute is running, all calls reentering any module will lead to an abort, given a stronger protection. The attribute `#[module_lock]` restriction is not the default behavior since it is too strong for typical patterns of higher-order programming. For example, `collection.find(|x| cond(x))` will lead to a reentrancy of the module which contains this expression, from the module which defines the collection type.

apps/nextra/pages/en/build/smart-contracts/book/move-2.mdx

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ The Move 2 language releases are described on this page. The reference documenta
66

77
The Move 2.2 language release adds the following features to Move:
88

9-
- **Acquires Optional**: the `acquires` annotation on function declarations can be omitted, to be inferred by the compiler
9+
- **Optional Acquires**: The `acquires` annotation on function declarations can be omitted, to be inferred by the compiler.
1010
- **Function Values**: Move now supports function values, which can be passed around as parameters and stored in resources. See the [reference doc here](functions.mdx#function-values).
1111
## Move 2.1
1212

0 commit comments

Comments
 (0)