From 7ba0e69374e244de7b361b092e2fcaa435546b51 Mon Sep 17 00:00:00 2001
From: igor-aptos <110557261+igor-aptos@users.noreply.github.com>
Date: Thu, 19 Oct 2023 09:35:38 -0700
Subject: [PATCH] [agg_v2] Adding aggregator_v2.move with sequential (fallback)
logic (#10397)
* [agg_v2] Adding aggregator_v2.move and fallback logic
Self-contained logic that can be landed on main, and so ecosystem PRs can land
independently of the rest of the aggregator changes
Copied from aggregators_v2 branch
Fixing tests and more comments
gas fixes and limit to string length
* addressing comments/updates
* fix lint
---------
---
.../src/gas_schedule/aptos_framework.rs | 9 +-
.../e2e-move-tests/src/tests/aggregator_v2.rs | 11 +-
.../aptos-framework/doc/aggregator_v2.md | 551 +++++++++++++++-
.../sources/aggregator_v2/aggregator_v2.move | 243 +++++--
.../aggregator_v2/aggregator_v2.spec.move | 46 ++
.../aggregator_natives/aggregator_v2.rs | 613 ++++++++++++++----
.../natives/aggregator_natives/helpers_v2.rs | 95 ++-
aptos-move/vm-genesis/src/lib.rs | 1 +
types/src/on_chain_config/aptos_features.rs | 2 +-
9 files changed, 1338 insertions(+), 233 deletions(-)
create mode 100644 aptos-move/framework/aptos-framework/sources/aggregator_v2/aggregator_v2.spec.move
diff --git a/aptos-move/aptos-gas-schedule/src/gas_schedule/aptos_framework.rs b/aptos-move/aptos-gas-schedule/src/gas_schedule/aptos_framework.rs
index 98bddc48a0e4e..9b1e5f534950f 100644
--- a/aptos-move/aptos-gas-schedule/src/gas_schedule/aptos_framework.rs
+++ b/aptos-move/aptos-gas-schedule/src/gas_schedule/aptos_framework.rs
@@ -201,10 +201,17 @@ crate::gas_schedule::macros::define_gas_parameters!(
[aggregator_destroy_base: InternalGas, "aggregator.destroy.base", 10000],
[aggregator_factory_new_aggregator_base: InternalGas, "aggregator_factory.new_aggregator.base", 10000],
+ [aggregator_v2_create_aggregator_base: InternalGas, {12.. => "aggregator_v2.create_aggregator.base"}, 10000],
+ [aggregator_v2_try_add_base: InternalGas, {12.. => "aggregator_v2.try_add.base"}, 6000],
+ [aggregator_v2_try_sub_base: InternalGas, {12.. => "aggregator_v2.try_sub.base"}, 6000],
+ [aggregator_v2_read_base: InternalGas, {12.. => "aggregator_v2.read.base"}, 12000],
+ [aggregator_v2_snapshot_base: InternalGas, {12.. => "aggregator_v2.snapshot.base"}, 6000],
+
[aggregator_v2_create_snapshot_base: InternalGas, {11.. => "aggregator_v2.create_snapshot.base"}, 6000],
[aggregator_v2_copy_snapshot_base: InternalGas, {11.. => "aggregator_v2.copy_snapshot.base"}, 6000],
- [aggregator_v2_read_snapshot_base: InternalGas, {11.. => "aggregator_v2.read_snapshot.base"}, 6000],
+ [aggregator_v2_read_snapshot_base: InternalGas, {11.. => "aggregator_v2.read_snapshot.base"}, 12000],
[aggregator_v2_string_concat_base: InternalGas, {11.. => "aggregator_v2.string_concat.base"}, 6000],
+ [aggregator_v2_string_concat_per_byte: InternalGasPerByte, { 12.. =>"aggregator_v2.string_concat.per_byte" }, 20],
[object_exists_at_base: InternalGas, { 7.. => "object.exists_at.base" }, 5000],
// These are dummy value, they copied from storage gas in aptos-core/aptos-vm/src/aptos_vm_impl.rs
diff --git a/aptos-move/e2e-move-tests/src/tests/aggregator_v2.rs b/aptos-move/e2e-move-tests/src/tests/aggregator_v2.rs
index 63055a6e28a72..76b105067bcf9 100644
--- a/aptos-move/e2e-move-tests/src/tests/aggregator_v2.rs
+++ b/aptos-move/e2e-move-tests/src/tests/aggregator_v2.rs
@@ -6,10 +6,13 @@ use crate::{
initialize, verify_copy_snapshot, verify_copy_string_snapshot, verify_string_concat,
verify_string_snapshot_concat,
},
- assert_success,
+ assert_abort, assert_success,
tests::common,
MoveHarness,
};
+use aptos_framework::natives::aggregator_natives::aggregator_v2::{
+ EAGGREGATOR_FUNCTION_NOT_YET_SUPPORTED, EUNSUPPORTED_AGGREGATOR_SNAPSHOT_TYPE,
+};
use aptos_language_e2e_tests::account::Account;
fn setup() -> (MoveHarness, Account) {
@@ -20,14 +23,14 @@ fn setup() -> (MoveHarness, Account) {
fn test_copy_snapshot() {
let (mut h, acc) = setup();
let txn = verify_copy_snapshot(&mut h, &acc);
- assert_success!(h.run(txn));
+ assert_abort!(h.run(txn), EAGGREGATOR_FUNCTION_NOT_YET_SUPPORTED);
}
#[test]
fn test_copy_string_snapshot() {
let (mut h, acc) = setup();
let txn = verify_copy_string_snapshot(&mut h, &acc);
- assert_success!(h.run(txn));
+ assert_abort!(h.run(txn), EAGGREGATOR_FUNCTION_NOT_YET_SUPPORTED);
}
#[test]
@@ -41,5 +44,5 @@ fn test_string_concat() {
fn test_string_snapshot_concat() {
let (mut h, acc) = setup();
let txn = verify_string_snapshot_concat(&mut h, &acc);
- assert_success!(h.run(txn));
+ assert_abort!(h.run(txn), EUNSUPPORTED_AGGREGATOR_SNAPSHOT_TYPE);
}
diff --git a/aptos-move/framework/aptos-framework/doc/aggregator_v2.md b/aptos-move/framework/aptos-framework/doc/aggregator_v2.md
index f903f22f91a43..44971e4af48bc 100644
--- a/aptos-move/framework/aptos-framework/doc/aggregator_v2.md
+++ b/aptos-move/framework/aptos-framework/doc/aggregator_v2.md
@@ -3,28 +3,97 @@
# Module `0x1::aggregator_v2`
-This module provides an interface for aggregators (version 2).
-Only skeleton - for AggregagtorSnapshot - is provided at this time,
-to allow transition of usages.
-
-
+This module provides an interface for aggregators (version 2). 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 try_add(X, 1)
for aggregator X
, and the second is
+doing try_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 a resource-intensive
+operation that also reduced parallelism, and should be avoided as much as possible.
+
+
+- [Struct `Aggregator`](#0x1_aggregator_v2_Aggregator)
- [Struct `AggregatorSnapshot`](#0x1_aggregator_v2_AggregatorSnapshot)
- [Constants](#@Constants_0)
+- [Function `max_value`](#0x1_aggregator_v2_max_value)
+- [Function `create_aggregator`](#0x1_aggregator_v2_create_aggregator)
+- [Function `create_unbounded_aggregator`](#0x1_aggregator_v2_create_unbounded_aggregator)
+- [Function `try_add`](#0x1_aggregator_v2_try_add)
+- [Function `add`](#0x1_aggregator_v2_add)
+- [Function `try_sub`](#0x1_aggregator_v2_try_sub)
+- [Function `sub`](#0x1_aggregator_v2_sub)
+- [Function `read`](#0x1_aggregator_v2_read)
+- [Function `snapshot`](#0x1_aggregator_v2_snapshot)
- [Function `create_snapshot`](#0x1_aggregator_v2_create_snapshot)
- [Function `copy_snapshot`](#0x1_aggregator_v2_copy_snapshot)
- [Function `read_snapshot`](#0x1_aggregator_v2_read_snapshot)
- [Function `string_concat`](#0x1_aggregator_v2_string_concat)
+- [Function `test_aggregator_valid_type`](#0x1_aggregator_v2_test_aggregator_valid_type)
+- [Specification](#@Specification_1)
+ - [Function `create_aggregator`](#@Specification_1_create_aggregator)
+ - [Function `create_unbounded_aggregator`](#@Specification_1_create_unbounded_aggregator)
+ - [Function `try_add`](#@Specification_1_try_add)
+ - [Function `try_sub`](#@Specification_1_try_sub)
+ - [Function `read`](#@Specification_1_read)
+ - [Function `snapshot`](#@Specification_1_snapshot)
+ - [Function `create_snapshot`](#@Specification_1_create_snapshot)
+ - [Function `copy_snapshot`](#@Specification_1_copy_snapshot)
+ - [Function `string_concat`](#@Specification_1_string_concat)
+
+
+
use 0x1::error;
+use 0x1::string;
+
+
+
+
+
+## Struct `Aggregator`
-use 0x1::string;
+Represents an integer which supports parallel additions and subtractions
+across multiple transactions. See the module description for more details.
+
+Currently supported types for IntElement are u64 and u128.
+
+
+struct Aggregator<IntElement> has drop, store
+
+Fields
+
+
+
+-
+
value: IntElement
+
+-
+
+
+-
+
max_value: IntElement
+
+-
+
+
+
+
+
+
+
## Struct `AggregatorSnapshot`
+Represents a constant value, that was derived from an aggregator at given instant in time.
+Unlike read() and storing the value directly, this enables parallel execution of transactions,
+while storing snapshot of aggregator state elsewhere.
struct AggregatorSnapshot<Element> has drop, store
@@ -53,12 +122,53 @@ to allow transition of usages.
## Constants
-
+
+
+The value of aggregator overflows. Raised by uncoditional add() call
+
+
+const EAGGREGATOR_OVERFLOW: u64 = 1;
+
+
+
+
+
+
+The value of aggregator underflows (goes below zero). Raised by uncoditional sub() call
+
+
+const EAGGREGATOR_UNDERFLOW: u64 = 2;
+
+
+
+
+
+
+The aggregator api v2 feature flag is not enabled.
+
-The aggregator snapshots feature flag is not enabled.
+const EAGGREGATOR_API_V2_NOT_ENABLED: u64 = 6;
+
+
+
+
+
+
+The native aggregator function, that is in the move file, is not yet supported.
+and any calls will raise this error.
+
+
+const EAGGREGATOR_FUNCTION_NOT_YET_SUPPORTED: u64 = 9;
+
+
+
+
+
+
+Arguments passed to concat exceed max limit of 256 bytes (for prefix and suffix together).
-const EAGGREGATOR_SNAPSHOTS_NOT_ENABLED: u64 = 6;
+const ECONCAT_STRING_LENGTH_TOO_LARGE: u64 = 8;
@@ -73,10 +183,246 @@ The generic type supplied to the aggregator snapshot is not supported.
+
+
+The generic type supplied to the aggregator is not supported.
+
+
+const EUNSUPPORTED_AGGREGATOR_TYPE: u64 = 7;
+
+
+
+
+
+
+## Function `max_value`
+
+Returns max_value
exceeding which aggregator overflows.
+
+
+public fun max_value<IntElement: copy, drop>(aggregator: &aggregator_v2::Aggregator<IntElement>): IntElement
+
+
+
+
+
+Implementation
+
+
+public fun max_value<IntElement: copy + drop>(aggregator: &Aggregator<IntElement>): IntElement {
+ aggregator.max_value
+}
+
+
+
+
+
+
+
+
+## Function `create_aggregator`
+
+Creates new aggregator, with given 'max_value'.
+
+Currently supported types for IntElement are u64 and u128.
+EAGGREGATOR_ELEMENT_TYPE_NOT_SUPPORTED raised if called with a different type.
+
+
+public fun create_aggregator<IntElement: copy, drop>(max_value: IntElement): aggregator_v2::Aggregator<IntElement>
+
+
+
+
+
+Implementation
+
+
+public native fun create_aggregator<IntElement: copy + drop>(max_value: IntElement): Aggregator<IntElement>;
+
+
+
+
+
+
+
+
+## Function `create_unbounded_aggregator`
+
+Creates new aggregator, without any 'max_value' on top of the implicit bound restriction
+due to the width of the type (i.e. MAX_U64 for u64, MAX_U128 for u128).
+
+Currently supported types for IntElement are u64 and u128.
+EAGGREGATOR_ELEMENT_TYPE_NOT_SUPPORTED raised if called with a different type.
+
+
+public fun create_unbounded_aggregator<IntElement: copy, drop>(): aggregator_v2::Aggregator<IntElement>
+
+
+
+
+
+Implementation
+
+
+public native fun create_unbounded_aggregator<IntElement: copy + drop>(): Aggregator<IntElement>;
+
+
+
+
+
+
+
+
+## Function `try_add`
+
+Adds value
to aggregator.
+If addition would exceed the max_value, false
is returned, and aggregator value is left unchanged.
+
+
+public fun try_add<IntElement>(aggregator: &mut aggregator_v2::Aggregator<IntElement>, value: IntElement): bool
+
+
+
+
+
+Implementation
+
+
+public native fun try_add<IntElement>(aggregator: &mut Aggregator<IntElement>, value: IntElement): bool;
+
+
+
+
+
+
+
+
+## Function `add`
+
+
+
+public fun add<IntElement>(aggregator: &mut aggregator_v2::Aggregator<IntElement>, value: IntElement)
+
+
+
+
+
+Implementation
+
+
+public fun add<IntElement>(aggregator: &mut Aggregator<IntElement>, value: IntElement) {
+ assert!(try_add(aggregator, value), error::out_of_range(EAGGREGATOR_OVERFLOW));
+}
+
+
+
+
+
+
+
+
+## Function `try_sub`
+
+Subtracts value
from aggregator.
+If subtraction would result in a negative value, false
is returned, and aggregator value is left unchanged.
+
+
+public fun try_sub<IntElement>(aggregator: &mut aggregator_v2::Aggregator<IntElement>, value: IntElement): bool
+
+
+
+
+
+Implementation
+
+
+public native fun try_sub<IntElement>(aggregator: &mut Aggregator<IntElement>, value: IntElement): bool;
+
+
+
+
+
+
+
+
+## Function `sub`
+
+
+
+public fun sub<IntElement>(aggregator: &mut aggregator_v2::Aggregator<IntElement>, value: IntElement)
+
+
+
+
+
+Implementation
+
+
+public fun sub<IntElement>(aggregator: &mut Aggregator<IntElement>, value: IntElement) {
+ assert!(try_sub(aggregator, value), error::out_of_range(EAGGREGATOR_UNDERFLOW));
+}
+
+
+
+
+
+
+
+
+## Function `read`
+
+Returns a value stored in this aggregator.
+Note: This operation is resource-intensive, and reduces parallelism.
+(Especially if called in a transaction that also modifies the aggregator,
+or has other read/write conflicts)
+
+
+public fun read<IntElement>(aggregator: &aggregator_v2::Aggregator<IntElement>): IntElement
+
+
+
+
+
+Implementation
+
+
+public native fun read<IntElement>(aggregator: &Aggregator<IntElement>): IntElement;
+
+
+
+
+
+
+
+
+## Function `snapshot`
+
+Returns a wrapper of a current value of an aggregator
+Unlike read(), it is fast and avoids sequential dependencies.
+
+
+public fun snapshot<IntElement>(aggregator: &aggregator_v2::Aggregator<IntElement>): aggregator_v2::AggregatorSnapshot<IntElement>
+
+
+
+
+
+Implementation
+
+
+public native fun snapshot<IntElement>(aggregator: &Aggregator<IntElement>): AggregatorSnapshot<IntElement>;
+
+
+
+
+
+
## Function `create_snapshot`
+Creates a snapshot of a given value.
+Useful for when object is sometimes created via snapshot() or string_concat(), and sometimes directly.
public fun create_snapshot<Element: copy, drop>(value: Element): aggregator_v2::AggregatorSnapshot<Element>
@@ -99,6 +445,7 @@ The generic type supplied to the aggregator snapshot is not supported.
## Function `copy_snapshot`
+NOT YET IMPLEMENTED, always raises EAGGREGATOR_FUNCTION_NOT_YET_SUPPORTED.
public fun copy_snapshot<Element: copy, drop>(snapshot: &aggregator_v2::AggregatorSnapshot<Element>): aggregator_v2::AggregatorSnapshot<Element>
@@ -121,6 +468,10 @@ The generic type supplied to the aggregator snapshot is not supported.
## Function `read_snapshot`
+Returns a value stored in this snapshot.
+Note: This operation is resource-intensive, and reduces parallelism.
+(Especially if called in a transaction that also modifies the aggregator,
+or has other read/write conflicts)
public fun read_snapshot<Element>(snapshot: &aggregator_v2::AggregatorSnapshot<Element>): Element
@@ -143,9 +494,35 @@ The generic type supplied to the aggregator snapshot is not supported.
## Function `string_concat`
+Concatenates before
, snapshot
and after
into a single string.
+snapshot passed needs to have integer type - currently supported types are u64 and u128.
+Raises EUNSUPPORTED_AGGREGATOR_SNAPSHOT_TYPE if called with another type.
+If length of prefix and suffix together exceed 256 bytes, ECONCAT_STRING_LENGTH_TOO_LARGE is raised.
+
+
+public fun string_concat<IntElement>(before: string::String, snapshot: &aggregator_v2::AggregatorSnapshot<IntElement>, after: string::String): aggregator_v2::AggregatorSnapshot<string::String>
+
+
+
+
+
+Implementation
+
+
+public native fun string_concat<IntElement>(before: String, snapshot: &AggregatorSnapshot<IntElement>, after: String): AggregatorSnapshot<String>;
+
+
+
+
+
+
+
+
+## Function `test_aggregator_valid_type`
+
-public fun string_concat<Element>(before: string::String, snapshot: &aggregator_v2::AggregatorSnapshot<Element>, after: string::String): aggregator_v2::AggregatorSnapshot<string::String>
+fun test_aggregator_valid_type()
@@ -154,12 +531,164 @@ The generic type supplied to the aggregator snapshot is not supported.
Implementation
-public native fun string_concat<Element>(before: String, snapshot: &AggregatorSnapshot<Element>, after: String): AggregatorSnapshot<String>;
+fun test_aggregator_valid_type() {
+ create_unbounded_aggregator<u64>();
+ create_unbounded_aggregator<u128>();
+ create_aggregator<u64>(5);
+ create_aggregator<u128>(5);
+}
+
+
+## Specification
+
+
+
+
+### Function `create_aggregator`
+
+
+public fun create_aggregator<IntElement: copy, drop>(max_value: IntElement): aggregator_v2::Aggregator<IntElement>
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `create_unbounded_aggregator`
+
+
+public fun create_unbounded_aggregator<IntElement: copy, drop>(): aggregator_v2::Aggregator<IntElement>
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `try_add`
+
+
+public fun try_add<IntElement>(aggregator: &mut aggregator_v2::Aggregator<IntElement>, value: IntElement): bool
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `try_sub`
+
+
+public fun try_sub<IntElement>(aggregator: &mut aggregator_v2::Aggregator<IntElement>, value: IntElement): bool
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `read`
+
+
+public fun read<IntElement>(aggregator: &aggregator_v2::Aggregator<IntElement>): IntElement
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `snapshot`
+
+
+public fun snapshot<IntElement>(aggregator: &aggregator_v2::Aggregator<IntElement>): aggregator_v2::AggregatorSnapshot<IntElement>
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `create_snapshot`
+
+
+public fun create_snapshot<Element: copy, drop>(value: Element): aggregator_v2::AggregatorSnapshot<Element>
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `copy_snapshot`
+
+
+public fun copy_snapshot<Element: copy, drop>(snapshot: &aggregator_v2::AggregatorSnapshot<Element>): aggregator_v2::AggregatorSnapshot<Element>
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `string_concat`
+
+
+public fun string_concat<IntElement>(before: string::String, snapshot: &aggregator_v2::AggregatorSnapshot<IntElement>, after: string::String): aggregator_v2::AggregatorSnapshot<string::String>
+
+
+
+
+
+pragma opaque;
+
+
[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/sources/aggregator_v2/aggregator_v2.move b/aptos-move/framework/aptos-framework/sources/aggregator_v2/aggregator_v2.move
index 68ce8d966df9c..ceaca49991698 100644
--- a/aptos-move/framework/aptos-framework/sources/aggregator_v2/aggregator_v2.move
+++ b/aptos-move/framework/aptos-framework/sources/aggregator_v2/aggregator_v2.move
@@ -1,97 +1,216 @@
-/// This module provides an interface for aggregators (version 2).
-/// Only skeleton - for AggregagtorSnapshot - is provided at this time,
-/// to allow transition of usages.
+/// This module provides an interface for aggregators (version 2). 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 `try_add(X, 1)` for aggregator `X`, and the second is
+/// doing `try_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 a resource-intensive
+/// operation that also reduced parallelism, and should be avoided as much as possible.
module aptos_framework::aggregator_v2 {
+ use std::error;
use std::string::String;
+ /// The value of aggregator overflows. Raised by uncoditional add() call
+ const EAGGREGATOR_OVERFLOW: u64 = 1;
+
+ /// The value of aggregator underflows (goes below zero). Raised by uncoditional sub() call
+ const EAGGREGATOR_UNDERFLOW: u64 = 2;
+
/// The generic type supplied to the aggregator snapshot is not supported.
const EUNSUPPORTED_AGGREGATOR_SNAPSHOT_TYPE: u64 = 5;
- /// The aggregator snapshots feature flag is not enabled.
- const EAGGREGATOR_SNAPSHOTS_NOT_ENABLED: u64 = 6;
+ /// The aggregator api v2 feature flag is not enabled.
+ const EAGGREGATOR_API_V2_NOT_ENABLED: u64 = 6;
+
+ /// The generic type supplied to the aggregator is not supported.
+ const EUNSUPPORTED_AGGREGATOR_TYPE: u64 = 7;
+
+ /// Arguments passed to concat exceed max limit of 256 bytes (for prefix and suffix together).
+ const ECONCAT_STRING_LENGTH_TOO_LARGE: u64 = 8;
+
+ /// The native aggregator function, that is in the move file, is not yet supported.
+ /// and any calls will raise this error.
+ const EAGGREGATOR_FUNCTION_NOT_YET_SUPPORTED: u64 = 9;
+
+ /// Represents an integer which supports parallel additions and subtractions
+ /// across multiple transactions. See the module description for more details.
+ ///
+ /// Currently supported types for IntElement are u64 and u128.
+ struct Aggregator has store, drop {
+ value: IntElement,
+ max_value: IntElement,
+ }
+ /// Represents a constant value, that was derived from an aggregator at given instant in time.
+ /// Unlike read() and storing the value directly, this enables parallel execution of transactions,
+ /// while storing snapshot of aggregator state elsewhere.
struct AggregatorSnapshot has store, drop {
value: Element,
}
+ /// Returns `max_value` exceeding which aggregator overflows.
+ public fun max_value(aggregator: &Aggregator): IntElement {
+ aggregator.max_value
+ }
+
+ /// Creates new aggregator, with given 'max_value'.
+ ///
+ /// Currently supported types for IntElement are u64 and u128.
+ /// EAGGREGATOR_ELEMENT_TYPE_NOT_SUPPORTED raised if called with a different type.
+ public native fun create_aggregator(max_value: IntElement): Aggregator;
+
+ /// Creates new aggregator, without any 'max_value' on top of the implicit bound restriction
+ /// due to the width of the type (i.e. MAX_U64 for u64, MAX_U128 for u128).
+ ///
+ /// Currently supported types for IntElement are u64 and u128.
+ /// EAGGREGATOR_ELEMENT_TYPE_NOT_SUPPORTED raised if called with a different type.
+ public native fun create_unbounded_aggregator(): Aggregator;
+
+ /// Adds `value` to aggregator.
+ /// If addition would exceed the max_value, `false` is returned, and aggregator value is left unchanged.
+ public native fun try_add(aggregator: &mut Aggregator, value: IntElement): bool;
+
+ // Adds `value` to aggregator, uncoditionally.
+ // If addition would exceed the max_value, EAGGREGATOR_OVERFLOW exception will be thrown.
+ public fun add(aggregator: &mut Aggregator, value: IntElement) {
+ assert!(try_add(aggregator, value), error::out_of_range(EAGGREGATOR_OVERFLOW));
+ }
+
+ /// Subtracts `value` from aggregator.
+ /// If subtraction would result in a negative value, `false` is returned, and aggregator value is left unchanged.
+ public native fun try_sub(aggregator: &mut Aggregator, value: IntElement): bool;
+
+ // Subtracts `value` to aggregator, uncoditionally.
+ // If subtraction would result in a negative value, EAGGREGATOR_UNDERFLOW exception will be thrown.
+ public fun sub(aggregator: &mut Aggregator, value: IntElement) {
+ assert!(try_sub(aggregator, value), error::out_of_range(EAGGREGATOR_UNDERFLOW));
+ }
+
+ /// Returns a value stored in this aggregator.
+ /// Note: This operation is resource-intensive, and reduces parallelism.
+ /// (Especially if called in a transaction that also modifies the aggregator,
+ /// or has other read/write conflicts)
+ public native fun read(aggregator: &Aggregator): IntElement;
+
+ /// Returns a wrapper of a current value of an aggregator
+ /// Unlike read(), it is fast and avoids sequential dependencies.
+ public native fun snapshot(aggregator: &Aggregator): AggregatorSnapshot;
+
+ /// Creates a snapshot of a given value.
+ /// Useful for when object is sometimes created via snapshot() or string_concat(), and sometimes directly.
public native fun create_snapshot(value: Element): AggregatorSnapshot;
+ /// NOT YET IMPLEMENTED, always raises EAGGREGATOR_FUNCTION_NOT_YET_SUPPORTED.
public native fun copy_snapshot(snapshot: &AggregatorSnapshot): AggregatorSnapshot;
+ /// Returns a value stored in this snapshot.
+ /// Note: This operation is resource-intensive, and reduces parallelism.
+ /// (Especially if called in a transaction that also modifies the aggregator,
+ /// or has other read/write conflicts)
public native fun read_snapshot(snapshot: &AggregatorSnapshot): Element;
- public native fun string_concat(before: String, snapshot: &AggregatorSnapshot, after: String): AggregatorSnapshot;
+ /// Concatenates `before`, `snapshot` and `after` into a single string.
+ /// snapshot passed needs to have integer type - currently supported types are u64 and u128.
+ /// Raises EUNSUPPORTED_AGGREGATOR_SNAPSHOT_TYPE if called with another type.
+ /// If length of prefix and suffix together exceed 256 bytes, ECONCAT_STRING_LENGTH_TOO_LARGE is raised.
+ public native fun string_concat(before: String, snapshot: &AggregatorSnapshot, after: String): AggregatorSnapshot;
+
+ #[test]
+ fun test_aggregator() {
+ let agg = create_aggregator(10);
+ assert!(try_add(&mut agg, 5), 1);
+ assert!(try_add(&mut agg, 5), 2);
+ assert!(read(&agg) == 10, 3);
+ assert!(!try_add(&mut agg, 5), 4);
+ assert!(read(&agg) == 10, 5);
+ assert!(try_sub(&mut agg, 5), 6);
+ assert!(read(&agg) == 5, 7);
+
+ let snap = snapshot(&agg);
+ assert!(try_add(&mut agg, 2), 8);
+ assert!(read(&agg) == 7, 9);
+ assert!(read_snapshot(&snap) == 5, 10);
+ }
- // #[test(fx = @std)]
- // public fun test_correct_read(fx: &signer) {
- // use std::features;
- // let feature = features::get_aggregator_snapshots_feature();
- // features::change_feature_flags(fx, vector[feature], vector[]);
+ #[test]
+ fun test_correct_read() {
+ let snapshot = create_snapshot(42);
+ assert!(read_snapshot(&snapshot) == 42, 0);
- // let snapshot = create_snapshot(42);
- // let snapshot2 = copy_snapshot(&snapshot);
- // assert!(read_snapshot(&snapshot) == 42, 0);
- // assert!(read_snapshot(&snapshot2) == 42, 0);
- // }
+ let snapshot = create_snapshot(std::string::utf8(b"42"));
+ assert!(read_snapshot(&snapshot) == std::string::utf8(b"42"), 0);
+ }
- // #[test(fx = @std)]
- // public fun test_correct_read_string(fx: &signer) {
- // use std::features;
- // let feature = features::get_aggregator_snapshots_feature();
- // features::change_feature_flags(fx, vector[feature], vector[]);
+ #[test]
+ #[expected_failure(abort_code = 0x030009, location = Self)]
+ fun test_copy_not_yet_supported() {
+ let snapshot = create_snapshot(42);
+ copy_snapshot(&snapshot);
+ }
- // let snapshot = create_snapshot(std::string::utf8(b"42"));
- // let snapshot2 = copy_snapshot(&snapshot);
- // assert!(read_snapshot(&snapshot) == std::string::utf8(b"42"), 0);
- // assert!(read_snapshot(&snapshot2) == std::string::utf8(b"42"), 0);
- // }
+ #[test]
+ fun test_string_concat1() {
+ let snapshot = create_snapshot(42);
+ let snapshot2 = string_concat(std::string::utf8(b"before"), &snapshot, std::string::utf8(b"after"));
+ assert!(read_snapshot(&snapshot2) == std::string::utf8(b"before42after"), 0);
+ }
- // #[test(fx = @std)]
- // public fun test_string_concat1(fx: &signer) {
- // use std::features;
- // let feature = features::get_aggregator_snapshots_feature();
- // features::change_feature_flags(fx, vector[feature], vector[]);
+ #[test]
+ #[expected_failure(abort_code = 0x030005, location = Self)]
+ fun test_string_concat_from_string_not_supported() {
+ let snapshot = create_snapshot(std::string::utf8(b"42"));
+ string_concat(std::string::utf8(b"before"), &snapshot, std::string::utf8(b"after"));
+ }
- // let snapshot = create_snapshot(42);
- // let snapshot2 = string_concat(std::string::utf8(b"before"), &snapshot, std::string::utf8(b"after"));
- // assert!(read_snapshot(&snapshot2) == std::string::utf8(b"before42after"), 0);
- // }
+ // Tests commented out, as flag used in rust cannot be disabled.
// #[test(fx = @std)]
- // public fun test_string_concat2(fx: &signer) {
- // use std::features;
- // let feature = features::get_aggregator_snapshots_feature();
- // features::change_feature_flags(fx, vector[feature], vector[]);
-
- // let snapshot = create_snapshot(std::string::utf8(b"42"));
- // let snapshot2 = string_concat(std::string::utf8(b"before"), &snapshot, std::string::utf8(b"after"));
- // assert!(read_snapshot(&snapshot2) == std::string::utf8(b"before42after"), 0);
- // }
-
- // #[test]
// #[expected_failure(abort_code = 0x030006, location = Self)]
- // public fun test_snapshot_feature_not_enabled() {
+ // fun test_snapshot_feature_not_enabled(fx: &signer) {
+ // use std::features;
+ // use aptos_framework::reconfiguration;
+ // let feature = features::get_aggregator_v2_api_feature();
+ // features::change_feature_flags(fx, vector[], vector[feature]);
+ // reconfiguration::reconfigure_for_test();
// create_snapshot(42);
// }
// #[test(fx = @std)]
- // #[expected_failure(abort_code = 0x030005, location = Self)]
- // public fun test_snpashot_invalid_type1(fx: &signer) {
+ // #[expected_failure(abort_code = 0x030006, location = Self)]
+ // fun test_aggregator_feature_not_enabled(fx: &signer) {
// use std::features;
- // use std::option;
- // let feature = features::get_aggregator_snapshots_feature();
- // features::change_feature_flags(fx, vector[feature], vector[]);
-
- // create_snapshot(option::some(42));
+ // use aptos_framework::reconfiguration;
+ // let feature = features::get_aggregator_v2_api_feature();
+ // features::change_feature_flags(fx, vector[], vector[feature]);
+ // reconfiguration::reconfigure_for_test();
+ // create_aggregator(42);
// }
- // #[test(fx = @std)]
- // #[expected_failure(abort_code = 0x030005, location = Self)]
- // public fun test_snpashot_invalid_type2(fx: &signer) {
- // use std::features;
- // let feature = features::get_aggregator_snapshots_feature();
- // features::change_feature_flags(fx, vector[feature], vector[]);
+ #[test]
+ #[expected_failure(abort_code = 0x030007, location = Self)]
+ fun test_aggregator_invalid_type1() {
+ create_unbounded_aggregator();
+ }
- // create_snapshot(vector[42]);
- // }
+ fun test_aggregator_valid_type() {
+ create_unbounded_aggregator();
+ create_unbounded_aggregator();
+ create_aggregator(5);
+ create_aggregator(5);
+ }
+
+ #[test]
+ #[expected_failure(abort_code = 0x030005, location = Self)]
+ fun test_snpashot_invalid_type1() {
+ use std::option;
+ create_snapshot(option::some(42));
+ }
+
+ #[test]
+ #[expected_failure(abort_code = 0x030005, location = Self)]
+ fun test_snpashot_invalid_type2() {
+ create_snapshot(vector[42]);
+ }
}
diff --git a/aptos-move/framework/aptos-framework/sources/aggregator_v2/aggregator_v2.spec.move b/aptos-move/framework/aptos-framework/sources/aggregator_v2/aggregator_v2.spec.move
new file mode 100644
index 0000000000000..459978eaa7186
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/sources/aggregator_v2/aggregator_v2.spec.move
@@ -0,0 +1,46 @@
+spec aptos_framework::aggregator_v2 {
+ spec create_aggregator {
+ // TODO: temporary mockup.
+ pragma opaque;
+ }
+
+ spec create_unbounded_aggregator {
+ // TODO: temporary mockup.
+ pragma opaque;
+ }
+
+ spec try_add {
+ // TODO: temporary mockup.
+ pragma opaque;
+ }
+
+ spec try_sub {
+ // TODO: temporary mockup.
+ pragma opaque;
+ }
+
+ spec read {
+ // TODO: temporary mockup.
+ pragma opaque;
+ }
+
+ spec snapshot {
+ // TODO: temporary mockup.
+ pragma opaque;
+ }
+
+ spec create_snapshot {
+ // TODO: temporary mockup.
+ pragma opaque;
+ }
+
+ spec copy_snapshot {
+ // TODO: temporary mockup.
+ pragma opaque;
+ }
+
+ spec string_concat {
+ // TODO: temporary mockup.
+ pragma opaque;
+ }
+}
diff --git a/aptos-move/framework/src/natives/aggregator_natives/aggregator_v2.rs b/aptos-move/framework/src/natives/aggregator_natives/aggregator_v2.rs
index 033896b334569..27435f954067b 100644
--- a/aptos-move/framework/src/natives/aggregator_natives/aggregator_v2.rs
+++ b/aptos-move/framework/src/natives/aggregator_natives/aggregator_v2.rs
@@ -3,17 +3,22 @@
use crate::natives::{
aggregator_natives::helpers_v2::{
- aggregator_snapshot_value_as_bytes, aggregator_snapshot_value_as_u128,
- aggregator_snapshot_value_as_u64, string_to_bytes,
+ aggregator_snapshot_field_value, get_aggregator_fields_u128, get_aggregator_fields_u64,
+ set_aggregator_value_field, string_to_bytes, to_utf8_bytes, u128_to_u64,
},
AccountAddress,
};
+use aptos_gas_algebra::NumBytes;
use aptos_gas_schedule::gas_params::natives::aptos_framework::*;
use aptos_native_interface::{
safely_pop_arg, RawSafeNative, SafeNativeBuilder, SafeNativeContext, SafeNativeError,
SafeNativeResult,
};
-use move_core_types::value::{MoveStructLayout, MoveTypeLayout};
+use move_binary_format::errors::PartialVMError;
+use move_core_types::{
+ value::{MoveStructLayout, MoveTypeLayout},
+ vm_status::StatusCode,
+};
use move_vm_runtime::native_functions::NativeFunction;
use move_vm_types::{
loaded_data::runtime_types::Type,
@@ -25,8 +30,20 @@ use std::{collections::VecDeque, ops::Deref};
/// The generic type supplied to aggregator snapshots is not supported.
pub const EUNSUPPORTED_AGGREGATOR_SNAPSHOT_TYPE: u64 = 0x03_0005;
-/// The aggregator snapshots feature is not enabled.
-pub const EAGGREGATOR_SNAPSHOTS_NOT_ENABLED: u64 = 0x03_0006;
+/// The aggregator api feature is not enabled.
+pub const EAGGREGATOR_API_NOT_ENABLED: u64 = 0x03_0006;
+
+/// The generic type supplied to the aggregators is not supported.
+pub const EUNSUPPORTED_AGGREGATOR_TYPE: u64 = 0x03_0007;
+
+/// Arguments passed to concat exceed max limit of 256 bytes (for prefix and suffix together).
+pub const ECONCAT_STRING_LENGTH_TOO_LARGE: u64 = 0x03_0008;
+
+/// The native aggregator function, that is in the move file, is not yet supported.
+/// and any calls will raise this error.
+pub const EAGGREGATOR_FUNCTION_NOT_YET_SUPPORTED: u64 = 0x03_0009;
+
+pub const CONCAT_PREFIX_AND_SUFFIX_MAX_LENGTH: usize = 256;
/// Checks if the type argument `type_arg` is a string type.
fn is_string_type(context: &SafeNativeContext, type_arg: &Type) -> SafeNativeResult {
@@ -39,98 +56,433 @@ fn is_string_type(context: &SafeNativeContext, type_arg: &Type) -> SafeNativeRes
Ok(false)
}
+/// Given the native function argument and a type, returns a tuple of its
+/// fields: (`aggregator id`, `max_value`).
+pub fn get_aggregator_fields_by_type(
+ ty_arg: &Type,
+ agg: &StructRef,
+) -> SafeNativeResult<(u128, u128)> {
+ match ty_arg {
+ Type::U128 => {
+ let (id, max_value) = get_aggregator_fields_u128(agg)?;
+ Ok((id, max_value))
+ },
+ Type::U64 => {
+ let (id, max_value) = get_aggregator_fields_u64(agg)?;
+ Ok((id as u128, max_value as u128))
+ },
+ _ => Err(SafeNativeError::Abort {
+ abort_code: EUNSUPPORTED_AGGREGATOR_TYPE,
+ }),
+ }
+}
+
+/// Given the list of native function arguments and a type, pop the next argument if it is of given type.
+pub fn pop_value_by_type(ty_arg: &Type, args: &mut VecDeque) -> SafeNativeResult {
+ match ty_arg {
+ Type::U128 => Ok(safely_pop_arg!(args, u128)),
+ Type::U64 => Ok(safely_pop_arg!(args, u64) as u128),
+ _ => Err(SafeNativeError::Abort {
+ abort_code: EUNSUPPORTED_AGGREGATOR_TYPE,
+ }),
+ }
+}
+
+pub fn create_value_by_type(ty_arg: &Type, value: u128) -> SafeNativeResult {
+ match ty_arg {
+ Type::U128 => Ok(Value::u128(value)),
+ Type::U64 => Ok(Value::u64(u128_to_u64(value)?)),
+ _ => Err(SafeNativeError::Abort {
+ abort_code: EUNSUPPORTED_AGGREGATOR_TYPE,
+ }),
+ }
+}
+
+// To avoid checking is_string_type multiple times, check type_arg only once, and convert into this enum
+enum SnapshotType {
+ U128,
+ U64,
+ String,
+}
+
+impl SnapshotType {
+ fn from_ty_arg(context: &SafeNativeContext, ty_arg: &Type) -> SafeNativeResult {
+ match ty_arg {
+ Type::U128 => Ok(Self::U128),
+ Type::U64 => Ok(Self::U64),
+ _ => {
+ // Check if the type is a string
+ if is_string_type(context, ty_arg)? {
+ Ok(Self::String)
+ } else {
+ // If not a string, return an error
+ Err(SafeNativeError::Abort {
+ abort_code: EUNSUPPORTED_AGGREGATOR_SNAPSHOT_TYPE,
+ })
+ }
+ },
+ }
+ }
+
+ pub fn pop_snapshot_field_by_type(
+ &self,
+ args: &mut VecDeque,
+ ) -> SafeNativeResult {
+ self.parse_snapshot_value_by_type(aggregator_snapshot_field_value(&safely_pop_arg!(
+ args, StructRef
+ ))?)
+ }
+
+ pub fn pop_snapshot_value_by_type(
+ &self,
+ args: &mut VecDeque,
+ ) -> SafeNativeResult {
+ match self {
+ SnapshotType::U128 => Ok(SnapshotValue::Integer(safely_pop_arg!(args, u128))),
+ SnapshotType::U64 => Ok(SnapshotValue::Integer(safely_pop_arg!(args, u64) as u128)),
+ SnapshotType::String => {
+ let input = string_to_bytes(safely_pop_arg!(args, Struct))?;
+ Ok(SnapshotValue::String(input))
+ },
+ }
+ }
+
+ pub fn parse_snapshot_value_by_type(&self, value: Value) -> SafeNativeResult {
+ // Simpler to wrap to be able to reuse safely_pop_arg functions
+ self.pop_snapshot_value_by_type(&mut VecDeque::from([value]))
+ }
+
+ pub fn create_snapshot_value_by_type(&self, value: SnapshotValue) -> SafeNativeResult {
+ match (self, value) {
+ (SnapshotType::U128, SnapshotValue::Integer(v)) => Ok(Value::u128(v)),
+ (SnapshotType::U64, SnapshotValue::Integer(v)) => Ok(Value::u64(u128_to_u64(v)?)),
+ (SnapshotType::String, value) => {
+ Ok(Value::struct_(Struct::pack(vec![Value::vector_u8(
+ match value {
+ SnapshotValue::String(v) => v,
+ SnapshotValue::Integer(v) => to_utf8_bytes(v),
+ },
+ )])))
+ },
+ // Type cannot be Integer, if value is String
+ _ => Err(SafeNativeError::Abort {
+ abort_code: EUNSUPPORTED_AGGREGATOR_SNAPSHOT_TYPE,
+ }),
+ }
+ }
+}
+
+// ================= START TEMPORARY CODE =================
+// TODO: aggregator_v2 branch will introduce these in different places in code
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum SnapshotValue {
+ Integer(u128),
+ String(Vec),
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum SnapshotToStringFormula {
+ Concat { prefix: Vec, suffix: Vec },
+}
+
+impl SnapshotToStringFormula {
+ pub fn apply_to(&self, base: u128) -> Vec {
+ match self {
+ SnapshotToStringFormula::Concat { prefix, suffix } => {
+ let middle_string = base.to_string();
+ let middle = middle_string.as_bytes();
+ let mut result = Vec::with_capacity(prefix.len() + middle.len() + suffix.len());
+ result.extend(prefix);
+ result.extend(middle);
+ result.extend(suffix);
+ result
+ },
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub enum BoundedMathError {
+ Overflow,
+ Underflow,
+}
+
+// Unsigned operations operate on [0, max_value] range.
+// Signed operations operate on [-max_value, max_value] range.
+pub struct BoundedMath {
+ max_value: u128,
+}
+
+impl BoundedMath {
+ pub fn new(max_value: u128) -> Self {
+ Self { max_value }
+ }
+
+ pub fn get_max_value(&self) -> u128 {
+ self.max_value
+ }
+
+ pub fn unsigned_add(&self, base: u128, value: u128) -> Result {
+ if self.max_value < base || value > (self.max_value - base) {
+ Err(BoundedMathError::Overflow)
+ } else {
+ Ok(base + value)
+ }
+ }
+
+ pub fn unsigned_subtract(&self, base: u128, value: u128) -> Result {
+ if value > base {
+ Err(BoundedMathError::Underflow)
+ } else {
+ Ok(base - value)
+ }
+ }
+}
+
+// ================= END TEMPORARY CODE =================
+
+macro_rules! abort_if_not_enabled {
+ ($context:expr) => {
+ if !$context.aggregator_v2_api_enabled() {
+ return Err(SafeNativeError::Abort {
+ abort_code: EAGGREGATOR_API_NOT_ENABLED,
+ });
+ }
+ };
+}
+
/***************************************************************************************************
- * native fun create_snapshot(value: Element): AggregatorSnapshot;
+ * native fun create_aggregator(max_value: IntElement): Aggregator;
**************************************************************************************************/
-fn native_create_snapshot(
+fn native_create_aggregator(
context: &mut SafeNativeContext,
ty_args: Vec,
mut args: VecDeque,
) -> SafeNativeResult> {
- if !context.aggregator_v2_api_enabled() {
- return Err(SafeNativeError::Abort {
- abort_code: EAGGREGATOR_SNAPSHOTS_NOT_ENABLED,
- });
- }
+ abort_if_not_enabled!(context);
+
+ debug_assert_eq!(args.len(), 1);
+ debug_assert_eq!(ty_args.len(), 1);
+
+ context.charge(AGGREGATOR_V2_CREATE_AGGREGATOR_BASE)?;
+ let max_value = pop_value_by_type(&ty_args[0], &mut args)?;
+
+ let value_field_value = 0;
+
+ Ok(smallvec![Value::struct_(Struct::pack(vec![
+ create_value_by_type(&ty_args[0], value_field_value)?,
+ create_value_by_type(&ty_args[0], max_value)?,
+ ]))])
+}
+
+/***************************************************************************************************
+ * native fun create_unbounded_aggregator(): Aggregator;
+ **************************************************************************************************/
+
+fn native_create_unbounded_aggregator(
+ context: &mut SafeNativeContext,
+ ty_args: Vec,
+ args: VecDeque,
+) -> SafeNativeResult> {
+ abort_if_not_enabled!(context);
+ debug_assert_eq!(args.len(), 0);
debug_assert_eq!(ty_args.len(), 1);
+
+ context.charge(AGGREGATOR_V2_CREATE_AGGREGATOR_BASE)?;
+ let max_value = {
+ match &ty_args[0] {
+ Type::U128 => u128::MAX,
+ Type::U64 => u64::MAX as u128,
+ _ => {
+ return Err(SafeNativeError::Abort {
+ abort_code: EUNSUPPORTED_AGGREGATOR_TYPE,
+ })
+ },
+ }
+ };
+
+ let value_field_value = 0;
+
+ Ok(smallvec![Value::struct_(Struct::pack(vec![
+ create_value_by_type(&ty_args[0], value_field_value)?,
+ create_value_by_type(&ty_args[0], max_value)?,
+ ]))])
+}
+
+/***************************************************************************************************
+ * native fun try_add(aggregator: &mut Aggregator, value: IntElement): bool;
+ **************************************************************************************************/
+fn native_try_add(
+ context: &mut SafeNativeContext,
+ ty_args: Vec,
+ mut args: VecDeque,
+) -> SafeNativeResult> {
+ abort_if_not_enabled!(context);
+
+ debug_assert_eq!(args.len(), 2);
+ debug_assert_eq!(ty_args.len(), 1);
+ context.charge(AGGREGATOR_V2_TRY_ADD_BASE)?;
+
+ let input = pop_value_by_type(&ty_args[0], &mut args)?;
+ let agg_struct = safely_pop_arg!(args, StructRef);
+ let (agg_value, agg_max_value) = get_aggregator_fields_by_type(&ty_args[0], &agg_struct)?;
+
+ let result_value = {
+ let math = BoundedMath::new(agg_max_value);
+ match math.unsigned_add(agg_value, input) {
+ Ok(sum) => {
+ set_aggregator_value_field(&agg_struct, create_value_by_type(&ty_args[0], sum)?)?;
+ true
+ },
+ Err(_) => false,
+ }
+ };
+
+ Ok(smallvec![Value::bool(result_value)])
+}
+
+/***************************************************************************************************
+ * native fun try_sub(aggregator: &mut Aggregator, value: IntElement): bool;
+ **************************************************************************************************/
+fn native_try_sub(
+ context: &mut SafeNativeContext,
+ ty_args: Vec,
+ mut args: VecDeque,
+) -> SafeNativeResult> {
+ abort_if_not_enabled!(context);
+
+ debug_assert_eq!(args.len(), 2);
+ debug_assert_eq!(ty_args.len(), 1);
+ context.charge(AGGREGATOR_V2_TRY_SUB_BASE)?;
+
+ let input = pop_value_by_type(&ty_args[0], &mut args)?;
+ let agg_struct = safely_pop_arg!(args, StructRef);
+ let (agg_value, agg_max_value) = get_aggregator_fields_by_type(&ty_args[0], &agg_struct)?;
+
+ let result_value = {
+ let math = BoundedMath::new(agg_max_value);
+ match math.unsigned_subtract(agg_value, input) {
+ Ok(sum) => {
+ set_aggregator_value_field(&agg_struct, create_value_by_type(&ty_args[0], sum)?)?;
+ true
+ },
+ Err(_) => false,
+ }
+ };
+ Ok(smallvec![Value::bool(result_value)])
+}
+
+/***************************************************************************************************
+ * native fun read(aggregator: &Aggregator): IntElement;
+ **************************************************************************************************/
+
+fn native_read(
+ context: &mut SafeNativeContext,
+ ty_args: Vec,
+ mut args: VecDeque,
+) -> SafeNativeResult> {
+ abort_if_not_enabled!(context);
+
debug_assert_eq!(args.len(), 1);
- context.charge(AGGREGATOR_V2_CREATE_SNAPSHOT_BASE)?;
+ debug_assert_eq!(ty_args.len(), 1);
+ context.charge(AGGREGATOR_V2_READ_BASE)?;
- match ty_args[0] {
- Type::U128 => {
- let input = safely_pop_arg!(args, u128);
- Ok(smallvec![Value::struct_(Struct::pack(vec![Value::u128(
- input
- )]))])
- },
- Type::U64 => {
- let input = safely_pop_arg!(args, u64);
- Ok(smallvec![Value::struct_(Struct::pack(vec![Value::u64(
- input
- )]))])
- },
- _ => {
- // Check if the type is a string
- if is_string_type(context, &ty_args[0])? {
- let input = string_to_bytes(safely_pop_arg!(args, Struct))?;
- let move_string_value = Value::struct_(Struct::pack(vec![Value::vector_u8(input)]));
- let move_snapshot_value = Value::struct_(Struct::pack(vec![move_string_value]));
- return Ok(smallvec![move_snapshot_value]);
- }
- // If not a string, return an error
- Err(SafeNativeError::Abort {
- abort_code: EUNSUPPORTED_AGGREGATOR_SNAPSHOT_TYPE,
- })
- },
- }
+ let (agg_value, agg_max_value) =
+ get_aggregator_fields_by_type(&ty_args[0], &safely_pop_arg!(args, StructRef))?;
+
+ let result_value = agg_value;
+
+ if result_value > agg_max_value {
+ return Err(SafeNativeError::InvariantViolation(PartialVMError::new(
+ StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR,
+ )));
+ };
+ Ok(smallvec![create_value_by_type(&ty_args[0], result_value)?])
}
/***************************************************************************************************
- * native fun copy_snapshot(snapshot: AggregatorSnapshot): AggregatorSnapshot;
+ * native fun snapshot(aggregator: &Aggregator): AggregatorSnapshot;
**************************************************************************************************/
-fn native_copy_snapshot(
+fn native_snapshot(
context: &mut SafeNativeContext,
ty_args: Vec,
mut args: VecDeque,
) -> SafeNativeResult> {
+ abort_if_not_enabled!(context);
+
+ debug_assert_eq!(args.len(), 1);
+ debug_assert_eq!(ty_args.len(), 1);
+ context.charge(AGGREGATOR_V2_SNAPSHOT_BASE)?;
+
+ let (agg_value, _agg_max_value) =
+ get_aggregator_fields_by_type(&ty_args[0], &safely_pop_arg!(args, StructRef))?;
+
+ let result_value = agg_value;
+
+ Ok(smallvec![Value::struct_(Struct::pack(vec![
+ create_value_by_type(&ty_args[0], result_value)?
+ ]))])
+}
+
+/***************************************************************************************************
+ * native fun create_snapshot(value: Element): AggregatorSnapshot
+ **************************************************************************************************/
+
+fn native_create_snapshot(
+ context: &mut SafeNativeContext,
+ ty_args: Vec,
+ mut args: VecDeque,
+) -> SafeNativeResult> {
+ abort_if_not_enabled!(context);
+
debug_assert_eq!(ty_args.len(), 1);
debug_assert_eq!(args.len(), 1);
- context.charge(AGGREGATOR_V2_COPY_SNAPSHOT_BASE)?;
+ context.charge(AGGREGATOR_V2_CREATE_SNAPSHOT_BASE)?;
- match ty_args[0] {
- Type::U128 => {
- let value = aggregator_snapshot_value_as_u128(&safely_pop_arg!(args, StructRef))?;
- Ok(smallvec![Value::struct_(Struct::pack(vec![Value::u128(
- value
- )]))])
- },
- Type::U64 => {
- let value = aggregator_snapshot_value_as_u64(&safely_pop_arg!(args, StructRef))?;
- Ok(smallvec![Value::struct_(Struct::pack(vec![Value::u64(
- value
- )]))])
- },
- _ => {
- // Check if the type is a string
- if is_string_type(context, &ty_args[0])? {
- let value = aggregator_snapshot_value_as_bytes(&safely_pop_arg!(args, StructRef))?;
- let move_string_value = Value::struct_(Struct::pack(vec![Value::vector_u8(value)]));
- let move_snapshot_value = Value::struct_(Struct::pack(vec![move_string_value]));
- return Ok(smallvec![move_snapshot_value]);
- }
- // If not a string, return an error
- Err(SafeNativeError::Abort {
- abort_code: EUNSUPPORTED_AGGREGATOR_SNAPSHOT_TYPE,
- })
- },
- }
+ let snapshot_type = SnapshotType::from_ty_arg(context, &ty_args[0])?;
+ let input = snapshot_type.pop_snapshot_value_by_type(&mut args)?;
+
+ let result_value = input;
+
+ Ok(smallvec![Value::struct_(Struct::pack(vec![
+ snapshot_type.create_snapshot_value_by_type(result_value)?
+ ]))])
}
/***************************************************************************************************
- * native fun read_snapshot(snapshot: AggregatorSnapshot): Element;
+ * native fun copy_snapshot(snapshot: &AggregatorSnapshot): AggregatorSnapshot
+ **************************************************************************************************/
+
+fn native_copy_snapshot(
+ context: &mut SafeNativeContext,
+ _ty_args: Vec,
+ _args: VecDeque,
+) -> SafeNativeResult> {
+ abort_if_not_enabled!(context);
+ Err(SafeNativeError::Abort {
+ abort_code: EAGGREGATOR_FUNCTION_NOT_YET_SUPPORTED,
+ })
+
+ // debug_assert_eq!(ty_args.len(), 1);
+ // debug_assert_eq!(args.len(), 1);
+ // context.charge(AGGREGATOR_V2_COPY_SNAPSHOT_BASE)?;
+
+ // let snapshot_type = SnapshotType::from_ty_arg(context, &ty_args[0])?;
+ // let snapshot_value = snapshot_type.pop_snapshot_field_by_type(&mut args)?;
+
+ // let result_value = snapshot_value;
+
+ // Ok(smallvec![Value::struct_(Struct::pack(vec![
+ // snapshot_type.create_snapshot_value_by_type(result_value)?
+ // ]))])
+}
+
+/***************************************************************************************************
+ * native fun read_snapshot(snapshot: &AggregatorSnapshot): Element;
**************************************************************************************************/
fn native_read_snapshot(
@@ -138,36 +490,24 @@ fn native_read_snapshot(
ty_args: Vec,
mut args: VecDeque,
) -> SafeNativeResult> {
+ abort_if_not_enabled!(context);
+
debug_assert_eq!(ty_args.len(), 1);
debug_assert_eq!(args.len(), 1);
context.charge(AGGREGATOR_V2_READ_SNAPSHOT_BASE)?;
- match ty_args[0] {
- Type::U128 => {
- let value = aggregator_snapshot_value_as_u128(&safely_pop_arg!(args, StructRef))?;
- Ok(smallvec![Value::u128(value)])
- },
- Type::U64 => {
- let value = aggregator_snapshot_value_as_u64(&safely_pop_arg!(args, StructRef))?;
- Ok(smallvec![Value::u64(value)])
- },
- _ => {
- // Check if the type is a string
- if is_string_type(context, &ty_args[0])? {
- let value = aggregator_snapshot_value_as_bytes(&safely_pop_arg!(args, StructRef))?;
- let move_string_value = Value::struct_(Struct::pack(vec![Value::vector_u8(value)]));
- return Ok(smallvec![move_string_value]);
- }
- // If not a string, return an error
- Err(SafeNativeError::Abort {
- abort_code: EUNSUPPORTED_AGGREGATOR_SNAPSHOT_TYPE,
- })
- },
- }
+ let snapshot_type = SnapshotType::from_ty_arg(context, &ty_args[0])?;
+ let snapshot_value = snapshot_type.pop_snapshot_field_by_type(&mut args)?;
+
+ let result_value = snapshot_value;
+
+ Ok(smallvec![
+ snapshot_type.create_snapshot_value_by_type(result_value)?
+ ])
}
/***************************************************************************************************
- * native fun native fun string_concat(before: String, snapshot: &AggregatorSnapshot, after: String): AggregatorSnapshot;
+ * native fun string_concat(before: String, snapshot: &AggregatorSnapshot, after: String): AggregatorSnapshot;
**************************************************************************************************/
fn native_string_concat(
@@ -175,41 +515,54 @@ fn native_string_concat(
ty_args: Vec,
mut args: VecDeque,
) -> SafeNativeResult> {
+ abort_if_not_enabled!(context);
+
debug_assert_eq!(ty_args.len(), 1);
debug_assert_eq!(args.len(), 3);
context.charge(AGGREGATOR_V2_STRING_CONCAT_BASE)?;
- let after = string_to_bytes(safely_pop_arg!(args, Struct))?;
+ let snapshot_input_type = SnapshotType::from_ty_arg(context, &ty_args[0])?;
- let snapshot_value = match ty_args[0] {
- Type::U128 => {
- let value = aggregator_snapshot_value_as_u128(&safely_pop_arg!(args, StructRef))?;
- Ok(value.to_string().into_bytes())
- },
- Type::U64 => {
- let value = aggregator_snapshot_value_as_u64(&safely_pop_arg!(args, StructRef))?;
- Ok(value.to_string().into_bytes())
- },
- _ => {
- // Check if the type is a string
- if is_string_type(context, &ty_args[0])? {
- Ok(aggregator_snapshot_value_as_bytes(&safely_pop_arg!(
- args, StructRef
- ))?)
- } else {
- Err(SafeNativeError::Abort {
- abort_code: EUNSUPPORTED_AGGREGATOR_SNAPSHOT_TYPE,
- })
- }
+ // Concat works only with integer snapshot types
+ // This is to avoid unnecessary recursive snapshot dependencies
+ if !matches!(snapshot_input_type, SnapshotType::U128 | SnapshotType::U64) {
+ return Err(SafeNativeError::Abort {
+ abort_code: EUNSUPPORTED_AGGREGATOR_SNAPSHOT_TYPE,
+ });
+ }
+
+ // popping arguments from the end
+ let suffix = string_to_bytes(safely_pop_arg!(args, Struct))?;
+ let snapshot_value = match snapshot_input_type.pop_snapshot_field_by_type(&mut args)? {
+ SnapshotValue::Integer(v) => v,
+ SnapshotValue::String(_) => {
+ return Err(SafeNativeError::Abort {
+ abort_code: EUNSUPPORTED_AGGREGATOR_SNAPSHOT_TYPE,
+ })
},
- }?;
- let before = string_to_bytes(safely_pop_arg!(args, Struct))?;
- let mut result = before.clone();
- result.extend(&snapshot_value);
- result.extend(&after);
- let move_string_value = Value::struct_(Struct::pack(vec![Value::vector_u8(result)]));
- let move_snapshot_value = Value::struct_(Struct::pack(vec![move_string_value]));
- Ok(smallvec![move_snapshot_value])
+ };
+
+ let prefix = string_to_bytes(safely_pop_arg!(args, Struct))?;
+
+ if prefix
+ .len()
+ .checked_add(suffix.len())
+ .map_or(false, |v| v > CONCAT_PREFIX_AND_SUFFIX_MAX_LENGTH)
+ {
+ return Err(SafeNativeError::Abort {
+ abort_code: ECONCAT_STRING_LENGTH_TOO_LARGE,
+ });
+ }
+
+ context.charge(STRING_UTILS_PER_BYTE * NumBytes::new((prefix.len() + suffix.len()) as u64))?;
+
+ let result_value = SnapshotValue::String(
+ SnapshotToStringFormula::Concat { prefix, suffix }.apply_to(snapshot_value),
+ );
+
+ Ok(smallvec![Value::struct_(Struct::pack(vec![
+ SnapshotType::String.create_snapshot_value_by_type(result_value)?
+ ]))])
}
/***************************************************************************************************
@@ -220,7 +573,19 @@ pub fn make_all(
builder: &SafeNativeBuilder,
) -> impl Iterator- + '_ {
let natives = [
- ("create_snapshot", native_create_snapshot as RawSafeNative),
+ (
+ "create_aggregator",
+ native_create_aggregator as RawSafeNative,
+ ),
+ (
+ "create_unbounded_aggregator",
+ native_create_unbounded_aggregator,
+ ),
+ ("try_add", native_try_add),
+ ("read", native_read),
+ ("try_sub", native_try_sub),
+ ("snapshot", native_snapshot),
+ ("create_snapshot", native_create_snapshot),
("copy_snapshot", native_copy_snapshot),
("read_snapshot", native_read_snapshot),
("string_concat", native_string_concat),
diff --git a/aptos-move/framework/src/natives/aggregator_natives/helpers_v2.rs b/aptos-move/framework/src/natives/aggregator_natives/helpers_v2.rs
index a4894d9ad2907..4e1f09088fe18 100644
--- a/aptos-move/framework/src/natives/aggregator_natives/helpers_v2.rs
+++ b/aptos-move/framework/src/natives/aggregator_natives/helpers_v2.rs
@@ -1,45 +1,80 @@
// Copyright © Aptos Foundation
// SPDX-License-Identifier: Apache-2.0
-use crate::natives::aggregator_natives::helpers::get_aggregator_field;
-use aptos_aggregator::aggregator_extension::extension_error;
+
use move_binary_format::errors::PartialVMResult;
-use move_vm_types::values::{Struct, StructRef, Value};
+use move_vm_types::{
+ natives::function::{PartialVMError, StatusCode},
+ values::{Reference, Struct, StructRef, Value},
+};
+/// Indices of `value` and `limit` fields in the `Aggregator` Move
+/// struct.
const VALUE_FIELD_INDEX: usize = 0;
+const LIMIT_FIELD_INDEX: usize = 1;
-/// Returns ID of aggregator snapshot based on a reference to `AggregatorSnapshot` Move struct.
-pub(crate) fn aggregator_snapshot_value_as_u128(
- aggregator_snapshot: &StructRef,
-) -> PartialVMResult
{
- let value = get_aggregator_field(aggregator_snapshot, VALUE_FIELD_INDEX)?.value_as::()?;
- Ok(value)
+/// Given a reference to `Aggregator` Move struct, returns a tuple of its
+/// fields: (`value`, `limit`).
+pub fn get_aggregator_fields_u128(aggregator: &StructRef) -> PartialVMResult<(u128, u128)> {
+ let value = get_aggregator_field(aggregator, VALUE_FIELD_INDEX)?.value_as::()?;
+ let limit = get_aggregator_field(aggregator, LIMIT_FIELD_INDEX)?.value_as::()?;
+ Ok((value, limit))
+}
+
+pub fn set_aggregator_value_field(aggregator: &StructRef, value: Value) -> PartialVMResult<()> {
+ set_aggregator_field(aggregator, VALUE_FIELD_INDEX, value)
+}
+
+/// Given a reference to `Aggregator` Move struct, returns a tuple of its
+/// fields: (`value`, `limit`).
+pub fn get_aggregator_fields_u64(aggregator: &StructRef) -> PartialVMResult<(u64, u64)> {
+ let value = get_aggregator_field(aggregator, VALUE_FIELD_INDEX)?.value_as::()?;
+ let limit = get_aggregator_field(aggregator, LIMIT_FIELD_INDEX)?.value_as::()?;
+ Ok((value, limit))
}
/// Returns ID of aggregator snapshot based on a reference to `AggregatorSnapshot` Move struct.
-pub(crate) fn aggregator_snapshot_value_as_u64(
+pub(crate) fn aggregator_snapshot_field_value(
aggregator_snapshot: &StructRef,
-) -> PartialVMResult {
- let value = get_aggregator_field(aggregator_snapshot, VALUE_FIELD_INDEX)?.value_as::()?;
- Ok(value)
+) -> PartialVMResult {
+ get_aggregator_field(aggregator_snapshot, VALUE_FIELD_INDEX)
}
-pub(crate) fn aggregator_snapshot_value_as_bytes(
- aggregator_snapshot: &StructRef,
-) -> PartialVMResult> {
- get_aggregator_field(aggregator_snapshot, VALUE_FIELD_INDEX)?
- .value_as::()?
- .unpack()?
- .collect::>()
- .pop()
- .map_or(
- Err(extension_error("unable to pop string field in snapshot")),
- |v| v.value_as::>(),
- )
-}
-
-pub(crate) fn string_to_bytes(string_value: Struct) -> PartialVMResult> {
- string_value.unpack()?.collect::>().pop().map_or(
- Err(extension_error("unable to extract string value")),
+// ================= START TEMPORARY CODE =================
+// TODO: aggregator_v2 branch will introduce these in different places in code
+
+/// Given a reference to `Aggregator` Move struct returns a field value at `index`.
+pub(crate) fn get_aggregator_field(aggregator: &StructRef, index: usize) -> PartialVMResult {
+ let field_ref = aggregator.borrow_field(index)?.value_as::()?;
+ field_ref.read_ref()
+}
+
+/// Given a reference to `Aggregator` Move struct, updates a field value at `index`.
+pub(crate) fn set_aggregator_field(
+ aggregator: &StructRef,
+ index: usize,
+ value: Value,
+) -> PartialVMResult<()> {
+ let field_ref = aggregator.borrow_field(index)?.value_as::()?;
+ field_ref.write_ref(value)
+}
+
+pub fn string_to_bytes(value: Struct) -> PartialVMResult> {
+ value.unpack()?.collect::>().pop().map_or(
+ Err(PartialVMError::new(StatusCode::VM_EXTENSION_ERROR)
+ .with_message("Unable to extract bytes from String".to_string())),
|v| v.value_as::>(),
)
}
+
+pub fn to_utf8_bytes(value: impl ToString) -> Vec {
+ value.to_string().into_bytes()
+}
+
+pub fn u128_to_u64(value: u128) -> PartialVMResult {
+ u64::try_from(value).map_err(|_| {
+ PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
+ .with_message("Cannot cast u128 into u64".to_string())
+ })
+}
+
+// ================= END TEMPORARY CODE =================
diff --git a/aptos-move/vm-genesis/src/lib.rs b/aptos-move/vm-genesis/src/lib.rs
index 5a9f82d14d726..0ac28fcae0d11 100644
--- a/aptos-move/vm-genesis/src/lib.rs
+++ b/aptos-move/vm-genesis/src/lib.rs
@@ -427,6 +427,7 @@ pub fn default_features() -> Vec {
FeatureFlag::EMIT_FEE_STATEMENT,
FeatureFlag::STORAGE_DELETION_REFUND,
FeatureFlag::SIGNATURE_CHECKER_V2_SCRIPT_FIX,
+ FeatureFlag::AGGREGATOR_V2_API,
FeatureFlag::SAFER_RESOURCE_GROUPS,
FeatureFlag::SAFER_METADATA,
FeatureFlag::SINGLE_SENDER_AUTHENTICATOR,
diff --git a/types/src/on_chain_config/aptos_features.rs b/types/src/on_chain_config/aptos_features.rs
index bf2c2686237ee..79d9a039243a2 100644
--- a/types/src/on_chain_config/aptos_features.rs
+++ b/types/src/on_chain_config/aptos_features.rs
@@ -57,7 +57,7 @@ pub struct Features {
impl Default for Features {
fn default() -> Self {
Features {
- features: vec![0b00100000, 0b00100000, 0b00001100, 0b00100000],
+ features: vec![0b00100000, 0b00100000, 0b00001100, 0b01100000],
}
}
}