/*
* Copyright (C) 2018 Matus Zamborsky & The ontology Authors
* This file is part of The ontology library.
*
* The ontology is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The ontology is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with The ontology. If not, see .
*/
import { Address } from '../../common/address';
import { TracedError } from '../../common/error';
import { bigIntFromBytes } from '../../common/utils';
import { PublicKey } from '../../crypto/publicKey';
import { MAX_BYTEARRAY_SIZE } from '../../vm/consts';
import { evaluationStackCount, popByteArray, popStackItem, pushData } from '../../vm/func/common';
import { ExecutionEngine } from '../../vm/interfaces/engine';
import { ArrayType, isArrayType } from '../../vm/types/array';
import { BooleanType, isBooleanType } from '../../vm/types/boolean';
import { ByteArrayType, isByteArrayType } from '../../vm/types/byteArray';
import { IntegerType, isIntegerType } from '../../vm/types/integer';
import { isMapType, MapType } from '../../vm/types/map';
import { StackItem } from '../../vm/types/stackItem';
import { isStructType, MAX_STRUCT_DEPTH, StructType } from '../../vm/types/struct';
import { Reader } from '../../vm/utils/reader';
import { LimitedWriter, Writer } from '../../vm/utils/writer';
import { convertNeoVmTypeHexString } from '../common';
import { VmService } from '../context';
/**
* HeaderGetNextConsensus put current block time to vm stack
*/
export function runtimeGetTime(service: VmService, engine: ExecutionEngine) {
pushData(engine, service.getTime());
}
/**
* RuntimeCheckWitness provide check permissions service.
* If param address isn't exist in authorization list, check fail
*/
export function runtimeCheckWitness(service: VmService, engine: ExecutionEngine) {
const data = popByteArray(engine);
let result: boolean;
if (data.length === 20) {
const address = Address.parseFromBytes(data);
result = service.getContextRef().checkWitness(address);
} else {
try {
const pk = PublicKey.deserialize(data);
result = service.getContextRef().checkWitness(Address.fromPubKey(pk));
} catch (e) {
throw new TracedError(`[RuntimeCheckWitness] data invalid: ${e}`);
}
}
pushData(engine, result);
}
export function runtimeSerialize(service: VmService, engine: ExecutionEngine) {
const item = popStackItem(engine);
const buf = serializeStackItem(item);
pushData(engine, buf);
}
export function runtimeDeserialize(service: VmService, engine: ExecutionEngine) {
const data = popByteArray(engine);
const item = deserializeStackItem(data);
if (item !== undefined) {
pushData(engine, item);
}
}
// RuntimeNotify put smart contract execute event notify to notifications
export function runtimeNotify(service: VmService, engine: ExecutionEngine) {
const item = popStackItem(engine);
const context = service.getContextRef().currentContext();
if (context === undefined) {
throw new TracedError('[RuntimeNotify] No context present');
}
service.addNotification({ contractAddress: context.contractAddress, states: convertNeoVmTypeHexString(item) });
}
// RuntimeLog push smart contract execute event log to client
export function runtimeLog(service: VmService, engine: ExecutionEngine) {
const item = popByteArray(engine);
const context = service.getContextRef().currentContext();
if (context === undefined) {
throw new TracedError('[RuntimeNotify] No context present');
}
service.addLog({ contractAddress: context.contractAddress, message: item.toString(), tx: service.getTx().getHash() });
}
export function runtimeGetTrigger(service: VmService, engine: ExecutionEngine) {
pushData(engine, 0);
}
export function runtimeBase58ToAddress(service: VmService, engine: ExecutionEngine) {
if (evaluationStackCount(engine) < 1) {
throw new TracedError('[RuntimeBase58ToAddress] Too few input parameters');
}
const item = popByteArray(engine);
const address = Address.fromBase58(item.toString());
pushData(engine, address.toArray());
}
export function runtimeAddressToBase58(service: VmService, engine: ExecutionEngine) {
if (evaluationStackCount(engine) < 1) {
throw new TracedError('[RuntimeAddressToBase58] Too few input parameters');
}
const item = popByteArray(engine);
const address = Address.parseFromBytes(item);
pushData(engine, new Buffer(address.toBase58()));
}
export function runtimeGetRandomHash(service: VmService, engine: ExecutionEngine) {
pushData(engine, service.getRandomHash().toArray());
}
export function serializeStackItem(item: StackItem): Buffer {
if (circularRefAndDepthDetection(item)) {
throw new TracedError('runtime serialize: can not serialize circular reference data');
}
const writer = new LimitedWriter(MAX_BYTEARRAY_SIZE);
serializeStackItemInternal(item, writer);
return writer.getBytes();
}
export function deserializeStackItem(data: Buffer): StackItem {
const r = new Reader(data);
return deserializeStackItemInternal(r);
}
function serializeStackItemInternal(item: StackItem, w: Writer) {
if (isByteArrayType(item)) {
try {
w.writeUint8(ByteArrayType.id);
const ba = item.getByteArray();
w.writeVarBytes(ba);
} catch (e) {
throw new TracedError(`Serialize ByteArray stackItems error.`, e);
}
} else if (isBooleanType(item)) {
try {
w.writeUint8(BooleanType.id);
const b = item.getBoolean();
w.writeUint8(b ? 1 : 0);
} catch (e) {
throw new TracedError(`Serialize Boolean stackItems error:`, e);
}
} else if (isIntegerType(item)) {
try {
w.writeUint8(IntegerType.id);
const i = item.getByteArray();
w.writeVarBytes(i);
} catch (e) {
throw new TracedError(`Serialize Integer stackItems error.`, e);
}
} else if (isArrayType(item)) {
try {
w.writeUint8(ArrayType.id);
const a = item.getArray();
w.writeVarUint(a.length);
for (const v of a) {
serializeStackItemInternal(v, w);
}
} catch (e) {
throw new TracedError(`Serialize Array stackItems error.`, e);
}
} else if (isStructType(item)) {
try {
w.writeUint8(StructType.id);
const a = item.getStruct();
w.writeVarUint(a.length);
for (const v of a) {
serializeStackItemInternal(v, w);
}
} catch (e) {
throw new TracedError(`Serialize Struct stackItems error.`, e);
}
} else if (isMapType(item)) {
const unsortKey: string[] = [];
const keyMap: Map = new Map();
const mp = item.getMap();
try {
w.writeUint8(MapType.id);
w.writeVarUint(mp.size);
for (const [k] of mp.entries()) {
if (isByteArrayType(k) || isIntegerType(k)) {
const ba = k.getByteArray();
const key = ba.toString();
if (key === '') {
throw new TracedError('Serialize Map error: invalid key type');
}
unsortKey.push(key);
keyMap.set(key, k);
} else {
throw new TracedError('Unsupport map key type.');
}
}
} catch (e) {
throw new TracedError(`Serialize Struct stackItems error.`, e);
}
unsortKey.sort();
for (const v of unsortKey) {
const key = keyMap.get(v)!;
serializeStackItemInternal(key, w);
serializeStackItemInternal(mp.get(key)!, w);
}
} else {
throw new TracedError('unknown type');
}
}
function deserializeStackItemInternal(r: Reader): StackItem {
try {
const t = r.readByte();
if (t === ByteArrayType.id) {
try {
const b = r.readVarBytes();
return new ByteArrayType(b);
} catch (e) {
throw new TracedError(`Deserialize stackItems ByteArray error.`, e);
}
} else if (t === BooleanType.id) {
try {
const b = r.readByte() > 0;
return new BooleanType(b);
} catch (e) {
throw new TracedError(`Deserialize stackItems Boolean error.`, e);
}
} else if (t === IntegerType.id) {
try {
const b = r.readVarBytes();
return new IntegerType(bigIntFromBytes(b));
} catch (e) {
throw new TracedError(`Deserialize stackItems Integer error.`, e);
}
} else if (t === ArrayType.id || t === StructType.id) {
try {
const count = r.readVarUInt().toNumber();
const arr: StackItem[] = [];
for (let i = 0; i < count; i++) {
const item = deserializeStackItemInternal(r);
arr.push(item);
}
if (t === StructType.id) {
return new StructType(arr);
} else {
return new ArrayType(arr);
}
} catch (e) {
throw new TracedError(`Deserialize stackItems error.`, e);
}
} else if (t === MapType.id) {
try {
const count = r.readVarUInt().toNumber();
const mp = new MapType();
const m = mp.getMap();
for (let i = 0; i < count; i++) {
const key = deserializeStackItemInternal(r);
const value = deserializeStackItemInternal(r);
m.set(key, value);
}
return mp;
} catch (e) {
throw new TracedError(`Deserialize stackItems map error.`, e);
}
} else {
throw new TracedError('unknown type');
}
} catch (e) {
throw new TracedError(`Deserialize error.`, e);
}
}
export function circularRefAndDepthDetection(value: StackItem): boolean {
return circularRefAndDepthDetectionInternal(value, new Map(), 0);
}
function circularRefAndDepthDetectionInternal(value: StackItem, visited: Map, depth: number): boolean {
if (depth > MAX_STRUCT_DEPTH) {
return true;
}
if (isArrayType(value)) {
const a = value.getArray();
if (a.length === 0) {
return false;
}
if (visited.get(a)) {
return true;
}
visited.set(a, true);
for (const v of a) {
if (circularRefAndDepthDetectionInternal(v, visited, depth + 1)) {
return true;
}
}
visited.delete(a);
return false;
} else if (isStructType(value)) {
const s = value.getStruct();
if (s.length === 0) {
return false;
}
if (visited.get(s)) {
return true;
}
visited.set(s, true);
for (const v of s) {
if (circularRefAndDepthDetectionInternal(v, visited, depth + 1)) {
return true;
}
}
visited.delete(s);
return false;
} else if (isMapType(value)) {
const mp = value.getMap();
if (visited.get(mp)) {
return true;
}
visited.set(mp, true);
for (const [k, v] of mp.entries()) {
if (circularRefAndDepthDetectionInternal(k, visited, depth + 1)) {
return true;
}
if (circularRefAndDepthDetectionInternal(v, visited, depth + 1)) {
return true;
}
}
visited.delete(mp);
return false;
} else {
return false;
}
}