has key {
-a: A,
-b: u128,
-}
-
-there is no way of getting a value of Foo::a
without hardcoding the layout
-of Foo
and the field offset. To mitigate this problem, one can use a table.
-Every item stored in the table is uniqely identified by (handle, key) pair:
-handle
identifies a table instance, key
identifies an item within the table.
-
-So how is this related to aggregator? Well, aggregator can reuse the table's
-approach for fine-grained storage. However, since native functions only see a
-reference to aggregator, we must ensure that both handle
and key
are
-included as fields. Therefore, the struct looks like
-
-struct Aggregator {
-handle: u128,
-key: u128,
-..
-}
-
-Remaining question is how to generate this (handle, key) pair. For that, we have
-a dedicated struct called AggregatorFactory
which is responsible for constructing
-aggregators. See aggregator_factory.move
for more details.
-
-Advice to users (V1)
-====================
-Users are encouraged to use "cheap" operations (e.g. additions) to exploit the
-parallelism in execution.
+This module provides an interface for aggregators. Aggregators are similar to
+unsigned integers and support addition and subtraction (aborting on underflow
+or on overflowing a custom upper limit). The difference from integers is that
+aggregators allow to perform both additions and subtractions in parallel across
+multiple transactions, enabling parallel execution. For example, if the first
+transaction is doing add(X, 1)
for aggregator resource X
, and the second
+is doing sub(X,3)
, they can be executed in parallel avoiding a read-modify-write
+dependency.
+However, reading the aggregator value (i.e. calling read(X)
) is an expensive
+operation and should be avoided as much as possible because it reduces the
+parallelism. Moreover, **aggregators can only be created by Aptos Framework (0x1)
+at the moment.**
- [Struct `Aggregator`](#0x1_aggregator_Aggregator)
@@ -88,6 +39,8 @@ parallelism in execution.
## Struct `Aggregator`
+Represents an integer which supports parallel additions and subtractions
+across multiple transactions. See the module description for more details.
struct Aggregator has store
@@ -130,7 +83,7 @@ parallelism in execution.
-When the value of aggregator (actual or accumulated) overflows (raised by native code).
+The value of aggregator overflows. Raised by native code.
const EAGGREGATOR_OVERFLOW: u64 = 1;
@@ -140,7 +93,7 @@ When the value of aggregator (actual or accumulated) overflows (raised by native
-When the value of aggregator (actual or accumulated) underflows, i.e goes below zero (raised by native code).
+The value of aggregator underflows (goes below zero). Raised by native code.
const EAGGREGATOR_UNDERFLOW: u64 = 2;
@@ -150,7 +103,7 @@ When the value of aggregator (actual or accumulated) underflows, i.e goes below
-When aggregator feature is not supported (raised by native code).
+Aggregator feature is not supported. Raised by native code.
const ENOT_SUPPORTED: u64 = 3;
diff --git a/aptos-move/framework/aptos-framework/doc/aggregator_factory.md b/aptos-move/framework/aptos-framework/doc/aggregator_factory.md
index bd37acaed36d8..e59707d642601 100644
--- a/aptos-move/framework/aptos-framework/doc/aggregator_factory.md
+++ b/aptos-move/framework/aptos-framework/doc/aggregator_factory.md
@@ -3,34 +3,11 @@
# Module `0x1::aggregator_factory`
-This module provides foundations to create aggregators in the system.
-
-Design rationale (V1)
-=====================
-First, we encourage the reader to see rationale of Aggregator
in
-aggregator.move
.
-
-Recall that the value of any aggregator can be identified in storage by
-(handle, key) pair. How this pair can be generated? Short answer: with
-AggregatorFactory
!
-
-AggregatorFactory
is a struct that can be stored as a resource on some
-account and which contains a phantom_table
field. When the factory is
-initialized, we initialize this table. Importantly, table initialization
-only generates a uniue table handle
- something we can reuse.
-
-When the user wants to create a new aggregator, he/she calls a constructor
-provided by the factory (create_aggregator(..)
). This constructor generates
-a unique key, which with the handle is used to initialize Aggregator
struct.
-
-Use cases
-=========
-We limit the usage of AggregatorFactory
by only storing it on the core
-account.
-
-When something whants to use an aggregator, the factory is queried and an
-aggregator instance is created. Once aggregator is no longer in use, it
-should be destroyed by the user.
+This module provides foundations to create aggregators. Currently only
+Aptos Framework (0x1) can create them, so this module helps to wrap
+the constructor of Aggregator
struct so that only a system account
+can initialize one. In the future, this might change and aggregators
+can be enabled for the public.
- [Resource `AggregatorFactory`](#0x1_aggregator_factory_AggregatorFactory)
@@ -55,7 +32,9 @@ should be destroyed by the user.
## Resource `AggregatorFactory`
-Struct that creates aggregators.
+Creates new aggregators. Used to control the numbers of aggregators in the
+system and who can create them. At the moment, only Aptos Framework (0x1)
+account can.
struct AggregatorFactory has key
@@ -86,7 +65,7 @@ Struct that creates aggregators.
-When aggregator factory is not published yet.
+Aggregator factory is not published yet.
const EAGGREGATOR_FACTORY_NOT_FOUND: u64 = 1;
@@ -98,8 +77,7 @@ When aggregator factory is not published yet.
## Function `initialize_aggregator_factory`
-Can only be called during genesis.
-Creates a new factory for aggregators.
+Creates a new factory for aggregators. Can only be called during genesis.
public(friend) fun initialize_aggregator_factory(aptos_framework: &signer)
@@ -187,6 +165,7 @@ to allow any signer to call.
## Function `new_aggregator`
+Returns a new aggregator.
fun new_aggregator(aggregator_factory: &mut aggregator_factory::AggregatorFactory, limit: u128): aggregator::Aggregator
diff --git a/aptos-move/framework/aptos-framework/doc/optional_aggregator.md b/aptos-move/framework/aptos-framework/doc/optional_aggregator.md
index 1051002e18dfe..c59bd32b79ef7 100644
--- a/aptos-move/framework/aptos-framework/doc/optional_aggregator.md
+++ b/aptos-move/framework/aptos-framework/doc/optional_aggregator.md
@@ -43,9 +43,7 @@ aggregator (parallelizable) or via normal integers.
## Struct `Integer`
-Wrapper around integer to have a custom overflow limit. Note that
-Move has no traits (and trait bounds), so integer value must be u128.
-Integer
provides API to add/subtract and read, just like Aggregator
.
+Wrapper around integer with a custom overflow limit. Supports add, subtract and read just like Aggregator
.
struct Integer has store
@@ -79,8 +77,7 @@ Move has no traits (and trait bounds), so integer value must be u128.
## Struct `OptionalAggregator`
-Struct that contains either an aggregator or a normal integer, both
-overflowing on limit.
+Contains either an aggregator or a normal integer, both overflowing on limit.
struct OptionalAggregator has store
@@ -117,6 +114,7 @@ overflowing on limit.
+The value of aggregator underflows (goes below zero). Raised by native code.
const EAGGREGATOR_OVERFLOW: u64 = 1;
@@ -126,6 +124,7 @@ overflowing on limit.
+Aggregator feature is not supported. Raised by native code.
const EAGGREGATOR_UNDERFLOW: u64 = 2;
@@ -137,6 +136,7 @@ overflowing on limit.
## Function `new_integer`
+Creates a new integer which overflows on exceeding a limit
.
fun new_integer(limit: u128): optional_aggregator::Integer
@@ -164,6 +164,7 @@ overflowing on limit.
## Function `add_integer`
+Adds value
to integer. Aborts on overflowing the limit.
fun add_integer(integer: &mut optional_aggregator::Integer, value: u128)
@@ -192,6 +193,7 @@ overflowing on limit.
## Function `sub_integer`
+Subtracts value
from integer. Aborts on going below zero.
fun sub_integer(integer: &mut optional_aggregator::Integer, value: u128)
@@ -217,6 +219,7 @@ overflowing on limit.
## Function `limit`
+Returns an overflow limit of integer.
fun limit(integer: &optional_aggregator::Integer): u128
@@ -241,6 +244,7 @@ overflowing on limit.
## Function `read_integer`
+Returns a value stored in this integer.
fun read_integer(integer: &optional_aggregator::Integer): u128
@@ -265,6 +269,7 @@ overflowing on limit.
## Function `destroy_integer`
+Destroys an integer.
fun destroy_integer(integer: optional_aggregator::Integer)
@@ -289,7 +294,7 @@ overflowing on limit.
## Function `new`
-Creates a new optional aggregator instance.
+Creates a new optional aggregator.
public(friend) fun new(limit: u128, parallelizable: bool): optional_aggregator::OptionalAggregator
@@ -534,7 +539,7 @@ Destroys non-parallelizable optional aggregator and returns its limit.
## Function `add`
-Adds to optional aggregator, aborting on exceeding the limit
.
+Adds value
to optional aggregator, aborting on exceeding the limit
.
public fun add(optional_aggregator: &mut optional_aggregator::OptionalAggregator, value: u128)
@@ -565,7 +570,7 @@ Adds to optional aggregator, aborting on exceeding the limit
.
## Function `sub`
-Subtracts from optional aggregator, aborting on going below zero.
+Subtracts value
from optional aggregator, aborting on going below zero.
public fun sub(optional_aggregator: &mut optional_aggregator::OptionalAggregator, value: u128)
@@ -627,7 +632,7 @@ Returns the value stored in optional aggregator.
## Function `is_parallelizable`
-Returns true is optional aggregator uses parallelizable implementation.
+Returns true if optional aggregator uses parallelizable implementation.
public fun is_parallelizable(optional_aggregator: &optional_aggregator::OptionalAggregator): bool
diff --git a/aptos-move/framework/aptos-framework/sources/aggregator/README.md b/aptos-move/framework/aptos-framework/sources/aggregator/README.md
new file mode 100644
index 0000000000000..9c57db04440b5
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/sources/aggregator/README.md
@@ -0,0 +1,47 @@
+# Design Rationale
+
+## Aggregators
+
+Aggregator is a parellizable integer that supports addition and subtraction.
+Unlike integer, aggregator has a user-defined `limit` which specifies when
+the value of aggregator overflows. Similarly to unsigned integers, the value
+of an aggregator underflows when going below zero.
+
+Both additions and subtractions are executed speculatively, and thus can be
+easily parallelized. On underflow or overflow, these operations are guaranteed
+to abort. Using these operations is encouraged.
+
+Reading an aggregator's value "materializes" the speculative value, and it
+can involve reading from the storage or checking for underflow or overflow.
+In general, using this operation is discouraged, or at least it should be used
+as rarely as possible.
+
+## Aggregator factory
+
+Unfortunately, aggregators cannot be part of a resource. At the moment, Move
+does not allow fine-grained access to resource fields, which ruins perfromance
+benefits aggregators can provide. In addition, getting the value of the field of
+a resource from storage is not possible without hardcoding the struct layout.
+For example, given a struct
+
+```move
+struct Foo has key {
+ a: A,
+ b: u128,
+}
+```
+
+there is no clean way of getting the value of `Foo::a` without knowing that the
+offset is 0.
+
+To mitigate the problem, we store aggregators as table items. Recall that every
+item stored in the table is uniquely identified by `(handle, key)` pair: `handle`
+identifies a table instance, and `key` identifies an item within the table. Now,
+if aggregator is a table item, it can be easily queried from storage and has a
+fine-grained access.
+
+To create an aggregator, one can use an `AggregatorFactory`. It is a resource
+which contains a single `phantom_table` field. When the factory is initialized,
+this field is used to generate a unique table `handle` which is passed to all
+new aggregators. When a new aggregator instance is created, it has a unique
+`key` which together with the `handle` is stored in `Aggregator` struct.
diff --git a/aptos-move/framework/aptos-framework/sources/aggregator/aggregator.move b/aptos-move/framework/aptos-framework/sources/aggregator/aggregator.move
index 549b795370b01..9a0baaff12916 100644
--- a/aptos-move/framework/aptos-framework/sources/aggregator/aggregator.move
+++ b/aptos-move/framework/aptos-framework/sources/aggregator/aggregator.move
@@ -1,75 +1,28 @@
-/// This module provides an API for aggregatable integers that allow addition,
-/// subtraction, and reading.
-///
-/// Design rationale (V1)
-/// =====================
-/// Aggregator can be seen as a parellizable integer that supports addition,
-/// subtraction and reading. The first version (V1) of aggregator has the
-/// the following specification.
-///
-/// add(value: u128)
-/// Speculatively adds a `value` to aggregator. This is a cheap operation
-/// which is parallelizable. If the result of addition overflows a `limit`
-/// (one of aggregator's fields), an error is produced and the execution
-/// aborts.
-///
-/// sub(value: u128)
-/// Speculatively subtracts a `value` from aggregator. This is a cheap
-/// operation which is parallelizable. If the result goes below zero, an
-/// error is produced and the execution aborts.
-///
-/// read(): u128
-/// Reads (materializes) the value of an aggregator. This is an expensive
-/// operation which usually involves reading from the storage.
-///
-/// destroy()
-/// Destroys and aggregator, also cleaning up storage if necessary.
-///
-/// Note that there is no constructor in `Aggregator` API. This is done on purpose.
-/// For every aggregator, we need to know where its value is stored on chain.
-/// Currently, Move does not allow fine grained access to struct fields. For
-/// example, given a struct
-///
-/// struct Foo has key {
-/// a: A,
-/// b: u128,
-/// }
-///
-/// there is no way of getting a value of `Foo::a` without hardcoding the layout
-/// of `Foo` and the field offset. To mitigate this problem, one can use a table.
-/// Every item stored in the table is uniqely identified by (handle, key) pair:
-/// `handle` identifies a table instance, `key` identifies an item within the table.
-///
-/// So how is this related to aggregator? Well, aggregator can reuse the table's
-/// approach for fine-grained storage. However, since native functions only see a
-/// reference to aggregator, we must ensure that both `handle` and `key` are
-/// included as fields. Therefore, the struct looks like
-///
-/// struct Aggregator {
-/// handle: u128,
-/// key: u128,
-/// ..
-/// }
-///
-/// Remaining question is how to generate this (handle, key) pair. For that, we have
-/// a dedicated struct called `AggregatorFactory` which is responsible for constructing
-/// aggregators. See `aggregator_factory.move` for more details.
-///
-/// Advice to users (V1)
-/// ====================
-/// Users are encouraged to use "cheap" operations (e.g. additions) to exploit the
-/// parallelism in execution.
+/// This module provides an interface for aggregators. Aggregators are similar to
+/// unsigned integers and support addition and subtraction (aborting on underflow
+/// or on overflowing a custom upper limit). The difference from integers is that
+/// aggregators allow to perform both additions and subtractions in parallel across
+/// multiple transactions, enabling parallel execution. For example, if the first
+/// transaction is doing `add(X, 1)` for aggregator resource `X`, and the second
+/// is doing `sub(X,3)`, they can be executed in parallel avoiding a read-modify-write
+/// dependency.
+/// However, reading the aggregator value (i.e. calling `read(X)`) is an expensive
+/// operation and should be avoided as much as possible because it reduces the
+/// parallelism. Moreover, **aggregators can only be created by Aptos Framework (0x1)
+/// at the moment.**
module aptos_framework::aggregator {
- /// When the value of aggregator (actual or accumulated) overflows (raised by native code).
+ /// The value of aggregator overflows. Raised by native code.
const EAGGREGATOR_OVERFLOW: u64 = 1;
- /// When the value of aggregator (actual or accumulated) underflows, i.e goes below zero (raised by native code).
+ /// The value of aggregator underflows (goes below zero). Raised by native code.
const EAGGREGATOR_UNDERFLOW: u64 = 2;
- /// When aggregator feature is not supported (raised by native code).
+ /// Aggregator feature is not supported. Raised by native code.
const ENOT_SUPPORTED: u64 = 3;
+ /// Represents an integer which supports parallel additions and subtractions
+ /// across multiple transactions. See the module description for more details.
struct Aggregator has store {
handle: address,
key: address,
diff --git a/aptos-move/framework/aptos-framework/sources/aggregator/aggregator_factory.move b/aptos-move/framework/aptos-framework/sources/aggregator/aggregator_factory.move
index 312e6c550b71b..cfdc79f30c4a4 100644
--- a/aptos-move/framework/aptos-framework/sources/aggregator/aggregator_factory.move
+++ b/aptos-move/framework/aptos-framework/sources/aggregator/aggregator_factory.move
@@ -1,31 +1,8 @@
-/// This module provides foundations to create aggregators in the system.
-///
-/// Design rationale (V1)
-/// =====================
-/// First, we encourage the reader to see rationale of `Aggregator` in
-/// `aggregator.move`.
-///
-/// Recall that the value of any aggregator can be identified in storage by
-/// (handle, key) pair. How this pair can be generated? Short answer: with
-/// `AggregatorFactory`!
-///
-/// `AggregatorFactory` is a struct that can be stored as a resource on some
-/// account and which contains a `phantom_table` field. When the factory is
-/// initialized, we initialize this table. Importantly, table initialization
-/// only generates a uniue table `handle` - something we can reuse.
-///
-/// When the user wants to create a new aggregator, he/she calls a constructor
-/// provided by the factory (`create_aggregator(..)`). This constructor generates
-/// a unique key, which with the handle is used to initialize `Aggregator` struct.
-///
-/// Use cases
-/// =========
-/// We limit the usage of `AggregatorFactory` by only storing it on the core
-/// account.
-///
-/// When something whants to use an aggregator, the factory is queried and an
-/// aggregator instance is created. Once aggregator is no longer in use, it
-/// should be destroyed by the user.
+/// This module provides foundations to create aggregators. Currently only
+/// Aptos Framework (0x1) can create them, so this module helps to wrap
+/// the constructor of `Aggregator` struct so that only a system account
+/// can initialize one. In the future, this might change and aggregators
+/// can be enabled for the public.
module aptos_framework::aggregator_factory {
use std::error;
@@ -36,16 +13,17 @@ module aptos_framework::aggregator_factory {
friend aptos_framework::genesis;
friend aptos_framework::optional_aggregator;
- /// When aggregator factory is not published yet.
+ /// Aggregator factory is not published yet.
const EAGGREGATOR_FACTORY_NOT_FOUND: u64 = 1;
- /// Struct that creates aggregators.
+ /// Creates new aggregators. Used to control the numbers of aggregators in the
+ /// system and who can create them. At the moment, only Aptos Framework (0x1)
+ /// account can.
struct AggregatorFactory has key {
phantom_table: Table,
}
- /// Can only be called during genesis.
- /// Creates a new factory for aggregators.
+ /// Creates a new factory for aggregators. Can only be called during genesis.
public(friend) fun initialize_aggregator_factory(aptos_framework: &signer) {
system_addresses::assert_aptos_framework(aptos_framework);
let aggregator_factory = AggregatorFactory {
@@ -73,6 +51,7 @@ module aptos_framework::aggregator_factory {
create_aggregator_internal(limit)
}
+ /// Returns a new aggregator.
native fun new_aggregator(aggregator_factory: &mut AggregatorFactory, limit: u128): Aggregator;
#[test_only]
diff --git a/aptos-move/framework/aptos-framework/sources/aggregator/optional_aggregator.move b/aptos-move/framework/aptos-framework/sources/aggregator/optional_aggregator.move
index 83fd45a39749a..216750442dbd9 100644
--- a/aptos-move/framework/aptos-framework/sources/aggregator/optional_aggregator.move
+++ b/aptos-move/framework/aptos-framework/sources/aggregator/optional_aggregator.move
@@ -9,19 +9,19 @@ module aptos_framework::optional_aggregator {
friend aptos_framework::coin;
- // These error codes are produced by `Aggregator` and used by `Integer` for
- // consistency.
+ /// The value of aggregator underflows (goes below zero). Raised by native code.
const EAGGREGATOR_OVERFLOW: u64 = 1;
+
+ /// Aggregator feature is not supported. Raised by native code.
const EAGGREGATOR_UNDERFLOW: u64 = 2;
- /// Wrapper around integer to have a custom overflow limit. Note that
- /// Move has no traits (and trait bounds), so integer value must be u128.
- /// `Integer` provides API to add/subtract and read, just like `Aggregator`.
+ /// Wrapper around integer with a custom overflow limit. Supports add, subtract and read just like `Aggregator`.
struct Integer has store {
value: u128,
limit: u128,
}
+ /// Creates a new integer which overflows on exceeding a `limit`.
fun new_integer(limit: u128): Integer {
Integer {
value: 0,
@@ -29,6 +29,7 @@ module aptos_framework::optional_aggregator {
}
}
+ /// Adds `value` to integer. Aborts on overflowing the limit.
fun add_integer(integer: &mut Integer, value: u128) {
assert!(
value <= (integer.limit - integer.value),
@@ -37,25 +38,28 @@ module aptos_framework::optional_aggregator {
integer.value = integer.value + value;
}
+ /// Subtracts `value` from integer. Aborts on going below zero.
fun sub_integer(integer: &mut Integer, value: u128) {
assert!(value <= integer.value, error::out_of_range(EAGGREGATOR_UNDERFLOW));
integer.value = integer.value - value;
}
+ /// Returns an overflow limit of integer.
fun limit(integer: &Integer): u128 {
integer.limit
}
+ /// Returns a value stored in this integer.
fun read_integer(integer: &Integer): u128 {
integer.value
}
+ /// Destroys an integer.
fun destroy_integer(integer: Integer) {
let Integer { value: _, limit: _ } = integer;
}
- /// Struct that contains either an aggregator or a normal integer, both
- /// overflowing on limit.
+ /// Contains either an aggregator or a normal integer, both overflowing on limit.
struct OptionalAggregator has store {
// Parallelizable.
aggregator: Option,
@@ -63,7 +67,7 @@ module aptos_framework::optional_aggregator {
integer: Option,
}
- /// Creates a new optional aggregator instance.
+ /// Creates a new optional aggregator.
public(friend) fun new(limit: u128, parallelizable: bool): OptionalAggregator {
if (parallelizable) {
OptionalAggregator {
@@ -148,7 +152,7 @@ module aptos_framework::optional_aggregator {
limit
}
- /// Adds to optional aggregator, aborting on exceeding the `limit`.
+ /// Adds `value` to optional aggregator, aborting on exceeding the `limit`.
public fun add(optional_aggregator: &mut OptionalAggregator, value: u128) {
if (option::is_some(&optional_aggregator.aggregator)) {
let aggregator = option::borrow_mut(&mut optional_aggregator.aggregator);
@@ -159,7 +163,7 @@ module aptos_framework::optional_aggregator {
}
}
- /// Subtracts from optional aggregator, aborting on going below zero.
+ /// Subtracts `value` from optional aggregator, aborting on going below zero.
public fun sub(optional_aggregator: &mut OptionalAggregator, value: u128) {
if (option::is_some(&optional_aggregator.aggregator)) {
let aggregator = option::borrow_mut(&mut optional_aggregator.aggregator);
@@ -181,7 +185,7 @@ module aptos_framework::optional_aggregator {
}
}
- /// Returns true is optional aggregator uses parallelizable implementation.
+ /// Returns true if optional aggregator uses parallelizable implementation.
public fun is_parallelizable(optional_aggregator: &OptionalAggregator): bool {
option::is_some(&optional_aggregator.aggregator)
}