2021-11-08 16:39:17 +00:00

1135 lines
32 KiB
Go

// Copyright 2017-2018 DERO Project. All rights reserved.
// Use of this source code in any form is governed by RESEARCH license.
// license can be found in the LICENSE file.
// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8
//
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package dvm
import "fmt"
import "text/scanner"
import "strings"
import "strconv"
import "unicode"
import "unicode/utf8"
import "go/ast"
import "go/parser"
import "go/token"
import "math"
import "runtime/debug"
import "github.com/blang/semver/v4"
import "github.com/deroproject/derohe/cryptography/crypto"
//import "github.com/deroproject/derohe/rpc"
type Vtype int
const (
Invalid Vtype = iota // default is invalid
Uint64 // uint64 data type
String // string
)
var replacer = strings.NewReplacer("< =", "<=", "> =", ">=", "= =", "==", "! =", "!=", "& &", "&&", "| |", "||", "< <", "<<", "> >", ">>", "< >", "!=")
// Some global variables are always accessible, namely
// SCID TXID which installed the SC
// TXID current TXID under which this SC is currently executing
// BLID current BLID under which TXID is found, THIS CAN be used as deterministic RND Generator, if the SC needs secure randomness
// BL_HEIGHT current height of blockchain
type Variable struct {
Name string `cbor:"N,omitempty" json:"N,omitempty"`
Type Vtype `cbor:"T,omitempty" json:"T,omitempty"` // we have only 2 data types
ValueUint64 uint64 `cbor:"V,omitempty" json:"VI,omitempty"`
ValueString string `cbor:"V,omitempty" json:"VS,omitempty"`
}
type Function struct {
Name string `cbor:"N,omitempty" json:"N,omitempty"`
Params []Variable `cbor:"P,omitempty" json:"P,omitempty"`
ReturnValue Variable `cbor:"R,omitempty" json:"R,omitempty"`
Lines map[uint64][]string `cbor:"L,omitempty" json:"L,omitempty"`
// map from line number to array index below
LinesNumberIndex map[uint64]uint64 `cbor:"LI,omitempty" json:"LI,omitempty"` // a map is used to avoid sorting/searching
LineNumbers []uint64 `cbor:"LN,omitempty" json:"LN,omitempty"`
}
const LIMIT_interpreted_lines = 2000 // testnet has hardcoded limit
const LIMIT_evals = 11000 // testnet has hardcoded limit eval limit
// each smart code is nothing but a collection of functions
type SmartContract struct {
Functions map[string]Function `cbor:"F,omitempty" json:"F,omitempty"`
}
// we have a rudimentary line by line parser
// SC authors must make sure code coverage is 100 %
// we are doing away with AST
func ParseSmartContract(src_code string) (SC SmartContract, pos string, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("Recovered in function %+v", r)
}
}()
var s scanner.Scanner
s.Init(strings.NewReader(src_code))
s.Filename = "code"
s.Mode = scanner.ScanIdents | scanner.ScanFloats | scanner.ScanChars | scanner.ScanStrings | scanner.ScanRawStrings | scanner.SkipComments | scanner.ScanComments // skip comments
skip_line := int32(-1)
var current_line int32 = -1
var line_tokens []string
var current_function *Function
SC.Functions = map[string]Function{}
for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
pos := s.Position.String()
txt := s.TokenText()
if strings.HasPrefix(txt, ";") || strings.HasPrefix(txt, "REM") { // skip line, if this is the first word
skip_line = int32(s.Position.Line)
}
if skip_line == int32(s.Position.Line) {
continue
}
/*if strings.HasPrefix(txt, "//") || strings.HasPrefix(txt, "/*") { // skip comments
continue
}*/
process_token:
if current_line == -1 {
current_line = int32(s.Position.Line)
}
if current_line == int32(s.Position.Line) { // collect a complete line
line_tokens = append(line_tokens, txt)
} else { // if new line found, process previous line
if err = parse_function_line(&SC, &current_function, line_tokens); err != nil {
return SC, pos, err
}
line_tokens = line_tokens[:0]
current_line = -1
goto process_token
}
// fmt.Printf("%s: %s line %+v\n", s.Position, txt, line_tokens)
}
if len(line_tokens) > 0 { // last line is processed here
if err = parse_function_line(&SC, &current_function, line_tokens); err != nil {
return SC, pos, err
}
}
if current_function != nil {
err = fmt.Errorf("EOF reached but End Function is missing \"%s\"", current_function.Name)
return SC, pos, err
}
return
}
// checks whether a function name is valid
// a valid name starts with a non digit and does not contain .
func check_valid_name(name string) bool {
r, size := utf8.DecodeRuneInString(name)
if r == utf8.RuneError || size == 0 {
return false
}
return unicode.IsLetter(r)
}
func check_valid_type(name string) Vtype {
switch name {
case "Uint64":
return Uint64
case "String":
return String
}
return Invalid
}
// this will parse 1 line at a time, if there is an error, it is returned
func parse_function_line(SC *SmartContract, function **Function, line []string) (err error) {
pos := 0
//fmt.Printf("parsing function line %+v\n", line)
if *function == nil { //if no current function, only legal is Sub
if !strings.EqualFold(line[pos], "Function") {
return fmt.Errorf("Expecting declaration of function but found \"%s\"", line[0])
}
pos++
var f Function
f.Lines = map[uint64][]string{} // initialize line map
f.LinesNumberIndex = map[uint64]uint64{}
if len(line) < (pos + 1) {
return fmt.Errorf("function name missing")
}
if !check_valid_name(line[pos]) {
return fmt.Errorf("function name \"%s\" contains invalid characters", line[pos])
}
f.Name = line[pos]
pos++
if len(line) < (pos+1) || line[pos] != "(" {
return fmt.Errorf("function \"%s\" missing '('", f.Name)
}
parse_params: // now lets parse function params, but lets filter out ,
pos++
if len(line) < (pos + 1) {
return fmt.Errorf("function \"%s\" missing function parameters", f.Name)
}
if line[pos] == "," {
goto parse_params
}
if line[pos] == ")" {
// function does not have any parameters
// or all parameters have been parsed
} else { // we must parse param name, param type as pairs
if len(line) < (pos + 2) {
return fmt.Errorf("function \"%s\" missing function parameters", f.Name)
}
param_name := line[pos]
param_type := check_valid_type(line[pos+1])
if !check_valid_name(param_name) {
return fmt.Errorf("function name \"%s\", variable name \"%s\" contains invalid characters", f.Name, param_name)
}
if param_type == Invalid {
return fmt.Errorf("function name \"%s\", variable type \"%s\" is invalid", f.Name, line[pos+1])
}
f.Params = append(f.Params, Variable{Name: param_name, Type: param_type})
pos++
goto parse_params
}
pos++
// check if we have return value
if len(line) < (pos + 1) { // we do not have return value
f.ReturnValue.Type = Invalid
} else {
return_type := check_valid_type(line[pos])
if return_type == Invalid {
return fmt.Errorf("function name \"%s\", return type \"%s\" is invalid", f.Name, line[pos])
}
f.ReturnValue.Type = return_type
}
*function = &f
return nil
} else if strings.EqualFold(line[pos], "End") && strings.EqualFold(line[pos+1], "Function") {
SC.Functions[(*function).Name] = **function
*function = nil
} else if strings.EqualFold(line[pos], "Function") {
return fmt.Errorf("Nested functions are not allowed")
} else { // add line to current function, provided line numbers are not duplicated, line numbers are mandatory
line_number, err := strconv.ParseUint(line[pos], 10, 64)
if err != nil {
return fmt.Errorf("Error Parsing line number \"%s\" in function \"%s\" ", line[pos], (*function).Name)
}
if line_number == 0 || line_number == math.MaxUint64 {
return fmt.Errorf("Error: line number cannot be %d in function \"%s\" ", line_number, (*function).Name)
}
if _, ok := (*function).Lines[line_number]; ok { // duplicate line number
return fmt.Errorf("Error: duplicate line number within function \"%s\" ", (*function).Name)
}
if len((*function).LineNumbers) >= 1 && line_number < (*function).LineNumbers[len((*function).LineNumbers)-1] {
return fmt.Errorf("Error: line number must be ascending within function \"%s\" ", (*function).Name)
}
line_copy := make([]string, len(line)-1, len(line)-1)
copy(line_copy, line[1:])
(*function).Lines[line_number] = line_copy // we need to copy and add ( since line is reused)
(*function).LinesNumberIndex[line_number] = uint64(len((*function).LineNumbers))
(*function).LineNumbers = append((*function).LineNumbers, line_number)
// fmt.Printf("%s %d %+v\n", (*function).Name, line_number, line[0:])
// fmt.Printf("%s %d %+v\n", (*function).Name, line_number, (*function).Lines[line_number])
}
return nil
}
// this will run a function from a loaded SC and execute it if possible
// it can run all internal functions
// parameters must be passed as strings
func runSmartContract_internal(SC *SmartContract, EntryPoint string, state *Shared_State, params map[string]interface{}) (result Variable, err error) {
// if smart contract does not contain function, trigger exception
function_call, ok := SC.Functions[EntryPoint]
if !ok {
err = fmt.Errorf("function \"%s\" is not available in SC", EntryPoint)
return
}
var dvm DVM_Interpreter
dvm.SC = SC
dvm.f = function_call
dvm.Locals = map[string]Variable{}
dvm.State = state // set state to execute current function
// parse parameters, rename them, make them available as local variables
for _, p := range function_call.Params {
variable := Variable{Name: p.Name, Type: p.Type}
value, ok := params[p.Name]
if !ok { // necessary parameter is missing from arguments
err = fmt.Errorf("Argument \"%s\" is missing while invoking \"%s\"", p.Name, EntryPoint)
return
}
// now lets parse the data,Uint64,Address,String,Blob
switch p.Type {
case Uint64:
if variable.ValueUint64, err = strconv.ParseUint(value.(string), 0, 64); err != nil {
return
}
case String:
variable.ValueString = value.(string)
default:
panic("unknown parameter type cannot have parameters")
}
dvm.Locals[variable.Name] = variable
}
// all variables have been collected, start interpreter
dvm.ReturnValue = dvm.f.ReturnValue // enforce return value to be of same type
dvm.State.Monitor_recursion++ // higher recursion
err = dvm.interpret_SmartContract()
if err != nil {
return
}
result = dvm.ReturnValue
return
}
// it is similar to internal functions, however it enforces the condition that only Exportable functions are callable
// any function which has first character ASCII and upper case is considered an exported function
func RunSmartContract(SC *SmartContract, EntryPoint string, state *Shared_State, params map[string]interface{}) (result Variable, err error) {
// if smart contract does not contain function, trigger exception
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("Recovered in function %+v stack %s", r, string(debug.Stack()))
}
}()
r, size := utf8.DecodeRuneInString(EntryPoint)
if r == utf8.RuneError || size == 0 {
return result, fmt.Errorf("Invalid function name")
}
if r >= unicode.MaxASCII {
return result, fmt.Errorf("Invalid function name, First character must be ASCII alphabet")
}
if !unicode.IsLetter(r) {
return result, fmt.Errorf("Invalid function name, First character must be ASCII Letter")
}
if !unicode.IsUpper(r) {
return result, fmt.Errorf("Invalid function name, First character must be Capital/Upper Case")
}
// initialize RND
if state.Monitor_recursion == 0 {
state.RND = Initialize_RND(state.Chain_inputs.SCID, state.Chain_inputs.BLID, state.Chain_inputs.TXID)
state.Assets_Transfer = map[string]map[string]uint64{}
}
result, err = runSmartContract_internal(SC, EntryPoint, state, params)
if err != nil {
return result, err
}
if state.Monitor_recursion != 0 { // recursion must be zero at end
return result, fmt.Errorf("Invalid recursion level %d", state.Monitor_recursion)
}
// if recursion level is zero, we should check return value and persist the state changes
return result, err
}
// this structure is all the inputs that are available to SC during execution
type Blockchain_Input struct {
SCID crypto.Hash // current smart contract which is executing
BLID crypto.Hash // BLID
TXID crypto.Hash // current TXID under which TX
Signer string // address which signed this, inn 33 byte form
BL_HEIGHT uint64 // current chain height under which current tx is valid
BL_TOPOHEIGHT uint64 // current block topo height which can be used to uniquely pinpoint the block
BL_TIMESTAMP uint64 // epoch second resolution
}
// all DVMs triggered by the first call, will share this structure
// sharing this structure means RND number attacks are not possible
// all storage state is shared, this means something similar to solidity delegatecall
// this is necessary to prevent number of attacks
type Shared_State struct {
SCIDZERO crypto.Hash // points to DERO SCID , which is zero
SCIDSELF crypto.Hash // points to SELF SCID
Persistance bool // whether the results will be persistant or it's just a demo/test call
Trace bool // enables tracing to screen
Chain_inputs *Blockchain_Input // all blockchain info is available here
Assets map[crypto.Hash]uint64 // all assets supplied with this tx, including DERO main asset
Assets_Transfer map[string]map[string]uint64 // any Assets that this TX wants to send OUT
// transfers are only processed after the contract has terminated successfully
RND *RND // this is initialized only once while invoking entrypoint
Store *TX_Storage // mechanism to access a data store, can discard changes
Monitor_recursion int64 // used to control recursion amount 64 calls are more than necessary
Monitor_lines_interpreted int64 // number of lines interpreted
Monitor_ops int64 // number of ops evaluated, for expressions, variables
}
type DVM_Interpreter struct {
Version semver.Version // current version set by setversion
Cost int64
CostLimit int64
SCID string
SC *SmartContract
EntryPoint string
f Function
IP uint64 // current line number
ReturnValue Variable // Result of current function call
Locals map[string]Variable // all local variables
Chain_inputs *Blockchain_Input // all blockchain info is available here
State *Shared_State // all shared state between DVM is available here
RND *RND // this is initialized only once while invoking entrypoint
store *TX_Storage // mechanism to access a data store, can discard changes
}
func (i *DVM_Interpreter) incrementIP(newip uint64) (line []string, err error) {
var ok bool
try_again:
// increase cost here
if newip == 0 { // we are simply falling through
index := i.f.LinesNumberIndex[i.IP] // find the pos in the line numbers and find the current line_number
index++
if i.IP == 0 {
index = 0 // start from first line
}
if uint64(len(i.f.LineNumbers)) <= index { // if function does not contain more lines to execute, return
err = fmt.Errorf("No lines after line number (%d) in SC %s in function %s", i.IP, i.SCID, i.f.Name)
return
}
i.IP = i.f.LineNumbers[index]
} else { // we have a GOTO and must jump to the line numbr mentioned
i.IP = newip
}
line, ok = i.f.Lines[i.IP]
if !ok {
err = fmt.Errorf("No such line number (%d) in SC %s in function %s", i.IP, i.SCID, i.f.Name)
return
}
i.State.Monitor_lines_interpreted++ // increment line interpreted
if len(line) == 0 { // increment to next line
goto try_again
}
return
}
// this runs a smart contract function with specific params
func (i *DVM_Interpreter) interpret_SmartContract() (err error) {
newIP := uint64(0)
for {
var line []string
line, err = i.incrementIP(newIP)
if err != nil {
return
}
newIP = 0 // this is necessary otherwise, it will trigger an infinite loop in the case given below
/*
* Function SetOwner(value Uint64, newowner String) Uint64
10 IF LOAD("owner") == SIGNER() THEN GOTO 30
20 RETURN 1
30 STORE("owner",newowner)
40 RETURN 0
End Function
*/
if i.State.Monitor_lines_interpreted > LIMIT_interpreted_lines {
panic(fmt.Sprintf("%d lines interpreted, reached limit %d", LIMIT_interpreted_lines, LIMIT_interpreted_lines))
}
//fmt.Printf("received line to interpret %+v err\n", line, err)
switch {
case strings.EqualFold(line[0], "DIM"):
newIP, err = i.interpret_DIM(line[1:])
case strings.EqualFold(line[0], "LET"):
newIP, err = i.interpret_LET(line[1:])
case strings.EqualFold(line[0], "GOTO"):
newIP, err = i.interpret_GOTO(line[1:])
case strings.EqualFold(line[0], "IF"):
newIP, err = i.interpret_IF(line[1:])
case strings.EqualFold(line[0], "RETURN"):
newIP, err = i.interpret_RETURN(line[1:])
//ability to print something for debugging purpose
case strings.EqualFold(line[0], "PRINT"):
fallthrough
case strings.EqualFold(line[0], "PRINTF"):
newIP, err = i.interpret_PRINT(line[1:])
// if we are here, the first part is unknown
default:
// we should try to evaluate expression and make sure it's a function call
// now lets evaluate the expression
expr, err1 := parser.ParseExpr(replacer.Replace(strings.Join(line, " ")))
if err1 != nil {
err = err1
return
}
if _, ok := expr.(*ast.CallExpr); !ok {
return fmt.Errorf("not a function call line %+v\n", line)
}
i.eval(expr)
}
if i.State.Trace {
fmt.Printf("interpreting line %+v err:'%s'\n", line, err)
}
if err != nil {
err = fmt.Errorf("err while interpreting line %+v err %s\n", line, err)
return
}
if newIP == math.MaxUint64 {
break
}
}
return
}
// this is very limited and can be used print only variables
func (dvm *DVM_Interpreter) interpret_PRINT(args []string) (newIP uint64, err error) {
var variable Variable
var ok bool
if len(args) > 0 {
params := []interface{}{}
for i := 1; i < len(args); i++ {
if variable, ok = dvm.Locals[args[i]]; !ok { // TODO what about printing globals
/*if variable,ok := dvm.Locals[exp.Name];!ok{
}*/
}
if ok {
switch variable.Type {
case Uint64:
params = append(params, variable.ValueUint64)
case String:
params = append(params, variable.ValueString)
default:
panic("Unhandled data_type")
}
} else {
params = append(params, fmt.Sprintf("unknown variable %s", args[i]))
}
}
_, err = fmt.Printf(strings.Trim(args[0], "\"")+"\n", params...)
}
return
}
// process DIM line
func (dvm *DVM_Interpreter) interpret_DIM(line []string) (newIP uint64, err error) {
if len(line) <= 2 || !strings.EqualFold(line[len(line)-2], "as") {
return 0, fmt.Errorf("Invalid DIM syntax")
}
// check last data type
data_type := check_valid_type(line[len(line)-1])
if data_type == Invalid {
return 0, fmt.Errorf("function name \"%s\", No such Data type \"%s\"", dvm.f.Name, line[len(line)-1])
}
for i := 0; i < len(line)-2; i++ {
if line[i] != "," { // ignore separators
if !check_valid_name(line[i]) {
return 0, fmt.Errorf("function name \"%s\", variable name \"%s\" contains invalid characters", dvm.f.Name, line[i])
}
// check whether variable is already defined
if _, ok := dvm.Locals[line[i]]; ok {
return 0, fmt.Errorf("function name \"%s\", variable name \"%s\" contains invalid characters", dvm.f.Name, line[i])
}
// all data variables are pre-initialized
switch data_type {
case Uint64:
dvm.Locals[line[i]] = Variable{Name: line[i], Type: Uint64, ValueUint64: uint64(0)}
case String:
dvm.Locals[line[i]] = Variable{Name: line[i], Type: String, ValueString: ""}
default:
panic("Unhandled data_type")
}
// fmt.Printf("Initialising variable %s %+v\n",line[i],dvm.Locals[line[i]])
}
}
return
}
// process LET statement
func (dvm *DVM_Interpreter) interpret_LET(line []string) (newIP uint64, err error) {
if len(line) <= 2 || !strings.EqualFold(line[1], "=") {
err = fmt.Errorf("Invalid LET syntax")
return
}
if _, ok := dvm.Locals[line[0]]; !ok {
err = fmt.Errorf("function name \"%s\", variable name \"%s\" is used without definition", dvm.f.Name, line[0])
return
}
result := dvm.Locals[line[0]]
expr, err := parser.ParseExpr(strings.Join(line[2:], " "))
if err != nil {
return
}
expr_result := dvm.eval(expr)
//fmt.Printf("expression %s = %+v\n", line[0],expr_result)
//fmt.Printf(" %+v \n", dvm.Locals[line[0]])
switch result.Type {
case Uint64:
result.ValueUint64 = expr_result.(uint64)
case String:
result.ValueString = expr_result.(string)
default:
panic("Unhandled data_type")
}
dvm.Locals[line[0]] = result
// fmt.Printf(" %+v \n", dvm.Locals[line[0]])
return
}
// process GOTO line
func (dvm *DVM_Interpreter) interpret_GOTO(line []string) (newIP uint64, err error) {
if len(line) != 1 {
err = fmt.Errorf("GOTO contains 1 mandatory line number as argument")
return
}
newIP, err = strconv.ParseUint(line[0], 0, 64)
if err != nil {
return
}
if newIP == 0 || newIP == math.MaxUint64 {
return 0, fmt.Errorf("GOTO has invalid line number \"%d\"", newIP)
}
return
}
// process IF line
// IF has two forms vis x,y are line numbers
// IF expr THEN GOTO x
// IF expr THEN GOTO x ELSE GOTO y
func (dvm *DVM_Interpreter) interpret_IF(line []string) (newIP uint64, err error) {
thenip := uint64(0)
elseip := uint64(0)
// first form of IF
if len(line) >= 4 && strings.EqualFold(line[len(line)-3], "THEN") && strings.EqualFold(line[len(line)-2], "GOTO") {
thenip, err = strconv.ParseUint(line[len(line)-1], 0, 64)
if err != nil {
return
}
line = line[:len(line)-3]
} else if len(line) >= 7 && strings.EqualFold(line[len(line)-6], "THEN") && strings.EqualFold(line[len(line)-5], "GOTO") && strings.EqualFold(line[len(line)-3], "ELSE") && strings.EqualFold(line[len(line)-2], "GOTO") {
thenip, err = strconv.ParseUint(line[len(line)-4], 0, 64)
if err != nil {
return
}
elseip, err = strconv.ParseUint(line[len(line)-1], 0, 64)
if err != nil {
return
}
if elseip == 0 || elseip == math.MaxUint64 {
return 0, fmt.Errorf("ELSE GOTO has invalid line number \"%d\"", thenip)
}
line = line[:len(line)-6]
} else {
err = fmt.Errorf("Invalid IF syntax")
return
}
if thenip == 0 || thenip == math.MaxUint64 {
return 0, fmt.Errorf("THEN GOTO has invalid line number \"%d\"", thenip)
}
// now lets evaluate the expression
expr, err := parser.ParseExpr(replacer.Replace(strings.Join(line, " ")))
if err != nil {
return
}
expr_result := dvm.eval(expr)
//fmt.Printf("if %d %T expr( %s)\n", expr_result, expr_result, replacer.Replace(strings.Join(line, " ")))
if result, ok := expr_result.(uint64); ok {
if result != 0 {
newIP = thenip
} else {
newIP = elseip
}
} else {
err = fmt.Errorf("Invalid IF expression \"%s\"", replacer.Replace(strings.Join(line, " ")))
}
return
}
// process RETURN line
func (dvm *DVM_Interpreter) interpret_RETURN(line []string) (newIP uint64, err error) {
if dvm.ReturnValue.Type == Invalid {
if len(line) != 0 {
err = fmt.Errorf("function name \"%s\" cannot return anything", dvm.f.Name)
return
}
dvm.State.Monitor_recursion-- // lower recursion
newIP = math.MaxUint64 // simple return
return
}
if len(line) == 0 {
err = fmt.Errorf("function name \"%s\" should return a value", dvm.f.Name)
return
}
// we may be returning an expression which must be solved
expr, err := parser.ParseExpr(replacer.Replace(strings.Join(line, " ")))
if err != nil {
return
}
expr_result := dvm.eval(expr)
//fmt.Printf("expression %+v %T\n", expr_result, expr_result)
switch dvm.ReturnValue.Type {
case Uint64:
dvm.ReturnValue.ValueUint64 = expr_result.(uint64)
case String:
dvm.ReturnValue.ValueString = expr_result.(string)
default:
panic("unexpected data type")
}
dvm.State.Monitor_recursion-- // lower recursion
newIP = math.MaxUint64 // simple return
return
}
// only returns identifiers
func (dvm *DVM_Interpreter) eval_identifier(exp ast.Expr) string {
switch exp := exp.(type) {
case *ast.Ident: // it's a variable,
return exp.Name
default:
panic("expecting identifier")
}
}
func (dvm *DVM_Interpreter) eval(exp ast.Expr) interface{} {
dvm.State.Monitor_ops++ // maintain counter
if dvm.State.Monitor_ops > LIMIT_evals {
panic(fmt.Sprintf("%d lines interpreted, evals reached limit %d", dvm.State.Monitor_lines_interpreted, LIMIT_evals))
}
//fmt.Printf("exp %+v %T\n", exp, exp)
switch exp := exp.(type) {
case *ast.ParenExpr:
return dvm.eval(exp.X)
case *ast.UnaryExpr: // there are 2 unary operators, one is binary NOT , second is logical not
switch exp.Op {
case token.XOR:
return ^(dvm.eval(exp.X).(uint64))
case token.NOT:
x := dvm.eval(exp.X)
switch x := x.(type) {
case uint64:
return ^x
case string:
if IsZero(x) == 1 {
return uint64(1)
}
return uint64(0)
}
}
case *ast.BinaryExpr:
return dvm.evalBinaryExpr(exp)
case *ast.Ident: // it's a variable,
if _, ok := dvm.Locals[exp.Name]; !ok {
panic(fmt.Sprintf("function name \"%s\", variable name \"%s\" is used without definition", dvm.f.Name, exp.Name))
}
//fmt.Printf("value %s %d\n",exp.Name, dvm.Locals[exp.Name].Value)
switch dvm.Locals[exp.Name].Type {
case Uint64:
return dvm.Locals[exp.Name].ValueUint64
case String:
return dvm.Locals[exp.Name].ValueString
default:
panic("unexpected data type")
}
// there are 2 types of calls, one within the smartcontract
// other one crosses smart contract boundaries
case *ast.CallExpr:
func_name := dvm.eval_identifier(exp.Fun)
//fmt.Printf("Call expression %+v %s \"%s\" \n",exp,exp.Fun, func_name)
// if call is internal
//
// try to handle internal functions, SC function cannot overide internal functions
if ok, result := dvm.Handle_Internal_Function(exp, func_name); ok {
return result
}
function_call, ok := dvm.SC.Functions[func_name]
if !ok {
panic(fmt.Sprintf("Unknown function called \"%s\"", exp.Fun))
}
if len(function_call.Params) != len(exp.Args) {
panic(fmt.Sprintf("function \"%s\" called with incorrect number of arguments , expected %d , actual %d", func_name, len(function_call.Params), len(exp.Args)))
}
arguments := map[string]interface{}{}
for i, p := range function_call.Params {
switch p.Type {
case Uint64:
arguments[p.Name] = fmt.Sprintf("%d", dvm.eval(exp.Args[i]).(uint64))
case String:
arguments[p.Name] = dvm.eval(exp.Args[i]).(string)
}
}
// allow calling unexported functions
result, err := runSmartContract_internal(dvm.SC, func_name, dvm.State, arguments)
if err != nil {
panic(err)
}
switch function_call.ReturnValue.Type {
case Uint64:
return result.ValueUint64
case String:
return result.ValueString
//default:
// panic(fmt.Sprintf("unexpected data type %T", function_call.ReturnValue.Type))
}
return nil
case *ast.BasicLit:
switch exp.Kind {
case token.INT:
i, err := strconv.ParseUint(exp.Value, 0, 64)
if err != nil {
panic(err)
}
return i
case token.STRING:
unquoted, err := strconv.Unquote(exp.Value)
if err != nil {
panic(err)
}
return unquoted
}
default:
panic(fmt.Sprintf("Unhandled expression type %+v", exp))
}
panic("We should never reach here while evaluating expressions")
return 0
}
// this can be used to check whether variable has a default value
// for uint64 , it is 0
// for string , it is ""
// TODO Address, Blob
func IsZero(value interface{}) uint64 {
switch v := value.(type) {
case uint64:
if v == 0 {
return 1
}
case string:
if v == "" {
return 1
}
default:
panic("IsZero not being handled")
}
return 0
}
func (dvm *DVM_Interpreter) evalBinaryExpr(exp *ast.BinaryExpr) interface{} {
left := dvm.eval(exp.X)
right := dvm.eval(exp.Y)
//fmt.Printf("left '%+v %T' %+v right '%+v %T'\n", left, left, exp.Op, right, right)
// special case to append uint64 to strings
if fmt.Sprintf("%T", left) == "string" && fmt.Sprintf("%T", right) == "uint64" {
return left.(string) + fmt.Sprintf("%d", right)
}
if fmt.Sprintf("%T", left) != fmt.Sprintf("%T", right) {
panic(fmt.Sprintf("Expressions cannot be different type(String/Uint64) left (val %+v %+v) right (%+v %+v)", left, exp.X, right, exp.Y))
}
// logical ops are handled differently
switch exp.Op {
case token.LAND:
if (IsZero(left) == 0) && (IsZero(right) == 0) { // both sides should be set
return uint64(1)
}
return uint64(0)
case token.LOR:
//fmt.Printf("left %d right %d\n", left,right)
//fmt.Printf("left %v right %v\n", (IsZero(left) != 0),(IsZero(right) != 0))
if (IsZero(left) == 0) || (IsZero(right) == 0) {
return uint64(1)
}
return uint64(0)
}
// handle string operands
if fmt.Sprintf("%T", left) == "string" {
left_string := left.(string)
right_string := right.(string)
switch exp.Op {
case token.ADD:
return left_string + right_string
case token.EQL:
if left_string == right_string {
return uint64(1)
}
return uint64(0)
case token.NEQ:
if left_string != right_string {
return uint64(1)
}
return uint64(0)
default:
panic(fmt.Sprintf("String data type only support addition operation ('%s') not supported", exp.Op))
}
}
left_uint64 := left.(uint64)
right_uint64 := right.(uint64)
switch exp.Op {
case token.ADD:
return left_uint64 + right_uint64 // TODO : can we add rounding case here and raise exception
case token.SUB:
return left_uint64 - right_uint64 // TODO : can we add rounding case here and raise exception
case token.MUL:
return left_uint64 * right_uint64
case token.QUO:
return left_uint64 / right_uint64
case token.REM:
return left_uint64 % right_uint64
//bitwise ops
case token.AND:
return left_uint64 & right_uint64
case token.OR:
return left_uint64 | right_uint64
case token.XOR:
return left_uint64 ^ right_uint64
case token.SHL:
return left_uint64 << right_uint64
case token.SHR:
return left_uint64 >> right_uint64
case token.EQL:
if left_uint64 == right_uint64 {
return uint64(1)
}
case token.NEQ:
if left_uint64 != right_uint64 {
return uint64(1)
}
case token.LEQ:
if left_uint64 <= right_uint64 {
return uint64(1)
}
case token.GEQ:
if left_uint64 >= right_uint64 {
return uint64(1)
}
case token.LSS:
if left_uint64 < right_uint64 {
return uint64(1)
}
case token.GTR:
if left_uint64 > right_uint64 {
return uint64(1)
}
default:
panic("This operation cannot be handled")
}
return uint64(0)
}
/*
func main() {
const src = `
Function HelloWorld(s Uint64) Uint64
5
10 Dim x1, x2 as Uint64
20 LET x1 = 3
25 LET x1 = 3 + 5 - 1
27 LET x2 = x1 + 3
28 RETURN HelloWorld2(s*s)
30 Printf "x1=%d x2=%d s = %d" x1 x2 s
35 IF x1 == 7 THEN GOTO 100 ELSE GOTO 38
38 Dim y1, y2 as String
40 LET y1 = "first string" + "second string"
60 GOTO 100
80 GOTO 10
100 RETURN 0
500 LET y = 45
501 y = 45
End Function
Function HelloWorld2(s Uint64) Uint64
900 Return s
950 y = 45
// Comment begins at column 5.
; This line should not be included in the output.
REM jj
7000 let x.ku[1+1]=0x55
End Function
`
// we should be build an AST here
sc, pos, err := ParseSmartContract(src)
if err != nil {
fmt.Printf("Error while parsing smart contract pos %s err : %s\n", pos, err)
return
}
result, err := RunSmartContract(&sc, "HelloWorld", map[string]interface{}{"s": "9999"})
fmt.Printf("result %+v err %s\n", result, err)
}
*/