Skip to content

Commit

Permalink
try some new tests
Browse files Browse the repository at this point in the history
  • Loading branch information
faddat committed Dec 25, 2024
1 parent 66c4080 commit 8a2f628
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 113 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ build-go:
.PHONY: test
test:
# Use package list mode to include all subdirectores. The -count=1 turns off caching.
RUST_BACKTRACE=1 go test -v -count=1 ./...
CGO_ENABLED=1 RUST_BACKTRACE=1 go test -v -count=1 ./...

.PHONY: test-safety
test-safety:
Expand Down
138 changes: 104 additions & 34 deletions internal/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,18 @@ func TestInstantiateWithVariousMsgFormats(t *testing.T) {
expectFailure: true,
expErrMsg: "Error parsing into type hackatom::msg::InstantiateMsg",
},
{
name: "big extra field",
jsonMsg: buildTestJSON(30, 5), // adjust repeats as needed
expectFailure: true,
expErrMsg: "Error parsing into type hackatom::msg::InstantiateMsg: missing field `beneficiary`",
},
{
name: "giant extra field",
jsonMsg: buildTestJSON(300, 50), // even bigger
expectFailure: true,
expErrMsg: "Error parsing into type hackatom::msg::InstantiateMsg: missing field `beneficiary`",
},
{
name: "Empty JSON message",
jsonMsg: `{}`,
Expand Down Expand Up @@ -284,50 +296,108 @@ func buildTestJSON(fieldRepeat, valueRepeat int) string {
}

func TestExtraFieldParsing(t *testing.T) {
cache, cleanup := withCache(t)
defer cleanup()

// Load the contract
wasmPath := "../../testdata/hackatom.wasm"
wasm, err := os.ReadFile(wasmPath)
require.NoError(t, err, "Could not read wasm file at %s", wasmPath)

// Store the code in the cache
checksum, err := StoreCode(cache, wasm, true)
require.NoError(t, err, "Storing code failed for %s", wasmPath)

// We'll create a few test scenarios that each produce extra-large JSON messages
// so we're sending multiple megabytes. We'll log how many MB are being sent.
tests := []struct {
name string
jsonMsg string
expectFailure bool
expErrMsg string
name string
fieldRepeat int
valueRepeat int
expErrMsg string
}{
{
name: "big extra field",
jsonMsg: buildTestJSON(30, 5), // adjust repeats as needed
expectFailure: true,
expErrMsg: "Error parsing into type hackatom::msg::InstantiateMsg: missing field `beneficiary`",
name: "0.01 MB of extra field data",
fieldRepeat: 150, // Tweak until you reach ~1MB total payload
valueRepeat: 25,
expErrMsg: "Error parsing into type hackatom::msg::InstantiateMsg",
},
{
name: "giant extra field",
jsonMsg: buildTestJSON(300, 50), // even bigger
expectFailure: true,
expErrMsg: "Error parsing into type hackatom::msg::InstantiateMsg: missing field `beneficiary`",
name: "0.1 MB of extra field data",
fieldRepeat: 15000, // Tweak until you reach ~1MB total payload
valueRepeat: 7000,
expErrMsg: "Error parsing into type hackatom::msg::InstantiateMsg",
},
{
name: "~2MB of extra field data",
fieldRepeat: 1500,
valueRepeat: 250,
expErrMsg: "Error parsing into type hackatom::msg::InstantiateMsg",
},
{
name: ">10MB of extra field data",
fieldRepeat: 100000,
valueRepeat: 100000,
expErrMsg: "Error parsing into type hackatom::msg::InstantiateMsg",
},
}

for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
// Instead of printing the entire JSON to the console,
// you would typically pass tc.jsonMsg to whatever JSON-parsing
// or instantiation logic you have, then check the result.
//
// Example pseudo-check:
err := parseJSONIntoHackatomInstantiateMsg(tc.jsonMsg)
if tc.expectFailure && err == nil {
t.Errorf("expected failure but got success")
} else if !tc.expectFailure && err != nil {
t.Errorf("did not expect an error but got one: %v", err)
}
if err != nil && !strings.Contains(err.Error(), tc.expErrMsg) {
t.Errorf("error message does not match. expected '%s', got '%s'",
tc.expErrMsg, err.Error())
}
// Build JSON with a huge extra field
jsonMsg := buildTestJSON(tc.fieldRepeat, tc.valueRepeat)

// Log how large the JSON message is (in MB)
sizeMB := float64(len(jsonMsg)) / (1024.0 * 1024.0)
t.Logf("[DEBUG] Using JSON of size: %.2f MB", sizeMB)

gasMeter := NewMockGasMeter(TESTING_GAS_LIMIT)
store := NewLookup(gasMeter)
api := NewMockAPI()
querier := DefaultQuerier(MOCK_CONTRACT_ADDR, nil)
env := MockEnvBin(t)
info := MockInfoBin(t, "creator")

msg := []byte(jsonMsg)

var igasMeter types.GasMeter = gasMeter
res, cost, err := Instantiate(
cache,
checksum,
env,
info,
msg,
&igasMeter,
store,
api,
&querier,
TESTING_GAS_LIMIT,
TESTING_PRINT_DEBUG,
)

t.Logf("[DEBUG] Gas Used: %d, Gas Remaining: %d", cost.UsedInternally, cost.Remaining)

// Ensure there's no Go-level fatal error
require.NoError(t, err,
"[GO-level error] Instantiation must not return a fatal error for scenario: %s", tc.name)

// Decode the contract result (CosmWasm-level error will appear in contractResult.Err if any)
var contractResult types.ContractResult
err = json.Unmarshal(res, &contractResult)
require.NoError(t, err,
"JSON unmarshal of contract result must succeed (scenario: %s)\nRaw contract response: %s",
tc.name, string(res),
)

// We expect the contract to reject such large messages. Adjust if your contract differs.
require.Nil(t, contractResult.Ok,
"Expected no Ok response for scenario: %s, but got: %+v", tc.name, contractResult.Ok)
require.Contains(t, contractResult.Err, tc.expErrMsg,
"Expected error containing '%s', but got '%s' for scenario: %s",
tc.expErrMsg, contractResult.Err, tc.name)

t.Logf("[OK] We got the expected contract-level error. Full error: %s", contractResult.Err)
})
}
}

// parseJSONIntoHackatomInstantiateMsg is a stand-in for your actual parsing logic
func parseJSONIntoHackatomInstantiateMsg(json string) error {
// Replace with your real JSON->struct parsing
// For demonstration, we just force an error containing the same error text.
return fmt.Errorf("Error parsing into type hackatom::msg::InstantiateMsg: missing field `beneficiary`")
}
69 changes: 46 additions & 23 deletions internal/api/bindings.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
#include <stdint.h>
#include <stdlib.h>

enum ErrnoValue {
enum ErrnoValue
{
ErrnoValue_Success = 0,
ErrnoValue_Other = 1,
ErrnoValue_OutOfGas = 2,
Expand All @@ -23,7 +24,8 @@ typedef int32_t ErrnoValue;
* 0 means no error, all the other cases are some sort of error.
*
*/
enum GoError {
enum GoError
{
GoError_None = 0,
/**
* Go panicked for an unexpected reason.
Expand Down Expand Up @@ -53,7 +55,8 @@ enum GoError {
};
typedef int32_t GoError;

typedef struct cache_t {
typedef struct cache_t
{

} cache_t;

Expand All @@ -64,7 +67,8 @@ typedef struct cache_t {
*
* Go's nil value is fully supported, such that we can differentiate between nil and an empty slice.
*/
typedef struct ByteSliceView {
typedef struct ByteSliceView
{
/**
* True if and only if the byte slice is nil in Go. If this is true, the other fields must be ignored.
*/
Expand Down Expand Up @@ -184,7 +188,8 @@ typedef struct ByteSliceView {
* // `output` is ready to be passed around
* ```
*/
typedef struct UnmanagedVector {
typedef struct UnmanagedVector
{
/**
* True if and only if this is None. If this is true, the other fields must be ignored.
*/
Expand All @@ -197,7 +202,8 @@ typedef struct UnmanagedVector {
/**
* A version of `Option<u64>` that can be used safely in FFI.
*/
typedef struct OptionalU64 {
typedef struct OptionalU64
{
bool is_some;
uint64_t value;
} OptionalU64;
Expand All @@ -209,7 +215,8 @@ typedef struct OptionalU64 {
* has to be destroyed exactly once. When calling `analyze_code`
* from Go this is done via `C.destroy_unmanaged_vector`.
*/
typedef struct AnalysisReport {
typedef struct AnalysisReport
{
/**
* `true` if and only if all required ibc exports exist as exported functions.
* This does not guarantee they are functional or even have the correct signatures.
Expand All @@ -234,7 +241,8 @@ typedef struct AnalysisReport {
struct OptionalU64 contract_migrate_version;
} AnalysisReport;

typedef struct Metrics {
typedef struct Metrics
{
uint32_t hits_pinned_memory_cache;
uint32_t hits_memory_cache;
uint32_t hits_fs_cache;
Expand All @@ -248,11 +256,13 @@ typedef struct Metrics {
/**
* An opaque type. `*gas_meter_t` represents a pointer to Go memory holding the gas meter.
*/
typedef struct gas_meter_t {
typedef struct gas_meter_t
{
uint8_t _private[0];
} gas_meter_t;

typedef struct db_t {
typedef struct db_t
{
uint8_t _private[0];
} db_t;

Expand All @@ -261,7 +271,8 @@ typedef struct db_t {
*
* This can be copied into a []byte in Go.
*/
typedef struct U8SliceView {
typedef struct U8SliceView
{
/**
* True if and only if this is None. If this is true, the other fields must be ignored.
*/
Expand All @@ -274,7 +285,8 @@ typedef struct U8SliceView {
* A reference to some tables on the Go side which allow accessing
* the actual iterator instance.
*/
typedef struct IteratorReference {
typedef struct IteratorReference
{
/**
* An ID assigned to this contract call
*/
Expand All @@ -285,7 +297,8 @@ typedef struct IteratorReference {
uint64_t iterator_id;
} IteratorReference;

typedef struct IteratorVtable {
typedef struct IteratorVtable
{
int32_t (*next)(struct IteratorReference iterator,
struct gas_meter_t *gas_meter,
uint64_t *gas_used,
Expand All @@ -304,7 +317,8 @@ typedef struct IteratorVtable {
struct UnmanagedVector *err_msg_out);
} IteratorVtable;

typedef struct GoIter {
typedef struct GoIter
{
struct gas_meter_t *gas_meter;
/**
* A reference which identifies the iterator and allows finding and accessing the
Expand All @@ -314,7 +328,8 @@ typedef struct GoIter {
struct IteratorVtable vtable;
} GoIter;

typedef struct DbVtable {
typedef struct DbVtable
{
int32_t (*read_db)(struct db_t *db,
struct gas_meter_t *gas_meter,
uint64_t *gas_used,
Expand Down Expand Up @@ -342,17 +357,20 @@ typedef struct DbVtable {
struct UnmanagedVector *err_msg_out);
} DbVtable;

typedef struct Db {
typedef struct Db
{
struct gas_meter_t *gas_meter;
struct db_t *state;
struct DbVtable vtable;
} Db;

typedef struct api_t {
typedef struct api_t
{
uint8_t _private[0];
} api_t;

typedef struct GoApiVtable {
typedef struct GoApiVtable
{
int32_t (*humanize_address)(const struct api_t *api,
struct U8SliceView input,
struct UnmanagedVector *humanized_address_out,
Expand All @@ -369,16 +387,19 @@ typedef struct GoApiVtable {
uint64_t *gas_used);
} GoApiVtable;

typedef struct GoApi {
typedef struct GoApi
{
const struct api_t *state;
struct GoApiVtable vtable;
} GoApi;

typedef struct querier_t {
typedef struct querier_t
{
uint8_t _private[0];
} querier_t;

typedef struct QuerierVtable {
typedef struct QuerierVtable
{
int32_t (*query_external)(const struct querier_t *querier,
uint64_t gas_limit,
uint64_t *gas_used,
Expand All @@ -387,12 +408,14 @@ typedef struct QuerierVtable {
struct UnmanagedVector *err_msg_out);
} QuerierVtable;

typedef struct GoQuerier {
typedef struct GoQuerier
{
const struct querier_t *state;
struct QuerierVtable vtable;
} GoQuerier;

typedef struct GasReport {
typedef struct GasReport
{
/**
* The original limit the instance was created with
*/
Expand Down
Loading

0 comments on commit 8a2f628

Please sign in to comment.