From 515fb91ba68a4e6a440ba4ff7b3b673ccc93015b Mon Sep 17 00:00:00 2001 From: Timothy Zakian Date: Thu, 25 Apr 2024 11:41:48 -0700 Subject: [PATCH] [graphql] Add support for clever error resolution in graphql --- Cargo.lock | 1 + .../tests/errors/clever_errors.exp | 248 ++++++++++++++++++ .../tests/errors/clever_errors.move | 233 ++++++++++++++++ .../src/types/transaction_block_effects.rs | 128 ++++++++- crates/sui-package-resolver/Cargo.toml | 1 + crates/sui-package-resolver/src/lib.rs | 145 ++++++++++ .../src/test_adapter.rs | 2 +- 7 files changed, 747 insertions(+), 11 deletions(-) create mode 100644 crates/sui-graphql-e2e-tests/tests/errors/clever_errors.exp create mode 100644 crates/sui-graphql-e2e-tests/tests/errors/clever_errors.move diff --git a/Cargo.lock b/Cargo.lock index 85580c4b2edd70..3022beb5b3d788 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13331,6 +13331,7 @@ dependencies = [ "insta", "lru 0.10.0", "move-binary-format", + "move-command-line-common", "move-compiler", "move-core-types", "serde", diff --git a/crates/sui-graphql-e2e-tests/tests/errors/clever_errors.exp b/crates/sui-graphql-e2e-tests/tests/errors/clever_errors.exp new file mode 100644 index 00000000000000..7043217c4a1e64 --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/errors/clever_errors.exp @@ -0,0 +1,248 @@ +processed 29 tasks + +init: +A: object(0,0) + +task 1 'publish'. lines 6-81: +created: object(1,0), object(1,1) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 9477200, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 83-83: +Error: Transaction Effects Status: Move Runtime Abort. Location: P0::m::callU8 (function index 0) at offset 1, Abort Code: 9223372161408827393 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: P0, name: Identifier("m") }, function: 0, instruction: 1, function_name: Some("callU8") }, 9223372161408827393), source: Some(VMError { major_status: ABORTED, sub_status: Some(9223372161408827393), message: Some("P0::m::callU8 at offset 1"), exec_state: None, location: Module(ModuleId { address: P0, name: Identifier("m") }), indices: [], offsets: [(FunctionDefinitionIndex(0), 1)] }), command: Some(0) } } + +task 3 'run'. lines 85-85: +Error: Transaction Effects Status: Move Runtime Abort. Location: P0::m::callU16 (function index 1) at offset 1, Abort Code: 9223372174293860355 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: P0, name: Identifier("m") }, function: 1, instruction: 1, function_name: Some("callU16") }, 9223372174293860355), source: Some(VMError { major_status: ABORTED, sub_status: Some(9223372174293860355), message: Some("P0::m::callU16 at offset 1"), exec_state: None, location: Module(ModuleId { address: P0, name: Identifier("m") }), indices: [], offsets: [(FunctionDefinitionIndex(1), 1)] }), command: Some(0) } } + +task 4 'run'. lines 87-87: +Error: Transaction Effects Status: Move Runtime Abort. Location: P0::m::callU32 (function index 2) at offset 1, Abort Code: 9223372187178893317 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: P0, name: Identifier("m") }, function: 2, instruction: 1, function_name: Some("callU32") }, 9223372187178893317), source: Some(VMError { major_status: ABORTED, sub_status: Some(9223372187178893317), message: Some("P0::m::callU32 at offset 1"), exec_state: None, location: Module(ModuleId { address: P0, name: Identifier("m") }), indices: [], offsets: [(FunctionDefinitionIndex(2), 1)] }), command: Some(0) } } + +task 5 'run'. lines 89-89: +Error: Transaction Effects Status: Move Runtime Abort. Location: P0::m::callU64 (function index 3) at offset 1, Abort Code: 9223372200063926279 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: P0, name: Identifier("m") }, function: 3, instruction: 1, function_name: Some("callU64") }, 9223372200063926279), source: Some(VMError { major_status: ABORTED, sub_status: Some(9223372200063926279), message: Some("P0::m::callU64 at offset 1"), exec_state: None, location: Module(ModuleId { address: P0, name: Identifier("m") }), indices: [], offsets: [(FunctionDefinitionIndex(3), 1)] }), command: Some(0) } } + +task 6 'run'. lines 91-91: +Error: Transaction Effects Status: Move Runtime Abort. Location: P0::m::callU128 (function index 4) at offset 1, Abort Code: 9223372212948959241 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: P0, name: Identifier("m") }, function: 4, instruction: 1, function_name: Some("callU128") }, 9223372212948959241), source: Some(VMError { major_status: ABORTED, sub_status: Some(9223372212948959241), message: Some("P0::m::callU128 at offset 1"), exec_state: None, location: Module(ModuleId { address: P0, name: Identifier("m") }), indices: [], offsets: [(FunctionDefinitionIndex(4), 1)] }), command: Some(0) } } + +task 7 'run'. lines 93-93: +Error: Transaction Effects Status: Move Runtime Abort. Location: P0::m::callU256 (function index 5) at offset 1, Abort Code: 9223372225833992203 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: P0, name: Identifier("m") }, function: 5, instruction: 1, function_name: Some("callU256") }, 9223372225833992203), source: Some(VMError { major_status: ABORTED, sub_status: Some(9223372225833992203), message: Some("P0::m::callU256 at offset 1"), exec_state: None, location: Module(ModuleId { address: P0, name: Identifier("m") }), indices: [], offsets: [(FunctionDefinitionIndex(5), 1)] }), command: Some(0) } } + +task 8 'run'. lines 95-95: +Error: Transaction Effects Status: Move Runtime Abort. Location: P0::m::callAddress (function index 6) at offset 1, Abort Code: 9223372238719156239 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: P0, name: Identifier("m") }, function: 6, instruction: 1, function_name: Some("callAddress") }, 9223372238719156239), source: Some(VMError { major_status: ABORTED, sub_status: Some(9223372238719156239), message: Some("P0::m::callAddress at offset 1"), exec_state: None, location: Module(ModuleId { address: P0, name: Identifier("m") }), indices: [], offsets: [(FunctionDefinitionIndex(6), 1)] }), command: Some(0) } } + +task 9 'run'. lines 97-97: +Error: Transaction Effects Status: Move Runtime Abort. Location: P0::m::callString (function index 7) at offset 1, Abort Code: 9223372251604189201 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: P0, name: Identifier("m") }, function: 7, instruction: 1, function_name: Some("callString") }, 9223372251604189201), source: Some(VMError { major_status: ABORTED, sub_status: Some(9223372251604189201), message: Some("P0::m::callString at offset 1"), exec_state: None, location: Module(ModuleId { address: P0, name: Identifier("m") }), indices: [], offsets: [(FunctionDefinitionIndex(7), 1)] }), command: Some(0) } } + +task 10 'run'. lines 99-99: +Error: Transaction Effects Status: Move Runtime Abort. Location: P0::m::callBytevec (function index 8) at offset 1, Abort Code: 9223372264489222163 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: P0, name: Identifier("m") }, function: 8, instruction: 1, function_name: Some("callBytevec") }, 9223372264489222163), source: Some(VMError { major_status: ABORTED, sub_status: Some(9223372264489222163), message: Some("P0::m::callBytevec at offset 1"), exec_state: None, location: Module(ModuleId { address: P0, name: Identifier("m") }), indices: [], offsets: [(FunctionDefinitionIndex(8), 1)] }), command: Some(0) } } + +task 11 'run'. lines 101-101: +Error: Transaction Effects Status: Move Runtime Abort. Location: P0::m::normalAbort (function index 9) at offset 1, Abort Code: 0 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: P0, name: Identifier("m") }, function: 9, instruction: 1, function_name: Some("normalAbort") }, 0), source: Some(VMError { major_status: ABORTED, sub_status: Some(0), message: Some("P0::m::normalAbort at offset 1"), exec_state: None, location: Module(ModuleId { address: P0, name: Identifier("m") }), indices: [], offsets: [(FunctionDefinitionIndex(9), 1)] }), command: Some(0) } } + +task 12 'run'. lines 103-103: +Error: Transaction Effects Status: Move Runtime Abort. Location: P0::m::assertLineNo (function index 10) at offset 1, Abort Code: 9223372294552813567 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: P0, name: Identifier("m") }, function: 10, instruction: 1, function_name: Some("assertLineNo") }, 9223372294552813567), source: Some(VMError { major_status: ABORTED, sub_status: Some(9223372294552813567), message: Some("P0::m::assertLineNo at offset 1"), exec_state: None, location: Module(ModuleId { address: P0, name: Identifier("m") }), indices: [], offsets: [(FunctionDefinitionIndex(10), 1)] }), command: Some(0) } } + +task 13 'create-checkpoint'. lines 105-105: +Checkpoint created: 1 + +task 14 'run-graphql'. lines 107-117: +Response: { + "data": { + "transactionBlocks": { + "nodes": [ + { + "effects": { + "status": "FAILURE", + "errors": "Error in 1st command\n'ImAU8' from module '0xb9092b0c2580b96bded4464f1668697ff34aa53a3b62aef7be0a03d1fc9ea1e7::m' in function 'callU8' at source line 29\n0" + } + }, + { + "effects": { + "status": "FAILURE", + "errors": "Error in 1st command\n'ImAU16' from module '0xb9092b0c2580b96bded4464f1668697ff34aa53a3b62aef7be0a03d1fc9ea1e7::m' in function 'callU16' at source line 32\n0" + } + }, + { + "effects": { + "status": "FAILURE", + "errors": "Error in 1st command\n'ImAU32' from module '0xb9092b0c2580b96bded4464f1668697ff34aa53a3b62aef7be0a03d1fc9ea1e7::m' in function 'callU32' at source line 35\n0" + } + }, + { + "effects": { + "status": "FAILURE", + "errors": "Error in 1st command\n'ImAU64' from module '0xb9092b0c2580b96bded4464f1668697ff34aa53a3b62aef7be0a03d1fc9ea1e7::m' in function 'callU64' at source line 38\n0" + } + }, + { + "effects": { + "status": "FAILURE", + "errors": "Error in 1st command\n'ImAU128' from module '0xb9092b0c2580b96bded4464f1668697ff34aa53a3b62aef7be0a03d1fc9ea1e7::m' in function 'callU128' at source line 41\n0" + } + }, + { + "effects": { + "status": "FAILURE", + "errors": "Error in 1st command\n'ImAU256' from module '0xb9092b0c2580b96bded4464f1668697ff34aa53a3b62aef7be0a03d1fc9ea1e7::m' in function 'callU256' at source line 44\n0" + } + }, + { + "effects": { + "status": "FAILURE", + "errors": "Error in 1st command\n'ImAAddress' from module '0xb9092b0c2580b96bded4464f1668697ff34aa53a3b62aef7be0a03d1fc9ea1e7::m' in function 'callAddress' at source line 47\n0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + { + "effects": { + "status": "FAILURE", + "errors": "Error in 1st command\n'ImAString' from module '0xb9092b0c2580b96bded4464f1668697ff34aa53a3b62aef7be0a03d1fc9ea1e7::m' in function 'callString' at source line 50\nThis is a string" + } + }, + { + "effects": { + "status": "FAILURE", + "errors": "Error in 1st command\n'ImNotAString' from module '0xb9092b0c2580b96bded4464f1668697ff34aa53a3b62aef7be0a03d1fc9ea1e7::m' in function 'callBytevec' at source line 53\n\u0001\u0002\u0003\u0004\u0005" + } + }, + { + "effects": { + "status": "FAILURE", + "errors": "Error in 1st command\nMove Runtime Abort. Location: b9092b0c2580b96bded4464f1668697ff34aa53a3b62aef7be0a03d1fc9ea1e7::m::normalAbort (function index 9) at offset 1, Abort Code: 0" + } + }, + { + "effects": { + "status": "FAILURE", + "errors": "Error in 1st command\nError from module '0xb9092b0c2580b96bded4464f1668697ff34aa53a3b62aef7be0a03d1fc9ea1e7::m' in function 'assertLineNo' at source line 59" + } + } + ] + } + } +} + +task 15 'upgrade'. lines 119-198: +created: object(15,0) +mutated: object(0,0), object(1,1) +gas summary: computation_cost: 1000000, storage_cost: 9530400, storage_rebate: 2595780, non_refundable_storage_fee: 26220 + +task 16 'run'. lines 200-200: +Error: Transaction Effects Status: Move Runtime Abort. Location: fake(1,0)::m::callU8 (function index 0) at offset 1, Abort Code: 9223372659625033729 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: fake(1,0), name: Identifier("m") }, function: 0, instruction: 1, function_name: Some("callU8") }, 9223372659625033729), source: Some(VMError { major_status: ABORTED, sub_status: Some(9223372659625033729), message: Some("fake(1,0)::m::callU8 at offset 1"), exec_state: None, location: Module(ModuleId { address: fake(1,0), name: Identifier("m") }), indices: [], offsets: [(FunctionDefinitionIndex(0), 1)] }), command: Some(0) } } + +task 17 'run'. lines 202-202: +Error: Transaction Effects Status: Move Runtime Abort. Location: fake(1,0)::m::callU16 (function index 1) at offset 1, Abort Code: 9223372672510066691 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: fake(1,0), name: Identifier("m") }, function: 1, instruction: 1, function_name: Some("callU16") }, 9223372672510066691), source: Some(VMError { major_status: ABORTED, sub_status: Some(9223372672510066691), message: Some("fake(1,0)::m::callU16 at offset 1"), exec_state: None, location: Module(ModuleId { address: fake(1,0), name: Identifier("m") }), indices: [], offsets: [(FunctionDefinitionIndex(1), 1)] }), command: Some(0) } } + +task 18 'run'. lines 204-204: +Error: Transaction Effects Status: Move Runtime Abort. Location: fake(1,0)::m::callU32 (function index 2) at offset 1, Abort Code: 9223372685395099653 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: fake(1,0), name: Identifier("m") }, function: 2, instruction: 1, function_name: Some("callU32") }, 9223372685395099653), source: Some(VMError { major_status: ABORTED, sub_status: Some(9223372685395099653), message: Some("fake(1,0)::m::callU32 at offset 1"), exec_state: None, location: Module(ModuleId { address: fake(1,0), name: Identifier("m") }), indices: [], offsets: [(FunctionDefinitionIndex(2), 1)] }), command: Some(0) } } + +task 19 'run'. lines 206-206: +Error: Transaction Effects Status: Move Runtime Abort. Location: fake(1,0)::m::callU64 (function index 3) at offset 1, Abort Code: 9223372698280132615 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: fake(1,0), name: Identifier("m") }, function: 3, instruction: 1, function_name: Some("callU64") }, 9223372698280132615), source: Some(VMError { major_status: ABORTED, sub_status: Some(9223372698280132615), message: Some("fake(1,0)::m::callU64 at offset 1"), exec_state: None, location: Module(ModuleId { address: fake(1,0), name: Identifier("m") }), indices: [], offsets: [(FunctionDefinitionIndex(3), 1)] }), command: Some(0) } } + +task 20 'run'. lines 208-208: +Error: Transaction Effects Status: Move Runtime Abort. Location: fake(1,0)::m::callU128 (function index 4) at offset 1, Abort Code: 9223372711165165577 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: fake(1,0), name: Identifier("m") }, function: 4, instruction: 1, function_name: Some("callU128") }, 9223372711165165577), source: Some(VMError { major_status: ABORTED, sub_status: Some(9223372711165165577), message: Some("fake(1,0)::m::callU128 at offset 1"), exec_state: None, location: Module(ModuleId { address: fake(1,0), name: Identifier("m") }), indices: [], offsets: [(FunctionDefinitionIndex(4), 1)] }), command: Some(0) } } + +task 21 'run'. lines 210-210: +Error: Transaction Effects Status: Move Runtime Abort. Location: fake(1,0)::m::callU256 (function index 5) at offset 1, Abort Code: 9223372724050198539 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: fake(1,0), name: Identifier("m") }, function: 5, instruction: 1, function_name: Some("callU256") }, 9223372724050198539), source: Some(VMError { major_status: ABORTED, sub_status: Some(9223372724050198539), message: Some("fake(1,0)::m::callU256 at offset 1"), exec_state: None, location: Module(ModuleId { address: fake(1,0), name: Identifier("m") }), indices: [], offsets: [(FunctionDefinitionIndex(5), 1)] }), command: Some(0) } } + +task 22 'run'. lines 212-212: +Error: Transaction Effects Status: Move Runtime Abort. Location: fake(1,0)::m::callAddress (function index 6) at offset 1, Abort Code: 9223372736935362575 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: fake(1,0), name: Identifier("m") }, function: 6, instruction: 1, function_name: Some("callAddress") }, 9223372736935362575), source: Some(VMError { major_status: ABORTED, sub_status: Some(9223372736935362575), message: Some("fake(1,0)::m::callAddress at offset 1"), exec_state: None, location: Module(ModuleId { address: fake(1,0), name: Identifier("m") }), indices: [], offsets: [(FunctionDefinitionIndex(6), 1)] }), command: Some(0) } } + +task 23 'run'. lines 214-214: +Error: Transaction Effects Status: Move Runtime Abort. Location: fake(1,0)::m::callString (function index 7) at offset 1, Abort Code: 9223372749820395537 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: fake(1,0), name: Identifier("m") }, function: 7, instruction: 1, function_name: Some("callString") }, 9223372749820395537), source: Some(VMError { major_status: ABORTED, sub_status: Some(9223372749820395537), message: Some("fake(1,0)::m::callString at offset 1"), exec_state: None, location: Module(ModuleId { address: fake(1,0), name: Identifier("m") }), indices: [], offsets: [(FunctionDefinitionIndex(7), 1)] }), command: Some(0) } } + +task 24 'run'. lines 216-216: +Error: Transaction Effects Status: Move Runtime Abort. Location: fake(1,0)::m::callBytevec (function index 8) at offset 1, Abort Code: 9223372762705428499 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: fake(1,0), name: Identifier("m") }, function: 8, instruction: 1, function_name: Some("callBytevec") }, 9223372762705428499), source: Some(VMError { major_status: ABORTED, sub_status: Some(9223372762705428499), message: Some("fake(1,0)::m::callBytevec at offset 1"), exec_state: None, location: Module(ModuleId { address: fake(1,0), name: Identifier("m") }), indices: [], offsets: [(FunctionDefinitionIndex(8), 1)] }), command: Some(0) } } + +task 25 'run'. lines 218-218: +Error: Transaction Effects Status: Move Runtime Abort. Location: fake(1,0)::m::normalAbort (function index 9) at offset 1, Abort Code: 0 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: fake(1,0), name: Identifier("m") }, function: 9, instruction: 1, function_name: Some("normalAbort") }, 0), source: Some(VMError { major_status: ABORTED, sub_status: Some(0), message: Some("fake(1,0)::m::normalAbort at offset 1"), exec_state: None, location: Module(ModuleId { address: fake(1,0), name: Identifier("m") }), indices: [], offsets: [(FunctionDefinitionIndex(9), 1)] }), command: Some(0) } } + +task 26 'run'. lines 220-220: +Error: Transaction Effects Status: Move Runtime Abort. Location: fake(1,0)::m::assertLineNo (function index 10) at offset 1, Abort Code: 9223372792769019903 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: fake(1,0), name: Identifier("m") }, function: 10, instruction: 1, function_name: Some("assertLineNo") }, 9223372792769019903), source: Some(VMError { major_status: ABORTED, sub_status: Some(9223372792769019903), message: Some("fake(1,0)::m::assertLineNo at offset 1"), exec_state: None, location: Module(ModuleId { address: fake(1,0), name: Identifier("m") }), indices: [], offsets: [(FunctionDefinitionIndex(10), 1)] }), command: Some(0) } } + +task 27 'create-checkpoint'. lines 222-222: +Checkpoint created: 2 + +task 28 'run-graphql'. lines 224-234: +Response: { + "data": { + "transactionBlocks": { + "nodes": [ + { + "effects": { + "status": "FAILURE", + "errors": "Error in 1st command\n'ImAU32' from module '0x37bf9a78d7cedde7ae91de9f1a76a663146693a152b93e24aa7b96c3dc86b012::m' in function 'callU32' at source line 151\n1" + } + }, + { + "effects": { + "status": "FAILURE", + "errors": "Error in 1st command\n'ImAU64' from module '0x37bf9a78d7cedde7ae91de9f1a76a663146693a152b93e24aa7b96c3dc86b012::m' in function 'callU64' at source line 154\n1" + } + }, + { + "effects": { + "status": "FAILURE", + "errors": "Error in 1st command\n'ImAU128' from module '0x37bf9a78d7cedde7ae91de9f1a76a663146693a152b93e24aa7b96c3dc86b012::m' in function 'callU128' at source line 157\n1" + } + }, + { + "effects": { + "status": "FAILURE", + "errors": "Error in 1st command\n'ImAU256' from module '0x37bf9a78d7cedde7ae91de9f1a76a663146693a152b93e24aa7b96c3dc86b012::m' in function 'callU256' at source line 160\n1" + } + }, + { + "effects": { + "status": "FAILURE", + "errors": "Error in 1st command\n'ImAAddress' from module '0x37bf9a78d7cedde7ae91de9f1a76a663146693a152b93e24aa7b96c3dc86b012::m' in function 'callAddress' at source line 163\n0x0000000000000000000000000000000000000000000000000000000000000001" + } + }, + { + "effects": { + "status": "FAILURE", + "errors": "Error in 1st command\n'ImAString' from module '0x37bf9a78d7cedde7ae91de9f1a76a663146693a152b93e24aa7b96c3dc86b012::m' in function 'callString' at source line 166\nThis is a string in v2" + } + }, + { + "effects": { + "status": "FAILURE", + "errors": "Error in 1st command\n'ImNotAString' from module '0x37bf9a78d7cedde7ae91de9f1a76a663146693a152b93e24aa7b96c3dc86b012::m' in function 'callBytevec' at source line 169\n\u0001\u0002\u0003\u0004\u0005\u0006" + } + }, + { + "effects": { + "status": "FAILURE", + "errors": "Error in 1st command\nMove Runtime Abort. Location: 37bf9a78d7cedde7ae91de9f1a76a663146693a152b93e24aa7b96c3dc86b012::m::normalAbort (function index 9) at offset 1, Abort Code: 0" + } + }, + { + "effects": { + "status": "FAILURE", + "errors": "Error in 1st command\nError from module '0x37bf9a78d7cedde7ae91de9f1a76a663146693a152b93e24aa7b96c3dc86b012::m' in function 'assertLineNo' at source line 175" + } + } + ] + } + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/errors/clever_errors.move b/crates/sui-graphql-e2e-tests/tests/errors/clever_errors.move new file mode 100644 index 00000000000000..f3d3fbd529f360 --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/errors/clever_errors.move @@ -0,0 +1,233 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --protocol-version 39 --addresses P0=0x0 P1=0x0 --accounts A --simulator + +//# publish --upgradeable --sender A +module P0::m { + #[error] + const ImAU8: u8 = 0; + + #[error] + const ImAU16: u16 = 0; + + #[error] + const ImAU32: u32 = 0; + + #[error] + const ImAU64: u64 = 0; + + #[error] + const ImAU128: u128 = 0; + + #[error] + const ImAU256: u256 = 0; + + #[error] + const ImABool: bool = true; + + #[error] + const ImAAddress: address = @0; + + #[error] + const ImAString: vector = b"This is a string"; + + #[error] + const ImNotAString: vector = vector[1,2,3,4,5]; + + public fun callU8() { + abort ImAU8 + } + + public fun callU16() { + abort ImAU16 + } + + public fun callU32() { + abort ImAU32 + } + + public fun callU64() { + abort ImAU64 + } + + public fun callU128() { + abort ImAU128 + } + + public fun callU256() { + abort ImAU256 + } + + public fun callAddress() { + abort ImAAddress + } + + public fun callString() { + abort ImAString + } + + public fun callBytevec() { + abort ImNotAString + } + + public fun normalAbort() { + abort 0 + } + + public fun assertLineNo() { + assert!(false); + } +} + +//# run P0::m::callU8 + +//# run P0::m::callU16 + +//# run P0::m::callU32 + +//# run P0::m::callU64 + +//# run P0::m::callU128 + +//# run P0::m::callU256 + +//# run P0::m::callAddress + +//# run P0::m::callString + +//# run P0::m::callBytevec + +//# run P0::m::normalAbort + +//# run P0::m::assertLineNo + +//# create-checkpoint + +//# run-graphql +{ + transactionBlocks(last: 11) { + nodes { + effects { + status + errors + } + } + } +} + +//# upgrade --package P0 --upgrade-capability 1,1 --sender A +// Upgrade the module with new error values but using the same constant names +// (etc) to make sure we properly resolve the module location for clever +// errors. +module P0::m { + #[error] + const ImAU8: u8 = 1; + + #[error] + const ImAU16: u16 = 1; + + #[error] + const ImAU32: u32 = 1; + + #[error] + const ImAU64: u64 = 1; + + #[error] + const ImAU128: u128 = 1; + + #[error] + const ImAU256: u256 = 1; + + #[error] + const ImABool: bool = false; + + #[error] + const ImAAddress: address = @1; + + #[error] + const ImAString: vector = b"This is a string in v2"; + + #[error] + const ImNotAString: vector = vector[1,2,3,4,5, 6]; + + public fun callU8() { + abort ImAU8 + } + + public fun callU16() { + abort ImAU16 + } + + public fun callU32() { + abort ImAU32 + } + + public fun callU64() { + abort ImAU64 + } + + public fun callU128() { + abort ImAU128 + } + + public fun callU256() { + abort ImAU256 + } + + public fun callAddress() { + abort ImAAddress + } + + public fun callString() { + abort ImAString + } + + public fun callBytevec() { + abort ImNotAString + } + + public fun normalAbort() { + abort 0 + } + + public fun assertLineNo() { + assert!(false); + } +} + +//# run P0::m::callU8 + +//# run P0::m::callU16 + +//# run P0::m::callU32 + +//# run P0::m::callU64 + +//# run P0::m::callU128 + +//# run P0::m::callU256 + +//# run P0::m::callAddress + +//# run P0::m::callString + +//# run P0::m::callBytevec + +//# run P0::m::normalAbort + +//# run P0::m::assertLineNo + +//# create-checkpoint + +//# run-graphql +{ + transactionBlocks(last: 9) { + nodes { + effects { + status + errors + } + } + } +} diff --git a/crates/sui-graphql-rpc/src/types/transaction_block_effects.rs b/crates/sui-graphql-rpc/src/types/transaction_block_effects.rs index 4d3c0acbdf7c58..0a4cdb762c1ccb 100644 --- a/crates/sui-graphql-rpc/src/types/transaction_block_effects.rs +++ b/crates/sui-graphql-rpc/src/types/transaction_block_effects.rs @@ -1,18 +1,28 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::{consistency::ConsistentIndexCursor, error::Error}; +use crate::{ + consistency::ConsistentIndexCursor, data::package_resolver::PackageResolver, error::Error, +}; use async_graphql::{ connection::{Connection, ConnectionNameType, CursorType, Edge, EdgeNameType, EmptyFields}, *, }; +use fastcrypto::encoding::{Base64 as FBase64, Encoding}; use sui_indexer::models::transactions::StoredTransaction; +use sui_package_resolver::CleverError; use sui_types::{ effects::{TransactionEffects as NativeTransactionEffects, TransactionEffectsAPI}, event::Event as NativeEvent, - execution_status::ExecutionStatus as NativeExecutionStatus, - transaction::SenderSignedData as NativeSenderSignedData, - transaction::TransactionData as NativeTransactionData, + execution_status::{ + ExecutionFailureStatus, ExecutionStatus as NativeExecutionStatus, MoveLocation, + MoveLocationOpt, + }, + transaction::{ + Command, ProgrammableTransaction, SenderSignedData as NativeSenderSignedData, + TransactionData as NativeTransactionData, TransactionDataAPI, + TransactionKind as NativeTransactionKind, + }, }; use super::{ @@ -105,19 +115,87 @@ impl TransactionBlockEffects { } /// The reason for a transaction failure, if it did fail. - async fn errors(&self) -> Option { - match self.native().status() { - NativeExecutionStatus::Success => None, + /// If the error is a MoveAbort, the error message will be resolved to a human-readable form if + /// possible, otherwise it will fall back to simply displaying the Move abort code and location. + async fn errors(&self, ctx: &Context<'_>) -> Result> { + let mut status = self.native().status().clone(); + let resolver: &PackageResolver = ctx.data_unchecked(); + + if let NativeExecutionStatus::Failure { + error: + ExecutionFailureStatus::MoveAbort(MoveLocation { module, .. }, _) + | ExecutionFailureStatus::MovePrimitiveRuntimeError(MoveLocationOpt(Some(MoveLocation { + module, + .. + }))), + command: Some(command_idx), + } = &mut status + { + // Get the Move call that this error is associated with. + if let Some(Command::MoveCall(ptb_call)) = self + .programmable_transaction()? + .and_then(|ptb| ptb.commands.into_iter().nth(*command_idx)) + { + let module_new = module.clone(); + // Resolve the runtime module ID in the Move abort to the storage ID of the package + // that the abort occured in. This is important to make sure that we look at the + // correct version of the module when resolving the error. + *module = resolver + .resolve_module_id(module_new, ptb_call.package.into()) + .await + .map_err(|e| Error::Internal(format!("Error resolving Move location: {e}")))?; + } + } + + match status { + NativeExecutionStatus::Success => Ok(None), NativeExecutionStatus::Failure { error, command: None, - } => Some(error.to_string()), + } => Ok(Some(error.to_string())), NativeExecutionStatus::Failure { error, command: Some(command), } => { + let error = 'error: { + let ExecutionFailureStatus::MoveAbort(loc, code) = &error else { + break 'error error.to_string(); + }; + let fname_string = if let Some(fname) = &loc.function_name { + format!(" in function '{}' ", fname) + } else { + " ".to_string() + }; + match resolver + .resolve_clever_error(loc.module.clone(), *code) + .await + { + Some(CleverError::CompleteError { + module_id, + error_constant, + source_line_number, + error_identifier, + }) => { + let const_str = error_constant.unwrap_or_else(FBase64::encode); + format!( + "'{error_identifier}' from module '{}'{fname_string}at source line {source_line_number}\n{const_str}", + module_id.to_canonical_display(true) + ) + } + Some(CleverError::LineNumberOnly { + module_id, + source_line_number, + }) => { + format!( + "Error from module '{}'{fname_string}at source line {source_line_number}", + module_id.to_canonical_display(true) + ) + } + None => error.to_string(), + } + }; // Convert the command index into an ordinal. let command = command + 1; let suffix = match command % 10 { @@ -126,8 +204,7 @@ impl TransactionBlockEffects { 3 => "rd", _ => "th", }; - - Some(format!("{error} in {command}{suffix} command.")) + Ok(Some(format!("Error in {command}{suffix} command\n{error}"))) } } } @@ -418,6 +495,37 @@ impl TransactionBlockEffects { TransactionBlockEffectsKind::DryRun { native, .. } => native, } } + + /// Get the transaction data from the transaction block effects. + /// Will error if the transaction data is not available/invalid, but this should not occur. + fn transaction_data(&self) -> Result { + Ok(match &self.kind { + TransactionBlockEffectsKind::Stored { stored_tx, .. } => { + let s: NativeSenderSignedData = bcs::from_bytes(&stored_tx.raw_transaction) + .map_err(|e| { + Error::Internal(format!("Error deserializing transaction data: {e}")) + })?; + s.transaction_data().clone() + } + TransactionBlockEffectsKind::Executed { tx_data, .. } => { + tx_data.transaction_data().clone() + } + TransactionBlockEffectsKind::DryRun { tx_data, .. } => tx_data.clone(), + }) + } + + /// Get the programmable transaction from the transaction block effects. + /// * If the transaction was unable to be retrieved, this will return an Err. + /// * If the transaction was able to be retrieved but was not a programmable transaction, this + /// will return Ok(None). + /// * If the transaction was a programmable transaction, this will return Ok(Some(tx)). + fn programmable_transaction(&self) -> Result> { + let tx_data = self.transaction_data()?; + match tx_data.into_kind() { + NativeTransactionKind::ProgrammableTransaction(tx) => Ok(Some(tx)), + _ => Ok(None), + } + } } impl ConnectionNameType for DependencyConnectionNames { diff --git a/crates/sui-package-resolver/Cargo.toml b/crates/sui-package-resolver/Cargo.toml index da71b10eb35954..df3bdbc6b8b60c 100644 --- a/crates/sui-package-resolver/Cargo.toml +++ b/crates/sui-package-resolver/Cargo.toml @@ -13,6 +13,7 @@ async-trait.workspace = true bcs.workspace = true move-binary-format.workspace = true move-core-types.workspace = true +move-command-line-common.workspace = true sui-types.workspace = true thiserror.workspace = true sui-rest-api.workspace = true diff --git a/crates/sui-package-resolver/src/lib.rs b/crates/sui-package-resolver/src/lib.rs index 30dfa5cadc7b63..3579701b168ee4 100644 --- a/crates/sui-package-resolver/src/lib.rs +++ b/crates/sui-package-resolver/src/lib.rs @@ -7,6 +7,9 @@ use move_binary_format::file_format::{ AbilitySet, FunctionDefinitionIndex, Signature as MoveSignature, SignatureIndex, StructTypeParameter, Visibility, }; +use move_command_line_common::error_bitset::ErrorBitset; +use move_core_types::language_storage::ModuleId; +use move_core_types::u256::U256; use std::collections::btree_map::Entry; use std::collections::BTreeSet; use std::num::NonZeroUsize; @@ -93,6 +96,32 @@ pub struct Package { type Linkage = BTreeMap; +#[derive(Clone, Debug)] +pub enum CleverError { + CompleteError { + /// The (storage) module ID of the module that the assertion failed in. + module_id: ModuleId, + /// The name of the error constant. + error_identifier: String, + /// The value of the error constant. In the case where the error constant is either a + /// * A vector of bytes convertible to a valid UTF-8 string; or + /// * A numeric value (u8, u16, u32, u64, u128, u256); or + /// * A boolean value; of + /// * An address value + /// Then `Ok()` is returned. Otherwise, `Err()` is returned, and + /// the caller is responsible for determining how best to display the error constant. + error_constant: std::result::Result>, + /// The line number in the source file where the error occured. + source_line_number: u16, + }, + LineNumberOnly { + /// The (storage) module ID of the module that the assertion failed in. + module_id: ModuleId, + /// The line number in the source file where the error occured. + source_line_number: u16, + }, +} + #[derive(Clone, Debug)] pub struct Module { bytecode: CompiledModule, @@ -448,6 +477,122 @@ impl Resolver { .map(|t| t.as_ref().and_then(|t| layouts.get(t).cloned())) .collect()) } + + /// Resolves a runtime address in a `ModuleId` to a storage `ModuleId` according to the linkage + /// table in the `context` which must refer to a package. + /// * Will fail if the wrong context is provided, i.e., is not a package, or + /// does not exist. + /// * Will fail if an invalid `context` is provided for the `location`, i.e., the package at + /// `context` does not contain the module that `location` refers to. + pub async fn resolve_module_id( + &self, + module_id: ModuleId, + context: AccountAddress, + ) -> Result { + let package = self.package_store.fetch(context).await?; + let storage_id = package.relocate(*module_id.address())?; + Ok(ModuleId::new(storage_id, module_id.name().to_owned())) + } + + /// Resolves an abort code following the clever error format to a `CleverError` enum. + /// The `module_id` must be the storage ID of the module (which can e.g., be gotten from the + /// `resolve_module_id` function) and not the runtime ID. + /// + /// If the `abort_code` is not a clever error (i.e., does not follow the tagging and layout as + /// defined in `ErrorBitset`), this function will return `None`. + /// + /// In the case where it is a clever error but only a line number is present (i.e., the error + /// is the result of an `assert!()` source expression) a `CleverError::LineNumberOnly` is + /// returned. Otherwise a `CleverError::CompleteError` is returned. + /// + /// If for any reason we are unable to resolve the abort code to a `CleverError`, this function + /// will return `None`. + pub async fn resolve_clever_error( + &self, + module_id: ModuleId, + abort_code: u64, + ) -> Option { + let Some(bitset) = ErrorBitset::from_u64(abort_code) else { + return None; + }; + + let package = self.package_store.fetch(*module_id.address()).await.ok()?; + let module = package.module(module_id.name().as_str()).ok()?.bytecode(); + + let source_line_number = bitset.line_number()?; + + // We only have a line number in our clever error, so return early. + if bitset.identifier_index().is_none() && bitset.constant_index().is_none() { + return Some(CleverError::LineNumberOnly { + module_id, + source_line_number, + }); + } + + let error_identifier_constant = module + .constant_pool() + .get(bitset.identifier_index()? as usize)?; + let error_value_constant = module + .constant_pool() + .get(bitset.constant_index()? as usize)?; + + if !matches!(&error_identifier_constant.type_, SignatureToken::Vector(x) if x.as_ref() == &SignatureToken::U8) + { + return None; + }; + + let error_identifier = bcs::from_bytes::>(&error_identifier_constant.data) + .ok() + .and_then(|x| String::from_utf8(x).ok())?; + let bytes = error_value_constant.data.clone(); + + let error_constant = match &error_value_constant.type_ { + SignatureToken::Vector(inner_ty) if inner_ty.as_ref() == &SignatureToken::U8 => { + bcs::from_bytes::>(&bytes) + .ok() + .and_then(|x| String::from_utf8(x).ok()) + .ok_or_else(|| bytes) + } + SignatureToken::U8 => bcs::from_bytes::(&bytes) + .map(|x| x.to_string()) + .map_err(|_| bytes), + SignatureToken::U16 => bcs::from_bytes::(&bytes) + .map(|x| x.to_string()) + .map_err(|_| bytes), + SignatureToken::U32 => bcs::from_bytes::(&bytes) + .map(|x| x.to_string()) + .map_err(|_| bytes), + SignatureToken::U64 => bcs::from_bytes::(&bytes) + .map(|x| x.to_string()) + .map_err(|_| bytes), + SignatureToken::U128 => bcs::from_bytes::(&bytes) + .map(|x| x.to_string()) + .map_err(|_| bytes), + SignatureToken::U256 => bcs::from_bytes::(&bytes) + .map(|x| x.to_string()) + .map_err(|_| bytes), + SignatureToken::Address => bcs::from_bytes::(&bytes) + .map(|x| x.to_canonical_string(true)) + .map_err(|_| bytes), + SignatureToken::Bool => bcs::from_bytes::(&bytes) + .map(|x| x.to_string()) + .map_err(|_| bytes), + SignatureToken::Signer + | SignatureToken::Vector(_) + | SignatureToken::Struct(_) + | SignatureToken::StructInstantiation(_) + | SignatureToken::Reference(_) + | SignatureToken::MutableReference(_) + | SignatureToken::TypeParameter(_) => Err(bytes), + }; + + Some(CleverError::CompleteError { + module_id, + error_identifier, + error_constant, + source_line_number, + }) + } } impl PackageStoreWithLruCache { diff --git a/crates/sui-transactional-test-runner/src/test_adapter.rs b/crates/sui-transactional-test-runner/src/test_adapter.rs index e4950a33545173..507b56114284ee 100644 --- a/crates/sui-transactional-test-runner/src/test_adapter.rs +++ b/crates/sui-transactional-test-runner/src/test_adapter.rs @@ -332,7 +332,7 @@ impl<'a> MoveTestAdapter<'a> for SuiTestAdapter { AccountAddress::ZERO.into_bytes(), NumberFormat::Hex, )), - Some(Edition::E2024_ALPHA), + Some(Edition::DEVELOPMENT), flavor.or(Some(Flavor::Sui)), ), package_upgrade_mapping: BTreeMap::new(),