1222 lines
37 KiB
TypeScript
1222 lines
37 KiB
TypeScript
/*
|
|
* @project: TERA
|
|
* @version: Development (beta)
|
|
* @license: MIT (not for evil)
|
|
* @copyright: Yuriy Ivanov (Vtools) 2017-2019 [progr76@gmail.com]
|
|
* Web: https://terafoundation.org
|
|
* Twitter: https://twitter.com/terafoundation
|
|
* Telegram: https://t.me/terafoundation
|
|
*/
|
|
|
|
"use strict";
|
|
import DApp from './dapp'
|
|
import DBRow from '../core/db/db-row'
|
|
import { secp256k1 } from '../core/library'
|
|
|
|
const LOC_ADD_NAME = "$";
|
|
require("../HTML/JS/lexer.js");
|
|
global.TickCounter = 0;
|
|
const TYPE_TRANSACTION_SMART_CREATE = 130;
|
|
global.TYPE_TRANSACTION_SMART_RUN = 135;
|
|
const TYPE_TRANSACTION_SMART_CHANGE = 140;
|
|
global.FORMAT_SMART_CREATE = "{\
|
|
Type:byte,\
|
|
TokenGenerate:byte,\
|
|
StartValue:uint,\
|
|
OwnerPubKey:byte,\
|
|
ISIN:str,\
|
|
Zip:byte,\
|
|
AccountLength:byte,\
|
|
StateFormat:str,\
|
|
Category1:byte,\
|
|
Category2:byte,\
|
|
Category3:byte,\
|
|
Reserve:arr20,\
|
|
IconBlockNum:uint,\
|
|
IconTrNum:uint16,\
|
|
ShortName:str5,\
|
|
Name:str,\
|
|
Description:str,\
|
|
Code:str,\
|
|
HTML:str,\
|
|
}";
|
|
const WorkStructCreate = {};
|
|
global.FORMAT_SMART_RUN = "{\
|
|
Type:byte,\
|
|
Account:uint,\
|
|
MethodName:str,\
|
|
Params:str,\
|
|
FromNum:uint,\
|
|
OperationID:uint,\
|
|
Reserve:arr10,\
|
|
Sign:arr64,\
|
|
}";
|
|
//@ts-ignore
|
|
const WorkStructRun = {};
|
|
global.FORMAT_SMART_CHANGE = "{\
|
|
Type:byte,\
|
|
Account:uint,\
|
|
Smart:uint32,\
|
|
Reserve:arr10,\
|
|
FromNum:uint,\
|
|
OperationID:uint,\
|
|
Sign:arr64,\
|
|
}";
|
|
const WorkStructChange = {};
|
|
class SmartApp extends DApp {
|
|
FORMAT_ROW
|
|
ROW_SIZE
|
|
DBSmart
|
|
RowHole
|
|
constructor() {
|
|
super()
|
|
var bReadOnly = (global.PROCESS_NAME !== "TX");
|
|
this.FORMAT_ROW = "{\
|
|
Version:byte,\
|
|
TokenGenerate:byte,\
|
|
ISIN:str12,\
|
|
Zip:byte,\
|
|
BlockNum:uint,\
|
|
TrNum:uint16,\
|
|
IconBlockNum:uint,\
|
|
IconTrNum:uint16,\
|
|
ShortName:str5,\
|
|
Name:str40,\
|
|
Account:uint,\
|
|
AccountLength:byte,\
|
|
Category1:byte,\
|
|
Category2:byte,\
|
|
Category3:byte,\
|
|
Owner:uint,\
|
|
Reserve:arr20,\
|
|
StateFormat:str,\
|
|
Description:str,\
|
|
Code:str,\
|
|
HTML:str,\
|
|
SumHash:hash,\
|
|
}"
|
|
this.ROW_SIZE = 2 * (1 << 13)
|
|
this.DBSmart = new DBRow("smart", this.ROW_SIZE, this.FORMAT_ROW, bReadOnly)
|
|
this.InitHole()
|
|
if (!bReadOnly)
|
|
this.Start()
|
|
}
|
|
Start() {
|
|
if (this.GetMaxNum() + 1 >= 7)
|
|
return;
|
|
this.DBSmartWrite({ Num: 0, ShortName: "TERA", Name: "TERA", Description: "TERA", BlockNum: 0, TokenGenerate: 1, Account: 0, Category1: 0 })
|
|
for (var i = 1; i < 8; i++)
|
|
this.DBSmartWrite({ Num: i, ShortName: "", Name: "", Description: "", BlockNum: 0, TokenGenerate: 1, Account: i, Category1: 0 })
|
|
}
|
|
Close() {
|
|
this.DBSmart.Close()
|
|
}
|
|
ClearDataBase() {
|
|
this.DBSmart.Truncate(- 1)
|
|
this.Start()
|
|
}
|
|
GetSenderNum(BlockNum, Body) {
|
|
var Type = Body[0];
|
|
if (Type && Body.length > 90) {
|
|
switch (Type) {
|
|
case global.TYPE_TRANSACTION_SMART_RUN:
|
|
var len = 1 + 6;
|
|
len += 2 + Body[len] + Body[len + 1] * 256
|
|
if (len + 64 > Body.length)
|
|
return 0;
|
|
len += 2 + Body[len] + Body[len + 1] * 256
|
|
if (len + 64 > Body.length)
|
|
return 0;
|
|
var Num = global.ReadUintFromArr(Body, len);
|
|
return Num;
|
|
case TYPE_TRANSACTION_SMART_CHANGE:
|
|
var Num = global.ReadUintFromArr(Body, 1);
|
|
return Num;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
OnDeleteBlock(Block) {
|
|
if (Block.BlockNum < 1)
|
|
return;
|
|
this.DBSmart.DeleteHistory(Block.BlockNum)
|
|
}
|
|
OnWriteBlockStart(Block) {
|
|
if (Block.BlockNum < 1)
|
|
return;
|
|
this.OnDeleteBlock(Block)
|
|
}
|
|
OnWriteBlockFinish(Block) {
|
|
}
|
|
OnWriteTransaction(Block, Body, BlockNum, TrNum, ContextFrom) {
|
|
var Type = Body[0];
|
|
if (!ContextFrom) {
|
|
global.DApps.Accounts.BeginTransaction()
|
|
}
|
|
var Result;
|
|
try {
|
|
switch (Type) {
|
|
case TYPE_TRANSACTION_SMART_CREATE:
|
|
Result = this.TRCreateSmart(Block, Body, BlockNum, TrNum, ContextFrom)
|
|
break;
|
|
case global.TYPE_TRANSACTION_SMART_RUN:
|
|
Result = this.TRRunSmart(Block, Body, BlockNum, TrNum, ContextFrom)
|
|
break;
|
|
case TYPE_TRANSACTION_SMART_CHANGE:
|
|
Result = this.TRChangeSmart(Block, Body, BlockNum, TrNum, ContextFrom)
|
|
break;
|
|
}
|
|
}
|
|
catch (e) {
|
|
Result = "" + e
|
|
}
|
|
return Result;
|
|
}
|
|
GetScriptTransaction(Body) {
|
|
var Type = Body[0];
|
|
var format;
|
|
if (Type === TYPE_TRANSACTION_SMART_CREATE)
|
|
format = global.FORMAT_SMART_CREATE
|
|
else
|
|
if (Type === global.TYPE_TRANSACTION_SMART_RUN)
|
|
format = global.FORMAT_SMART_RUN
|
|
else
|
|
if (Type === TYPE_TRANSACTION_SMART_CHANGE)
|
|
format = global.FORMAT_SMART_CHANGE
|
|
if (!format)
|
|
return "";
|
|
var TR = global.BufLib.GetObjectFromBuffer(Body, format, {});
|
|
global.ConvertBufferToStr(TR)
|
|
return JSON.stringify(TR, undefined, 2);
|
|
}
|
|
GetVerifyTransaction(Block, BlockNum, TrNum, Body) {
|
|
return 1;
|
|
}
|
|
TRCreateSmart(Block, Body, BlockNum, TrNum, ContextFrom) {
|
|
if (!ContextFrom)
|
|
return "Pay context required";
|
|
if (Body.length < 31)
|
|
return "Error length transaction (min size)";
|
|
if (Body.length > 16000)
|
|
return "Error length transaction (max size)";
|
|
if (BlockNum < global.SMART_BLOCKNUM_START)
|
|
return "Error block num";
|
|
var TR = global.BufLib.GetObjectFromBuffer(Body, global.FORMAT_SMART_CREATE, WorkStructCreate);
|
|
if (!TR.Name.trim())
|
|
return "Name required";
|
|
if (TR.AccountLength > 50)
|
|
return "Error AccountLength=" + TR.AccountLength;
|
|
if (TR.AccountLength < 1)
|
|
TR.AccountLength = 1
|
|
var AddAccount = TR.AccountLength - 1;
|
|
var Price;
|
|
if (TR.TokenGenerate)
|
|
Price = global.PRICE_DAO(BlockNum).NewTokenSmart
|
|
else
|
|
Price = global.PRICE_DAO(BlockNum).NewSmart
|
|
Price += AddAccount * global.PRICE_DAO(BlockNum).NewAccount
|
|
if (!(ContextFrom && ContextFrom.To.length === 1 && ContextFrom.To[0].ID === 0 && ContextFrom.To[0].SumCOIN >= Price)) {
|
|
return "Not money in the transaction";
|
|
}
|
|
ContextFrom.ToID = ContextFrom.To[0].ID
|
|
var Smart = TR;
|
|
Smart.Version = 0
|
|
Smart.Zip = 0
|
|
Smart.BlockNum = BlockNum
|
|
Smart.TrNum = TrNum
|
|
Smart.Reserve = []
|
|
Smart.Num = undefined
|
|
Smart.Owner = ContextFrom.FromID
|
|
this.DBSmart.CheckNewNum(Smart)
|
|
var Account = global.DApps.Accounts.NewAccountTR(BlockNum, TrNum);
|
|
Account.Value.Smart = Smart.Num
|
|
Account.Name = TR.Name
|
|
if (Smart.TokenGenerate) {
|
|
Account.Currency = Smart.Num
|
|
Account.Value.SumCOIN = TR.StartValue
|
|
}
|
|
if (TR.OwnerPubKey)
|
|
Account.PubKey = ContextFrom.FromPubKey
|
|
global.DApps.Accounts.WriteStateTR(Account, TrNum)
|
|
for (var i = 0; i < AddAccount; i++) {
|
|
var CurAccount = global.DApps.Accounts.NewAccountTR(BlockNum, TrNum);
|
|
CurAccount.Value.Smart = Smart.Num
|
|
CurAccount.Name = TR.Name
|
|
if (Smart.TokenGenerate)
|
|
CurAccount.Currency = Smart.Num
|
|
if (TR.OwnerPubKey)
|
|
CurAccount.PubKey = ContextFrom.FromPubKey
|
|
global.DApps.Accounts.WriteStateTR(CurAccount, TrNum)
|
|
}
|
|
Smart.Account = Account.Num
|
|
this.DBSmart.DeleteMap("EVAL" + Smart.Num)
|
|
try {
|
|
RunSmartMethod(Block, Smart, Account, BlockNum, TrNum, ContextFrom, "OnCreate")
|
|
}
|
|
catch (e) {
|
|
this.DBSmart.DeleteMap("EVAL" + Smart.Num)
|
|
return e;
|
|
}
|
|
this.DBSmartWrite(Smart)
|
|
return true;
|
|
}
|
|
CheckSignFrom(Body, TR, BlockNum, TrNum) {
|
|
var ContextFrom = { FromID: TR.FromNum };
|
|
var AccountFrom = global.DApps.Accounts.ReadStateTR(TR.FromNum);
|
|
if (!AccountFrom)
|
|
return "Error account FromNum: " + TR.FromNum;
|
|
if (TR.OperationID < AccountFrom.Value.OperationID)
|
|
return "Error OperationID (expected: " + AccountFrom.Value.OperationID + " for ID: " + TR.FromNum + ")";
|
|
var MaxCountOperationID = 100;
|
|
if (BlockNum >= global.BLOCKNUM_TICKET_ALGO)
|
|
MaxCountOperationID = 1000000
|
|
if (TR.OperationID > AccountFrom.Value.OperationID + MaxCountOperationID)
|
|
return "Error too much OperationID (expected max: " + (AccountFrom.Value.OperationID + MaxCountOperationID) + " for ID: " + TR.FromNum + ")";
|
|
var hash = global.SHA3BUF(Body.slice(0, Body.length - 64 - 12), BlockNum);
|
|
var Result: any = 0;
|
|
if (AccountFrom.PubKey[0] === 2 || AccountFrom.PubKey[0] === 3)
|
|
try {
|
|
Result = secp256k1.verify(hash, TR.Sign, AccountFrom.PubKey)
|
|
} catch (e) {
|
|
}
|
|
if (!Result) {
|
|
return "Error sign transaction";
|
|
}
|
|
if (BlockNum >= 13000000) {
|
|
AccountFrom.Value.OperationID = TR.OperationID + 1
|
|
global.DApps.Accounts.WriteStateTR(AccountFrom, TrNum)
|
|
}
|
|
else
|
|
if (AccountFrom.Value.OperationID !== TR.OperationID) {
|
|
AccountFrom.Value.OperationID = TR.OperationID
|
|
global.DApps.Accounts.WriteStateTR(AccountFrom, TrNum)
|
|
}
|
|
return ContextFrom;
|
|
}
|
|
TRRunSmart(Block, Body, BlockNum, TrNum, ContextFrom) {
|
|
if (Body.length < 100)
|
|
return "Error length transaction (min size)";
|
|
if (BlockNum < global.SMART_BLOCKNUM_START)
|
|
return "Error block num";
|
|
var TR = global.BufLib.GetObjectFromBuffer(Body, global.FORMAT_SMART_RUN, WorkStructRun);
|
|
var Account = global.DApps.Accounts.ReadStateTR(TR.Account);
|
|
if (!Account)
|
|
return "RunSmart: Error account Num: " + TR.Account;
|
|
if (!ContextFrom && TR.FromNum) {
|
|
var ResultCheck = this.CheckSignFrom(Body, TR, BlockNum, TrNum);
|
|
if (typeof ResultCheck === "string")
|
|
return ResultCheck;
|
|
ContextFrom = ResultCheck
|
|
}
|
|
try {
|
|
var Params = JSON.parse(TR.Params);
|
|
RunSmartMethod(Block, Account.Value.Smart, Account, BlockNum, TrNum, ContextFrom, TR.MethodName, Params, 1)
|
|
}
|
|
catch (e) {
|
|
return e;
|
|
}
|
|
return true;
|
|
}
|
|
TRChangeSmart(Block, Body, BlockNum, TrNum, ContextFrom) {
|
|
if (Body.length < 21)
|
|
return "Error length transaction (min size)";
|
|
if (BlockNum < global.SMART_BLOCKNUM_START)
|
|
return "Error block num";
|
|
var TR = global.BufLib.GetObjectFromBuffer(Body, global.FORMAT_SMART_CHANGE, WorkStructChange);
|
|
if (!ContextFrom) {
|
|
var ResultCheck = this.CheckSignFrom(Body, TR, BlockNum, TrNum);
|
|
if (typeof ResultCheck === "string")
|
|
return ResultCheck;
|
|
ContextFrom = ResultCheck
|
|
}
|
|
if (TR.Smart > this.GetMaxNum())
|
|
TR.Smart = 0
|
|
if (ContextFrom.FromID !== TR.Account)
|
|
return "ChangeSmart: Error account FromNum: " + TR.Account;
|
|
var Account = global.DApps.Accounts.ReadStateTR(TR.Account);
|
|
if (!Account)
|
|
return "Error read account Num: " + TR.Account;
|
|
if (BlockNum >= 13000000) {
|
|
if (Account.Value.Smart === TR.Smart)
|
|
return "The value has not changed";
|
|
}
|
|
if (Account.Value.Smart) {
|
|
var Smart = this.ReadSmart(Account.Value.Smart);
|
|
if (Smart.Account === TR.Account)
|
|
return "Can't change base account";
|
|
try {
|
|
RunSmartMethod(Block, Account.Value.Smart, Account, BlockNum, TrNum, ContextFrom, "OnDeleteSmart")
|
|
}
|
|
catch (e) {
|
|
return e;
|
|
}
|
|
}
|
|
Account.Value.Smart = TR.Smart
|
|
Account.Value.Data = []
|
|
global.DApps.Accounts.WriteStateTR(Account, TrNum)
|
|
if (Account.Value.Smart) {
|
|
try {
|
|
RunSmartMethod(Block, Account.Value.Smart, Account, BlockNum, TrNum, ContextFrom, "OnSetSmart")
|
|
}
|
|
catch (e) {
|
|
return e;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
GetRows(start, count, Filter, Category, GetAllData, bTokenGenerate?) {
|
|
if (Filter) {
|
|
Filter = Filter.trim()
|
|
Filter = Filter.toUpperCase()
|
|
}
|
|
if (Category)
|
|
Category = global.ParseNum(Category)
|
|
var WasError = 0;
|
|
var arr = [];
|
|
var Data;
|
|
for (var num = start; true; num++) {
|
|
if (this.IsHole(num))
|
|
continue;
|
|
if (GetAllData)
|
|
Data = this.ReadSmart(num)
|
|
else
|
|
Data = this.ReadSimple(num)
|
|
if (!Data)
|
|
break;
|
|
if (bTokenGenerate && !Data.TokenGenerate)
|
|
continue;
|
|
if (Category) {
|
|
if (Data.Category1 !== Category && Data.Category2 !== Category && Data.Category3 !== Category)
|
|
continue;
|
|
}
|
|
if (Filter) {
|
|
var Str = "" + Data.ShortName.toUpperCase() + Data.ISIN.toUpperCase() + Data.Name.toUpperCase() + Data.Description.toUpperCase();
|
|
if (Data.TokenGenerate)
|
|
Str += "TOKEN GENERATE"
|
|
if (Str.indexOf(Filter) < 0)
|
|
continue;
|
|
}
|
|
var CanAdd = 1;
|
|
var DataState = global.DApps.Accounts.ReadState(Data.Account);
|
|
if (DataState && !global.ALL_VIEW_ROWS) {
|
|
Data.BaseState = global.DApps.Accounts.GetSmartState(DataState, Data.StateFormat)
|
|
if (typeof Data.BaseState === "object" && Data.BaseState.HTMLBlock === 404)
|
|
CanAdd = 0
|
|
}
|
|
if (CanAdd) {
|
|
arr.push(Data)
|
|
}
|
|
count--
|
|
if (count < 1)
|
|
break;
|
|
}
|
|
return arr;
|
|
}
|
|
GetMaxNum() {
|
|
return this.DBSmart.GetMaxNum();
|
|
}
|
|
DBSmartWrite(Item) {
|
|
var PrevNum;
|
|
if (Item.Num === undefined)
|
|
PrevNum = this.GetMaxNum()
|
|
else
|
|
PrevNum = Item.Num - 1
|
|
Item.SumHash = []
|
|
var Buf = global.BufLib.GetBufferFromObject(Item, this.FORMAT_ROW, 20000, {});
|
|
var Hash = global.sha3(Buf);
|
|
if (PrevNum < 0)
|
|
Item.SumHash = Hash
|
|
else {
|
|
var PrevItem = this.DBSmart.Read(PrevNum);
|
|
if (!PrevItem) {
|
|
throw "!PrevItem of Smart num = " + PrevNum;
|
|
}
|
|
Item.SumHash = global.sha3arr2(PrevItem.SumHash, Hash)
|
|
}
|
|
this.DBSmart.Write(Item)
|
|
}
|
|
ReadSmart(Num) {
|
|
Num = global.ParseNum(Num)
|
|
var Smart = this.DBSmart.GetMap("ITEM" + Num);
|
|
if (!Smart) {
|
|
Smart = this.DBSmart.Read(Num)
|
|
if (Smart) {
|
|
if (!Smart.WorkStruct)
|
|
Smart.WorkStruct = {}
|
|
Smart.CodeLength = Smart.Code.length
|
|
Smart.HTMLLength = Smart.HTML.length
|
|
this.DBSmart.SetMap("ITEM" + Num, Smart)
|
|
}
|
|
}
|
|
return Smart;
|
|
}
|
|
ReadSimple(Num) {
|
|
var Smart = this.DBSmart.GetMap("SIMPLE" + Num);
|
|
if (!Smart) {
|
|
Smart = this.DBSmart.Read(Num)
|
|
if (Smart) {
|
|
Smart.CodeLength = Smart.Code.length
|
|
Smart.HTMLLength = Smart.HTML.length
|
|
Smart.Code = undefined
|
|
Smart.HTML = undefined
|
|
Smart.Description = undefined
|
|
this.DBSmart.SetMap("SIMPLE" + Num, Smart)
|
|
}
|
|
}
|
|
return Smart;
|
|
}
|
|
InitHole() {
|
|
if (global.LOCAL_RUN || global.TEST_NETWORK)
|
|
this.RowHole = {}
|
|
else
|
|
this.RowHole = { "10": 1, "19": 1, "22": 1, "23": 1, "24": 1, "26": 1, "27": 1, "29": 1, "30": 1, "34": 1, "56": 1, "57": 1 }
|
|
for (var Num = 0; Num < 8; Num++)
|
|
this.RowHole[Num] = 1
|
|
}
|
|
IsHole(num) {
|
|
if (global.ALL_VIEW_ROWS)
|
|
return 0;
|
|
return this.RowHole[num];
|
|
}
|
|
};
|
|
|
|
function GetParsing(Str) {
|
|
global.LexerJS.ParseCode(Str);
|
|
var Code = global.LexerJS.stream;
|
|
for (var key in global.LexerJS.FunctionMap) {
|
|
Code += ";\nfunclist." + key + "=" + LOC_ADD_NAME + key;
|
|
}
|
|
for (var key in global.LexerJS.ExternMap) {
|
|
Code += ";\npublist." + key + "=" + LOC_ADD_NAME + key;
|
|
}
|
|
Code += "\n\
|
|
var context;\
|
|
funclist.SetContext=function(cont){context=cont;};\
|
|
";
|
|
return Code;
|
|
};
|
|
|
|
function GetSmartEvalContext(Smart) {
|
|
var EvalContext = global.DApps.Smart.DBSmart.GetMap("EVAL" + Smart.Num);
|
|
if (0)
|
|
if (Smart.Num === 26) {
|
|
const fs = require("fs");
|
|
var Path = "./dapp-smart/test-test.js";
|
|
Smart.Code = fs.readFileSync(Path, { encoding: "utf8" });
|
|
EvalContext = undefined;
|
|
}
|
|
if (!EvalContext) {
|
|
var CodeLex = GetParsing(Smart.Code);
|
|
var publist = {};
|
|
var funclist = {};
|
|
eval(CodeLex);
|
|
EvalContext = { publist: publist, funclist: funclist };
|
|
for (var key in funclist) {
|
|
Object.freeze(funclist[key]);
|
|
}
|
|
Object.freeze(funclist);
|
|
Object.freeze(publist);
|
|
global.DApps.Smart.DBSmart.SetMap("EVAL" + Smart.Num, EvalContext);
|
|
}
|
|
return EvalContext;
|
|
};
|
|
var RunContext = undefined;
|
|
global.RunSmartMethod = RunSmartMethod;
|
|
|
|
function RunSmartMethod(Block, SmartOrSmartID, Account, BlockNum?, TrNum?, PayContext?, MethodName?, Params?, bPublic?) {
|
|
var Smart = SmartOrSmartID;
|
|
if (typeof SmartOrSmartID === "number") {
|
|
Smart = global.DApps.Smart.ReadSmart(SmartOrSmartID);
|
|
if (!Smart) {
|
|
if (bPublic)
|
|
throw "Smart does not exist. Error id number: " + SmartOrSmartID;
|
|
else
|
|
return;
|
|
}
|
|
}
|
|
var EvalContext = GetSmartEvalContext(Smart);
|
|
if (!EvalContext.funclist[MethodName] || (bPublic && !EvalContext.publist[MethodName])) {
|
|
if (bPublic)
|
|
throw "Method '" + MethodName + "' not found in smart contract";
|
|
else
|
|
return;
|
|
}
|
|
var context: any = {};
|
|
if (PayContext) {
|
|
context.BlockNum = BlockNum;
|
|
context.BlockHash = global.CopyArr(Block.Hash);
|
|
context.BlockAddrHash = global.CopyArr(Block.AddrHash);
|
|
context.TrNum = TrNum;
|
|
context.Account = GET_ACCOUNT(Account);
|
|
context.Smart = GET_SMART(Smart);
|
|
context.FromNum = PayContext.FromID;
|
|
context.ToNum = PayContext.ToID;
|
|
context.Description = PayContext.Description;
|
|
if (PayContext.Value)
|
|
context.Value = { SumCOIN: PayContext.Value.SumCOIN, SumCENT: PayContext.Value.SumCENT };
|
|
}
|
|
if (BlockNum === 0) {
|
|
context.GetBlockHeader = StaticGetBlockHeader;
|
|
context.GetBlockNumDB = StaticGetBlockNumDB;
|
|
context.GetSmart = StaticGetSmart;
|
|
}
|
|
var LocalRunContext = { Block: Block, Smart: Smart, Account: Account, BlockNum: BlockNum, TrNum: TrNum, context: context };
|
|
var RetValue;
|
|
var _RunContext = RunContext;
|
|
RunContext = LocalRunContext;
|
|
EvalContext.funclist.SetContext(RunContext.context);
|
|
try {
|
|
RetValue = EvalContext.funclist[MethodName](Params);
|
|
}
|
|
catch (e) {
|
|
throw e;
|
|
}
|
|
finally {
|
|
RunContext = _RunContext;
|
|
}
|
|
return RetValue;
|
|
};
|
|
|
|
function GET_ACCOUNT(Obj) {
|
|
let Data = Obj;
|
|
var GET_PROP = {
|
|
get Num() {
|
|
return Data.Num;
|
|
}, get Currency() {
|
|
return Data.Currency;
|
|
}, get PubKey() {
|
|
return global.CopyArr(Data.PubKey);
|
|
}, get Name() {
|
|
return Data.Name;
|
|
}, get BlockNumCreate() {
|
|
return Data.BlockNumCreate;
|
|
}, get Adviser() {
|
|
return Data.Adviser;
|
|
}, get Smart() {
|
|
return Data.Smart;
|
|
}, get Value() {
|
|
return { SumCOIN: Data.Value.SumCOIN, SumCENT: Data.Value.SumCENT, OperationID: Data.Value.OperationID, Smart: Data.Value.Smart };
|
|
},
|
|
};
|
|
return GET_PROP;
|
|
};
|
|
|
|
function GET_SMART(Obj) {
|
|
let Data = Obj;
|
|
var GET_PROP = {
|
|
get Num() {
|
|
return Data.Num;
|
|
}, get Version() {
|
|
return Data.Version;
|
|
}, get TokenGenerate() {
|
|
return Data.TokenGenerate;
|
|
}, get ISIN() {
|
|
return Data.ISIN;
|
|
}, get Zip() {
|
|
return Data.Zip;
|
|
}, get BlockNum() {
|
|
return Data.BlockNum;
|
|
}, get TrNum() {
|
|
return Data.TrNum;
|
|
}, get IconBlockNum() {
|
|
return Data.IconBlockNum;
|
|
}, get IconTrNum() {
|
|
return Data.IconTrNum;
|
|
}, get ShortName() {
|
|
return Data.ShortName;
|
|
}, get Name() {
|
|
return Data.Name;
|
|
}, get Description() {
|
|
return Data.Description;
|
|
}, get Account() {
|
|
return Data.Account;
|
|
}, get AccountLength() {
|
|
return Data.AccountLength;
|
|
}, get Owner() {
|
|
return Data.Owner;
|
|
}, get Code() {
|
|
return Data.Code;
|
|
}, get HTML() {
|
|
return Data.HTML;
|
|
},
|
|
};
|
|
return GET_PROP;
|
|
};
|
|
|
|
function InitEval() {
|
|
$Math.abs = function() {
|
|
DO(6);
|
|
return Math.abs.apply(Math, arguments);
|
|
};
|
|
$Math.acos = function() {
|
|
DO(16);
|
|
return Math.acos.apply(Math, arguments);
|
|
};
|
|
$Math.acosh = function() {
|
|
DO(9);
|
|
return Math.acosh.apply(Math, arguments);
|
|
};
|
|
$Math.asin = function() {
|
|
DO(19);
|
|
return Math.asin.apply(Math, arguments);
|
|
};
|
|
$Math.asinh = function() {
|
|
DO(32);
|
|
return Math.asinh.apply(Math, arguments);
|
|
};
|
|
$Math.atan = function() {
|
|
DO(13);
|
|
return Math.atan.apply(Math, arguments);
|
|
};
|
|
$Math.atanh = function() {
|
|
DO(30);
|
|
return Math.atanh.apply(Math, arguments);
|
|
};
|
|
$Math.atan2 = function() {
|
|
DO(15);
|
|
return Math.atan2.apply(Math, arguments);
|
|
};
|
|
$Math.ceil = function() {
|
|
DO(6);
|
|
return Math.ceil.apply(Math, arguments);
|
|
};
|
|
$Math.cbrt = function() {
|
|
DO(22);
|
|
return Math.cbrt.apply(Math, arguments);
|
|
};
|
|
$Math.expm1 = function() {
|
|
DO(18);
|
|
return Math.expm1.apply(Math, arguments);
|
|
};
|
|
$Math.clz32 = function() {
|
|
DO(5);
|
|
return Math.clz32.apply(Math, arguments);
|
|
};
|
|
$Math.cos = function() {
|
|
DO(12);
|
|
return Math.cos.apply(Math, arguments);
|
|
};
|
|
$Math.cosh = function() {
|
|
DO(20);
|
|
return Math.cosh.apply(Math, arguments);
|
|
};
|
|
$Math.exp = function() {
|
|
DO(16);
|
|
return Math.exp.apply(Math, arguments);
|
|
};
|
|
$Math.floor = function() {
|
|
DO(7);
|
|
return Math.floor.apply(Math, arguments);
|
|
};
|
|
$Math.fround = function() {
|
|
DO(6);
|
|
return Math.fround.apply(Math, arguments);
|
|
};
|
|
$Math.hypot = function() {
|
|
DO(56);
|
|
return Math.hypot.apply(Math, arguments);
|
|
};
|
|
$Math.imul = function() {
|
|
DO(3);
|
|
return Math.imul.apply(Math, arguments);
|
|
};
|
|
$Math.log = function() {
|
|
DO(10);
|
|
return Math.log.apply(Math, arguments);
|
|
};
|
|
$Math.log1p = function() {
|
|
DO(23);
|
|
return Math.log1p.apply(Math, arguments);
|
|
};
|
|
$Math.log2 = function() {
|
|
DO(19);
|
|
return Math.log2.apply(Math, arguments);
|
|
};
|
|
$Math.log10 = function() {
|
|
DO(16);
|
|
return Math.log10.apply(Math, arguments);
|
|
};
|
|
$Math.max = function() {
|
|
DO(6);
|
|
return Math.max.apply(Math, arguments);
|
|
};
|
|
$Math.min = function() {
|
|
DO(6);
|
|
return Math.min.apply(Math, arguments);
|
|
};
|
|
$Math.pow = function() {
|
|
DO(40);
|
|
return Math.pow.apply(Math, arguments);
|
|
};
|
|
$Math.round = function() {
|
|
DO(7);
|
|
return Math.round.apply(Math, arguments);
|
|
};
|
|
$Math.sign = function() {
|
|
DO(5);
|
|
return Math.sign.apply(Math, arguments);
|
|
};
|
|
$Math.sin = function() {
|
|
DO(10);
|
|
return Math.sin.apply(Math, arguments);
|
|
};
|
|
$Math.sinh = function() {
|
|
DO(24);
|
|
return Math.sinh.apply(Math, arguments);
|
|
};
|
|
$Math.sqrt = function() {
|
|
DO(6);
|
|
return Math.sqrt.apply(Math, arguments);
|
|
};
|
|
$Math.tan = function() {
|
|
DO(13);
|
|
return Math.tan.apply(Math, arguments);
|
|
};
|
|
$Math.tanh = function() {
|
|
DO(24);
|
|
return Math.tanh.apply(Math, arguments);
|
|
};
|
|
$Math.trunc = function() {
|
|
DO(6);
|
|
return Math.trunc.apply(Math, arguments);
|
|
};
|
|
$Math.random = function() {
|
|
DO(1);
|
|
return 0;
|
|
};
|
|
Object.freeze($SetValue);
|
|
Object.freeze($Send);
|
|
Object.freeze($Move);
|
|
Object.freeze($Event);
|
|
Object.freeze($ReadAccount);
|
|
Object.freeze($ReadState);
|
|
Object.freeze($WriteState);
|
|
Object.freeze($GetMaxAccount);
|
|
Object.freeze($ADD);
|
|
Object.freeze($SUB);
|
|
Object.freeze($ISZERO);
|
|
Object.freeze($FLOAT_FROM_COIN);
|
|
Object.freeze($COIN_FROM_FLOAT);
|
|
Object.freeze($COIN_FROM_STRING);
|
|
Object.freeze($GetHexFromArr);
|
|
Object.freeze($GetArrFromHex);
|
|
Object.freeze($sha);
|
|
Object.freeze($isFinite);
|
|
Object.freeze($isNaN);
|
|
Object.freeze($parseFloat);
|
|
Object.freeze($parseInt);
|
|
Object.freeze($parseUint);
|
|
Object.freeze($String);
|
|
Object.freeze($Number);
|
|
Object.freeze($Boolean);
|
|
var arr = Object.getOwnPropertyNames(JSON);
|
|
for (var name of arr) {
|
|
$JSON[name] = JSON[name];
|
|
}
|
|
FreezeObjectChilds($Math);
|
|
Object.freeze($Math);
|
|
FreezeObjectChilds($JSON);
|
|
Object.freeze($JSON);
|
|
FreezeObjectChilds(Number.prototype);
|
|
FreezeObjectChilds(String.prototype);
|
|
FreezeObjectChilds(Boolean.prototype);
|
|
FreezeObjectChilds(Array.prototype);
|
|
FreezeObjectChilds(Object.prototype);
|
|
};
|
|
|
|
function FreezeObjectChilds(Value) {
|
|
var arr = Object.getOwnPropertyNames(Value);
|
|
for (var name of arr) {
|
|
Object.freeze(Value[name]);
|
|
}
|
|
};
|
|
|
|
function ChangePrototype() {
|
|
var Array_prototype_concat = Array.prototype.concat;
|
|
var Array_prototype_toString = Array.prototype.toString;
|
|
Array.prototype.concat = function() {
|
|
if (RunContext)
|
|
throw "Error Access denied: concat";
|
|
else
|
|
return Array_prototype_concat.apply(this, arguments);
|
|
};
|
|
Array.prototype.toString = function() {
|
|
if (RunContext)
|
|
throw "Error Access denied: toString";
|
|
else
|
|
return Array_prototype_toString.apply(this, arguments);
|
|
};
|
|
Array.prototype.toLocaleString = Array.prototype.toString;
|
|
Number.prototype.toLocaleString = function() {
|
|
return this.toString();
|
|
};
|
|
String.prototype.toLocaleLowerCase = String.prototype.toLowerCase;
|
|
String.prototype.toLocaleUpperCase = String.prototype.toUpperCase;
|
|
var String_prototype_localeCompare = String.prototype.localeCompare;
|
|
String.prototype.localeCompare = function() {
|
|
if (RunContext)
|
|
throw "Error Access denied: localeCompare";
|
|
else
|
|
return String_prototype_localeCompare.apply(this, arguments);
|
|
};
|
|
var String_prototype_match = String.prototype.match;
|
|
String.prototype.match = function() {
|
|
if (RunContext)
|
|
throw "Error Access denied: match";
|
|
else
|
|
return String_prototype_match.apply(this, arguments);
|
|
};
|
|
var String_prototype_repeat = String.prototype.repeat;
|
|
String.prototype.repeat = function() {
|
|
if (RunContext)
|
|
throw "Error Access denied: repeat";
|
|
else
|
|
return String_prototype_repeat.apply(this, arguments);
|
|
};
|
|
var String_prototype_search = String.prototype.search;
|
|
String.prototype.search = function() {
|
|
if (RunContext)
|
|
throw "Error Access denied: search";
|
|
else
|
|
return String_prototype_search.apply(this, arguments);
|
|
};
|
|
var String_prototype_padStart = String.prototype.padStart;
|
|
String.prototype.padStart = function() {
|
|
if (RunContext)
|
|
throw "Error Access denied: padStart";
|
|
else
|
|
return String_prototype_padStart.apply(this, arguments);
|
|
};
|
|
var String_prototype_padEnd = String.prototype.padEnd;
|
|
String.prototype.padEnd = function() {
|
|
if (RunContext)
|
|
throw "Error Access denied: padEnd";
|
|
else
|
|
return String_prototype_padEnd.apply(this, arguments);
|
|
};
|
|
String.prototype.right = function(count) {
|
|
if (this.length > count)
|
|
return this.substr(this.length - count, count);
|
|
else
|
|
return this.substr(0, this.length);
|
|
};
|
|
};
|
|
const MAX_LENGTH_STRING = 5000;
|
|
const $Math: any = {};
|
|
const $JSON: any = {};
|
|
|
|
function DO(Count) {
|
|
global.TickCounter -= Count;
|
|
if (global.TickCounter < 0)
|
|
throw new Error("Stop the execution code. The limit of ticks is over.");
|
|
};
|
|
|
|
function $SetValue(ID, CoinSum) {
|
|
DO(3000);
|
|
ID = global.ParseNum(ID);
|
|
if (!RunContext.Smart.TokenGenerate) {
|
|
throw "The smart-contract is not token generate, access to change values is denied";
|
|
}
|
|
var ToData = global.DApps.Accounts.ReadStateTR(ID);
|
|
if (!ToData) {
|
|
throw "Account does not exist.Error id number: " + ID;
|
|
}
|
|
if (ToData.Currency !== RunContext.Smart.Num) {
|
|
throw "The account currency does not belong to the smart-contract, access to change values is denied";
|
|
}
|
|
if (typeof CoinSum === "number") {
|
|
CoinSum = global.COIN_FROM_FLOAT(CoinSum);
|
|
}
|
|
if (CoinSum.SumCENT >= 1e9) {
|
|
throw "ERROR SumCENT>=1e9";
|
|
}
|
|
if (CoinSum.SumCOIN < 0 || CoinSum.SumCENT < 0) {
|
|
throw "ERROR Sum<0";
|
|
}
|
|
ToData.Value.SumCOIN = Math.trunc(CoinSum.SumCOIN);
|
|
ToData.Value.SumCENT = Math.trunc(CoinSum.SumCENT);
|
|
global.DApps.Accounts.WriteStateTR(ToData, RunContext.TrNum);
|
|
return true;
|
|
};
|
|
|
|
function $Send(ToID, CoinSum, Description) {
|
|
DO(3000);
|
|
ToID = global.ParseNum(ToID);
|
|
if (typeof CoinSum === "number")
|
|
CoinSum = global.COIN_FROM_FLOAT(CoinSum);
|
|
if (CoinSum.SumCENT >= 1e9) {
|
|
throw "ERROR SumCENT>=1e9";
|
|
}
|
|
if (CoinSum.SumCOIN < 0 || CoinSum.SumCENT < 0) {
|
|
throw "ERROR Sum<0";
|
|
}
|
|
var ToData = global.DApps.Accounts.ReadStateTR(ToID);
|
|
if (RunContext.Account.Currency !== ToData.Currency) {
|
|
throw "Different currencies";
|
|
}
|
|
global.DApps.Accounts.SendMoneyTR(RunContext.Block, RunContext.Account.Num, ToID, CoinSum, RunContext.BlockNum, RunContext.TrNum,
|
|
Description, Description, 1);
|
|
};
|
|
|
|
function $Move(FromID, ToID, CoinSum, Description) {
|
|
DO(3000);
|
|
FromID = global.ParseNum(FromID);
|
|
ToID = global.ParseNum(ToID);
|
|
var FromData = global.DApps.Accounts.ReadStateTR(FromID);
|
|
var ToData = global.DApps.Accounts.ReadStateTR(ToID);
|
|
if (FromData.Currency !== ToData.Currency) {
|
|
throw "Different currencies";
|
|
}
|
|
if (FromData.Value.Smart !== RunContext.Smart.Num) {
|
|
throw "The account smart does not belong to the smart-contract, access is denied";
|
|
}
|
|
if (typeof CoinSum === "number") {
|
|
CoinSum = global.COIN_FROM_FLOAT(CoinSum);
|
|
}
|
|
if (CoinSum.SumCENT >= 1e9) {
|
|
throw "ERROR SumCENT>=1e9";
|
|
}
|
|
if (CoinSum.SumCOIN < 0 || CoinSum.SumCENT < 0) {
|
|
throw "ERROR Sum<0";
|
|
}
|
|
CoinSum.SumCOIN = Math.trunc(CoinSum.SumCOIN);
|
|
CoinSum.SumCENT = Math.trunc(CoinSum.SumCENT);
|
|
global.DApps.Accounts.SendMoneyTR(RunContext.Block, FromID, ToID, CoinSum, RunContext.BlockNum, RunContext.TrNum, Description, Description,
|
|
1);
|
|
};
|
|
|
|
function $Event(Description) {
|
|
DO(50);
|
|
global.DApps.Accounts.DBChanges.TREvent.push({
|
|
Description: Description, Smart: RunContext.Smart.Num, Account: RunContext.Account.Num,
|
|
BlockNum: RunContext.BlockNum, TrNum: RunContext.TrNum
|
|
});
|
|
if (global.DebugEvent)
|
|
global.DebugEvent(Description);
|
|
if (global.CurTrItem) {
|
|
global.ToLogClient(Description, global.CurTrItem, false);
|
|
}
|
|
};
|
|
|
|
function $ReadAccount(ID) {
|
|
DO(900);
|
|
ID = global.ParseNum(ID);
|
|
var Account = global.DApps.Accounts.ReadStateTR(ID);
|
|
if (!Account)
|
|
throw "Error read account Num: " + ID;
|
|
return GET_ACCOUNT(Account);
|
|
};
|
|
|
|
function $ReadState(ID) {
|
|
DO(900);
|
|
ID = global.ParseNum(ID);
|
|
var Account = global.DApps.Accounts.ReadStateTR(ID);
|
|
if (!Account)
|
|
throw "Error read state account Num: " + ID;
|
|
var Smart;
|
|
if (Account.Value.Smart === RunContext.Smart.Num) {
|
|
Smart = RunContext.Smart;
|
|
}
|
|
else {
|
|
DO(100);
|
|
var Smart = global.DApps.Smart.ReadSmart(Account.Value.Smart);
|
|
if (!Smart) {
|
|
throw "Error smart ID: " + Account.Value.Smart;
|
|
}
|
|
}
|
|
var Data;
|
|
if (Smart.StateFormat)
|
|
Data = global.BufLib.GetObjectFromBuffer(Account.Value.Data, Smart.StateFormat, Smart.WorkStruct, 1);
|
|
else
|
|
Data = {};
|
|
if (typeof Data === "object")
|
|
Data.Num = ID;
|
|
return Data;
|
|
};
|
|
|
|
function $WriteState(Obj, ID) {
|
|
DO(3000);
|
|
if (ID === undefined)
|
|
ID = Obj.Num;
|
|
ID = global.ParseNum(ID);
|
|
var Account = global.DApps.Accounts.ReadStateTR(ID);
|
|
if (!Account)
|
|
throw "Error write account Num: " + ID;
|
|
var Smart = RunContext.Smart;
|
|
if (Account.Value.Smart !== Smart.Num) {
|
|
throw "The account does not belong to the smart-contract, access to change state is denied";
|
|
}
|
|
Account.Value.Data = global.BufLib.GetBufferFromObject(Obj, Smart.StateFormat, 80, Smart.WorkStruct, 1);
|
|
global.DApps.Accounts.WriteStateTR(Account, RunContext.TrNum);
|
|
};
|
|
|
|
function $GetMaxAccount() {
|
|
DO(20);
|
|
return global.DApps.Accounts.DBChanges.TRMaxAccount;
|
|
};
|
|
|
|
function $ADD(Coin, Value2) {
|
|
DO(5);
|
|
return global.ADD(Coin, Value2);
|
|
};
|
|
|
|
function $SUB(Coin, Value2) {
|
|
DO(5);
|
|
return global.SUB(Coin, Value2);
|
|
};
|
|
|
|
function $ISZERO(Coin) {
|
|
DO(5);
|
|
if (Coin.SumCOIN === 0 && Coin.SumCENT === 0)
|
|
return true;
|
|
else
|
|
return false;
|
|
};
|
|
|
|
function $FLOAT_FROM_COIN(Coin) {
|
|
DO(5);
|
|
return global.FLOAT_FROM_COIN(Coin);
|
|
};
|
|
|
|
function $COIN_FROM_FLOAT(Sum) {
|
|
DO(20);
|
|
return global.COIN_FROM_FLOAT(Sum);
|
|
};
|
|
|
|
function $COIN_FROM_STRING(Sum) {
|
|
DO(20);
|
|
return global.COIN_FROM_STRING(Sum);
|
|
};
|
|
|
|
function $require(SmartNum) {
|
|
DO(2000);
|
|
SmartNum = global.ParseNum(SmartNum);
|
|
var Smart = global.DApps.Smart.ReadSmart(SmartNum);
|
|
if (!Smart) {
|
|
throw "Smart does not exist. Error id number: " + SmartNum;
|
|
}
|
|
var EvalContext = GetSmartEvalContext(Smart);
|
|
EvalContext.funclist.SetContext(RunContext.context);
|
|
return EvalContext.publist;
|
|
};
|
|
|
|
function $GetHexFromArr(Arr) {
|
|
DO(20);
|
|
return global.GetHexFromArr(Arr);
|
|
};
|
|
|
|
function $GetArrFromHex(Str) {
|
|
DO(20);
|
|
return global.GetArrFromHex(Str);
|
|
};
|
|
|
|
function $sha(Str) {
|
|
DO(1000);
|
|
return global.shaarr(Str);
|
|
};
|
|
|
|
function $isFinite(a) {
|
|
DO(5);
|
|
return isFinite(a);
|
|
};
|
|
|
|
function $isNaN(a) {
|
|
DO(5);
|
|
return isNaN(a);
|
|
};
|
|
|
|
function $parseFloat(a) {
|
|
DO(10);
|
|
var Num = parseFloat(a);
|
|
if (!Num)
|
|
Num = 0;
|
|
if (isNaN(Num))
|
|
Num = 0;
|
|
return Num;
|
|
};
|
|
|
|
function $parseInt(a) {
|
|
DO(10);
|
|
var Num = parseInt(a);
|
|
if (!Num)
|
|
Num = 0;
|
|
if (isNaN(Num))
|
|
Num = 0;
|
|
return Num;
|
|
};
|
|
|
|
function $parseUint(a) {
|
|
DO(10);
|
|
return global.ParseNum(a);
|
|
};
|
|
|
|
function $String(a) {
|
|
DO(5);
|
|
return String(a);
|
|
};
|
|
|
|
function $Number(a) {
|
|
DO(5);
|
|
return Number(a);
|
|
};
|
|
|
|
function $Boolean(a) {
|
|
DO(5);
|
|
return Boolean(a);
|
|
};
|
|
|
|
function CHKL(Str) {
|
|
if (typeof Str === "string" && Str.length > MAX_LENGTH_STRING)
|
|
throw new Error("Invalid string length:" + Str.length);
|
|
return Str;
|
|
};
|
|
var BlockRandomInit;
|
|
var m_w = 123456789;
|
|
var m_z = 987654321;
|
|
var mask = 0xffffffff;
|
|
|
|
function MathRandom() {
|
|
DO(5);
|
|
|
|
function seed(i) {
|
|
m_w = i;
|
|
m_z = 987654321;
|
|
};
|
|
|
|
function random() {
|
|
m_z = (36969 * (m_z & 65535) + (m_z >> 16)) & mask;
|
|
m_w = (18000 * (m_w & 65535) + (m_w >> 16)) & mask;
|
|
var result = ((m_z << 16) + m_w) & mask;
|
|
result /= 4294967296;
|
|
return result + 0.5;
|
|
};
|
|
if (BlockRandomInit === RunContext.Block.BlockNum)
|
|
return random();
|
|
BlockRandomInit = RunContext.Block.BlockNum;
|
|
RunContext.Block.Hash;
|
|
return 0;
|
|
};
|
|
|
|
function StaticGetBlockHeader(BlockNum) {
|
|
DO(100);
|
|
return global.SERVER.ReadBlockHeaderDB(BlockNum);
|
|
};
|
|
|
|
function StaticGetBlockNumDB() {
|
|
return global.SERVER.GetMaxNumBlockDB();
|
|
};
|
|
|
|
function StaticGetSmart(Num) {
|
|
DO(100);
|
|
var Smart = global.DApps.Smart.ReadSmart(Num);
|
|
return GET_SMART(Smart);
|
|
};
|
|
ChangePrototype();
|
|
InitEval();
|
|
var smartApp = new SmartApp;
|
|
global.DApps.Smart = smartApp;
|
|
global.DAppByType[TYPE_TRANSACTION_SMART_CREATE] = smartApp;
|
|
global.DAppByType[global.TYPE_TRANSACTION_SMART_RUN] = smartApp;
|
|
global.DAppByType[TYPE_TRANSACTION_SMART_CHANGE] = smartApp;
|
|
|
|
export default SmartApp |