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], } } }