Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions graph/src/runtime/gas/costs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,10 @@ pub const JSON_FROM_BYTES: GasOp = GasOp {
base_cost: DEFAULT_BASE_COST,
size_mult: DEFAULT_GAS_PER_BYTE * 100,
};

// Deeply nested YAML can take up more than 100 times the memory of the serialized format.
// Multiplying the size cost by 100 accounts for this.
pub const YAML_FROM_BYTES: GasOp = GasOp {
base_cost: DEFAULT_BASE_COST,
size_mult: DEFAULT_GAS_PER_BYTE * 100,
};
12 changes: 11 additions & 1 deletion graph/src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,17 @@ pub enum IndexForAscTypeId {
// Subgraph Data Source types
AscEntityTrigger = 4500,

// Reserved discriminant space for a future blockchain type IDs: [4,500, 5,499]
// Reserved discriminant space for YAML type IDs: [5,500, 6,499]
YamlValue = 5500,
YamlTaggedValue = 5501,
YamlTypedMapEntryValueValue = 5502,
YamlTypedMapValueValue = 5503,
YamlArrayValue = 5504,
YamlArrayTypedMapEntryValueValue = 5505,
YamlWrappedValue = 5506,
YamlResultValueBool = 5507,

// Reserved discriminant space for a future blockchain type IDs: [6,500, 7,499]
//
// Generated with the following shell script:
//
Expand Down
68 changes: 68 additions & 0 deletions runtime/test/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1698,3 +1698,71 @@ async fn test_store_ts() {
"Cannot get entity of type `Stats`. The type must be an @entity type",
);
}

async fn test_yaml_parsing(api_version: Version, gas_used: u64) {
let mut module = test_module(
"yamlParsing",
mock_data_source(
&wasm_file_path("yaml_parsing.wasm", api_version.clone()),
api_version.clone(),
),
api_version,
)
.await;

let mut test = |input: &str, expected: &str| {
let ptr: AscPtr<AscString> = module.invoke_export1("handleYaml", input.as_bytes());
let resp: String = module.asc_get(ptr).unwrap();
assert_eq!(resp, expected, "failed on input: {input}");
};

// Test invalid YAML;
test("{a: 1, - b: 2}", "error");

// Test size limit;
test(&"x".repeat(10_000_0001), "error");

// Test nulls;
test("null", "(0) null");

// Test booleans;
test("false", "(1) false");
test("true", "(1) true");

// Test numbers;
test("12345", "(2) 12345");
test("12345.6789", "(2) 12345.6789");

// Test strings;
test("aa bb cc", "(3) aa bb cc");
test("\"aa bb cc\"", "(3) aa bb cc");

// Test arrays;
test("[1, 2, 3, 4]", "(4) [(2) 1, (2) 2, (2) 3, (2) 4]");
test("- 1\n- 2\n- 3\n- 4", "(4) [(2) 1, (2) 2, (2) 3, (2) 4]");

// Test objects;
test("{a: 1, b: 2, c: 3}", "(5) {a: (2) 1, b: (2) 2, c: (2) 3}");
test("a: 1\nb: 2\nc: 3", "(5) {a: (2) 1, b: (2) 2, c: (2) 3}");

// Test tagged values;
test("!AA bb cc", "(6) !AA (3) bb cc");

// Test nesting;
test(
"aa:\n bb:\n - cc: !DD ee",
"(5) {aa: (5) {bb: (4) [(5) {cc: (6) !DD (3) ee}]}}",
);

assert_eq!(module.gas_used(), gas_used, "gas used");
}

#[tokio::test]
async fn yaml_parsing_v0_0_4() {
test_yaml_parsing(API_VERSION_0_0_4, 10462217077171).await;
}

#[tokio::test]
async fn yaml_parsing_v0_0_5() {
test_yaml_parsing(API_VERSION_0_0_5, 10462245390665).await;
}
20 changes: 20 additions & 0 deletions runtime/test/wasm_test/api_version_0_0_4/yaml_parsing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import "allocator/arena";

import {Bytes, Result} from "../api_version_0_0_5/common/types";
import {debug, YAMLValue} from "../api_version_0_0_5/common/yaml";

export {memory};

declare namespace yaml {
function try_fromBytes(data: Bytes): Result<YAMLValue, boolean>;
}

export function handleYaml(data: Bytes): string {
let result = yaml.try_fromBytes(data);

if (result.isError) {
return "error";
}

return debug(result.value);
}
Binary file not shown.
139 changes: 139 additions & 0 deletions runtime/test/wasm_test/api_version_0_0_5/common/yaml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import {TypedMap} from './types';

export enum YAMLValueKind {
NULL = 0,
BOOL = 1,
NUMBER = 2,
STRING = 3,
ARRAY = 4,
OBJECT = 5,
TAGGED = 6,
}

export class YAMLValue {
kind: YAMLValueKind;
data: u64;

isBool(): boolean {
return this.kind == YAMLValueKind.BOOL;
}

isNumber(): boolean {
return this.kind == YAMLValueKind.NUMBER;
}

isString(): boolean {
return this.kind == YAMLValueKind.STRING;
}

isArray(): boolean {
return this.kind == YAMLValueKind.ARRAY;
}

isObject(): boolean {
return this.kind == YAMLValueKind.OBJECT;
}

isTagged(): boolean {
return this.kind == YAMLValueKind.TAGGED;
}


toBool(): boolean {
assert(this.isBool(), 'YAML value is not a boolean');
return this.data != 0;
}

toNumber(): string {
assert(this.isNumber(), 'YAML value is not a number');
return changetype<string>(this.data as usize);
}

toString(): string {
assert(this.isString(), 'YAML value is not a string');
return changetype<string>(this.data as usize);
}

toArray(): Array<YAMLValue> {
assert(this.isArray(), 'YAML value is not an array');
return changetype<Array<YAMLValue>>(this.data as usize);
}

toObject(): TypedMap<YAMLValue, YAMLValue> {
assert(this.isObject(), 'YAML value is not an object');
return changetype<TypedMap<YAMLValue, YAMLValue>>(this.data as usize);
}

toTagged(): YAMLTaggedValue {
assert(this.isTagged(), 'YAML value is not tagged');
return changetype<YAMLTaggedValue>(this.data as usize);
}
}

export class YAMLTaggedValue {
tag: string;
value: YAMLValue;
}


export function debug(value: YAMLValue): string {
return "(" + value.kind.toString() + ") " + debug_value(value);
}

function debug_value(value: YAMLValue): string {
switch (value.kind) {
case YAMLValueKind.NULL:
return "null";
case YAMLValueKind.BOOL:
return value.toBool() ? "true" : "false";
case YAMLValueKind.NUMBER:
return value.toNumber();
case YAMLValueKind.STRING:
return value.toString();
case YAMLValueKind.ARRAY: {
let arr = value.toArray();

let s = "[";
for (let i = 0; i < arr.length; i++) {
if (i > 0) {
s += ", ";
}
s += debug(arr[i]);
}
s += "]";

return s;
}
case YAMLValueKind.OBJECT: {
let arr = value.toObject().entries.sort((a, b) => {
if (a.key.toString() < b.key.toString()) {
return -1;
}

if (a.key.toString() > b.key.toString()) {
return 1;
}

return 0;
});

let s = "{";
for (let i = 0; i < arr.length; i++) {
if (i > 0) {
s += ", ";
}
s += debug_value(arr[i].key) + ": " + debug(arr[i].value);
}
s += "}";

return s;
}
case YAMLValueKind.TAGGED: {
let tagged = value.toTagged();

return tagged.tag + " " + debug(tagged.value);
}
default:
return "undefined";
}
}
62 changes: 62 additions & 0 deletions runtime/test/wasm_test/api_version_0_0_5/yaml_parsing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {debug, YAMLValue, YAMLTaggedValue} from './common/yaml';
import {Bytes, Result, TypedMap, TypedMapEntry, Wrapped} from './common/types';

enum TypeId {
STRING = 0,
UINT8_ARRAY = 6,

YamlValue = 5500,
YamlTaggedValue = 5501,
YamlTypedMapEntryValueValue = 5502,
YamlTypedMapValueValue = 5503,
YamlArrayValue = 5504,
YamlArrayTypedMapEntryValueValue = 5505,
YamlWrappedValue = 5506,
YamlResultValueBool = 5507,
}

export function id_of_type(type_id_index: TypeId): usize {
switch (type_id_index) {
case TypeId.STRING:
return idof<string>();
case TypeId.UINT8_ARRAY:
return idof<Uint8Array>();

case TypeId.YamlValue:
return idof<YAMLValue>();
case TypeId.YamlTaggedValue:
return idof<YAMLTaggedValue>();
case TypeId.YamlTypedMapEntryValueValue:
return idof<TypedMapEntry<YAMLValue, YAMLValue>>();
case TypeId.YamlTypedMapValueValue:
return idof<TypedMap<YAMLValue, YAMLValue>>();
case TypeId.YamlArrayValue:
return idof<Array<YAMLValue>>();
case TypeId.YamlArrayTypedMapEntryValueValue:
return idof<Array<TypedMapEntry<YAMLValue, YAMLValue>>>();
case TypeId.YamlWrappedValue:
return idof<Wrapped<YAMLValue>>();
case TypeId.YamlResultValueBool:
return idof<Result<YAMLValue, boolean>>();
default:
return 0;
}
}

export function allocate(n: usize): usize {
return __alloc(n);
}

declare namespace yaml {
function try_fromBytes(data: Bytes): Result<YAMLValue, boolean>;
}

export function handleYaml(data: Bytes): string {
let result = yaml.try_fromBytes(data);

if (result.isError) {
return "error";
}

return debug(result.value);
}
Binary file not shown.
2 changes: 2 additions & 0 deletions runtime/wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ wasm-instrument = { version = "0.2.0", features = ["std", "sign_ext"] }

# AssemblyScript uses sign extensions
parity-wasm = { version = "0.45", features = ["std", "sign_ext"] }

serde_yaml = { workspace = true }
Loading
Loading