diff --git a/.travis.yml b/.travis.yml index 4555503..703f1c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,5 @@ language: node_js node_js: - - "10" + - "12" + - "14" + - "16" diff --git a/chain/ethereum.ts b/chain/ethereum.ts index f92ae93..d74f5e1 100644 --- a/chain/ethereum.ts +++ b/chain/ethereum.ts @@ -1,4 +1,6 @@ -import { Address, BigInt, Bytes, Wrapped } from '..' +import '../common/eager_offset' +import { Bytes, Wrapped } from '../common/collections' +import { Address, BigInt } from '../common/numbers' /** Host Ethereum interface */ export declare namespace ethereum { @@ -33,8 +35,22 @@ export namespace ethereum { * A dynamically typed value used when accessing Ethereum data. */ export class Value { - kind: ValueKind - data: ValuePayload + constructor( + public kind: ValueKind, + public data: ValuePayload, + ) {} + + @operator('<') + lt(other: Value): boolean { + abort("Less than operator isn't supported in Value") + return false + } + + @operator('>') + gt(other: Value): boolean { + abort("Greater than operator isn't supported in Value") + return false + } toAddress(): Address { assert(this.kind == ValueKind.ADDRESS, 'Ethereum value is not an address') @@ -97,7 +113,7 @@ export namespace ethereum { let valueArray = this.toArray() let out = new Array(valueArray.length) for (let i: i32 = 0; i < valueArray.length; i++) { - out[i] = valueArray[i].toTuple() as T + out[i] = valueArray[i].toTuple() } return out } @@ -182,81 +198,47 @@ export namespace ethereum { static fromAddress(address: Address): Value { assert(address.length == 20, 'Address must contain exactly 20 bytes') - - let token = new Value() - token.kind = ValueKind.ADDRESS - token.data = address as u64 - return token + return new Value(ValueKind.ADDRESS, changetype(address)) } static fromBoolean(b: boolean): Value { - let token = new Value() - token.kind = ValueKind.BOOL - token.data = b ? 1 : 0 - return token + return new Value(ValueKind.BOOL, b ? 1 : 0) } static fromBytes(bytes: Bytes): Value { - let token = new Value() - token.kind = ValueKind.BYTES - token.data = bytes as u64 - return token + return new Value(ValueKind.BYTES, changetype(bytes)) } static fromFixedBytes(bytes: Bytes): Value { - let token = new Value() - token.kind = ValueKind.FIXED_BYTES - token.data = bytes as u64 - return token + return new Value(ValueKind.FIXED_BYTES, changetype(bytes)) } static fromI32(i: i32): Value { - let token = new Value() - token.kind = ValueKind.INT - token.data = BigInt.fromI32(i) as u64 - return token + return new Value(ValueKind.INT, changetype(BigInt.fromI32(i))) } static fromSignedBigInt(i: BigInt): Value { - let token = new Value() - token.kind = ValueKind.INT - token.data = i as u64 - return token + return new Value(ValueKind.INT, changetype(i)) } static fromUnsignedBigInt(i: BigInt): Value { - let token = new Value() - token.kind = ValueKind.UINT - token.data = i as u64 - return token + return new Value(ValueKind.UINT, changetype(i)) } static fromString(s: string): Value { - let token = new Value() - token.kind = ValueKind.STRING - token.data = s as u64 - return token + return new Value(ValueKind.STRING, changetype(s)) } static fromArray(values: Array): Value { - let token = new Value() - token.kind = ValueKind.ARRAY - token.data = values as u64 - return token + return new Value(ValueKind.ARRAY, changetype(values)) } static fromFixedSizedArray(values: Array): Value { - let token = new Value() - token.kind = ValueKind.FIXED_ARRAY - token.data = values as u64 - return token + return new Value(ValueKind.FIXED_ARRAY, changetype(values)) } static fromTuple(values: Tuple): Value { - let token = new Value() - token.kind = ValueKind.TUPLE - token.data = values as u64 - return token + return new Value(ValueKind.TUPLE, changetype(values)) } static fromTupleArray(values: Array): Value { @@ -345,67 +327,67 @@ export namespace ethereum { * An Ethereum block. */ export class Block { - hash: Bytes - parentHash: Bytes - unclesHash: Bytes - author: Address - stateRoot: Bytes - transactionsRoot: Bytes - receiptsRoot: Bytes - number: BigInt - gasUsed: BigInt - gasLimit: BigInt - timestamp: BigInt - difficulty: BigInt - totalDifficulty: BigInt - size: BigInt | null + hash!: Bytes + parentHash!: Bytes + unclesHash!: Bytes + author!: Address + stateRoot!: Bytes + transactionsRoot!: Bytes + receiptsRoot!: Bytes + number!: BigInt + gasUsed!: BigInt + gasLimit!: BigInt + timestamp!: BigInt + difficulty!: BigInt + totalDifficulty!: BigInt + size!: BigInt | null } /** * An Ethereum transaction. */ export class Transaction { - hash: Bytes - index: BigInt - from: Address - to: Address | null - value: BigInt - gasLimit: BigInt - gasPrice: BigInt - input: Bytes + hash!: Bytes + index!: BigInt + from!: Address + to!: Address | null + value!: BigInt + gasLimit!: BigInt + gasPrice!: BigInt + input!: Bytes } /** * Common representation for Ethereum smart contract calls. */ export class Call { - to: Address - from: Address - block: Block - transaction: Transaction - inputValues: Array - outputValues: Array + to!: Address + from!: Address + block!: Block + transaction!: Transaction + inputValues!: Array + outputValues!: Array } /** * Common representation for Ethereum smart contract events. */ export class Event { - address: Address - logIndex: BigInt - transactionLogIndex: BigInt - logType: string | null - block: Block - transaction: Transaction - parameters: Array + address!: Address + logIndex!: BigInt + transactionLogIndex!: BigInt + logType!: string | null + block!: Block + transaction!: Transaction + parameters!: Array } /** * A dynamically-typed Ethereum event parameter. */ export class EventParam { - name: string - value: Value + name!: string + value!: Value } export class SmartContractCall { @@ -452,7 +434,7 @@ export namespace ethereum { name + '` to handle this in the mapping.', ) - return result as Array + return changetype>(result) } tryCall( @@ -465,7 +447,7 @@ export namespace ethereum { if (result == null) { return new CallResult() } else { - return CallResult.fromValue(result as Array) + return CallResult.fromValue(changetype>(result)) } } } @@ -494,7 +476,7 @@ export namespace ethereum { 'accessed value of a reverted call, ' + 'please check the `reverted` field before accessing the `value` field', ) - return (this._value as Wrapped).inner + return changetype>(this._value).inner } } } diff --git a/common/collections.ts b/common/collections.ts new file mode 100644 index 0000000..7151bdc --- /dev/null +++ b/common/collections.ts @@ -0,0 +1,316 @@ +import { BigInt, BigDecimal } from './numbers' +import { JSONValue, Value } from './value' +import { typeConversion } from './conversion' + +/** + * Byte array + */ +export class ByteArray extends Uint8Array { + /** + * Returns bytes in little-endian order. + */ + static fromI32(x: i32): ByteArray { + let self = new ByteArray(4) + self[0] = x as u8 + self[1] = (x >> 8) as u8 + self[2] = (x >> 16) as u8 + self[3] = (x >> 24) as u8 + return self + } + + /** + * Input length must be even. + */ + static fromHexString(hex: string): ByteArray { + assert(hex.length % 2 == 0, 'input ' + hex + ' has odd length') + // Skip possible `0x` prefix. + if (hex.length >= 2 && hex[0] == '0' && hex[1] == 'x') { + hex = hex.substr(2) + } + let output = new Bytes(hex.length / 2) + for (let i = 0; i < hex.length; i += 2) { + output[i / 2] = I8.parseInt(hex.substr(i, 2), 16) + } + return output + } + + static fromUTF8(str: String): ByteArray { + // AssemblyScript counts a null terminator, we don't want that. + let utf8 = String.UTF8.encode(str) + return changetype(ByteArray.wrap(utf8)) + } + + static fromBigInt(bigInt: BigInt): ByteArray { + return changetype(bigInt) + } + + toHex(): string { + return typeConversion.bytesToHex(this) + } + + toHexString(): string { + return typeConversion.bytesToHex(this) + } + + toString(): string { + return typeConversion.bytesToString(this) + } + + toBase58(): string { + return typeConversion.bytesToBase58(this) + } + + /** + * Interprets the byte array as a little-endian U32. + * Throws in case of overflow. + */ + + toU32(): u32 { + for (let i = 4; i < this.length; i++) { + if (this[i] != 0) { + assert(false, 'overflow converting ' + this.toHexString() + ' to u32') + } + } + let paddedBytes = new Bytes(4) + paddedBytes[0] = 0 + paddedBytes[1] = 0 + paddedBytes[2] = 0 + paddedBytes[3] = 0 + let minLen = paddedBytes.length < this.length ? paddedBytes.length : this.length + for (let i = 0; i < minLen; i++) { + paddedBytes[i] = this[i] + } + let x: u32 = 0 + x = (x | paddedBytes[3]) << 8 + x = (x | paddedBytes[2]) << 8 + x = (x | paddedBytes[1]) << 8 + x = x | paddedBytes[0] + return x + } + + /** + * Interprets the byte array as a little-endian I32. + * Throws in case of overflow. + */ + + toI32(): i32 { + let isNeg = this.length > 0 && this[this.length - 1] >> 7 == 1 + let padding = isNeg ? 255 : 0 + for (let i = 4; i < this.length; i++) { + if (this[i] != padding) { + assert(false, 'overflow converting ' + this.toHexString() + ' to i32') + } + } + let paddedBytes = new Bytes(4) + paddedBytes[0] = padding + paddedBytes[1] = padding + paddedBytes[2] = padding + paddedBytes[3] = padding + let minLen = paddedBytes.length < this.length ? paddedBytes.length : this.length + for (let i = 0; i < minLen; i++) { + paddedBytes[i] = this[i] + } + let x: i32 = 0 + x = (x | paddedBytes[3]) << 8 + x = (x | paddedBytes[2]) << 8 + x = (x | paddedBytes[1]) << 8 + x = x | paddedBytes[0] + return x + } + + @operator('==') + equals(other: ByteArray): boolean { + if (this.length != other.length) { + return false + } + for (let i = 0; i < this.length; i++) { + if (this[i] != other[i]) { + return false + } + } + return true + } + + @operator('!=') + notEqual(other: ByteArray): boolean { + return !(this == other) + } +} + +/** A dynamically-sized byte array. */ +export class Bytes extends ByteArray { + static fromByteArray(byteArray: ByteArray): Bytes { + return changetype(byteArray) + } + + static fromUint8Array(uint8Array: Uint8Array): Bytes { + return changetype(uint8Array) + } +} + +/** + * TypedMap entry. + */ +export class TypedMapEntry { + key: K + value: V + + constructor(key: K, value: V) { + this.key = key + this.value = value + } +} + +/** Typed map */ +export class TypedMap { + entries: Array> + + constructor() { + this.entries = new Array>(0) + // this.entries = [] + } + + set(key: K, value: V): void { + let entry = this.getEntry(key) + if (entry !== null) { + entry.value = value + } else { + let entry = new TypedMapEntry(key, value) + this.entries.push(entry) + } + } + + getEntry(key: K): TypedMapEntry | null { + for (let i: i32 = 0; i < this.entries.length; i++) { + if (this.entries[i].key == key) { + return this.entries[i] + } + } + return null + } + + get(key: K): V | null { + for (let i: i32 = 0; i < this.entries.length; i++) { + if (this.entries[i].key == key) { + return this.entries[i].value + } + } + return null + } + + isSet(key: K): bool { + for (let i: i32 = 0; i < this.entries.length; i++) { + if (this.entries[i].key == key) { + return true + } + } + return false + } +} + +/** + * Common representation for entity data, storing entity attributes + * as `string` keys and the attribute values as dynamically-typed + * `Value` objects. + */ +export class Entity extends TypedMap { + unset(key: string): void { + this.set(key, Value.fromNull()) + } + + /** Assigns properties from sources to this Entity in right-to-left order */ + merge(sources: Array): Entity { + var target = this + for (let i = 0; i < sources.length; i++) { + let entries = sources[i].entries + for (let j = 0; j < entries.length; j++) { + target.set(entries[j].key, entries[j].value) + } + } + return target + } + + setString(key: string, value: string): void { + this.set(key, Value.fromString(value)) + } + + setI32(key: string, value: i32): void { + this.set(key, Value.fromI32(value)) + } + + setBigInt(key: string, value: BigInt): void { + this.set(key, Value.fromBigInt(value)) + } + + setBytes(key: string, value: Bytes): void { + this.set(key, Value.fromBytes(value)) + } + + setBoolean(key: string, value: bool): void { + this.set(key, Value.fromBoolean(value)) + } + + setBigDecimal(key: string, value: BigDecimal): void { + this.set(key, Value.fromBigDecimal(value)) + } + + getString(key: string): string { + return this.get(key)!.toString() + } + + getI32(key: string): i32 { + return this.get(key)!.toI32() + } + + getBigInt(key: string): BigInt { + return this.get(key)!.toBigInt() + } + + getBytes(key: string): Bytes { + return this.get(key)!.toBytes() + } + + getBoolean(key: string): boolean { + return this.get(key)!.toBoolean() + } + + getBigDecimal(key: string): BigDecimal { + return this.get(key)!.toBigDecimal() + } +} + +/** + * The result of an operation, with a corresponding value and error type. + */ +export class Result { + _value: Wrapped | null + _error: Wrapped | null + + get isOk(): boolean { + return this._value !== null + } + + get isError(): boolean { + return this._error !== null + } + + get value(): V { + assert(this._value != null, 'Trying to get a value from an error result') + return changetype>(this._value).inner + } + + get error(): E { + assert(this._error != null, 'Trying to get an error from a successful result') + return changetype>(this._error).inner + } +} + +// This is used to wrap a generic so that it can be unioned with `null`, working around limitations +// with primitives. +export class Wrapped { + inner: T + + constructor(inner: T) { + this.inner = inner + } +} diff --git a/common/conversion.ts b/common/conversion.ts new file mode 100644 index 0000000..31341f9 --- /dev/null +++ b/common/conversion.ts @@ -0,0 +1,12 @@ +import './eager_offset' +import { Bytes } from './collections' + +/** Host type conversion interface */ +export declare namespace typeConversion { + function bytesToString(bytes: Uint8Array): string + function bytesToHex(bytes: Uint8Array): string + function bigIntToString(bigInt: Uint8Array): string + function bigIntToHex(bigInt: Uint8Array): string + function stringToH160(s: string): Bytes + function bytesToBase58(n: Uint8Array): string +} diff --git a/common/datasource.ts b/common/datasource.ts new file mode 100644 index 0000000..af338dd --- /dev/null +++ b/common/datasource.ts @@ -0,0 +1,44 @@ +import './eager_offset' +import { Address } from './numbers' +import { Entity } from './collections' + +/** Host interface for managing data sources */ +export declare namespace dataSource { + function create(name: string, params: Array): void + function createWithContext( + name: string, + params: Array, + context: DataSourceContext, + ): void + + // Properties of the data source that fired the event. + function address(): Address + function network(): string + function context(): DataSourceContext +} + +/** Context for dynamic data sources */ +export class DataSourceContext extends Entity {} + +/** + * Base class for data source templates. Allows to dynamically create + * data sources from templates at runtime. + */ +export class DataSourceTemplate { + /** + * Dynamically creates a data source from the template with the + * given name, using the parameter strings to configure the new + * data source. + */ + static create(name: string, params: Array): void { + dataSource.create(name, params) + } + + static createWithContext( + name: string, + params: Array, + context: DataSourceContext, + ): void { + dataSource.createWithContext(name, params, context) + } +} diff --git a/common/eager_offset.ts b/common/eager_offset.ts new file mode 100644 index 0000000..c93224b --- /dev/null +++ b/common/eager_offset.ts @@ -0,0 +1,41 @@ +// # What is this file? +// This file is a "hack" to allow global variables in subgraphs and +// on this library (`graph-ts`). +// +// # Why is it needed? +// It's necessary because of one of the features of the AssemblyScript +// compiler we use, the stub runtime. +// +// The problem happens because we call the stub runtime allocation +// (`__alloc`) directly on Rust side (`graph-node`), and that doesn't +// trigger some AssemblyScript aspects of the code. +// +// If you take a look at the stub runtime's code, you'll see that the +// `__alloc` function uses a variable named `offset` tagged as `lazy`. +// Like said above, since we call it on Rust side, this variable is not +// "triggered" to be used, then it's declared below the `__alloc` call +// in the compiled WASM code. +// +// That makes the `graph-node` WASM runtime break because of this out +// of order variable usage. +// +// # How does this fix the issue? +// The way this workaround works is by calling the `__alloc` function +// before everything in the AssemblyScript side. This makes the `offset` +// `lazy` variable be eagerly evaluated when the mappings are compiled +// (since they always import `graph-ts`). +// +// So when we're on Rust side calling `__alloc` it will be fine, because +// the `offset` is declared before call (order fixed because of this file). +// +// The 0 argument to the function call is just because we need no memory +// to be allocated. +// +// # IMPORTANT +// This should be imported in EVERY file which uses external namespaces (`graph-node` host-exports code), +// just to make sure no one imports a file directly and gets an error on global variables. +// +// # Reference +// - Runtimes in AS: https://www.assemblyscript.org/garbage-collection.html#runtime-variants +// - `offset` variable in question: https://github.com/AssemblyScript/assemblyscript/blob/f4091b8f3b6b029d30cd917cf84d97421faadeeb/std/assembly/rt/stub.ts#L9 +__alloc(0); diff --git a/common/json.ts b/common/json.ts new file mode 100644 index 0000000..01323bd --- /dev/null +++ b/common/json.ts @@ -0,0 +1,14 @@ +import './eager_offset' +import { BigInt } from './numbers' +import { JSONValue } from './value' +import { Bytes, Result } from './collections' + +/** Host JSON interface */ +export declare namespace json { + function fromBytes(data: Bytes): JSONValue + function try_fromBytes(data: Bytes): Result + function toI64(decimal: string): i64 + function toU64(decimal: string): u64 + function toF64(decimal: string): f64 + function toBigInt(decimal: string): BigInt +} diff --git a/common/numbers.ts b/common/numbers.ts new file mode 100644 index 0000000..debe5b8 --- /dev/null +++ b/common/numbers.ts @@ -0,0 +1,367 @@ +import './eager_offset' +import { Bytes, ByteArray } from './collections' +import { typeConversion } from './conversion' + +/** Host interface for BigInt arithmetic */ +export declare namespace bigInt { + function plus(x: BigInt, y: BigInt): BigInt + function minus(x: BigInt, y: BigInt): BigInt + function times(x: BigInt, y: BigInt): BigInt + function dividedBy(x: BigInt, y: BigInt): BigInt + function dividedByDecimal(x: BigInt, y: BigDecimal): BigDecimal + function mod(x: BigInt, y: BigInt): BigInt + function pow(x: BigInt, exp: u8): BigInt + function fromString(s: string): BigInt + function bitOr(x: BigInt, y: BigInt): BigInt + function bitAnd(x: BigInt, y: BigInt): BigInt + function leftShift(x: BigInt, bits: u8): BigInt + function rightShift(x: BigInt, bits: u8): BigInt +} + +/** Host interface for BigDecimal */ +export declare namespace bigDecimal { + function plus(x: BigDecimal, y: BigDecimal): BigDecimal + function minus(x: BigDecimal, y: BigDecimal): BigDecimal + function times(x: BigDecimal, y: BigDecimal): BigDecimal + function dividedBy(x: BigDecimal, y: BigDecimal): BigDecimal + function equals(x: BigDecimal, y: BigDecimal): boolean + function toString(bigDecimal: BigDecimal): string + function fromString(s: string): BigDecimal +} + +/** An Ethereum address (20 bytes). */ +export class Address extends Bytes { + static fromString(s: string): Address { + return changetype
(typeConversion.stringToH160(s)) + } +} + +/** An arbitrary size integer represented as an array of bytes. */ +export class BigInt extends Uint8Array { + static fromI32(x: i32): BigInt { + let byteArray = ByteArray.fromI32(x) + return BigInt.fromByteArray(byteArray) + } + + /** + * `bytes` assumed to be little-endian. If your input is big-endian, call `.reverse()` first. + */ + + static fromSignedBytes(bytes: Bytes): BigInt { + let byteArray = bytes + return BigInt.fromByteArray(byteArray) + } + + static fromByteArray(byteArray: ByteArray): BigInt { + return changetype(byteArray) + } + + /** + * `bytes` assumed to be little-endian. If your input is big-endian, call `.reverse()` first. + */ + + static fromUnsignedBytes(bytes: Bytes): BigInt { + let signedBytes = new BigInt(bytes.length + 1) + for (let i = 0; i < bytes.length; i++) { + signedBytes[i] = bytes[i] + } + signedBytes[bytes.length] = 0 + return signedBytes + } + + toHex(): string { + return typeConversion.bigIntToHex(this) + } + + toHexString(): string { + return typeConversion.bigIntToHex(this) + } + + toString(): string { + return typeConversion.bigIntToString(this) + } + + static fromString(s: string): BigInt { + return bigInt.fromString(s) + } + + toI32(): i32 { + let uint8Array = changetype(this) + let byteArray = changetype(uint8Array) + return byteArray.toI32() + } + + toBigDecimal(): BigDecimal { + return new BigDecimal(this) + } + + isZero(): boolean { + return this == BigInt.fromI32(0) + } + + isI32(): boolean { + return BigInt.fromI32(i32.MIN_VALUE) <= this && this <= BigInt.fromI32(i32.MAX_VALUE) + } + + abs(): BigInt { + return this < BigInt.fromI32(0) ? -this : this + } + + sqrt(): BigInt { + let x = this + let z = x.plus(BigInt.fromI32(1)).div(BigInt.fromI32(2)) + let y = x + while (z < y) { + y = z + z = x.div(z).plus(z).div(BigInt.fromI32(2)) + } + + return y + } + + // Operators + + @operator('+') + plus(other: BigInt): BigInt { + return bigInt.plus(this, other) + } + + @operator('-') + minus(other: BigInt): BigInt { + return bigInt.minus(this, other) + } + + @operator('*') + times(other: BigInt): BigInt { + return bigInt.times(this, other) + } + + @operator('/') + div(other: BigInt): BigInt { + return bigInt.dividedBy(this, other) + } + + divDecimal(other: BigDecimal): BigDecimal { + return bigInt.dividedByDecimal(this, other) + } + + @operator('%') + mod(other: BigInt): BigInt { + return bigInt.mod(this, other) + } + + @operator('==') + equals(other: BigInt): boolean { + return BigInt.compare(this, other) == 0 + } + + @operator('!=') + notEqual(other: BigInt): boolean { + return !(this == other) + } + + @operator('<') + lt(other: BigInt): boolean { + return BigInt.compare(this, other) == -1 + } + + @operator('>') + gt(other: BigInt): boolean { + return BigInt.compare(this, other) == 1 + } + + @operator('<=') + le(other: BigInt): boolean { + return !(this > other) + } + + @operator('>=') + ge(other: BigInt): boolean { + return !(this < other) + } + + @operator.prefix('-') + neg(): BigInt { + return BigInt.fromI32(0) - this + } + + @operator('|') + bitOr(other: BigInt): BigInt { + return bigInt.bitOr(this, other) + } + + @operator('&') + bitAnd(other: BigInt): BigInt { + return bigInt.bitAnd(this, other) + } + + @operator('<<') + leftShift(bits: u8): BigInt { + return bigInt.leftShift(this, bits) + } + + @operator('>>') + rightShift(bits: u8): BigInt { + return bigInt.rightShift(this, bits) + } + + /// Limited to a low exponent to discourage creating a huge BigInt. + pow(exp: u8): BigInt { + return bigInt.pow(this, exp) + } + + /** + * Returns −1 if a < b, 1 if a > b, and 0 if A == B + */ + static compare(a: BigInt, b: BigInt): i32 { + // Check if a and b have the same sign. + let aIsNeg = a.length > 0 && a[a.length - 1] >> 7 == 1 + let bIsNeg = b.length > 0 && b[b.length - 1] >> 7 == 1 + + if (!aIsNeg && bIsNeg) { + return 1 + } else if (aIsNeg && !bIsNeg) { + return -1 + } + + // Check how many bytes of a and b are relevant to the magnitude. + let aRelevantBytes = a.length + while ( + aRelevantBytes > 0 && + ((!aIsNeg && a[aRelevantBytes - 1] == 0) || + (aIsNeg && a[aRelevantBytes - 1] == 255)) + ) { + aRelevantBytes -= 1 + } + let bRelevantBytes = b.length + while ( + bRelevantBytes > 0 && + ((!bIsNeg && b[bRelevantBytes - 1] == 0) || + (bIsNeg && b[bRelevantBytes - 1] == 255)) + ) { + bRelevantBytes -= 1 + } + + // If a and b are positive then the one with more relevant bytes is larger. + // Otherwise the one with less relevant bytes is larger. + if (aRelevantBytes > bRelevantBytes) { + return aIsNeg ? -1 : 1 + } else if (bRelevantBytes > aRelevantBytes) { + return aIsNeg ? 1 : -1 + } + + // We now know that a and b have the same sign and number of relevant bytes. + // If a and b are both negative then the one of lesser magnitude is the + // largest, however since in two's complement the magnitude is flipped, we + // may use the same logic as if a and b are positive. + let relevantBytes = aRelevantBytes + for (let i = 1; i <= relevantBytes; i++) { + if (a[relevantBytes - i] < b[relevantBytes - i]) { + return -1 + } else if (a[relevantBytes - i] > b[relevantBytes - i]) { + return 1 + } + } + + return 0 + } +} + +export class BigDecimal { + digits: BigInt + exp: BigInt + + constructor(bigInt: BigInt) { + this.digits = bigInt + this.exp = BigInt.fromI32(0) + } + + static fromString(s: string): BigDecimal { + return bigDecimal.fromString(s) + } + + toString(): string { + return bigDecimal.toString(this) + } + + truncate(decimals: i32): BigDecimal { + let digitsRightOfZero = this.digits.toString().length + this.exp.toI32() + let newDigitLength = decimals + digitsRightOfZero + let truncateLength = this.digits.toString().length - newDigitLength + if (truncateLength < 0) { + return this + } else { + for (let i = 0; i < truncateLength; i++) { + this.digits = this.digits.div(BigInt.fromI32(10)) + } + this.exp = BigInt.fromI32(decimals * -1) + return this + } + } + + @operator('+') + plus(other: BigDecimal): BigDecimal { + return bigDecimal.plus(this, other) + } + + @operator('-') + minus(other: BigDecimal): BigDecimal { + return bigDecimal.minus(this, other) + } + + @operator('*') + times(other: BigDecimal): BigDecimal { + return bigDecimal.times(this, other) + } + + @operator('/') + div(other: BigDecimal): BigDecimal { + return bigDecimal.dividedBy(this, other) + } + + @operator('==') + equals(other: BigDecimal): boolean { + return BigDecimal.compare(this, other) == 0 + } + + @operator('!=') + notEqual(other: BigDecimal): boolean { + return !(this == other) + } + + @operator('<') + lt(other: BigDecimal): boolean { + return BigDecimal.compare(this, other) == -1 + } + + @operator('>') + gt(other: BigDecimal): boolean { + return BigDecimal.compare(this, other) == 1 + } + + @operator('<=') + le(other: BigDecimal): boolean { + return !(this > other) + } + + @operator('>=') + ge(other: BigDecimal): boolean { + return !(this < other) + } + + @operator.prefix('-') + neg(): BigDecimal { + return new BigDecimal(new BigInt(0)) - this + } + + /** + * Returns −1 if a < b, 1 if a > b, and 0 if A == B + */ + static compare(a: BigDecimal, b: BigDecimal): i32 { + let diff = a - b + if (diff.digits.isZero()) { + return 0 + } + return diff.digits > BigInt.fromI32(0) ? 1 : -1 + } +} diff --git a/common/value.ts b/common/value.ts new file mode 100644 index 0000000..b84a62c --- /dev/null +++ b/common/value.ts @@ -0,0 +1,296 @@ +import { Address, BigInt, BigDecimal } from './numbers' +import { Bytes, TypedMap } from './collections' +import { json } from './json' + +/** + * Enum for supported value types. + */ +export enum ValueKind { + STRING = 0, + INT = 1, + BIGDECIMAL = 2, + BOOL = 3, + ARRAY = 4, + NULL = 5, + BYTES = 6, + BIGINT = 7, +} + +/** + * Pointer type for Value data. + * + * Big enough to fit any pointer or native `this.data`. + */ +export type ValuePayload = u64 + +/** + * A dynamically typed value. + */ +export class Value { + constructor( + public kind: ValueKind, + public data: ValuePayload, + ) {} + + toAddress(): Address { + assert(this.kind == ValueKind.BYTES, 'Value is not an address.') + return changetype
(this.data as u32) + } + + toBoolean(): boolean { + if (this.kind == ValueKind.NULL) { + return false + } + assert(this.kind == ValueKind.BOOL, 'Value is not a boolean.') + return this.data != 0 + } + + toBytes(): Bytes { + assert(this.kind == ValueKind.BYTES, 'Value is not a byte array.') + return changetype(this.data as u32) + } + + toI32(): i32 { + if (this.kind == ValueKind.NULL) { + return 0 + } + assert(this.kind == ValueKind.INT, 'Value is not an i32.') + return this.data as i32 + } + + toString(): string { + assert(this.kind == ValueKind.STRING, 'Value is not a string.') + return changetype(this.data as u32) + } + + toBigInt(): BigInt { + assert(this.kind == ValueKind.BIGINT, 'Value is not a BigInt.') + return changetype(this.data as u32) + } + + toBigDecimal(): BigDecimal { + assert(this.kind == ValueKind.BIGDECIMAL, 'Value is not a BigDecimal.') + return changetype(this.data as u32) + } + + toArray(): Array { + assert(this.kind == ValueKind.ARRAY, 'Value is not an array.') + return changetype>(this.data as u32) + } + + toBooleanArray(): Array { + let values = this.toArray() + let output = new Array(values.length) + for (let i: i32 = 0; i < values.length; i++) { + output[i] = values[i].toBoolean() + } + return output + } + + toBytesArray(): Array { + let values = this.toArray() + let output = new Array(values.length) + for (let i: i32 = 0; i < values.length; i++) { + output[i] = values[i].toBytes() + } + return output + } + + toStringArray(): Array { + let values = this.toArray() + let output = new Array(values.length) + for (let i: i32 = 0; i < values.length; i++) { + output[i] = values[i].toString() + } + return output + } + + toI32Array(): Array { + let values = this.toArray() + let output = new Array(values.length) + for (let i: i32 = 0; i < values.length; i++) { + output[i] = values[i].toI32() + } + return output + } + + toBigIntArray(): Array { + let values = this.toArray() + let output = new Array(values.length) + for (let i: i32 = 0; i < values.length; i++) { + output[i] = values[i].toBigInt() + } + return output + } + + toBigDecimalArray(): Array { + let values = this.toArray() + let output = new Array(values.length) + for (let i: i32 = 0; i < values.length; i++) { + output[i] = values[i].toBigDecimal() + } + return output + } + + static fromBooleanArray(input: Array): Value { + let output = new Array(input.length) + for (let i: i32 = 0; i < input.length; i++) { + output[i] = Value.fromBoolean(input[i]) + } + return Value.fromArray(output) + } + + static fromBytesArray(input: Array): Value { + let output = new Array(input.length) + for (let i: i32 = 0; i < input.length; i++) { + output[i] = Value.fromBytes(input[i]) + } + return Value.fromArray(output) + } + + static fromI32Array(input: Array): Value { + let output = new Array(input.length) + for (let i: i32 = 0; i < input.length; i++) { + output[i] = Value.fromI32(input[i]) + } + return Value.fromArray(output) + } + + static fromBigIntArray(input: Array): Value { + let output = new Array(input.length) + for (let i: i32 = 0; i < input.length; i++) { + output[i] = Value.fromBigInt(input[i]) + } + return Value.fromArray(output) + } + + static fromBigDecimalArray(input: Array): Value { + let output = new Array(input.length) + for (let i: i32 = 0; i < input.length; i++) { + output[i] = Value.fromBigDecimal(input[i]) + } + return Value.fromArray(output) + } + + static fromStringArray(input: Array): Value { + let output = new Array(input.length) + for (let i: i32 = 0; i < input.length; i++) { + output[i] = Value.fromString(input[i]) + } + return Value.fromArray(output) + } + + static fromAddressArray(input: Array
): Value { + let output = new Array(input.length) + for (let i: i32 = 0; i < input.length; i++) { + output[i] = Value.fromAddress(input[i]) + } + return Value.fromArray(output) + } + + static fromArray(input: Array): Value { + return new Value(ValueKind.ARRAY, changetype(input)) + } + + static fromBigInt(n: BigInt): Value { + return new Value(ValueKind.BIGINT, changetype(n)) + } + + static fromBoolean(b: boolean): Value { + return new Value(ValueKind.BOOL, b ? 1 : 0) + } + + static fromBytes(bytes: Bytes): Value { + return new Value(ValueKind.BYTES, changetype(bytes)) + } + + static fromNull(): Value { + return new Value(ValueKind.NULL, 0) + } + + static fromI32(n: i32): Value { + return new Value(ValueKind.INT, n as u64) + } + + static fromString(s: string): Value { + return new Value(ValueKind.STRING, changetype(s)) + } + + static fromAddress(s: Address): Value { + return new Value(ValueKind.BYTES, changetype(s)) + } + + static fromBigDecimal(n: BigDecimal): Value { + return new Value(ValueKind.BIGDECIMAL, changetype(n)) + } +} + +/** Type hint for JSON values. */ +export enum JSONValueKind { + NULL = 0, + BOOL = 1, + NUMBER = 2, + STRING = 3, + ARRAY = 4, + OBJECT = 5, +} + +/** + * Pointer type for JSONValue data. + * + * Big enough to fit any pointer or native `this.data`. + */ +export type JSONValuePayload = u64 + +export class JSONValue { + kind: JSONValueKind + data: JSONValuePayload + + isNull(): boolean { + return this.kind == JSONValueKind.NULL + } + + toBool(): boolean { + assert(this.kind == JSONValueKind.BOOL, 'JSON value is not a boolean.') + return this.data != 0 + } + + toI64(): i64 { + assert(this.kind == JSONValueKind.NUMBER, 'JSON value is not a number.') + let decimalString = changetype(this.data as u32) + return json.toI64(decimalString) + } + + toU64(): u64 { + assert(this.kind == JSONValueKind.NUMBER, 'JSON value is not a number.') + let decimalString = changetype(this.data as u32) + return json.toU64(decimalString) + } + + toF64(): f64 { + assert(this.kind == JSONValueKind.NUMBER, 'JSON value is not a number.') + let decimalString = changetype(this.data as u32) + return json.toF64(decimalString) + } + + toBigInt(): BigInt { + assert(this.kind == JSONValueKind.NUMBER, 'JSON value is not a number.') + let decimalString = changetype(this.data as u32) + return json.toBigInt(decimalString) + } + + toString(): string { + assert(this.kind == JSONValueKind.STRING, 'JSON value is not a string.') + return changetype(this.data as u32) + } + + toArray(): Array { + assert(this.kind == JSONValueKind.ARRAY, 'JSON value is not an array.') + return changetype>(this.data as u32) + } + + toObject(): TypedMap { + assert(this.kind == JSONValueKind.OBJECT, 'JSON value is not an object.') + return changetype>(this.data as u32) + } +} diff --git a/global/global.ts b/global/global.ts index a320cd7..e92a04d 100644 --- a/global/global.ts +++ b/global/global.ts @@ -1,2 +1,174 @@ -import 'allocator/arena' -export { memory } +import { BigDecimal } from '../common/numbers' +import { TypedMapEntry, Entity, TypedMap, Result, Wrapped } from '../common/collections' +import { JSONValue, Value } from '../common/value' +import { ethereum } from '../chain/ethereum' + +export enum TypeId { + String = 0, + ArrayBuffer = 1, + Int8Array = 2, + Int16Array = 3, + Int32Array = 4, + Int64Array = 5, + Uint8Array = 6, + Uint16Array = 7, + Uint32Array = 8, + Uint64Array = 9, + Float32Array = 10, + Float64Array = 11, + BigDecimal = 12, + ArrayBool = 13, + ArrayUint8Array = 14, + ArrayEthereumValue = 15, + ArrayStoreValue = 16, + ArrayJsonValue = 17, + ArrayString = 18, + ArrayEventParam = 19, + ArrayTypedMapEntryStringJsonValue = 20, + ArrayTypedMapEntryStringStoreValue = 21, + SmartContractCall = 22, + EventParam = 23, + EthereumTransaction = 24, + EthereumBlock = 25, + EthereumCall = 26, + WrappedTypedMapStringJsonValue = 27, + WrappedBool = 28, + WrappedJsonValue = 29, + EthereumValue = 30, + StoreValue = 31, + JsonValue = 32, + EthereumEvent = 33, + TypedMapEntryStringStoreValue = 34, + TypedMapEntryStringJsonValue = 35, + TypedMapStringStoreValue = 36, + TypedMapStringJsonValue = 37, + TypedMapStringTypedMapStringJsonValue = 38, + ResultTypedMapStringJsonValueBool = 39, + ResultJsonValueBool = 40, + ArrayU8 = 41, + ArrayU16 = 42, + ArrayU32 = 43, + ArrayU64 = 44, + ArrayI8 = 45, + ArrayI16 = 46, + ArrayI32 = 47, + ArrayI64 = 48, + ArrayF32 = 49, + ArrayF64 = 50, + ArrayBigDecimal = 51, +} + +export function id_of_type(typeId: TypeId): usize { + switch (typeId) { + case TypeId.String: + return idof() + case TypeId.ArrayBuffer: + return idof() + case TypeId.Int8Array: + return idof() + case TypeId.Int16Array: + return idof() + case TypeId.Int32Array: + return idof() + case TypeId.Int64Array: + return idof() + case TypeId.Uint8Array: + return idof() + case TypeId.Uint16Array: + return idof() + case TypeId.Uint32Array: + return idof() + case TypeId.Uint64Array: + return idof() + case TypeId.Float32Array: + return idof() + case TypeId.Float64Array: + return idof() + case TypeId.BigDecimal: + return idof() + case TypeId.ArrayBool: + return idof>() + case TypeId.ArrayUint8Array: + return idof>() + case TypeId.ArrayEthereumValue: + return idof>() + case TypeId.ArrayStoreValue: + return idof>() + case TypeId.ArrayJsonValue: + return idof>() + case TypeId.ArrayString: + return idof>() + case TypeId.ArrayEventParam: + return idof>() + case TypeId.ArrayTypedMapEntryStringJsonValue: + return idof>>() + case TypeId.ArrayTypedMapEntryStringStoreValue: + return idof>() + case TypeId.WrappedTypedMapStringJsonValue: + return idof>>() + case TypeId.WrappedBool: + return idof>() + case TypeId.WrappedJsonValue: + return idof>() + case TypeId.SmartContractCall: + return idof() + case TypeId.EventParam: + return idof() + case TypeId.EthereumTransaction: + return idof() + case TypeId.EthereumBlock: + return idof() + case TypeId.EthereumCall: + return idof() + case TypeId.EthereumValue: + return idof() + case TypeId.StoreValue: + return idof() + case TypeId.JsonValue: + return idof() + case TypeId.EthereumEvent: + return idof() + case TypeId.TypedMapEntryStringStoreValue: + return idof() + case TypeId.TypedMapEntryStringJsonValue: + return idof>() + case TypeId.TypedMapStringStoreValue: + return idof>() + case TypeId.TypedMapStringJsonValue: + return idof>() + case TypeId.TypedMapStringTypedMapStringJsonValue: + return idof>>() + case TypeId.ResultTypedMapStringJsonValueBool: + return idof, boolean>>() + case TypeId.ResultJsonValueBool: + return idof>() + case TypeId.ArrayU8: + return idof>() + case TypeId.ArrayU16: + return idof>() + case TypeId.ArrayU32: + return idof>() + case TypeId.ArrayU64: + return idof>() + case TypeId.ArrayI8: + return idof>() + case TypeId.ArrayI16: + return idof>() + case TypeId.ArrayI32: + return idof>() + case TypeId.ArrayI64: + return idof>() + case TypeId.ArrayF32: + return idof>() + case TypeId.ArrayF64: + return idof>() + case TypeId.ArrayBigDecimal: + return idof>() + default: + return 0 + } +} + +export function allocate(size: usize): usize { + return __alloc(size) +} diff --git a/helper-functions.ts b/helper-functions.ts index 7897d18..bbff682 100644 --- a/helper-functions.ts +++ b/helper-functions.ts @@ -14,7 +14,7 @@ export function concat(a: ByteArray, b: ByteArray): ByteArray { for (let j = 0; j < b.length; j++) { out[a.length + j] = b[j] } - return out as ByteArray + return changetype(out) } /** @@ -77,5 +77,5 @@ export function addQm(a: ByteArray): ByteArray { for (let i = 0; i < 32; i++) { out[i + 2] = a[i] } - return out as ByteArray + return changetype(out) } diff --git a/index.ts b/index.ts index 7c1c8a2..b04a0db 100644 --- a/index.ts +++ b/index.ts @@ -1,8 +1,28 @@ -// For testing isolated compilation. -import 'allocator/arena' - +// Side-effect to evaluate eagerly the offset of stub AS runtime +import './common/eager_offset' // Ethereum support export * from './chain/ethereum' +// Regular re-exports +export * from './common/numbers' +export * from './common/collections' +export * from './common/value' +export * from './common/conversion' +export * from './common/json' +export * from './common/datasource' + +import { + Address, + BigInt, + BigDecimal, +} from './common/numbers' +import { + Bytes, + ByteArray, + Result, + TypedMap, + Entity, +} from './common/collections' +import { JSONValue, Value } from './common/value' /** * Host store interface. @@ -21,7 +41,7 @@ export declare namespace ipfs { export namespace ipfs { export function mapJSON(hash: string, callback: string, userData: Value): void { - map(hash, callback, userData, ['json']) + ipfs.map(hash, callback, userData, ['json']) } } @@ -30,68 +50,6 @@ export declare namespace crypto { function keccak256(input: ByteArray): ByteArray } -/** Host JSON interface */ -export declare namespace json { - function fromBytes(data: Bytes): JSONValue - function try_fromBytes(data: Bytes): Result - function toI64(decimal: string): i64 - function toU64(decimal: string): u64 - function toF64(decimal: string): f64 - function toBigInt(decimal: string): BigInt -} - -/** Host type conversion interface */ -declare namespace typeConversion { - function bytesToString(bytes: Uint8Array): string - function bytesToHex(bytes: Uint8Array): string - function bigIntToString(bigInt: Uint8Array): string - function bigIntToHex(bigInt: Uint8Array): string - function stringToH160(s: string): Bytes - function bytesToBase58(n: Uint8Array): string -} - -/** Host interface for BigInt arithmetic */ -declare namespace bigInt { - function plus(x: BigInt, y: BigInt): BigInt - function minus(x: BigInt, y: BigInt): BigInt - function times(x: BigInt, y: BigInt): BigInt - function dividedBy(x: BigInt, y: BigInt): BigInt - function dividedByDecimal(x: BigInt, y: BigDecimal): BigDecimal - function mod(x: BigInt, y: BigInt): BigInt - function pow(x: BigInt, exp: u8): BigInt - function fromString(s: string): BigInt - function bitOr(x: BigInt, y: BigInt): BigInt - function bitAnd(x: BigInt, y: BigInt): BigInt - function leftShift(x: BigInt, bits: u8): BigInt - function rightShift(x: BigInt, bits: u8): BigInt -} - -/** Host interface for BigDecimal */ -declare namespace bigDecimal { - function plus(x: BigDecimal, y: BigDecimal): BigDecimal - function minus(x: BigDecimal, y: BigDecimal): BigDecimal - function times(x: BigDecimal, y: BigDecimal): BigDecimal - function dividedBy(x: BigDecimal, y: BigDecimal): BigDecimal - function equals(x: BigDecimal, y: BigDecimal): boolean - function toString(bigDecimal: BigDecimal): string - function fromString(s: string): BigDecimal -} - -/** Host interface for managing data sources */ -export declare namespace dataSource { - function create(name: string, params: Array): void - function createWithContext( - name: string, - params: Array, - context: DataSourceContext, - ): void - - // Properties of the data source that fired the event. - function address(): Address - function network(): string - function context(): DataSourceContext -} - /** * Special function for ENS name lookups, not meant for general purpose use. * This function will only be useful if the graph-node instance has additional @@ -145,7 +103,7 @@ export namespace log { * @param args Format string arguments. */ export function critical(msg: string, args: Array): void { - log(Level.CRITICAL, format(msg, args)) + log.log(Level.CRITICAL, format(msg, args)) } /** @@ -155,7 +113,7 @@ export namespace log { * @param args Format string arguments. */ export function error(msg: string, args: Array): void { - log(Level.ERROR, format(msg, args)) + log.log(Level.ERROR, format(msg, args)) } /** Logs a warning message. @@ -164,7 +122,7 @@ export namespace log { * @param args Format string arguments. */ export function warning(msg: string, args: Array): void { - log(Level.WARNING, format(msg, args)) + log.log(Level.WARNING, format(msg, args)) } /** Logs an info message. @@ -173,7 +131,7 @@ export namespace log { * @param args Format string arguments. */ export function info(msg: string, args: Array): void { - log(Level.INFO, format(msg, args)) + log.log(Level.INFO, format(msg, args)) } /** Logs a debug message. @@ -182,983 +140,6 @@ export namespace log { * @param args Format string arguments. */ export function debug(msg: string, args: Array): void { - log(Level.DEBUG, format(msg, args)) - } -} - -/** - * The result of an operation, with a corresponding value and error type. - */ -export class Result { - _value: Wrapped | null - _error: Wrapped | null - - get isOk(): boolean { - return this._value !== null - } - - get isError(): boolean { - return this._error !== null - } - - get value(): V { - assert(this._value != null, 'Trying to get a value from an error result') - return (this._value as Wrapped).inner - } - - get error(): E { - assert(this._error != null, 'Trying to get an error from a successful result') - return (this._error as Wrapped).inner - } -} - -/** - * TypedMap entry. - */ -export class TypedMapEntry { - key: K - value: V - - constructor(key: K, value: V) { - this.key = key - this.value = value - } -} - -/** Typed map */ -export class TypedMap { - entries: Array> - - constructor() { - this.entries = new Array>(0) - } - - set(key: K, value: V): void { - let entry = this.getEntry(key) - if (entry !== null) { - entry.value = value - } else { - let entry = new TypedMapEntry(key, value) - this.entries.push(entry) - } - } - - getEntry(key: K): TypedMapEntry | null { - for (let i: i32 = 0; i < this.entries.length; i++) { - if (this.entries[i].key == key) { - return this.entries[i] - } - } - return null - } - - get(key: K): V | null { - for (let i: i32 = 0; i < this.entries.length; i++) { - if (this.entries[i].key == key) { - return this.entries[i].value - } - } - return null - } - - isSet(key: K): bool { - for (let i: i32 = 0; i < this.entries.length; i++) { - if (this.entries[i].key == key) { - return true - } - } - return false - } -} - -/** - * Byte array - */ -export class ByteArray extends Uint8Array { - /** - * Returns bytes in little-endian order. - */ - static fromI32(x: i32): ByteArray { - let self = new ByteArray(4) - self[0] = x as u8 - self[1] = (x >> 8) as u8 - self[2] = (x >> 16) as u8 - self[3] = (x >> 24) as u8 - return self - } - - /** - * Input length must be even. - */ - static fromHexString(hex: string): ByteArray { - assert(hex.length % 2 == 0, 'input ' + hex + ' has odd length') - // Skip possible `0x` prefix. - if (hex.length >= 2 && hex[0] == '0' && hex[1] == 'x') { - hex = hex.substr(2) - } - let output = new Bytes(hex.length / 2) - for (let i = 0; i < hex.length; i += 2) { - output[i / 2] = I8.parseInt(hex.substr(i, 2), 16) - } - return output - } - - static fromUTF8(string: String): ByteArray { - // AssemblyScript counts a null terminator, we don't want that. - let len = string.lengthUTF8 - 1 - let utf8 = string.toUTF8() - let bytes = new ByteArray(len) - for (let i: i32 = 0; i < len; i++) { - bytes[i] = load(utf8 + i) - } - return bytes - } - - toHex(): string { - return typeConversion.bytesToHex(this) - } - - toHexString(): string { - return typeConversion.bytesToHex(this) - } - - toString(): string { - return typeConversion.bytesToString(this) - } - - toBase58(): string { - return typeConversion.bytesToBase58(this) - } - - /** - * Interprets the byte array as a little-endian U32. - * Throws in case of overflow. - */ - - toU32(): u32 { - for (let i = 4; i < this.length; i++) { - if (this[i] != 0) { - assert(false, 'overflow converting ' + this.toHexString() + ' to u32') - } - } - let paddedBytes = new Bytes(4) - paddedBytes[0] = 0 - paddedBytes[1] = 0 - paddedBytes[2] = 0 - paddedBytes[3] = 0 - let minLen = paddedBytes.length < this.length ? paddedBytes.length : this.length - for (let i = 0; i < minLen; i++) { - paddedBytes[i] = this[i] - } - let x: u32 = 0 - x = (x | paddedBytes[3]) << 8 - x = (x | paddedBytes[2]) << 8 - x = (x | paddedBytes[1]) << 8 - x = x | paddedBytes[0] - return x - } - - /** - * Interprets the byte array as a little-endian I32. - * Throws in case of overflow. - */ - - toI32(): i32 { - let isNeg = this.length > 0 && this[this.length - 1] >> 7 == 1 - let padding = isNeg ? 255 : 0 - for (let i = 4; i < this.length; i++) { - if (this[i] != padding) { - assert(false, 'overflow converting ' + this.toHexString() + ' to i32') - } - } - let paddedBytes = new Bytes(4) - paddedBytes[0] = padding - paddedBytes[1] = padding - paddedBytes[2] = padding - paddedBytes[3] = padding - let minLen = paddedBytes.length < this.length ? paddedBytes.length : this.length - for (let i = 0; i < minLen; i++) { - paddedBytes[i] = this[i] - } - let x: i32 = 0 - x = (x | paddedBytes[3]) << 8 - x = (x | paddedBytes[2]) << 8 - x = (x | paddedBytes[1]) << 8 - x = x | paddedBytes[0] - return x - } - - @operator('==') - equals(other: ByteArray): boolean { - if (this.length != other.length) { - return false - } - for (let i = 0; i < this.length; i++) { - if (this[i] != other[i]) { - return false - } - } - return true - } - - @operator('!=') - notEqual(other: ByteArray): boolean { - return !(this == other) - } -} - -/** A dynamically-sized byte array. */ -export class Bytes extends ByteArray {} - -/** An Ethereum address (20 bytes). */ -export class Address extends Bytes { - static fromString(s: string): Address { - return typeConversion.stringToH160(s) as Address - } -} - -/** An arbitrary size integer represented as an array of bytes. */ -export class BigInt extends Uint8Array { - static fromI32(x: i32): BigInt { - return (ByteArray.fromI32(x) as Uint8Array) as BigInt - } - - /** - * `bytes` assumed to be little-endian. If your input is big-endian, call `.reverse()` first. - */ - - static fromSignedBytes(bytes: Bytes): BigInt { - return (bytes as Uint8Array) as BigInt - } - - /** - * `bytes` assumed to be little-endian. If your input is big-endian, call `.reverse()` first. - */ - - static fromUnsignedBytes(bytes: Bytes): BigInt { - let signedBytes = new BigInt(bytes.length + 1) - for (let i = 0; i < bytes.length; i++) { - signedBytes[i] = bytes[i] - } - signedBytes[bytes.length] = 0 - return signedBytes - } - - toHex(): string { - return typeConversion.bigIntToHex(this) - } - - toHexString(): string { - return typeConversion.bigIntToHex(this) - } - - toString(): string { - return typeConversion.bigIntToString(this) - } - - static fromString(s: string): BigInt { - return bigInt.fromString(s) - } - - toI32(): i32 { - return ((this as Uint8Array) as ByteArray).toI32() - } - - toBigDecimal(): BigDecimal { - return new BigDecimal(this) - } - - isZero(): boolean { - return this == BigInt.fromI32(0) - } - - isI32(): boolean { - return BigInt.fromI32(i32.MIN_VALUE) <= this && this <= BigInt.fromI32(i32.MAX_VALUE) - } - - abs(): BigInt { - return this < BigInt.fromI32(0) ? -this : this - } - - sqrt(): BigInt { - let x = this - let z = x.plus(BigInt.fromI32(1)).div(BigInt.fromI32(2)) - let y = x - while (z < y) { - y = z - z = x.div(z).plus(z).div(BigInt.fromI32(2)) - } - - return y - } - - // Operators - - @operator('+') - plus(other: BigInt): BigInt { - return bigInt.plus(this, other) - } - - @operator('-') - minus(other: BigInt): BigInt { - return bigInt.minus(this, other) - } - - @operator('*') - times(other: BigInt): BigInt { - return bigInt.times(this, other) - } - - @operator('/') - div(other: BigInt): BigInt { - return bigInt.dividedBy(this, other) - } - - divDecimal(other: BigDecimal): BigDecimal { - return bigInt.dividedByDecimal(this, other) - } - - @operator('%') - mod(other: BigInt): BigInt { - return bigInt.mod(this, other) - } - - @operator('==') - equals(other: BigInt): boolean { - return BigInt.compare(this, other) == 0 - } - - @operator('!=') - notEqual(other: BigInt): boolean { - return !(this == other) - } - - @operator('<') - lt(other: BigInt): boolean { - return BigInt.compare(this, other) == -1 - } - - @operator('>') - gt(other: BigInt): boolean { - return BigInt.compare(this, other) == 1 - } - - @operator('<=') - le(other: BigInt): boolean { - return !(this > other) - } - - @operator('>=') - ge(other: BigInt): boolean { - return !(this < other) - } - - @operator.prefix('-') - neg(): BigInt { - return BigInt.fromI32(0) - this - } - - @operator('|') - bitOr(other: BigInt): BigInt { - return bigInt.bitOr(this, other) - } - - @operator('&') - bitAnd(other: BigInt): BigInt { - return bigInt.bitAnd(this, other) - } - - @operator('<<') - leftShift(bits: u8): BigInt { - return bigInt.leftShift(this, bits) - } - - @operator('>>') - rightShift(bits: u8): BigInt { - return bigInt.rightShift(this, bits) - } - - /// Limited to a low exponent to discourage creating a huge BigInt. - pow(exp: u8): BigInt { - return bigInt.pow(this, exp) - } - - /** - * Returns −1 if a < b, 1 if a > b, and 0 if A == B - */ - static compare(a: BigInt, b: BigInt): i32 { - // Check if a and b have the same sign. - let aIsNeg = a.length > 0 && a[a.length - 1] >> 7 == 1 - let bIsNeg = b.length > 0 && b[b.length - 1] >> 7 == 1 - - if (!aIsNeg && bIsNeg) { - return 1 - } else if (aIsNeg && !bIsNeg) { - return -1 - } - - // Check how many bytes of a and b are relevant to the magnitude. - let aRelevantBytes = a.length - while ( - aRelevantBytes > 0 && - ((!aIsNeg && a[aRelevantBytes - 1] == 0) || - (aIsNeg && a[aRelevantBytes - 1] == 255)) - ) { - aRelevantBytes -= 1 - } - let bRelevantBytes = b.length - while ( - bRelevantBytes > 0 && - ((!bIsNeg && b[bRelevantBytes - 1] == 0) || - (bIsNeg && b[bRelevantBytes - 1] == 255)) - ) { - bRelevantBytes -= 1 - } - - // If a and b are positive then the one with more relevant bytes is larger. - // Otherwise the one with less relevant bytes is larger. - if (aRelevantBytes > bRelevantBytes) { - return aIsNeg ? -1 : 1 - } else if (bRelevantBytes > aRelevantBytes) { - return aIsNeg ? 1 : -1 - } - - // We now know that a and b have the same sign and number of relevant bytes. - // If a and b are both negative then the one of lesser magnitude is the - // largest, however since in two's complement the magnitude is flipped, we - // may use the same logic as if a and b are positive. - let relevantBytes = aRelevantBytes - for (let i = 1; i <= relevantBytes; i++) { - if (a[relevantBytes - i] < b[relevantBytes - i]) { - return -1 - } else if (a[relevantBytes - i] > b[relevantBytes - i]) { - return 1 - } - } - - return 0 - } -} - -export class BigDecimal { - digits: BigInt - exp: BigInt - - constructor(bigInt: BigInt) { - this.digits = bigInt - this.exp = BigInt.fromI32(0) - } - - static fromString(s: string): BigDecimal { - return bigDecimal.fromString(s) - } - - toString(): string { - return bigDecimal.toString(this) - } - - truncate(decimals: i32): BigDecimal { - let digitsRightOfZero = this.digits.toString().length + this.exp.toI32() - let newDigitLength = decimals + digitsRightOfZero - let truncateLength = this.digits.toString().length - newDigitLength - if (truncateLength < 0) { - return this - } else { - for (let i = 0; i < truncateLength; i++) { - this.digits = this.digits.div(BigInt.fromI32(10)) - } - this.exp = BigInt.fromI32(decimals * -1) - return this - } - } - - @operator('+') - plus(other: BigDecimal): BigDecimal { - return bigDecimal.plus(this, other) - } - - @operator('-') - minus(other: BigDecimal): BigDecimal { - return bigDecimal.minus(this, other) - } - - @operator('*') - times(other: BigDecimal): BigDecimal { - return bigDecimal.times(this, other) - } - - @operator('/') - div(other: BigDecimal): BigDecimal { - return bigDecimal.dividedBy(this, other) - } - - @operator('==') - equals(other: BigDecimal): boolean { - return BigDecimal.compare(this, other) == 0 - } - - @operator('!=') - notEqual(other: BigDecimal): boolean { - return !(this == other) - } - - @operator('<') - lt(other: BigDecimal): boolean { - return BigDecimal.compare(this, other) == -1 - } - - @operator('>') - gt(other: BigDecimal): boolean { - return BigDecimal.compare(this, other) == 1 - } - - @operator('<=') - le(other: BigDecimal): boolean { - return !(this > other) - } - - @operator('>=') - ge(other: BigDecimal): boolean { - return !(this < other) - } - - @operator.prefix('-') - neg(): BigDecimal { - return new BigDecimal(new BigInt(0)) - this - } - - /** - * Returns −1 if a < b, 1 if a > b, and 0 if A == B - */ - static compare(a: BigDecimal, b: BigDecimal): i32 { - let diff = a - b - if (diff.digits.isZero()) { - return 0 - } - return diff.digits > BigInt.fromI32(0) ? 1 : -1 - } -} - -/** - * Enum for supported value types. - */ -export enum ValueKind { - STRING = 0, - INT = 1, - BIGDECIMAL = 2, - BOOL = 3, - ARRAY = 4, - NULL = 5, - BYTES = 6, - BIGINT = 7, -} - -/** - * Pointer type for Value data. - * - * Big enough to fit any pointer or native `this.data`. - */ -export type ValuePayload = u64 - -/** - * A dynamically typed value. - */ -export class Value { - kind: ValueKind - data: ValuePayload - - toAddress(): Address { - assert(this.kind == ValueKind.BYTES, 'Value is not an address.') - return changetype
(this.data as u32) - } - - toBoolean(): boolean { - if (this.kind == ValueKind.NULL) { - return false - } - assert(this.kind == ValueKind.BOOL, 'Value is not a boolean.') - return this.data != 0 - } - - toBytes(): Bytes { - assert(this.kind == ValueKind.BYTES, 'Value is not a byte array.') - return changetype(this.data as u32) - } - - toI32(): i32 { - if (this.kind == ValueKind.NULL) { - return 0 - } - assert(this.kind == ValueKind.INT, 'Value is not an i32.') - return this.data as i32 - } - - toString(): string { - assert(this.kind == ValueKind.STRING, 'Value is not a string.') - return changetype(this.data as u32) - } - - toBigInt(): BigInt { - assert(this.kind == ValueKind.BIGINT, 'Value is not a BigInt.') - return changetype(this.data as u32) - } - - toBigDecimal(): BigDecimal { - assert(this.kind == ValueKind.BIGDECIMAL, 'Value is not a BigDecimal.') - return changetype(this.data as u32) - } - - toArray(): Array { - assert(this.kind == ValueKind.ARRAY, 'Value is not an array.') - return changetype>(this.data as u32) - } - - toBooleanArray(): Array { - let values = this.toArray() - let output = new Array(values.length) - for (let i: i32; i < values.length; i++) { - output[i] = values[i].toBoolean() - } - return output - } - - toBytesArray(): Array { - let values = this.toArray() - let output = new Array(values.length) - for (let i: i32 = 0; i < values.length; i++) { - output[i] = values[i].toBytes() - } - return output - } - - toStringArray(): Array { - let values = this.toArray() - let output = new Array(values.length) - for (let i: i32 = 0; i < values.length; i++) { - output[i] = values[i].toString() - } - return output - } - - toI32Array(): Array { - let values = this.toArray() - let output = new Array(values.length) - for (let i: i32 = 0; i < values.length; i++) { - output[i] = values[i].toI32() - } - return output - } - - toBigIntArray(): Array { - let values = this.toArray() - let output = new Array(values.length) - for (let i: i32 = 0; i < values.length; i++) { - output[i] = values[i].toBigInt() - } - return output - } - - toBigDecimalArray(): Array { - let values = this.toArray() - let output = new Array(values.length) - for (let i: i32 = 0; i < values.length; i++) { - output[i] = values[i].toBigDecimal() - } - return output - } - - static fromBooleanArray(input: Array): Value { - let output = new Array(input.length) - for (let i: i32 = 0; i < input.length; i++) { - output[i] = Value.fromBoolean(input[i]) - } - return Value.fromArray(output) - } - - static fromBytesArray(input: Array): Value { - let output = new Array(input.length) - for (let i: i32 = 0; i < input.length; i++) { - output[i] = Value.fromBytes(input[i]) - } - return Value.fromArray(output) - } - - static fromI32Array(input: Array): Value { - let output = new Array(input.length) - for (let i: i32 = 0; i < input.length; i++) { - output[i] = Value.fromI32(input[i]) - } - return Value.fromArray(output) - } - - static fromBigIntArray(input: Array): Value { - let output = new Array(input.length) - for (let i: i32 = 0; i < input.length; i++) { - output[i] = Value.fromBigInt(input[i]) - } - return Value.fromArray(output) - } - - static fromBigDecimalArray(input: Array): Value { - let output = new Array(input.length) - for (let i: i32 = 0; i < input.length; i++) { - output[i] = Value.fromBigDecimal(input[i]) - } - return Value.fromArray(output) - } - - static fromStringArray(input: Array): Value { - let output = new Array(input.length) - for (let i: i32 = 0; i < input.length; i++) { - output[i] = Value.fromString(input[i]) - } - return Value.fromArray(output) - } - - static fromAddressArray(input: Array
): Value { - let output = new Array(input.length) - for (let i: i32 = 0; i < input.length; i++) { - output[i] = Value.fromAddress(input[i]) - } - return Value.fromArray(output) - } - - static fromArray(input: Array): Value { - let value = new Value() - value.kind = ValueKind.ARRAY - value.data = input as u64 - return value - } - - static fromBigInt(n: BigInt): Value { - let value = new Value() - value.kind = ValueKind.BIGINT - value.data = n as u64 - return value - } - - static fromBoolean(b: boolean): Value { - let value = new Value() - value.kind = ValueKind.BOOL - value.data = b ? 1 : 0 - return value - } - - static fromBytes(bytes: Bytes): Value { - let value = new Value() - value.kind = ValueKind.BYTES - value.data = bytes as u64 - return value - } - - static fromNull(): Value { - let value = new Value() - value.kind = ValueKind.NULL - return value - } - - static fromI32(n: i32): Value { - let value = new Value() - value.kind = ValueKind.INT - value.data = n as u64 - return value - } - - static fromString(s: string): Value { - let value = new Value() - value.kind = ValueKind.STRING - value.data = s as u64 - return value - } - - static fromAddress(s: Address): Value { - let value = new Value() - value.kind = ValueKind.BYTES - value.data = s as u64 - return value - } - - static fromBigDecimal(n: BigDecimal): Value { - let value = new Value() - value.kind = ValueKind.BIGDECIMAL - value.data = n as u64 - return value - } -} - -/** - * Common representation for entity data, storing entity attributes - * as `string` keys and the attribute values as dynamically-typed - * `Value` objects. - */ -export class Entity extends TypedMap { - unset(key: string): void { - this.set(key, Value.fromNull()) - } - - /** Assigns properties from sources to this Entity in right-to-left order */ - merge(sources: Array): Entity { - var target = this - for (let i = 0; i < sources.length; i++) { - let entries = sources[i].entries - for (let j = 0; j < entries.length; j++) { - target.set(entries[j].key, entries[j].value) - } - } - return target - } - - setString(key: string, value: string): void { - this.set(key, Value.fromString(value)) - } - - setI32(key: string, value: i32): void { - this.set(key, Value.fromI32(value)) - } - - setBigInt(key: string, value: BigInt): void { - this.set(key, Value.fromBigInt(value)) - } - - setBytes(key: string, value: Bytes): void { - this.set(key, Value.fromBytes(value)) - } - - setBoolean(key: string, value: bool): void { - this.set(key, Value.fromBoolean(value)) - } - - setBigDecimal(key: string, value: BigDecimal): void { - this.set(key, Value.fromBigDecimal(value)) - } - - getString(key: string): string { - return this.get(key).toString() - } - - getI32(key: string): i32 { - return this.get(key).toI32() - } - - getBigInt(key: string): BigInt { - return this.get(key).toBigInt() - } - - getBytes(key: string): Bytes { - return this.get(key).toBytes() - } - - getBoolean(key: string): boolean { - return this.get(key).toBoolean() - } - - getBigDecimal(key: string): BigDecimal { - return this.get(key).toBigDecimal() - } -} - -/** Context for dynamic data sources */ -export class DataSourceContext extends Entity {} - -/** Type hint for JSON values. */ -export enum JSONValueKind { - NULL = 0, - BOOL = 1, - NUMBER = 2, - STRING = 3, - ARRAY = 4, - OBJECT = 5, -} - -/** - * Pointer type for JSONValue data. - * - * Big enough to fit any pointer or native `this.data`. - */ -export type JSONValuePayload = u64 - -export class JSONValue { - kind: JSONValueKind - data: JSONValuePayload - - isNull(): boolean { - return this.kind == JSONValueKind.NULL - } - - toBool(): boolean { - assert(this.kind == JSONValueKind.BOOL, 'JSON value is not a boolean.') - return this.data != 0 - } - - toI64(): i64 { - assert(this.kind == JSONValueKind.NUMBER, 'JSON value is not a number.') - let decimalString = changetype(this.data as u32) - return json.toI64(decimalString) - } - - toU64(): u64 { - assert(this.kind == JSONValueKind.NUMBER, 'JSON value is not a number.') - let decimalString = changetype(this.data as u32) - return json.toU64(decimalString) - } - - toF64(): f64 { - assert(this.kind == JSONValueKind.NUMBER, 'JSON value is not a number.') - let decimalString = changetype(this.data as u32) - return json.toF64(decimalString) - } - - toBigInt(): BigInt { - assert(this.kind == JSONValueKind.NUMBER, 'JSON value is not a number.') - let decimalString = changetype(this.data as u32) - return json.toBigInt(decimalString) - } - - toString(): string { - assert(this.kind == JSONValueKind.STRING, 'JSON value is not a string.') - return changetype(this.data as u32) - } - - toArray(): Array { - assert(this.kind == JSONValueKind.ARRAY, 'JSON value is not an array.') - return changetype>(this.data as u32) - } - - toObject(): TypedMap { - assert(this.kind == JSONValueKind.OBJECT, 'JSON value is not an object.') - return changetype>(this.data as u32) - } -} - -/** - * Base class for data source templates. Allows to dynamically create - * data sources from templates at runtime. - */ -export class DataSourceTemplate { - /** - * Dynamically creates a data source from the template with the - * given name, using the parameter strings to configure the new - * data source. - */ - static create(name: string, params: Array): void { - dataSource.create(name, params) - } - - static createWithContext( - name: string, - params: Array, - context: DataSourceContext, - ): void { - dataSource.createWithContext(name, params, context) - } -} - -// This is used to wrap a generic so that it can be unioned with `null`, working around limitations -// with primitives. -export class Wrapped { - inner: T - - constructor(inner: T) { - this.inner = inner + log.log(Level.DEBUG, format(msg, args)) } } diff --git a/package.json b/package.json index 1636154..0218bcd 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "@graphprotocol/graph-ts", "description": "TypeScript/AssemblyScript library for writing subgraph mappings for The Graph", - "version": "0.20.1", + "version": "0.21.0", "module": "index.ts", "types": "index.ts", "main": "index.ts", "dependencies": { - "assemblyscript": "git+https://github.com/AssemblyScript/assemblyscript.git#v0.6" + "assemblyscript": "0.19.10" }, "devDependencies": { "glob": "^7.1.2" @@ -14,6 +14,7 @@ "scripts": { "test": "node test/test.js", "release:patch": "npm version patch && npm publish && git push origin master && git push --tags", - "release:minor": "npm version minor && npm publish && git push origin master && git push --tags" + "release:minor": "npm version minor && npm publish && git push origin master && git push --tags", + "release:alpha:minor": "npm version preminor --preid alpha && npm publish && git push origin master && git push --tags" } } diff --git a/test/test.ts b/test/bigInt.ts similarity index 83% rename from test/test.ts rename to test/bigInt.ts index 5cfddb1..68f434b 100644 --- a/test/test.ts +++ b/test/bigInt.ts @@ -1,11 +1,12 @@ -import { BigInt, ByteArray, Bytes } from 'temp_lib/index' +import { BigInt } from 'temp_lib/index' +import { ByteArray, Bytes } from 'temp_lib/index' // Test some BigInt methods. -export function test(): void { +export function testBigInt(): void { let minusFiveBytes = new ByteArray(2) minusFiveBytes[0] = 251 minusFiveBytes[1] = 255 - let minusFive = BigInt.fromSignedBytes(minusFiveBytes as Bytes) + let minusFive = BigInt.fromSignedBytes(Bytes.fromByteArray(minusFiveBytes)) assert(minusFive == BigInt.fromI32(-5)) assert(!minusFive.isZero() && minusFive.isI32()) assert(minusFiveBytes.toU32() == 65531) @@ -14,54 +15,54 @@ export function test(): void { let fiveBytes = new ByteArray(2) fiveBytes[0] = 5 fiveBytes[1] = 0 - let five = BigInt.fromSignedBytes(fiveBytes as Bytes) + let five = BigInt.fromSignedBytes(Bytes.fromByteArray(fiveBytes)) assert(!five.isZero() && five.isI32()) assert(five == BigInt.fromI32(5)) assert(five != minusFive) - assert(five == BigInt.fromUnsignedBytes(fiveBytes.subarray(0, 1) as Bytes)) + assert(five == BigInt.fromUnsignedBytes(Bytes.fromUint8Array(fiveBytes.subarray(0, 1)))) assert(fiveBytes.toU32() == 5) assert(fiveBytes.toI32() == 5) let x = new ByteArray(1) x[0] = 255 - assert(BigInt.fromUnsignedBytes(x as Bytes) == BigInt.fromI32(255)) - - let zero = BigInt.fromSignedBytes(new ByteArray(0) as Bytes) + assert(BigInt.fromUnsignedBytes(Bytes.fromByteArray(x)) == BigInt.fromI32(255)) + + let zero = BigInt.fromSignedBytes(Bytes.fromByteArray(new ByteArray(0))) assert(zero.isZero() && zero.isI32()) assert(zero != five) assert(zero != minusFive) assert(minusFive < zero && minusFive <= zero) assert(five > zero && five >= zero) - + let aI32 = 77123455 let a = BigInt.fromI32(aI32) assert(a == a && a.isI32() && a.toI32() == aI32) - + let bI32 = 48294181 let b = BigInt.fromI32(bI32) assert(b == b && b.isI32() && b.toI32() == bI32) assert(b < a && b <= a) - + aI32 = 9292928 a = BigInt.fromI32(9292928) assert(a == a && a.isI32() && a.toI32() == aI32) assert(a < b && a <= b) - + bI32 = -9717735 b = BigInt.fromI32(bI32) assert(b == b && b.isI32() && b.toI32() == bI32) assert(b < a && b <= a) - + aI32 = 53499369 a = BigInt.fromI32(aI32) assert(a == a && a.isI32() && a.toI32() == aI32) assert(b < a && b <= a) - + bI32 = 10242178 b = BigInt.fromI32(bI32) assert(b == b && b.isI32() && b.toI32() == bI32) assert(b < a && b <= a) - + a = BigInt.fromI32(1000) b = BigInt.fromI32(900) assert(b < a && b <= a) @@ -83,7 +84,7 @@ export function test(): void { blockNumber[0] = 180 blockNumber[1] = 42 blockNumber[2] = 123 - let blockNumberBigInt = blockNumber as BigInt + let blockNumberBigInt = BigInt.fromByteArray(blockNumber) let latestBlock = BigInt.fromI32(8200001) assert(!blockNumberBigInt.gt(latestBlock)) diff --git a/test/testBytes.ts b/test/bytes.ts similarity index 95% rename from test/testBytes.ts rename to test/bytes.ts index 7e42166..2f390cc 100644 --- a/test/testBytes.ts +++ b/test/bytes.ts @@ -1,7 +1,7 @@ import { ByteArray, Bytes } from 'temp_lib/index' // Test some Bytes methods. -export function test(): void { +export function testBytes(): void { let longArray = new ByteArray(5) longArray[0] = 251 longArray[1] = 255 diff --git a/test/entity.ts b/test/entity.ts index 31e7b4d..a553ee5 100644 --- a/test/entity.ts +++ b/test/entity.ts @@ -1,6 +1,7 @@ -import { Entity, Bytes, BigDecimal, BigInt } from 'temp_lib/index' +import { BigDecimal, BigInt } from 'temp_lib/index' +import { Entity, Bytes } from 'temp_lib/index' -export function test(): void { +export function testEntity(): void { let entity = new Entity() entity.setBytes('x', new Bytes(1)) diff --git a/test/test.js b/test/test.js index fe44069..283e444 100644 --- a/test/test.js +++ b/test/test.js @@ -15,6 +15,17 @@ if (!fs.existsSync('test/temp_lib/chain')) { fs.mkdirSync('test/temp_lib/chain') } +if (!fs.existsSync('test/temp_lib/common')) { + fs.mkdirSync('test/temp_lib/common') +} + +fs.copyFileSync('common/datasource.ts', 'test/temp_lib/common/datasource.ts') +fs.copyFileSync('common/eager_offset.ts', 'test/temp_lib/common/eager_offset.ts') +fs.copyFileSync('common/json.ts', 'test/temp_lib/common/json.ts') +fs.copyFileSync('common/numbers.ts', 'test/temp_lib/common/numbers.ts') +fs.copyFileSync('common/collections.ts', 'test/temp_lib/common/collections.ts') +fs.copyFileSync('common/conversion.ts', 'test/temp_lib/common/conversion.ts') +fs.copyFileSync('common/value.ts', 'test/temp_lib/common/value.ts') fs.copyFileSync('chain/ethereum.ts', 'test/temp_lib/chain/ethereum.ts') fs.copyFileSync('index.ts', 'test/temp_lib/index.ts') let output_path = 'test/temp_out/test.wasm' @@ -22,24 +33,45 @@ let output_path = 'test/temp_out/test.wasm' const env = { abort: function (message, fileName, lineNumber, columnNumber) { console.log('aborted') + console.log('message', message) + console.log('fileName', fileName) + console.log('lineNumber', lineNumber) + console.log('columnNumber', columnNumber) }, } try { - testFile('test/test.ts') - testFile('test/testBytes.ts') + testFile('test/bigInt.ts') + testFile('test/bytes.ts') testFile('test/entity.ts') // Cleanup fs.unlinkSync('test/temp_lib/index.ts') + fs.unlinkSync('test/temp_lib/common/eager_offset.ts') + fs.unlinkSync('test/temp_lib/common/numbers.ts') + fs.unlinkSync('test/temp_lib/common/collections.ts') + fs.unlinkSync('test/temp_lib/common/conversion.ts') + fs.unlinkSync('test/temp_lib/common/value.ts') + fs.unlinkSync('test/temp_lib/common/datasource.ts') + fs.unlinkSync('test/temp_lib/common/json.ts') + fs.rmdirSync('test/temp_lib/common') fs.unlinkSync('test/temp_lib/chain/ethereum.ts') fs.rmdirSync('test/temp_lib/chain') fs.rmdirSync('test/temp_lib') fs.unlinkSync('test/temp_out/test.wasm') fs.rmdirSync('test/temp_out') } catch (e) { + console.error(e) process.exitCode = 1 fs.unlinkSync('test/temp_lib/index.ts') + fs.unlinkSync('test/temp_lib/common/numbers.ts') + fs.unlinkSync('test/temp_lib/common/eager_offset.ts') + fs.unlinkSync('test/temp_lib/common/collections.ts') + fs.unlinkSync('test/temp_lib/common/conversion.ts') + fs.unlinkSync('test/temp_lib/common/value.ts') + fs.unlinkSync('test/temp_lib/common/datasource.ts') + fs.unlinkSync('test/temp_lib/common/conversion.ts') + fs.rmdirSync('test/temp_lib/common') fs.unlinkSync('test/temp_lib/chain/ethereum.ts') fs.rmdirSync('test/temp_lib/chain') fs.rmdirSync('test/temp_lib') @@ -49,17 +81,25 @@ try { } function testFile(path) { - if (asc.main([path, '--lib', 'test', '--validate', '-b', output_path]) != 0) { + if (asc.main(['--explicitStart', '--exportRuntime', '--runtime', 'stub', path, '--lib', 'test', '-b', output_path]) != 0) { throw Error('failed to compile') } let test_wasm = new Uint8Array(fs.readFileSync(output_path)) WebAssembly.instantiate(test_wasm, { env, - index: { + conversion: { 'typeConversion.bytesToHex': function () {}, }, }).then((module) => { - module.instance.exports.test() + // Call AS start explicitly + module.instance.exports._start() + + for (const [testName, testFn] of Object.entries(module.instance.exports)) { + if (typeof testFn === 'function' && testName.startsWith('test')) { + console.log(`Running "${testName}"...`) + testFn() + } + } }) } diff --git a/yarn.lock b/yarn.lock index 5c79222..dcb96a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,30 +2,22 @@ # yarn lockfile v1 -"@protobufjs/utf8@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" - integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= - -"assemblyscript@git+https://github.com/AssemblyScript/assemblyscript.git#v0.6": - version "0.6.0" - resolved "git+https://github.com/AssemblyScript/assemblyscript.git#3ed76a97f05335504166fce1653da75f4face28f" +assemblyscript@0.19.10: + version "0.19.10" + resolved "https://registry.yarnpkg.com/assemblyscript/-/assemblyscript-0.19.10.tgz#7ede6d99c797a219beb4fa4614c3eab9e6343c8e" + integrity sha512-HavcUBXB3mBTRGJcpvaQjmnmaqKHBGREjSPNsIvnAk2f9dj78y4BkMaSSdvBQYWcDDzsHQjyUC8stICFkD1Odg== dependencies: - "@protobufjs/utf8" "^1.1.0" - binaryen "77.0.0-nightly.20190407" - glob "^7.1.3" + binaryen "101.0.0-nightly.20210723" long "^4.0.0" - opencollective-postinstall "^2.0.0" - source-map-support "^0.5.11" balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" -binaryen@77.0.0-nightly.20190407: - version "77.0.0-nightly.20190407" - resolved "https://registry.yarnpkg.com/binaryen/-/binaryen-77.0.0-nightly.20190407.tgz#fbe4f8ba0d6bd0809a84eb519d2d5b5ddff3a7d1" - integrity sha512-1mxYNvQ0xywMe582K7V6Vo2zzhZZxMTeGHH8aE/+/AND8f64D8Q1GThVY3RVRwGY/4p+p95ccw9Xbw2ovFXRIg== +binaryen@101.0.0-nightly.20210723: + version "101.0.0-nightly.20210723" + resolved "https://registry.yarnpkg.com/binaryen/-/binaryen-101.0.0-nightly.20210723.tgz#b6bb7f3501341727681a03866c0856500eec3740" + integrity sha512-eioJNqhHlkguVSbblHOtLqlhtC882SOEPKmNFZaDuz1hzQjolxZ+eu3/kaS10n3sGPONsIZsO7R9fR00UyhEUA== brace-expansion@^1.1.7: version "1.1.11" @@ -34,11 +26,6 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -59,18 +46,6 @@ glob@^7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.3: - version "7.1.7" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" - integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -98,28 +73,10 @@ once@^1.3.0: dependencies: wrappy "1" -opencollective-postinstall@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" - integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== - path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" -source-map-support@^0.5.11: - version "0.5.19" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" - integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"