diff --git a/internal/api/bindings.h b/internal/api/bindings.h index 60f301408..1f097dcec 100644 --- a/internal/api/bindings.h +++ b/internal/api/bindings.h @@ -342,6 +342,26 @@ typedef struct GoQuerier { struct Querier_vtable vtable; } GoQuerier; +typedef struct GasReport { + /** + * The original limit the instance was created with + */ + uint64_t limit; + /** + * The remaining gas that can be spend + */ + uint64_t remaining; + /** + * The amount of gas that was spend and metered externally in operations triggered by this instance + */ + uint64_t used_externally; + /** + * The amount of gas that was spend and metered internally (i.e. by executing Wasm and calling + * API methods which are not metered externally) + */ + uint64_t used_internally; +} GasReport; + struct cache_t *init_cache(struct ByteSliceView data_dir, struct ByteSliceView available_capabilities, uint32_t cache_size, @@ -391,7 +411,7 @@ struct UnmanagedVector instantiate(struct cache_t *cache, struct GoQuerier querier, uint64_t gas_limit, bool print_debug, - uint64_t *gas_used, + struct GasReport *gas_used, struct UnmanagedVector *error_msg); struct UnmanagedVector execute(struct cache_t *cache, @@ -404,7 +424,7 @@ struct UnmanagedVector execute(struct cache_t *cache, struct GoQuerier querier, uint64_t gas_limit, bool print_debug, - uint64_t *gas_used, + struct GasReport *gas_used, struct UnmanagedVector *error_msg); struct UnmanagedVector migrate(struct cache_t *cache, @@ -416,7 +436,7 @@ struct UnmanagedVector migrate(struct cache_t *cache, struct GoQuerier querier, uint64_t gas_limit, bool print_debug, - uint64_t *gas_used, + struct GasReport *gas_used, struct UnmanagedVector *error_msg); struct UnmanagedVector sudo(struct cache_t *cache, @@ -428,7 +448,7 @@ struct UnmanagedVector sudo(struct cache_t *cache, struct GoQuerier querier, uint64_t gas_limit, bool print_debug, - uint64_t *gas_used, + struct GasReport *gas_used, struct UnmanagedVector *error_msg); struct UnmanagedVector reply(struct cache_t *cache, @@ -440,7 +460,7 @@ struct UnmanagedVector reply(struct cache_t *cache, struct GoQuerier querier, uint64_t gas_limit, bool print_debug, - uint64_t *gas_used, + struct GasReport *gas_used, struct UnmanagedVector *error_msg); struct UnmanagedVector query(struct cache_t *cache, @@ -452,7 +472,7 @@ struct UnmanagedVector query(struct cache_t *cache, struct GoQuerier querier, uint64_t gas_limit, bool print_debug, - uint64_t *gas_used, + struct GasReport *gas_used, struct UnmanagedVector *error_msg); struct UnmanagedVector ibc_channel_open(struct cache_t *cache, @@ -464,7 +484,7 @@ struct UnmanagedVector ibc_channel_open(struct cache_t *cache, struct GoQuerier querier, uint64_t gas_limit, bool print_debug, - uint64_t *gas_used, + struct GasReport *gas_used, struct UnmanagedVector *error_msg); struct UnmanagedVector ibc_channel_connect(struct cache_t *cache, @@ -476,7 +496,7 @@ struct UnmanagedVector ibc_channel_connect(struct cache_t *cache, struct GoQuerier querier, uint64_t gas_limit, bool print_debug, - uint64_t *gas_used, + struct GasReport *gas_used, struct UnmanagedVector *error_msg); struct UnmanagedVector ibc_channel_close(struct cache_t *cache, @@ -488,7 +508,7 @@ struct UnmanagedVector ibc_channel_close(struct cache_t *cache, struct GoQuerier querier, uint64_t gas_limit, bool print_debug, - uint64_t *gas_used, + struct GasReport *gas_used, struct UnmanagedVector *error_msg); struct UnmanagedVector ibc_packet_receive(struct cache_t *cache, @@ -500,7 +520,7 @@ struct UnmanagedVector ibc_packet_receive(struct cache_t *cache, struct GoQuerier querier, uint64_t gas_limit, bool print_debug, - uint64_t *gas_used, + struct GasReport *gas_used, struct UnmanagedVector *error_msg); struct UnmanagedVector ibc_packet_ack(struct cache_t *cache, @@ -512,7 +532,7 @@ struct UnmanagedVector ibc_packet_ack(struct cache_t *cache, struct GoQuerier querier, uint64_t gas_limit, bool print_debug, - uint64_t *gas_used, + struct GasReport *gas_used, struct UnmanagedVector *error_msg); struct UnmanagedVector ibc_packet_timeout(struct cache_t *cache, @@ -524,7 +544,7 @@ struct UnmanagedVector ibc_packet_timeout(struct cache_t *cache, struct GoQuerier querier, uint64_t gas_limit, bool print_debug, - uint64_t *gas_used, + struct GasReport *gas_used, struct UnmanagedVector *error_msg); struct UnmanagedVector new_unmanaged_vector(bool nil, const uint8_t *ptr, uintptr_t length); diff --git a/internal/api/lib.go b/internal/api/lib.go index 4f4f07236..b73874307 100644 --- a/internal/api/lib.go +++ b/internal/api/lib.go @@ -172,7 +172,7 @@ func Instantiate( querier *Querier, gasLimit uint64, printDebug bool, -) ([]byte, uint64, error) { +) ([]byte, types.GasReport, error) { cs := makeView(checksum) defer runtime.KeepAlive(checksum) e := makeView(env) @@ -189,15 +189,15 @@ func Instantiate( db := buildDB(&dbState, gasMeter) a := buildAPI(api) q := buildQuerier(querier) - var gasUsed cu64 + var gasReport C.GasReport errmsg := uninitializedUnmanagedVector() - res, err := C.instantiate(cache.ptr, cs, e, i, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasUsed, &errmsg) + res, err := C.instantiate(cache.ptr, cs, e, i, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. - return nil, uint64(gasUsed), errorWithMessage(err, errmsg) + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) } - return copyAndDestroyUnmanagedVector(res), uint64(gasUsed), nil + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil } func Execute( @@ -212,7 +212,7 @@ func Execute( querier *Querier, gasLimit uint64, printDebug bool, -) ([]byte, uint64, error) { +) ([]byte, types.GasReport, error) { cs := makeView(checksum) defer runtime.KeepAlive(checksum) e := makeView(env) @@ -229,15 +229,15 @@ func Execute( db := buildDB(&dbState, gasMeter) a := buildAPI(api) q := buildQuerier(querier) - var gasUsed cu64 + var gasReport C.GasReport errmsg := uninitializedUnmanagedVector() - res, err := C.execute(cache.ptr, cs, e, i, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasUsed, &errmsg) + res, err := C.execute(cache.ptr, cs, e, i, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. - return nil, uint64(gasUsed), errorWithMessage(err, errmsg) + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) } - return copyAndDestroyUnmanagedVector(res), uint64(gasUsed), nil + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil } func Migrate( @@ -251,7 +251,7 @@ func Migrate( querier *Querier, gasLimit uint64, printDebug bool, -) ([]byte, uint64, error) { +) ([]byte, types.GasReport, error) { cs := makeView(checksum) defer runtime.KeepAlive(checksum) e := makeView(env) @@ -266,15 +266,15 @@ func Migrate( db := buildDB(&dbState, gasMeter) a := buildAPI(api) q := buildQuerier(querier) - var gasUsed cu64 + var gasReport C.GasReport errmsg := uninitializedUnmanagedVector() - res, err := C.migrate(cache.ptr, cs, e, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasUsed, &errmsg) + res, err := C.migrate(cache.ptr, cs, e, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. - return nil, uint64(gasUsed), errorWithMessage(err, errmsg) + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) } - return copyAndDestroyUnmanagedVector(res), uint64(gasUsed), nil + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil } func Sudo( @@ -288,7 +288,7 @@ func Sudo( querier *Querier, gasLimit uint64, printDebug bool, -) ([]byte, uint64, error) { +) ([]byte, types.GasReport, error) { cs := makeView(checksum) defer runtime.KeepAlive(checksum) e := makeView(env) @@ -303,15 +303,15 @@ func Sudo( db := buildDB(&dbState, gasMeter) a := buildAPI(api) q := buildQuerier(querier) - var gasUsed cu64 + var gasReport C.GasReport errmsg := uninitializedUnmanagedVector() - res, err := C.sudo(cache.ptr, cs, e, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasUsed, &errmsg) + res, err := C.sudo(cache.ptr, cs, e, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. - return nil, uint64(gasUsed), errorWithMessage(err, errmsg) + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) } - return copyAndDestroyUnmanagedVector(res), uint64(gasUsed), nil + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil } func Reply( @@ -325,7 +325,7 @@ func Reply( querier *Querier, gasLimit uint64, printDebug bool, -) ([]byte, uint64, error) { +) ([]byte, types.GasReport, error) { cs := makeView(checksum) defer runtime.KeepAlive(checksum) e := makeView(env) @@ -340,15 +340,15 @@ func Reply( db := buildDB(&dbState, gasMeter) a := buildAPI(api) q := buildQuerier(querier) - var gasUsed cu64 + var gasReport C.GasReport errmsg := uninitializedUnmanagedVector() - res, err := C.reply(cache.ptr, cs, e, r, db, a, q, cu64(gasLimit), cbool(printDebug), &gasUsed, &errmsg) + res, err := C.reply(cache.ptr, cs, e, r, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. - return nil, uint64(gasUsed), errorWithMessage(err, errmsg) + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) } - return copyAndDestroyUnmanagedVector(res), uint64(gasUsed), nil + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil } func Query( @@ -362,7 +362,7 @@ func Query( querier *Querier, gasLimit uint64, printDebug bool, -) ([]byte, uint64, error) { +) ([]byte, types.GasReport, error) { cs := makeView(checksum) defer runtime.KeepAlive(checksum) e := makeView(env) @@ -377,15 +377,15 @@ func Query( db := buildDB(&dbState, gasMeter) a := buildAPI(api) q := buildQuerier(querier) - var gasUsed cu64 + var gasReport C.GasReport errmsg := uninitializedUnmanagedVector() - res, err := C.query(cache.ptr, cs, e, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasUsed, &errmsg) + res, err := C.query(cache.ptr, cs, e, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. - return nil, uint64(gasUsed), errorWithMessage(err, errmsg) + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) } - return copyAndDestroyUnmanagedVector(res), uint64(gasUsed), nil + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil } func IBCChannelOpen( @@ -399,7 +399,7 @@ func IBCChannelOpen( querier *Querier, gasLimit uint64, printDebug bool, -) ([]byte, uint64, error) { +) ([]byte, types.GasReport, error) { cs := makeView(checksum) defer runtime.KeepAlive(checksum) e := makeView(env) @@ -414,15 +414,15 @@ func IBCChannelOpen( db := buildDB(&dbState, gasMeter) a := buildAPI(api) q := buildQuerier(querier) - var gasUsed cu64 + var gasReport C.GasReport errmsg := uninitializedUnmanagedVector() - res, err := C.ibc_channel_open(cache.ptr, cs, e, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasUsed, &errmsg) + res, err := C.ibc_channel_open(cache.ptr, cs, e, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. - return nil, uint64(gasUsed), errorWithMessage(err, errmsg) + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) } - return copyAndDestroyUnmanagedVector(res), uint64(gasUsed), nil + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil } func IBCChannelConnect( @@ -436,7 +436,7 @@ func IBCChannelConnect( querier *Querier, gasLimit uint64, printDebug bool, -) ([]byte, uint64, error) { +) ([]byte, types.GasReport, error) { cs := makeView(checksum) defer runtime.KeepAlive(checksum) e := makeView(env) @@ -451,15 +451,15 @@ func IBCChannelConnect( db := buildDB(&dbState, gasMeter) a := buildAPI(api) q := buildQuerier(querier) - var gasUsed cu64 + var gasReport C.GasReport errmsg := uninitializedUnmanagedVector() - res, err := C.ibc_channel_connect(cache.ptr, cs, e, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasUsed, &errmsg) + res, err := C.ibc_channel_connect(cache.ptr, cs, e, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. - return nil, uint64(gasUsed), errorWithMessage(err, errmsg) + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) } - return copyAndDestroyUnmanagedVector(res), uint64(gasUsed), nil + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil } func IBCChannelClose( @@ -473,7 +473,7 @@ func IBCChannelClose( querier *Querier, gasLimit uint64, printDebug bool, -) ([]byte, uint64, error) { +) ([]byte, types.GasReport, error) { cs := makeView(checksum) defer runtime.KeepAlive(checksum) e := makeView(env) @@ -488,15 +488,15 @@ func IBCChannelClose( db := buildDB(&dbState, gasMeter) a := buildAPI(api) q := buildQuerier(querier) - var gasUsed cu64 + var gasReport C.GasReport errmsg := uninitializedUnmanagedVector() - res, err := C.ibc_channel_close(cache.ptr, cs, e, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasUsed, &errmsg) + res, err := C.ibc_channel_close(cache.ptr, cs, e, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. - return nil, uint64(gasUsed), errorWithMessage(err, errmsg) + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) } - return copyAndDestroyUnmanagedVector(res), uint64(gasUsed), nil + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil } func IBCPacketReceive( @@ -510,7 +510,7 @@ func IBCPacketReceive( querier *Querier, gasLimit uint64, printDebug bool, -) ([]byte, uint64, error) { +) ([]byte, types.GasReport, error) { cs := makeView(checksum) defer runtime.KeepAlive(checksum) e := makeView(env) @@ -525,15 +525,15 @@ func IBCPacketReceive( db := buildDB(&dbState, gasMeter) a := buildAPI(api) q := buildQuerier(querier) - var gasUsed cu64 + var gasReport C.GasReport errmsg := uninitializedUnmanagedVector() - res, err := C.ibc_packet_receive(cache.ptr, cs, e, pa, db, a, q, cu64(gasLimit), cbool(printDebug), &gasUsed, &errmsg) + res, err := C.ibc_packet_receive(cache.ptr, cs, e, pa, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. - return nil, uint64(gasUsed), errorWithMessage(err, errmsg) + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) } - return copyAndDestroyUnmanagedVector(res), uint64(gasUsed), nil + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil } func IBCPacketAck( @@ -547,7 +547,7 @@ func IBCPacketAck( querier *Querier, gasLimit uint64, printDebug bool, -) ([]byte, uint64, error) { +) ([]byte, types.GasReport, error) { cs := makeView(checksum) defer runtime.KeepAlive(checksum) e := makeView(env) @@ -562,15 +562,15 @@ func IBCPacketAck( db := buildDB(&dbState, gasMeter) a := buildAPI(api) q := buildQuerier(querier) - var gasUsed cu64 + var gasReport C.GasReport errmsg := uninitializedUnmanagedVector() - res, err := C.ibc_packet_ack(cache.ptr, cs, e, ac, db, a, q, cu64(gasLimit), cbool(printDebug), &gasUsed, &errmsg) + res, err := C.ibc_packet_ack(cache.ptr, cs, e, ac, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. - return nil, uint64(gasUsed), errorWithMessage(err, errmsg) + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) } - return copyAndDestroyUnmanagedVector(res), uint64(gasUsed), nil + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil } func IBCPacketTimeout( @@ -584,7 +584,7 @@ func IBCPacketTimeout( querier *Querier, gasLimit uint64, printDebug bool, -) ([]byte, uint64, error) { +) ([]byte, types.GasReport, error) { cs := makeView(checksum) defer runtime.KeepAlive(checksum) e := makeView(env) @@ -599,15 +599,24 @@ func IBCPacketTimeout( db := buildDB(&dbState, gasMeter) a := buildAPI(api) q := buildQuerier(querier) - var gasUsed cu64 + var gasReport C.GasReport errmsg := uninitializedUnmanagedVector() - res, err := C.ibc_packet_timeout(cache.ptr, cs, e, pa, db, a, q, cu64(gasLimit), cbool(printDebug), &gasUsed, &errmsg) + res, err := C.ibc_packet_timeout(cache.ptr, cs, e, pa, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. - return nil, uint64(gasUsed), errorWithMessage(err, errmsg) + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) + } + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil +} + +func convertGasReport(report C.GasReport) types.GasReport { + return types.GasReport{ + Limit: uint64(report.limit), + Remaining: uint64(report.remaining), + UsedExternally: uint64(report.used_externally), + UsedInternally: uint64(report.used_internally), } - return copyAndDestroyUnmanagedVector(res), uint64(gasUsed), nil } /**** To error module ***/ diff --git a/internal/api/lib_test.go b/internal/api/lib_test.go index 9fd43e72b..e4d926f40 100644 --- a/internal/api/lib_test.go +++ b/internal/api/lib_test.go @@ -360,7 +360,7 @@ func TestInstantiate(t *testing.T) { res, cost, err := Instantiate(cache, checksum, env, info, msg, &igasMeter, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) require.NoError(t, err) requireOkResponse(t, res, 0) - assert.Equal(t, uint64(0x13a78a36c), cost) + assert.Equal(t, uint64(0x13a78a36c), cost.UsedInternally) var result types.ContractResult err = json.Unmarshal(res, &result) @@ -391,8 +391,8 @@ func TestExecute(t *testing.T) { diff := time.Since(start) require.NoError(t, err) requireOkResponse(t, res, 0) - assert.Equal(t, uint64(0x13a78a36c), cost) - t.Logf("Time (%d gas): %s\n", cost, diff) + assert.Equal(t, uint64(0x13a78a36c), cost.UsedInternally) + t.Logf("Time (%d gas): %s\n", cost.UsedInternally, diff) // execute with the same store gasMeter2 := NewMockGasMeter(TESTING_GAS_LIMIT) @@ -404,8 +404,8 @@ func TestExecute(t *testing.T) { res, cost, err = Execute(cache, checksum, env, info, []byte(`{"release":{}}`), &igasMeter2, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) diff = time.Since(start) require.NoError(t, err) - assert.Equal(t, uint64(0x222892d70), cost) - t.Logf("Time (%d gas): %s\n", cost, diff) + assert.Equal(t, uint64(0x222892d70), cost.UsedInternally) + t.Logf("Time (%d gas): %s\n", cost.UsedInternally, diff) // make sure it read the balance properly and we got 250 atoms var result types.ContractResult @@ -512,8 +512,8 @@ func TestExecuteCpuLoop(t *testing.T) { diff := time.Since(start) require.NoError(t, err) requireOkResponse(t, res, 0) - assert.Equal(t, uint64(0xd45091d0), cost) - t.Logf("Time (%d gas): %s\n", cost, diff) + assert.Equal(t, uint64(0xd45091d0), cost.UsedInternally) + t.Logf("Time (%d gas): %s\n", cost.UsedInternally, diff) // execute a cpu loop maxGas := uint64(40_000_000) @@ -525,8 +525,8 @@ func TestExecuteCpuLoop(t *testing.T) { _, cost, err = Execute(cache, checksum, env, info, []byte(`{"cpu_loop":{}}`), &igasMeter2, store, api, &querier, maxGas, TESTING_PRINT_DEBUG) diff = time.Since(start) require.Error(t, err) - assert.Equal(t, cost, maxGas) - t.Logf("CPULoop Time (%d gas): %s\n", cost, diff) + assert.Equal(t, cost.UsedInternally, maxGas) + t.Logf("CPULoop Time (%d gas): %s\n", cost.UsedInternally, diff) } func TestExecuteStorageLoop(t *testing.T) { @@ -557,15 +557,15 @@ func TestExecuteStorageLoop(t *testing.T) { store.SetGasMeter(gasMeter2) info = MockInfoBin(t, "fred") start := time.Now() - _, cost, err := Execute(cache, checksum, env, info, []byte(`{"storage_loop":{}}`), &igasMeter2, store, api, &querier, maxGas, TESTING_PRINT_DEBUG) + _, gasReport, err := Execute(cache, checksum, env, info, []byte(`{"storage_loop":{}}`), &igasMeter2, store, api, &querier, maxGas, TESTING_PRINT_DEBUG) diff := time.Since(start) require.Error(t, err) - t.Logf("StorageLoop Time (%d gas): %s\n", cost, diff) + t.Logf("StorageLoop Time (%d gas): %s\n", gasReport.UsedInternally, diff) t.Logf("Gas used: %d\n", gasMeter2.GasConsumed()) - t.Logf("Wasm gas: %d\n", cost) + t.Logf("Wasm gas: %d\n", gasReport.UsedInternally) // the "sdk gas" * GasMultiplier + the wasm cost should equal the maxGas (or be very close) - totalCost := cost + gasMeter2.GasConsumed() + totalCost := gasReport.UsedInternally + gasMeter2.GasConsumed() require.Equal(t, int64(maxGas), int64(totalCost)) } @@ -663,7 +663,7 @@ func TestMultipleInstances(t *testing.T) { require.NoError(t, err) requireOkResponse(t, res, 0) // we now count wasm gas charges and db writes - assert.Equal(t, uint64(0x138559c5c), cost) + assert.Equal(t, uint64(0x138559c5c), cost.UsedInternally) // instance2 controlled by mary gasMeter2 := NewMockGasMeter(TESTING_GAS_LIMIT) @@ -674,7 +674,7 @@ func TestMultipleInstances(t *testing.T) { res, cost, err = Instantiate(cache, checksum, env, info, msg, &igasMeter2, store2, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) require.NoError(t, err) requireOkResponse(t, res, 0) - assert.Equal(t, uint64(0x1399177bc), cost) + assert.Equal(t, uint64(0x1399177bc), cost.UsedInternally) // fail to execute store1 with mary resp := exec(t, cache, checksum, "mary", store1, api, querier, 0x1218ff5d0) @@ -922,7 +922,7 @@ func exec(t *testing.T, cache Cache, checksum []byte, signer types.HumanAddress, info := MockInfoBin(t, signer) res, cost, err := Execute(cache, checksum, env, info, []byte(`{"release":{}}`), &igasMeter, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) require.NoError(t, err) - assert.Equal(t, gasExpected, cost) + assert.Equal(t, gasExpected, cost.UsedInternally) var result types.ContractResult err = json.Unmarshal(res, &result) diff --git a/lib.go b/lib.go index 99f842fd8..622c031d3 100644 --- a/lib.go +++ b/lib.go @@ -127,35 +127,29 @@ func (vm *VM) Instantiate( gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction, -) (*types.Response, uint64, error) { +) (*types.Response, types.GasReport, error) { envBin, err := json.Marshal(env) if err != nil { - return nil, 0, err + return nil, types.EmptyGasReport(gasLimit), err } infoBin, err := json.Marshal(info) if err != nil { - return nil, 0, err + return nil, types.EmptyGasReport(gasLimit), err } - data, gasUsed, err := api.Instantiate(vm.cache, checksum, envBin, infoBin, initMsg, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + data, gasReport, err := api.Instantiate(vm.cache, checksum, envBin, infoBin, initMsg, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) if err != nil { - return nil, gasUsed, err + return nil, gasReport, err } - gasForDeserialization := deserCost.Mul(uint64(len(data))).Floor() - if gasLimit < gasForDeserialization+gasUsed { - return nil, gasUsed, fmt.Errorf("Insufficient gas left to deserialize contract execution result (%d bytes)", len(data)) - } - gasUsed += gasForDeserialization - var result types.ContractResult - err = json.Unmarshal(data, &result) + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &result) if err != nil { - return nil, gasUsed, err + return nil, gasReport, err } if result.Err != "" { - return nil, gasUsed, fmt.Errorf("%s", result.Err) + return nil, gasReport, fmt.Errorf("%s", result.Err) } - return result.Ok, gasUsed, nil + return result.Ok, gasReport, nil } // Execute calls a given contract. Since the only difference between contracts with the same Checksum is the @@ -175,35 +169,29 @@ func (vm *VM) Execute( gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction, -) (*types.Response, uint64, error) { +) (*types.Response, types.GasReport, error) { envBin, err := json.Marshal(env) if err != nil { - return nil, 0, err + return nil, types.EmptyGasReport(gasLimit), err } infoBin, err := json.Marshal(info) if err != nil { - return nil, 0, err + return nil, types.EmptyGasReport(gasLimit), err } - data, gasUsed, err := api.Execute(vm.cache, checksum, envBin, infoBin, executeMsg, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + data, gasReport, err := api.Execute(vm.cache, checksum, envBin, infoBin, executeMsg, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) if err != nil { - return nil, gasUsed, err - } - - gasForDeserialization := deserCost.Mul(uint64(len(data))).Floor() - if gasLimit < gasForDeserialization+gasUsed { - return nil, gasUsed, fmt.Errorf("Insufficient gas left to deserialize contract execution result (%d bytes)", len(data)) + return nil, gasReport, err } - gasUsed += gasForDeserialization var result types.ContractResult - err = json.Unmarshal(data, &result) + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &result) if err != nil { - return nil, gasUsed, err + return nil, gasReport, err } if result.Err != "" { - return nil, gasUsed, fmt.Errorf("%s", result.Err) + return nil, gasReport, fmt.Errorf("%s", result.Err) } - return result.Ok, gasUsed, nil + return result.Ok, gasReport, nil } // Query allows a client to execute a contract-specific query. If the result is not empty, it should be @@ -219,31 +207,25 @@ func (vm *VM) Query( gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction, -) ([]byte, uint64, error) { +) ([]byte, types.GasReport, error) { envBin, err := json.Marshal(env) if err != nil { - return nil, 0, err + return nil, types.EmptyGasReport(gasLimit), err } - data, gasUsed, err := api.Query(vm.cache, checksum, envBin, queryMsg, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + data, gasReport, err := api.Query(vm.cache, checksum, envBin, queryMsg, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) if err != nil { - return nil, gasUsed, err + return nil, gasReport, err } - gasForDeserialization := deserCost.Mul(uint64(len(data))).Floor() - if gasLimit < gasForDeserialization+gasUsed { - return nil, gasUsed, fmt.Errorf("Insufficient gas left to deserialize contract execution result (%d bytes)", len(data)) - } - gasUsed += gasForDeserialization - var resp types.QueryResponse - err = json.Unmarshal(data, &resp) + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &resp) if err != nil { - return nil, gasUsed, err + return nil, gasReport, err } if resp.Err != "" { - return nil, gasUsed, fmt.Errorf("%s", resp.Err) + return nil, gasReport, fmt.Errorf("%s", resp.Err) } - return resp.Ok, gasUsed, nil + return resp.Ok, gasReport, nil } // Migrate will migrate an existing contract to a new code binary. @@ -262,31 +244,25 @@ func (vm *VM) Migrate( gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction, -) (*types.Response, uint64, error) { +) (*types.Response, types.GasReport, error) { envBin, err := json.Marshal(env) if err != nil { - return nil, 0, err + return nil, types.EmptyGasReport(gasLimit), err } - data, gasUsed, err := api.Migrate(vm.cache, checksum, envBin, migrateMsg, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + data, gasReport, err := api.Migrate(vm.cache, checksum, envBin, migrateMsg, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) if err != nil { - return nil, gasUsed, err + return nil, gasReport, err } - gasForDeserialization := deserCost.Mul(uint64(len(data))).Floor() - if gasLimit < gasForDeserialization+gasUsed { - return nil, gasUsed, fmt.Errorf("Insufficient gas left to deserialize contract execution result (%d bytes)", len(data)) - } - gasUsed += gasForDeserialization - var resp types.ContractResult - err = json.Unmarshal(data, &resp) + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &resp) if err != nil { - return nil, gasUsed, err + return nil, gasReport, err } if resp.Err != "" { - return nil, gasUsed, fmt.Errorf("%s", resp.Err) + return nil, gasReport, fmt.Errorf("%s", resp.Err) } - return resp.Ok, gasUsed, nil + return resp.Ok, gasReport, nil } // Sudo allows native Go modules to make priviledged (sudo) calls on the contract. @@ -305,31 +281,25 @@ func (vm *VM) Sudo( gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction, -) (*types.Response, uint64, error) { +) (*types.Response, types.GasReport, error) { envBin, err := json.Marshal(env) if err != nil { - return nil, 0, err + return nil, types.EmptyGasReport(gasLimit), err } - data, gasUsed, err := api.Sudo(vm.cache, checksum, envBin, sudoMsg, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + data, gasReport, err := api.Sudo(vm.cache, checksum, envBin, sudoMsg, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) if err != nil { - return nil, gasUsed, err + return nil, gasReport, err } - gasForDeserialization := deserCost.Mul(uint64(len(data))).Floor() - if gasLimit < gasForDeserialization+gasUsed { - return nil, gasUsed, fmt.Errorf("Insufficient gas left to deserialize contract execution result (%d bytes)", len(data)) - } - gasUsed += gasForDeserialization - var resp types.ContractResult - err = json.Unmarshal(data, &resp) + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &resp) if err != nil { - return nil, gasUsed, err + return nil, gasReport, err } if resp.Err != "" { - return nil, gasUsed, fmt.Errorf("%s", resp.Err) + return nil, gasReport, fmt.Errorf("%s", resp.Err) } - return resp.Ok, gasUsed, nil + return resp.Ok, gasReport, nil } // Reply allows the native Go wasm modules to make a priviledged call to return the result @@ -346,35 +316,29 @@ func (vm *VM) Reply( gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction, -) (*types.Response, uint64, error) { +) (*types.Response, types.GasReport, error) { envBin, err := json.Marshal(env) if err != nil { - return nil, 0, err + return nil, types.EmptyGasReport(gasLimit), err } replyBin, err := json.Marshal(reply) if err != nil { - return nil, 0, err + return nil, types.EmptyGasReport(gasLimit), err } - data, gasUsed, err := api.Reply(vm.cache, checksum, envBin, replyBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + data, gasReport, err := api.Reply(vm.cache, checksum, envBin, replyBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) if err != nil { - return nil, gasUsed, err - } - - gasForDeserialization := deserCost.Mul(uint64(len(data))).Floor() - if gasLimit < gasForDeserialization+gasUsed { - return nil, gasUsed, fmt.Errorf("Insufficient gas left to deserialize contract execution result (%d bytes)", len(data)) + return nil, gasReport, err } - gasUsed += gasForDeserialization var resp types.ContractResult - err = json.Unmarshal(data, &resp) + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &resp) if err != nil { - return nil, gasUsed, err + return nil, gasReport, err } if resp.Err != "" { - return nil, gasUsed, fmt.Errorf("%s", resp.Err) + return nil, gasReport, fmt.Errorf("%s", resp.Err) } - return resp.Ok, gasUsed, nil + return resp.Ok, gasReport, nil } // IBCChannelOpen is available on IBC-enabled contracts and is a hook to call into @@ -389,35 +353,29 @@ func (vm *VM) IBCChannelOpen( gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction, -) (*types.IBC3ChannelOpenResponse, uint64, error) { +) (*types.IBC3ChannelOpenResponse, types.GasReport, error) { envBin, err := json.Marshal(env) if err != nil { - return nil, 0, err + return nil, types.EmptyGasReport(gasLimit), err } msgBin, err := json.Marshal(msg) if err != nil { - return nil, 0, err + return nil, types.EmptyGasReport(gasLimit), err } - data, gasUsed, err := api.IBCChannelOpen(vm.cache, checksum, envBin, msgBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + data, gasReport, err := api.IBCChannelOpen(vm.cache, checksum, envBin, msgBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) if err != nil { - return nil, gasUsed, err - } - - gasForDeserialization := deserCost.Mul(uint64(len(data))).Floor() - if gasLimit < gasForDeserialization+gasUsed { - return nil, gasUsed, fmt.Errorf("insufficient gas left to deserialize contract execution result (%d bytes)", len(data)) + return nil, gasReport, err } - gasUsed += gasForDeserialization var resp types.IBCChannelOpenResult - err = json.Unmarshal(data, &resp) + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &resp) if err != nil { - return nil, gasUsed, err + return nil, gasReport, err } if resp.Err != "" { - return nil, gasUsed, fmt.Errorf("%s", resp.Err) + return nil, gasReport, fmt.Errorf("%s", resp.Err) } - return resp.Ok, gasUsed, nil + return resp.Ok, gasReport, nil } // IBCChannelConnect is available on IBC-enabled contracts and is a hook to call into @@ -432,35 +390,29 @@ func (vm *VM) IBCChannelConnect( gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction, -) (*types.IBCBasicResponse, uint64, error) { +) (*types.IBCBasicResponse, types.GasReport, error) { envBin, err := json.Marshal(env) if err != nil { - return nil, 0, err + return nil, types.EmptyGasReport(gasLimit), err } msgBin, err := json.Marshal(msg) if err != nil { - return nil, 0, err + return nil, types.EmptyGasReport(gasLimit), err } - data, gasUsed, err := api.IBCChannelConnect(vm.cache, checksum, envBin, msgBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + data, gasReport, err := api.IBCChannelConnect(vm.cache, checksum, envBin, msgBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) if err != nil { - return nil, gasUsed, err + return nil, gasReport, err } - gasForDeserialization := deserCost.Mul(uint64(len(data))).Floor() - if gasLimit < gasForDeserialization+gasUsed { - return nil, gasUsed, fmt.Errorf("Insufficient gas left to deserialize contract execution result (%d bytes)", len(data)) - } - gasUsed += gasForDeserialization - var resp types.IBCBasicResult - err = json.Unmarshal(data, &resp) + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &resp) if err != nil { - return nil, gasUsed, err + return nil, gasReport, err } if resp.Err != "" { - return nil, gasUsed, fmt.Errorf("%s", resp.Err) + return nil, gasReport, fmt.Errorf("%s", resp.Err) } - return resp.Ok, gasUsed, nil + return resp.Ok, gasReport, nil } // IBCChannelClose is available on IBC-enabled contracts and is a hook to call into @@ -475,35 +427,29 @@ func (vm *VM) IBCChannelClose( gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction, -) (*types.IBCBasicResponse, uint64, error) { +) (*types.IBCBasicResponse, types.GasReport, error) { envBin, err := json.Marshal(env) if err != nil { - return nil, 0, err + return nil, types.EmptyGasReport(gasLimit), err } msgBin, err := json.Marshal(msg) if err != nil { - return nil, 0, err + return nil, types.EmptyGasReport(gasLimit), err } - data, gasUsed, err := api.IBCChannelClose(vm.cache, checksum, envBin, msgBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + data, gasReport, err := api.IBCChannelClose(vm.cache, checksum, envBin, msgBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) if err != nil { - return nil, gasUsed, err - } - - gasForDeserialization := deserCost.Mul(uint64(len(data))).Floor() - if gasLimit < gasForDeserialization+gasUsed { - return nil, gasUsed, fmt.Errorf("Insufficient gas left to deserialize contract execution result (%d bytes)", len(data)) + return nil, gasReport, err } - gasUsed += gasForDeserialization var resp types.IBCBasicResult - err = json.Unmarshal(data, &resp) + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &resp) if err != nil { - return nil, gasUsed, err + return nil, gasReport, err } if resp.Err != "" { - return nil, gasUsed, fmt.Errorf("%s", resp.Err) + return nil, gasReport, fmt.Errorf("%s", resp.Err) } - return resp.Ok, gasUsed, nil + return resp.Ok, gasReport, nil } // IBCPacketReceive is available on IBC-enabled contracts and is called when an incoming @@ -518,32 +464,26 @@ func (vm *VM) IBCPacketReceive( gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction, -) (*types.IBCReceiveResult, uint64, error) { +) (*types.IBCReceiveResult, types.GasReport, error) { envBin, err := json.Marshal(env) if err != nil { - return nil, 0, err + return nil, types.EmptyGasReport(gasLimit), err } msgBin, err := json.Marshal(msg) if err != nil { - return nil, 0, err + return nil, types.EmptyGasReport(gasLimit), err } - data, gasUsed, err := api.IBCPacketReceive(vm.cache, checksum, envBin, msgBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + data, gasReport, err := api.IBCPacketReceive(vm.cache, checksum, envBin, msgBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) if err != nil { - return nil, gasUsed, err + return nil, gasReport, err } - gasForDeserialization := deserCost.Mul(uint64(len(data))).Floor() - if gasLimit < gasForDeserialization+gasUsed { - return nil, gasUsed, fmt.Errorf("Insufficient gas left to deserialize contract execution result (%d bytes)", len(data)) - } - gasUsed += gasForDeserialization - var resp types.IBCReceiveResult - err = json.Unmarshal(data, &resp) + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &resp) if err != nil { - return nil, gasUsed, err + return nil, gasReport, err } - return &resp, gasUsed, nil + return &resp, gasReport, nil } // IBCPacketAck is available on IBC-enabled contracts and is called when an @@ -559,35 +499,29 @@ func (vm *VM) IBCPacketAck( gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction, -) (*types.IBCBasicResponse, uint64, error) { +) (*types.IBCBasicResponse, types.GasReport, error) { envBin, err := json.Marshal(env) if err != nil { - return nil, 0, err + return nil, types.EmptyGasReport(gasLimit), err } msgBin, err := json.Marshal(msg) if err != nil { - return nil, 0, err + return nil, types.EmptyGasReport(gasLimit), err } - data, gasUsed, err := api.IBCPacketAck(vm.cache, checksum, envBin, msgBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + data, gasReport, err := api.IBCPacketAck(vm.cache, checksum, envBin, msgBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) if err != nil { - return nil, gasUsed, err + return nil, gasReport, err } - gasForDeserialization := deserCost.Mul(uint64(len(data))).Floor() - if gasLimit < gasForDeserialization+gasUsed { - return nil, gasUsed, fmt.Errorf("Insufficient gas left to deserialize contract execution result (%d bytes)", len(data)) - } - gasUsed += gasForDeserialization - var resp types.IBCBasicResult - err = json.Unmarshal(data, &resp) + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &resp) if err != nil { - return nil, gasUsed, err + return nil, gasReport, err } if resp.Err != "" { - return nil, gasUsed, fmt.Errorf("%s", resp.Err) + return nil, gasReport, fmt.Errorf("%s", resp.Err) } - return resp.Ok, gasUsed, nil + return resp.Ok, gasReport, nil } // IBCPacketTimeout is available on IBC-enabled contracts and is called when an @@ -603,33 +537,40 @@ func (vm *VM) IBCPacketTimeout( gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction, -) (*types.IBCBasicResponse, uint64, error) { +) (*types.IBCBasicResponse, types.GasReport, error) { envBin, err := json.Marshal(env) if err != nil { - return nil, 0, err + return nil, types.EmptyGasReport(gasLimit), err } msgBin, err := json.Marshal(msg) if err != nil { - return nil, 0, err + return nil, types.EmptyGasReport(gasLimit), err } - data, gasUsed, err := api.IBCPacketTimeout(vm.cache, checksum, envBin, msgBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + data, gasReport, err := api.IBCPacketTimeout(vm.cache, checksum, envBin, msgBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) if err != nil { - return nil, gasUsed, err + return nil, gasReport, err + } + + var resp types.IBCBasicResult + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &resp) + if resp.Err != "" { + return nil, gasReport, fmt.Errorf("%s", resp.Err) } + return resp.Ok, gasReport, nil +} +func DeserializeResponse(gasLimit uint64, deserCost types.UFraction, gasReport *types.GasReport, data []byte, response any) error { gasForDeserialization := deserCost.Mul(uint64(len(data))).Floor() - if gasLimit < gasForDeserialization+gasUsed { - return nil, gasUsed, fmt.Errorf("Insufficient gas left to deserialize contract execution result (%d bytes)", len(data)) + if gasLimit < gasForDeserialization+gasReport.UsedInternally { + return fmt.Errorf("Insufficient gas left to deserialize contract execution result (%d bytes)", len(data)) } - gasUsed += gasForDeserialization + gasReport.UsedInternally += gasForDeserialization + gasReport.Remaining -= gasForDeserialization - var resp types.IBCBasicResult - err = json.Unmarshal(data, &resp) + err := json.Unmarshal(data, response) if err != nil { - return nil, gasUsed, err + return err } - if resp.Err != "" { - return nil, gasUsed, fmt.Errorf("%s", resp.Err) - } - return resp.Ok, gasUsed, nil + + return nil } diff --git a/types/types.go b/types/types.go index aa2cecdac..cfc3ed857 100644 --- a/types/types.go +++ b/types/types.go @@ -115,6 +115,22 @@ func (o OutOfGasError) Error() string { return "Out of gas" } +type GasReport struct { + Limit uint64 + Remaining uint64 + UsedExternally uint64 + UsedInternally uint64 +} + +func EmptyGasReport(limit uint64) GasReport { + return GasReport{ + Limit: limit, + Remaining: limit, + UsedExternally: 0, + UsedInternally: 0, + } +} + // Contains static analysis info of the contract (the Wasm code to be precise). // This type is returned by VM.AnalyzeCode(). type AnalysisReport struct {