Skip to content

Commit bfb71dd

Browse files
committed
add u64 overflow prevention
1 parent ee1280f commit bfb71dd

File tree

11 files changed

+345
-50
lines changed

11 files changed

+345
-50
lines changed

Cargo.lock

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ msim-macros = { git = "https://github.com/MystenLabs/mysten-sim.git", rev = "427
440440
multiaddr = "0.17.0"
441441
nexlint = { git = "https://github.com/nextest-rs/nexlint.git", rev = "7ce56bd591242a57660ed05f14ca2483c37d895b" }
442442
nexlint-lints = { git = "https://github.com/nextest-rs/nexlint.git", rev = "7ce56bd591242a57660ed05f14ca2483c37d895b" }
443-
nonempty = "0.9.0"
443+
nonempty = { version = "0.9.0", features = ["serialize"] }
444444
nonzero_ext = "0.3.0"
445445
notify = "6.1.1"
446446
ntest = "0.9.0"
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright (c) Mysten Labs, Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Test demonstrating arithmetic overflow protection when withdrawing from address balances.
5+
// Creates two independent Supply objects, mints 18446744073709551614 (u64::MAX - 1) from each,
6+
// sends both amounts to address A, then attempts to withdraw both amounts in a single PTB.
7+
// The withdraw operation fails with arithmetic error because the total exceeds u64::MAX.
8+
9+
//# init --addresses test=0x0 --accounts A B --enable-accumulators --simulator
10+
11+
//# publish --sender A
12+
module test::large_balance {
13+
use sui::balance::{Self, Supply};
14+
15+
public struct MARKER has drop {}
16+
17+
public struct SupplyHolder has key, store {
18+
id: UID,
19+
supply: Supply<MARKER>,
20+
}
21+
22+
public fun create_holder(ctx: &mut TxContext): SupplyHolder {
23+
SupplyHolder {
24+
id: object::new(ctx),
25+
supply: balance::create_supply(MARKER {}),
26+
}
27+
}
28+
29+
public fun send_large_balance(holder: &mut SupplyHolder, recipient: address, amount: u64) {
30+
let balance = holder.supply.increase_supply(amount);
31+
balance::send_funds(balance, recipient);
32+
}
33+
}
34+
35+
// Create two supply holders
36+
//# programmable --sender A --inputs @A
37+
//> 0: test::large_balance::create_holder();
38+
//> 1: test::large_balance::create_holder();
39+
//> TransferObjects([Result(0), Result(1)], Input(0))
40+
41+
// Send two large transfers in a single PTB - should cause Move abort due to overflow
42+
//# programmable --sender A --inputs object(2,0) object(2,1) @A 18446744073709551614
43+
//> 0: test::large_balance::send_large_balance(Input(0), Input(2), Input(3));
44+
//> 1: test::large_balance::send_large_balance(Input(1), Input(2), Input(3));
45+
46+
//# create-checkpoint
47+
48+
// Send first large amount separately - should succeed
49+
//# run test::large_balance::send_large_balance --args object(2,0) @A 18446744073709551614 --sender A
50+
51+
//# create-checkpoint
52+
53+
// Send second large amount separately - should succeed
54+
//# run test::large_balance::send_large_balance --args object(2,1) @A 18446744073709551614 --sender A
55+
56+
//# create-checkpoint
57+
58+
// Withdraw first large amount - should succeed
59+
//# programmable --sender A --inputs withdraw<sui::balance::Balance<test::large_balance::MARKER>>(18446744073709551614) @B
60+
//> 0: sui::balance::redeem_funds<test::large_balance::MARKER>(Input(0));
61+
//> 1: sui::balance::send_funds<test::large_balance::MARKER>(Result(0), Input(1));
62+
63+
//# create-checkpoint
64+
65+
// Withdraw second large amount - should succeed
66+
//# programmable --sender A --inputs withdraw<sui::balance::Balance<test::large_balance::MARKER>>(18446744073709551614) @B
67+
//> 0: sui::balance::redeem_funds<test::large_balance::MARKER>(Input(0));
68+
//> 1: sui::balance::send_funds<test::large_balance::MARKER>(Result(0), Input(1));
69+
70+
//# create-checkpoint
71+
72+
// Attempt to withdraw both large amounts in a single PTB - should fail with arithmetic error
73+
//# programmable --sender A --inputs withdraw<sui::balance::Balance<test::large_balance::MARKER>>(18446744073709551614) withdraw<sui::balance::Balance<test::large_balance::MARKER>>(18446744073709551614) @B
74+
//> 0: sui::balance::redeem_funds<test::large_balance::MARKER>(Input(0));
75+
//> 1: sui::balance::redeem_funds<test::large_balance::MARKER>(Input(1));
76+
//> 2: sui::balance::join<test::large_balance::MARKER>(Result(0), Result(1));
77+
//> 3: sui::balance::send_funds<test::large_balance::MARKER>(Result(0), Input(2));
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
---
2+
source: external-crates/move/crates/move-transactional-test-runner/src/framework.rs
3+
---
4+
processed 14 tasks
5+
6+
init:
7+
A: object(0,0), B: object(0,1)
8+
9+
task 1, lines 11-35:
10+
//# publish --sender A
11+
created: object(1,0)
12+
mutated: object(0,0)
13+
gas summary: computation_cost: 1000000, storage_cost: 7311200, storage_rebate: 0, non_refundable_storage_fee: 0
14+
15+
task 2, lines 36-41:
16+
//# programmable --sender A --inputs @A
17+
//> 0: test::large_balance::create_holder();
18+
//> 1: test::large_balance::create_holder();
19+
//> TransferObjects([Result(0), Result(1)], Input(0))
20+
// Send two large transfers in a single PTB - should cause Move abort due to overflow
21+
created: object(2,0), object(2,1)
22+
mutated: object(0,0)
23+
gas summary: computation_cost: 1000000, storage_cost: 3876000, storage_rebate: 978120, non_refundable_storage_fee: 9880
24+
25+
task 3, lines 42-44:
26+
//# programmable --sender A --inputs object(2,0) object(2,1) @A 18446744073709551614
27+
//> 0: test::large_balance::send_large_balance(Input(0), Input(2), Input(3));
28+
//> 1: test::large_balance::send_large_balance(Input(1), Input(2), Input(3));
29+
Error: Transaction Effects Status: Move Primitive Runtime Error. Location: sui::funds_accumulator::add_to_accumulator_address (function index 8) at offset 0. Arithmetic error, stack overflow, max value depth, etc.
30+
Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MovePrimitiveRuntimeError(MoveLocationOpt(Some(MoveLocation { module: ModuleId { address: sui, name: Identifier("funds_accumulator") }, function: 8, instruction: 0, function_name: Some("add_to_accumulator_address") }))), source: Some(VMError { major_status: ARITHMETIC_ERROR, sub_status: None, message: Some("accumulator merge overflow: total merges 36893488147419103228 exceed u64::MAX"), exec_state: None, location: Module(ModuleId { address: sui, name: Identifier("funds_accumulator") }), indices: [], offsets: [(FunctionDefinitionIndex(8), 0)] }), command: Some(1) } }
31+
32+
task 4, lines 46-48:
33+
//# create-checkpoint
34+
Checkpoint created: 1
35+
36+
task 5, line 49:
37+
//# run test::large_balance::send_large_balance --args object(2,0) @A 18446744073709551614 --sender A
38+
mutated: object(0,0), object(2,0)
39+
unchanged_shared: 0x0000000000000000000000000000000000000000000000000000000000000403
40+
accumulators_written: (object(5,0), A, sui::balance::Balance<test::large_balance::MARKER>, Merge)
41+
gas summary: computation_cost: 1000000, storage_cost: 2432000, storage_rebate: 2407680, non_refundable_storage_fee: 24320
42+
43+
task 6, lines 51-53:
44+
//# create-checkpoint
45+
Checkpoint created: 2
46+
47+
task 7, line 54:
48+
//# run test::large_balance::send_large_balance --args object(2,1) @A 18446744073709551614 --sender A
49+
mutated: object(0,0), object(2,1)
50+
unchanged_shared: 0x0000000000000000000000000000000000000000000000000000000000000403
51+
accumulators_written: (object(5,0), A, sui::balance::Balance<test::large_balance::MARKER>, Merge)
52+
gas summary: computation_cost: 1000000, storage_cost: 2432000, storage_rebate: 2407680, non_refundable_storage_fee: 24320
53+
54+
task 8, lines 56-58:
55+
//# create-checkpoint
56+
Checkpoint created: 3
57+
58+
task 9, lines 59-61:
59+
//# programmable --sender A --inputs withdraw<sui::balance::Balance<test::large_balance::MARKER>>(18446744073709551614) @B
60+
//> 0: sui::balance::redeem_funds<test::large_balance::MARKER>(Input(0));
61+
//> 1: sui::balance::send_funds<test::large_balance::MARKER>(Result(0), Input(1));
62+
mutated: object(0,0)
63+
unchanged_shared: 0x0000000000000000000000000000000000000000000000000000000000000403
64+
accumulators_written: (object(5,0), A, sui::balance::Balance<test::large_balance::MARKER>, Split), (object(9,0), B, sui::balance::Balance<test::large_balance::MARKER>, Merge)
65+
gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 978120, non_refundable_storage_fee: 9880
66+
67+
task 10, lines 63-65:
68+
//# create-checkpoint
69+
Checkpoint created: 4
70+
71+
task 11, lines 66-68:
72+
//# programmable --sender A --inputs withdraw<sui::balance::Balance<test::large_balance::MARKER>>(18446744073709551614) @B
73+
//> 0: sui::balance::redeem_funds<test::large_balance::MARKER>(Input(0));
74+
//> 1: sui::balance::send_funds<test::large_balance::MARKER>(Result(0), Input(1));
75+
mutated: object(0,0)
76+
unchanged_shared: 0x0000000000000000000000000000000000000000000000000000000000000403
77+
accumulators_written: (object(5,0), A, sui::balance::Balance<test::large_balance::MARKER>, Split), (object(9,0), B, sui::balance::Balance<test::large_balance::MARKER>, Merge)
78+
gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 978120, non_refundable_storage_fee: 9880
79+
80+
task 12, lines 70-72:
81+
//# create-checkpoint
82+
Checkpoint created: 5
83+
84+
task 13, lines 73-77:
85+
//# programmable --sender A --inputs withdraw<sui::balance::Balance<test::large_balance::MARKER>>(18446744073709551614) withdraw<sui::balance::Balance<test::large_balance::MARKER>>(18446744073709551614) @B
86+
//> 0: sui::balance::redeem_funds<test::large_balance::MARKER>(Input(0));
87+
//> 1: sui::balance::redeem_funds<test::large_balance::MARKER>(Input(1));
88+
//> 2: sui::balance::join<test::large_balance::MARKER>(Result(0), Result(1));
89+
//> 3: sui::balance::send_funds<test::large_balance::MARKER>(Result(0), Input(2));
90+
Error: Transaction Effects Status: Move Primitive Runtime Error. Location: sui::funds_accumulator::withdraw_from_accumulator_address (function index 9) at offset 0. Arithmetic error, stack overflow, max value depth, etc.
91+
Debug of error: MovePrimitiveRuntimeError(MoveLocationOpt(Some(MoveLocation { module: ModuleId { address: sui, name: Identifier("funds_accumulator") }, function: 9, instruction: 0, function_name: Some("withdraw_from_accumulator_address") }))) at command Some(1)
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
---
2+
source: external-crates/move/crates/move-transactional-test-runner/src/framework.rs
3+
---
4+
processed 14 tasks
5+
6+
init:
7+
A: object(0,0), B: object(0,1)
8+
9+
task 1, lines 11-35:
10+
//# publish --sender A
11+
created: object(1,0)
12+
mutated: object(0,0)
13+
gas summary: computation_cost: 1000000, storage_cost: 7311200, storage_rebate: 0, non_refundable_storage_fee: 0
14+
15+
task 2, lines 36-41:
16+
//# programmable --sender A --inputs @A
17+
//> 0: test::large_balance::create_holder();
18+
//> 1: test::large_balance::create_holder();
19+
//> TransferObjects([Result(0), Result(1)], Input(0))
20+
// Send two large transfers in a single PTB - should cause Move abort due to overflow
21+
created: object(2,0), object(2,1)
22+
mutated: object(0,0)
23+
gas summary: computation_cost: 1000000, storage_cost: 3876000, storage_rebate: 978120, non_refundable_storage_fee: 9880
24+
25+
task 3, lines 42-44:
26+
//# programmable --sender A --inputs object(2,0) object(2,1) @A 18446744073709551614
27+
//> 0: test::large_balance::send_large_balance(Input(0), Input(2), Input(3));
28+
//> 1: test::large_balance::send_large_balance(Input(1), Input(2), Input(3));
29+
Error: Transaction Effects Status: Move Primitive Runtime Error. Location: sui::funds_accumulator::add_to_accumulator_address (function index 8) at offset 0. Arithmetic error, stack overflow, max value depth, etc.
30+
Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MovePrimitiveRuntimeError(MoveLocationOpt(Some(MoveLocation { module: ModuleId { address: sui, name: Identifier("funds_accumulator") }, function: 8, instruction: 0, function_name: Some("add_to_accumulator_address") }))), source: Some(VMError { major_status: ARITHMETIC_ERROR, sub_status: None, message: Some("accumulator merge overflow: total merges 36893488147419103228 exceed u64::MAX"), exec_state: None, location: Module(ModuleId { address: sui, name: Identifier("funds_accumulator") }), indices: [], offsets: [(FunctionDefinitionIndex(8), 0)] }), command: Some(1) } }
31+
32+
task 4, lines 46-48:
33+
//# create-checkpoint
34+
Checkpoint created: 1
35+
36+
task 5, line 49:
37+
//# run test::large_balance::send_large_balance --args object(2,0) @A 18446744073709551614 --sender A
38+
mutated: object(0,0), object(2,0)
39+
unchanged_shared: 0x0000000000000000000000000000000000000000000000000000000000000403
40+
accumulators_written: (object(5,0), A, sui::balance::Balance<test::large_balance::MARKER>, Merge)
41+
gas summary: computation_cost: 1000000, storage_cost: 2432000, storage_rebate: 2407680, non_refundable_storage_fee: 24320
42+
43+
task 6, lines 51-53:
44+
//# create-checkpoint
45+
Checkpoint created: 2
46+
47+
task 7, line 54:
48+
//# run test::large_balance::send_large_balance --args object(2,1) @A 18446744073709551614 --sender A
49+
mutated: object(0,0), object(2,1)
50+
unchanged_shared: 0x0000000000000000000000000000000000000000000000000000000000000403
51+
accumulators_written: (object(5,0), A, sui::balance::Balance<test::large_balance::MARKER>, Merge)
52+
gas summary: computation_cost: 1000000, storage_cost: 2432000, storage_rebate: 2407680, non_refundable_storage_fee: 24320
53+
54+
task 8, lines 56-58:
55+
//# create-checkpoint
56+
Checkpoint created: 3
57+
58+
task 9, lines 59-61:
59+
//# programmable --sender A --inputs withdraw<sui::balance::Balance<test::large_balance::MARKER>>(18446744073709551614) @B
60+
//> 0: sui::balance::redeem_funds<test::large_balance::MARKER>(Input(0));
61+
//> 1: sui::balance::send_funds<test::large_balance::MARKER>(Result(0), Input(1));
62+
mutated: object(0,0)
63+
unchanged_shared: 0x0000000000000000000000000000000000000000000000000000000000000403
64+
accumulators_written: (object(5,0), A, sui::balance::Balance<test::large_balance::MARKER>, Split), (object(9,0), B, sui::balance::Balance<test::large_balance::MARKER>, Merge)
65+
gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 978120, non_refundable_storage_fee: 9880
66+
67+
task 10, lines 63-65:
68+
//# create-checkpoint
69+
Checkpoint created: 4
70+
71+
task 11, lines 66-68:
72+
//# programmable --sender A --inputs withdraw<sui::balance::Balance<test::large_balance::MARKER>>(18446744073709551614) @B
73+
//> 0: sui::balance::redeem_funds<test::large_balance::MARKER>(Input(0));
74+
//> 1: sui::balance::send_funds<test::large_balance::MARKER>(Result(0), Input(1));
75+
mutated: object(0,0)
76+
unchanged_shared: 0x0000000000000000000000000000000000000000000000000000000000000403
77+
accumulators_written: (object(5,0), A, sui::balance::Balance<test::large_balance::MARKER>, Split), (object(9,0), B, sui::balance::Balance<test::large_balance::MARKER>, Merge)
78+
gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 978120, non_refundable_storage_fee: 9880
79+
80+
task 12, lines 70-72:
81+
//# create-checkpoint
82+
Checkpoint created: 5
83+
84+
task 13, lines 73-77:
85+
//# programmable --sender A --inputs withdraw<sui::balance::Balance<test::large_balance::MARKER>>(18446744073709551614) withdraw<sui::balance::Balance<test::large_balance::MARKER>>(18446744073709551614) @B
86+
//> 0: sui::balance::redeem_funds<test::large_balance::MARKER>(Input(0));
87+
//> 1: sui::balance::redeem_funds<test::large_balance::MARKER>(Input(1));
88+
//> 2: sui::balance::join<test::large_balance::MARKER>(Result(0), Result(1));
89+
//> 3: sui::balance::send_funds<test::large_balance::MARKER>(Result(0), Input(2));
90+
Error: Transaction Effects Status: Move Primitive Runtime Error. Location: sui::funds_accumulator::withdraw_from_accumulator_address (function index 9) at offset 0. Arithmetic error, stack overflow, max value depth, etc.
91+
Debug of error: MovePrimitiveRuntimeError(MoveLocationOpt(Some(MoveLocation { module: ModuleId { address: sui, name: Identifier("funds_accumulator") }, function: 9, instruction: 0, function_name: Some("withdraw_from_accumulator_address") }))) at command Some(1)

crates/sui-json-rpc-types/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ serde_json.workspace = true
1515
serde_with.workspace = true
1616
colored.workspace = true
1717
itertools.workspace = true
18+
nonempty.workspace = true
1819
tracing.workspace = true
1920
bcs.workspace = true
2021
sui-protocol-config.workspace = true

crates/sui-json-rpc-types/src/sui_transaction.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use move_core_types::annotated_value::MoveTypeLayout;
2020
use move_core_types::identifier::{IdentStr, Identifier};
2121
use move_core_types::language_storage::{ModuleId, StructTag, TypeTag};
2222
use mysten_metrics::monitored_scope;
23+
use nonempty::NonEmpty;
2324
use sui_json::{SuiJsonValue, primitive_type};
2425
use sui_types::SUI_FRAMEWORK_ADDRESS;
2526
use sui_types::accumulator_event::AccumulatorEvent;
@@ -822,7 +823,8 @@ impl From<AccumulatorOperation> for SuiAccumulatorOperation {
822823
pub enum SuiAccumulatorValue {
823824
Integer(u64),
824825
IntegerTuple(u64, u64),
825-
EventDigest(Vec<(u64 /* event index in the transaction */, Digest)>),
826+
#[schemars(with = "Vec<(u64, Digest)>")]
827+
EventDigest(NonEmpty<(u64 /* event index in the transaction */, Digest)>),
826828
}
827829

828830
impl From<AccumulatorValue> for SuiAccumulatorValue {

0 commit comments

Comments
 (0)