diff --git a/dvm/dvm.go b/dvm/dvm.go index 22d92ed5..94808c52 100644 --- a/dvm/dvm.go +++ b/dvm/dvm.go @@ -26,12 +26,19 @@ import "go/ast" import "go/parser" import "go/token" import "math" +import "math/big" +import "regexp" import "runtime/debug" import "github.com/blang/semver/v4" import "github.com/deroproject/derohe/cryptography/crypto" +import "github.com/holiman/uint256" -//import "github.com/deroproject/derohe/rpc" +// Jul 2022 - DeroHolic: Added Uint256 support +// Literals are interperated as uint64 +// Binary operations of mixed type (uint64 / uint256) are first casted to uint256 +// (use function UINT64() on Uint256 expressions to override this behavior) +// Auto casting when assigning to or returning from different type type Vtype int @@ -41,6 +48,7 @@ const ( Invalid Vtype = 0x3 // default is invalid Uint64 Vtype = 0x4 // uint64 data type String Vtype = 0x5 // string + Uint256 Vtype = 0x6 ) var replacer = strings.NewReplacer("< =", "<=", "> =", ">=", "= =", "==", "! =", "!=", "& &", "&&", "| |", "||", "< <", "<<", "> >", ">>", "< >", "!=") @@ -52,10 +60,11 @@ var replacer = strings.NewReplacer("< =", "<=", "> =", ">=", "= =", "==", "! =", // 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"` + 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"` + ValueUint256 uint256.Int `cbor:"V,omitempty" json:"VI,omitempty"` } type Function struct { @@ -163,6 +172,8 @@ func check_valid_type(name string) Vtype { return Uint64 case "string": return String + case "uint256": + return Uint256 } return Invalid } @@ -296,6 +307,9 @@ func runSmartContract_internal(SC *SmartContract, EntryPoint string, state *Shar dvm.f = function_call dvm.Locals = map[string]Variable{} + re := regexp.MustCompile(`^.*\.`) + dvm.Prefix = re.FindString(EntryPoint) + dvm.State = state // set state to execute current function // parse parameters, rename them, make them available as local variables @@ -313,6 +327,21 @@ func runSmartContract_internal(SC *SmartContract, EntryPoint string, state *Shar if variable.ValueUint64, err = strconv.ParseUint(value.(string), 0, 64); err != nil { return } + case Uint256: + t := new(big.Int) + var t_valid bool + + if t, t_valid = t.SetString(value.(string), 0); t_valid { + fb, fb_overflow := uint256.FromBig(t) + if fb_overflow { + err = fmt.Errorf("Argument \"%s\" overflow", p.Name) + return + } + variable.ValueUint256 = *fb + } else { + err = fmt.Errorf("Argument \"%s\" cannot be parsed", p.Name) + return + } case String: variable.ValueString = value.(string) @@ -464,6 +493,7 @@ type DVM_Interpreter struct { IP uint64 // current line number ReturnValue Variable // Result of current function call Locals map[string]Variable // all local variables + Prefix string // current recursive imported contract prefix Chain_inputs *Blockchain_Input // all blockchain info is available here @@ -542,6 +572,8 @@ func (i *DVM_Interpreter) interpret_SmartContract() (err error) { 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], "IMPORT"): + newIP, err = i.interpret_IMPORT(line[1:]) case strings.EqualFold(line[0], "GOTO"): newIP, err = i.interpret_GOTO(line[1:]) case strings.EqualFold(line[0], "IF"): @@ -608,6 +640,8 @@ func (dvm *DVM_Interpreter) interpret_PRINT(args []string) (newIP uint64, err er params = append(params, variable.ValueUint64) case String: params = append(params, variable.ValueString) + case Uint256: + params = append(params, &variable.ValueUint256) default: panic("Unhandled data_type") @@ -655,6 +689,8 @@ func (dvm *DVM_Interpreter) interpret_DIM(line []string) (newIP uint64, err erro 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: ""} + case Uint256: + dvm.Locals[line[i]] = Variable{Name: line[i], Type: Uint256, ValueUint256: *uint256.NewInt(0)} default: panic("Unhandled data_type") @@ -667,6 +703,49 @@ func (dvm *DVM_Interpreter) interpret_DIM(line []string) (newIP uint64, err erro return } +// process IMPORT line +func (dvm *DVM_Interpreter) interpret_IMPORT(line []string) (newIP uint64, err error) { + if len(line) <= 2 || !strings.EqualFold(line[1], "from") { + return 0, fmt.Errorf("Invalid IMPORT syntax") + } + + expr, err := parser.ParseExpr(strings.Join(line[2:], " ")) + if err != nil { + return + } + + scid := dvm.eval(expr) + + if _, ok := scid.(string); !ok { + panic("asset must be type string") + } + if len(scid.(string)) != 32 { + panic("asset must be 32 byte length") + } + + var asset crypto.Hash + copy(asset[:], ([]byte(scid.(string)))) + + code := dvm.SCLoad(asset, convertdatatovariable("C")) + + if len(code.(string)) < 1 { + panic("cannot load asseet's code") + } + + sc, _, err := ParseSmartContract(code.(string)) + + if err != nil { + panic("cannot parse asseet's code") + } + + for k, v := range sc.Functions { + v.Name = dvm.Prefix + line[0] + "." + k + dvm.SC.Functions[v.Name] = v + } + + return +} + // process LET statement func (dvm *DVM_Interpreter) interpret_LET(line []string) (newIP uint64, err error) { @@ -692,9 +771,17 @@ func (dvm *DVM_Interpreter) interpret_LET(line []string) (newIP uint64, err erro //fmt.Printf(" %+v \n", dvm.Locals[line[0]]) switch result.Type { case Uint64: + if fmt.Sprintf("%T", expr_result) != "uint64" { + expr_result = dvm.castToUint64(expr_result) + } result.ValueUint64 = expr_result.(uint64) case String: result.ValueString = expr_result.(string) + case Uint256: + if fmt.Sprintf("%T", expr_result) == "uint64" { + expr_result = dvm.castToUint256(expr_result) + } + result.ValueUint256 = *expr_result.(*uint256.Int) default: panic("Unhandled data_type") @@ -776,7 +863,7 @@ func (dvm *DVM_Interpreter) interpret_IF(line []string) (newIP uint64, err error return } - expr_result := dvm.eval(expr) + expr_result := dvm.castToUint64(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 { @@ -819,13 +906,21 @@ func (dvm *DVM_Interpreter) interpret_RETURN(line []string) (newIP uint64, err e } expr_result := dvm.eval(expr) - //fmt.Printf("expression %+v %T\n", expr_result, expr_result) switch dvm.ReturnValue.Type { case Uint64: + if fmt.Sprintf("%T", expr_result) != "uint64" { + expr_result = dvm.castToUint64(expr_result) + } dvm.ReturnValue.ValueUint64 = expr_result.(uint64) case String: dvm.ReturnValue.ValueString = expr_result.(string) + case Uint256: + if fmt.Sprintf("%T", expr_result) == "uint64" { + expr_result = dvm.castToUint256(expr_result) + } + + dvm.ReturnValue.ValueUint256 = *expr_result.(*uint256.Int) default: panic("unexpected data type") @@ -840,14 +935,46 @@ func (dvm *DVM_Interpreter) interpret_RETURN(line []string) (newIP uint64, err e // only returns identifiers func (dvm *DVM_Interpreter) eval_identifier(exp ast.Expr) string { - switch exp := exp.(type) { + switch e2 := exp.(type) { + case *ast.SelectorExpr: + return dvm.eval_identifier(e2.X) + "." + e2.Sel.Name case *ast.Ident: // it's a variable, - return exp.Name + return e2.Name default: panic("expecting identifier") } } +func (dvm *DVM_Interpreter) castToUint64(x interface{}) interface{} { + switch x := x.(type) { + case uint64: + return x + case *uint256.Int: + return x.Uint64() + case string: + panic("unexpected data type") + } + + panic("We should never reach here while evaluating expressions") + + return 0 +} + +func (dvm *DVM_Interpreter) castToUint256(x interface{}) interface{} { + switch x := x.(type) { + case uint64: + return uint256.NewInt(x) + case *uint256.Int: + return x + case string: + panic("unexpected data type") + } + + panic("We should never reach here while evaluating expressions") + + return 0 +} + func (dvm *DVM_Interpreter) eval(exp ast.Expr) interface{} { dvm.State.Monitor_ops++ // maintain counter @@ -864,12 +991,23 @@ func (dvm *DVM_Interpreter) eval(exp ast.Expr) interface{} { 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)) + x := dvm.eval(exp.X) + switch x := x.(type) { + case uint64: + return ^x + case *uint256.Int: + return x.Not(x) + case string: + panic("unexpected data type") + } + case token.NOT: x := dvm.eval(exp.X) switch x := x.(type) { case uint64: return ^x + case *uint256.Int: + return x.Not(x) case string: if IsZero(x) == 1 { return uint64(1) @@ -893,6 +1031,9 @@ func (dvm *DVM_Interpreter) eval(exp ast.Expr) interface{} { return dvm.Locals[exp.Name].ValueUint64 case String: return dvm.Locals[exp.Name].ValueString + case Uint256: + z := dvm.Locals[exp.Name].ValueUint256 + return &z default: panic("unexpected data type") } @@ -900,8 +1041,7 @@ func (dvm *DVM_Interpreter) eval(exp ast.Expr) interface{} { // 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) + func_name := dvm.Prefix + dvm.eval_identifier(exp.Fun) // if call is internal // @@ -913,17 +1053,28 @@ func (dvm *DVM_Interpreter) eval(exp ast.Expr) interface{} { 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 { + expr_result := dvm.eval(exp.Args[i]) + switch p.Type { case Uint64: - arguments[p.Name] = fmt.Sprintf("%d", dvm.eval(exp.Args[i]).(uint64)) + if fmt.Sprintf("%T", expr_result) != "uint64" { + expr_result = dvm.castToUint64(expr_result) + } + arguments[p.Name] = fmt.Sprintf("%d", expr_result.(uint64)) case String: - arguments[p.Name] = dvm.eval(exp.Args[i]).(string) + arguments[p.Name] = expr_result.(string) + case Uint256: + if fmt.Sprintf("%T", expr_result) == "uint64" { + expr_result = dvm.castToUint256(expr_result) + } + arguments[p.Name] = expr_result.(*uint256.Int).String() } } @@ -939,6 +1090,8 @@ func (dvm *DVM_Interpreter) eval(exp ast.Expr) interface{} { return result.ValueString //default: // panic(fmt.Sprintf("unexpected data type %T", function_call.ReturnValue.Type)) + case Uint256: + return &result.ValueUint256 } return nil @@ -980,6 +1133,10 @@ func IsZero(value interface{}) uint64 { if v == "" { return 1 } + case *uint256.Int: + if v.IsZero() { + return 1 + } default: panic("IsZero not being handled") @@ -1003,25 +1160,35 @@ func (dvm *DVM_Interpreter) evalBinaryExpr(exp *ast.BinaryExpr) interface{} { return left.(string) + fmt.Sprintf("%d", right) } + if fmt.Sprintf("%T", left) == "uint64" && fmt.Sprintf("%T", right) == "*uint256.Int" { + left = dvm.castToUint256(left) + } + + if fmt.Sprintf("%T", left) == "*uint256.Int" && fmt.Sprintf("%T", right) == "uint64" { + right = dvm.castToUint256(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) - } + if (fmt.Sprintf("%T", left) != "*uint256.Int") { + 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) + 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) } - return uint64(0) } // handle string operands @@ -1050,59 +1217,141 @@ func (dvm *DVM_Interpreter) evalBinaryExpr(exp *ast.BinaryExpr) interface{} { } } - 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) + if (fmt.Sprintf("%T", left) == "uint64") { + 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") } - case token.GTR: - if left_uint64 > right_uint64 { - return uint64(1) + return uint64(0) + } else { + // Uint256 + x := left.(*uint256.Int) + y := right.(*uint256.Int) + z := uint256.NewInt(0) + + switch exp.Op { + case token.LAND: + if (IsZero(left) == 0) && (IsZero(right) == 0) { // both sides should be set + return uint256.NewInt(1) + } + return z + case token.LOR: + if (IsZero(left) == 0) || (IsZero(right) == 0) { + return uint256.NewInt(1) + } + return z + case token.ADD: + z.Add(x, y) + return z + case token.SUB: + z.Sub(x, y) + return z + case token.MUL: + z.Mul(x, y) + return z + case token.QUO: + z.Div(x, y) + return z + case token.REM: + z.Mod(x, y) + return z + + //bitwise ops + case token.AND: + z.And(x, y) + return z + case token.OR: + z.Or(x, y) + return z + case token.XOR: + z.Xor(x, y) + return z + case token.SHL: + y := uint(y.Uint64()) + z.Lsh(x, y) + return z + case token.SHR: + y := uint(y.Uint64()) + z.Rsh(x, y) + return z + + case token.EQL: + if x.Eq(y) { + return uint256.NewInt(1) + } + case token.NEQ: + if !x.Eq(y) { + return uint256.NewInt(1) + } + case token.LEQ: + if x.Lt(y) || x.Eq(y) { + return uint256.NewInt(1) + } + case token.GEQ: + if x.Gt(y) || x.Eq(y) { + return uint256.NewInt(1) + } + case token.LSS: + if x.Lt(y) { + return uint256.NewInt(1) + } + case token.GTR: + if x.Gt(y) { + return uint256.NewInt(1) + } + default: + panic("This operation cannot be handled") } - default: - panic("This operation cannot be handled") + return uint256.NewInt(0) } - return uint64(0) } diff --git a/dvm/dvm_execution_test.go b/dvm/dvm_execution_test.go index cbd2718f..a6a61cc6 100644 --- a/dvm/dvm_execution_test.go +++ b/dvm/dvm_execution_test.go @@ -19,6 +19,7 @@ package dvm import "fmt" import "reflect" import "testing" +import "github.com/holiman/uint256" //import "github.com/deroproject/derosuite/crypto" @@ -869,6 +870,694 @@ var execution_tests_if = []struct { fmt.Errorf("dummy"), Variable{Type: Uint64, ValueUint64: uint64(2)}, }, + + // test all arithmetic operations for uint256 + { + "valid function testing substraction ", + `Function TestRun(a1 Uint256, a2 Uint256) Uint256 + 10 dim s1, s2 as Uint64 + 20 + 30 return a1 - a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "12", "a2": "4"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(8)}, + }, { + "valid function testing multiplication ", + `Function TestRun(a1 Uint256, a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 20 + 30 return a1 * a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "4"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(8)}, + }, { + "valid function testing division ", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 20 + 30 return a1 / a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "25", "a2": "3"}, // it is 25 to confirm we are doing integer division + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(8)}, + }, { + "valid function testing modulus ", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 20 + 30 return a1 % a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "35", "a2": "9"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(8)}, + }, { + "valid function testing SHL ", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 20 + 30 return a1 << a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "4", "a2": "1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(8)}, + }, { + "valid function testing SHR ", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 20 + 30 return a1 >> a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "32", "a2": "2"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(8)}, + }, { + "valid function testing OR ", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 20 + 30 return a1 | a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "8", "a2": "8"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(8)}, + }, { + "valid function testing AND ", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 20 + 30 return a1 & a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "9", "a2": "10"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(8)}, + }, { + "valid function testing NOT ", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 20 + 30 return !a1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "9", "a2": "10"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(0).Not(uint256.NewInt(9))}, + }, { + "valid function testing ^XOR ", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 20 + 30 return ^a1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "9", "a2": "10"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(0).Not(uint256.NewInt(9))}, + }, { + "valid function testing XOR ", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 20 + 30 return a1 ^ a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "60", "a2": "13"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(49)}, + }, { + "valid function testing || ", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 20 + 30 return a1 || a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "0", "a2": "1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(1)}, + }, { + "valid function testing && 1 ", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 20 + 30 return a1 && a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "0", "a2": "1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(0)}, + }, { + "valid function testing && 2", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 20 + 30 return (a1 && a2) + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(1)}, + }, + + { + "valid function testing && 3", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 20 + 30 return 0 || (a1 && a2) + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(1)}, + }, + { + "valid function testing LET ", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 20 LET s1 = a1 + a2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(2)}, + }, + { + "valid function testing IF THEN form1 fallthrough", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 15 if a1 == 2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(2)}, + }, + { + "valid function testing IF THEN form1 THEN case", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 15 if a1 == 2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(0)}, + }, + { + "valid function testing IF THEN ELSE form1 THEN case", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 15 if a1 == 2 then GOTO 30 ELSE GOTO 20 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(0)}, + }, { + "valid function testing IF THEN ELSE form1 ELSE case", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 15 if a1 == 2 then GOTO 30 ELSE GOTO 20 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "77", "a2": "1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(2)}, + }, + { + "valid function testing != success ", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 13 LET s1 = 99 + 15 if a1 != 3 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(99)}, + }, + { + "valid function testing != failed", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 13 LET s1 = 99 + 15 if a1 != 3 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "3", "a2": "1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(2)}, + }, + + { + "valid function testing <> ", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 13 LET s1 = 99 + 15 if a1 <> 3 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(99)}, + }, { + "invalid operator testing = ", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 13 LET s1 = 99 + 15 if a1 = 3 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(99)}, + }, + { + "valid function testing > success ", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 13 LET s1 = 99 + 15 if a1 > 1 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(99)}, + }, { + "valid function testing > failed ", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 13 LET s1 = 99 + 15 if 1 > a1 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(2)}, + }, { + "valid function testing >= success", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 13 LET s1 = 99 + 15 if a1 >= 2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(99)}, + }, { + "valid function testing >= failed", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 13 LET s1 = 99 + 15 if a1 >= 2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(2)}, + }, { + "valid function testing < success ", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 13 LET s1 = 99 + 15 if a1 < 3 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(99)}, + }, { + "valid function testing < failed ", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 13 LET s1 = 99 + 15 if a1 < 3 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "5", "a2": "1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(2)}, + }, { + "valid function testing <= success ", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 13 LET s1 = 99 + 15 if a1 <= 2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(99)}, + }, { + "valid function testing <= success ", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 13 LET s1 = 99 + 15 if a1 <= 2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "4", "a2": "1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(2)}, + }, { + "valid function testing string == success", + `Function TestRun(a1 String,a2 String) Uint256 + 10 dim s1, s2 as Uint256 + 13 LET s1 = 99 + 15 if a1 == a2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "asdf", "a2": "asdf"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(99)}, + }, { + "valid function testing string == success", + `Function TestRun(a1 String,a2 String) Uint256 + 10 dim s1, s2 as Uint256 + 13 LET s1 = 99 + 15 if a1 == "asdf" then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "asdf", "a2": "asdf"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(99)}, + }, { + "valid function testing string == failed", + `Function TestRun(a1 String,a2 String) Uint256 + 10 dim s1, s2 as Uint256 + 13 LET s1 = 99 + 15 if a1 == a2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "asdf", "a2": "asdf1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(2)}, + }, { + "valid function testing !string success ", + `Function TestRun(a1 String,a2 String) Uint256 + 10 dim s1, s2 as Uint256 + 13 LET s1 = 99 + 15 if !a1 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "", "a2": "asdf1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(99)}, + }, { + "valid function testing !string fail ", + `Function TestRun(a1 String,a2 String) Uint256 + 10 dim s1, s2 as Uint256 + 13 LET s1 = 99 + 15 if !a1 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "a1", "a2": "asdf1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(2)}, + }, { + "valid function testing string != ", + `Function TestRun(a1 String,a2 String) Uint256 + 10 dim s1, s2 as Uint256 + 13 LET s1 = 99 + 15 if a1 != a2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "asdf", "a2": "asdfz"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(99)}, + }, { + "valid function testing LOR ", + `Function TestRun(a1 String,a2 String) Uint256 + 10 dim s1, s2 as Uint256 + 13 LET s1 = 99 + 15 if a1 != a2 || a1 != a2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "asdf", "a2": "asdf"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(2)}, + }, + { + "invalid function testing comparision of uint256 /string ", + `Function TestRun(a1 String,a2 String) Uint256 + 10 dim s1, s2 as Uint256 + 13 LET s1 = 99 + 15 if a1 != s1 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "asdf", "a2": "asdfz"}, + fmt.Errorf("dummy"), + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(99)}, + }, + + { + "valid function arbitrary function evaluation", + `Function TestRun(a1 Uint256,a2 Uint256) Uint256 + 10 dim s1, s2 as Uint256 + 15 fact_recursive(10) + 20 LET s1 = fact_recursive(10) + 30 return s1 + End Function + Function fact_recursive(s Uint256) Uint256 + 10 IF s == 1 THEN GOTO 20 ELSE GOTO 30 + 20 RETURN 1 + 30 RETURN s * fact_recursive(s -1) + End Function + `, + "TestRun", + map[string]interface{}{"a1": "77", "a2": "1"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(3628800)}, + }, + + { + "Invalid function with 2 string params substractions", + `Function TestRun(a1 String,a2 String) String + 10 dim s1, s2 as Uint256 + 20 + 30 return a1 - a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "4", "a2": "4"}, + fmt.Errorf("dummy"), + Variable{Type: String, ValueString: "44"}, + }, + + // mixed type testing + { + "invalid function testing comparision of uint256 / uint64 ", + `Function TestRun(a1 Uint64, a2 Uint256) Uint256 + 10 DIM s1 AS uint256 + 13 LET s1 = 99 + 15 if a1 == a2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "101", "a2": "101"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(99)}, + }, + { + "invalid function testing addition of uint256 / uint64 ", + `Function TestRun(a1 Uint64, a2 Uint256) Uint256 + 10 DIM s1, s2 AS uint256 + 13 LET s1 = 99 + 15 LET s2 = a1 + a2 + 30 return s2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "101", "a2": "101"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(202)}, + }, + { + "invalid function return of different type uint256 / uint64 ", + `Function TestRun(a1 Uint64, a2 Uint256) Uint256 + 10 DIM s1, s2 AS uint64 + 13 LET s1 = 99 + 15 LET s2 = a1 + a2 + 30 return s2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "101", "a2": "101"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(202)}, + }, + { + "invalid function return of different type uint64 / uint256", + `Function TestRun(a1 Uint64, a2 Uint256) Uint64 + 10 DIM s1, s2 AS uint256 + 13 LET s1 = 99 + 15 LET s2 = a1 + a2 + 30 return s2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "101", "a2": "101"}, + nil, + Variable{Type: Uint64, ValueUint64: uint64(202)}, + }, + { + "invalid function testing comparison of uint256 / uint64 ", + `Function TestRun(a1 Uint64, a2 Uint256) Uint256 + 10 DIM s1, s2 AS uint64 + 13 LET s1 = 99 + 15 IF a2 > s1 THEN GOTO 30 + 20 RETURN a2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "101", "a2": "101"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(99)}, + }, + { + "invalid function testing trucation of uint256", + `Function TestRun(a1 Uint64, a2 Uint256) Uint64 + 10 DIM s1 AS uint256 + 15 let s1 = a1 * a2 + 20 RETURN s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "8589934592", "a2": "8589934593"}, + nil, + Variable{Type: Uint64, ValueUint64: uint256.NewInt(0).Mul(uint256.NewInt(8589934592), uint256.NewInt(8589934593)).Uint64()}, + }, + { + "invalid function testing trucation of uint256", + `Function TestRun(a1 Uint64, a2 Uint256) Uint256 + 10 DIM s1 AS uint64 + 15 let s1 = a1 * a2 + 20 RETURN s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "0xffffffffffffffff", "a2": "8589934593"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(uint256.NewInt(0).Mul(uint256.NewInt(0xffffffffffffffff), uint256.NewInt(8589934593)).Uint64())}, + }, + { + "invalid function testing avoid truncation of intermediate product", + `Function TestRun(a1 Uint64, a2 Uint64, a3 Uint64) Uint64 + 10 DIM s1 AS uint64 + 11 DIM s2 AS uint256 + 12 LET s2 = a2 + 15 RETURN a1 * s2 / a3 + 20 RETURN s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "0xffffffffffffffff", "a2": "4294967287", "a3": "4294967288"}, + nil, + Variable{Type: Uint64, ValueUint64: uint256.NewInt(0).Div(uint256.NewInt(0).Mul(uint256.NewInt(0xffffffffffffffff), uint256.NewInt(4294967287)), uint256.NewInt(4294967288)).Uint64()}, + }, } // run the test diff --git a/dvm/dvm_functions.go b/dvm/dvm_functions.go index 010e1368..c6d96377 100644 --- a/dvm/dvm_functions.go +++ b/dvm/dvm_functions.go @@ -22,11 +22,14 @@ import "strconv" import "strings" import "crypto/sha256" import "encoding/hex" +import "math/big" import "golang.org/x/crypto/sha3" import "github.com/blang/semver/v4" import "github.com/deroproject/derohe/rpc" import "github.com/deroproject/derohe/cryptography/crypto" +import "github.com/deroproject/derohe/globals" +import "github.com/holiman/uint256" // this files defines external functions which can be called in DVM // for example to load and store data from the blockchain and other basic functions @@ -40,6 +43,7 @@ import "github.com/deroproject/derohe/cryptography/crypto" // also, more investigation is required to enable predetermined external oracles type DVM_FUNCTION_PTR_UINT64 func(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) +type DVM_FUNCTION_PTR_UINT256 func(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result *uint256.Int) type DVM_FUNCTION_PTR_STRING func(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result string) type DVM_FUNCTION_PTR_ANY func(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) @@ -50,13 +54,16 @@ type func_data struct { ComputeCost int64 StorageCost int64 PtrU DVM_FUNCTION_PTR_UINT64 + PtrL DVM_FUNCTION_PTR_UINT256 // long PtrS DVM_FUNCTION_PTR_STRING Ptr DVM_FUNCTION_PTR_ANY } func init() { func_table["version"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 1000, StorageCost: 0, PtrU: dvm_version}} + func_table["scload"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 5000, StorageCost: 0, Ptr: dvm_scload}} func_table["load"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 5000, StorageCost: 0, Ptr: dvm_load}} + func_table["scexists"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 5000, StorageCost: 0, PtrU: dvm_scexists}} func_table["exists"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 5000, StorageCost: 0, PtrU: dvm_exists}} func_table["store"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 10000, StorageCost: 0, PtrU: dvm_store}} func_table["delete"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 3000, StorageCost: 0, PtrU: dvm_delete}} @@ -80,17 +87,25 @@ func init() { func_table["send_asset_to_address"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 90000, StorageCost: 0, PtrU: dvm_send_asset_to_address}} func_table["derovalue"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 10000, StorageCost: 0, PtrU: dvm_derovalue}} func_table["assetvalue"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 10000, StorageCost: 0, PtrU: dvm_assetvalue}} + func_table["uint64"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 100, StorageCost: 0, PtrU: dvm_uint64}} + func_table["uint256"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 100, StorageCost: 0, PtrL: dvm_uint256}} + func_table["ismainnet"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 100, StorageCost: 0, PtrU: dvm_ismainnet}} func_table["atoi"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 5000, StorageCost: 0, PtrU: dvm_atoi}} func_table["itoa"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 5000, StorageCost: 0, PtrS: dvm_itoa}} + func_table["sqrt"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 10000, StorageCost: 0, PtrL: dvm_sqrt}} + func_table["pow"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 5000, StorageCost: 0, PtrL: dvm_pow}} func_table["sha256"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 25000, StorageCost: 0, PtrS: dvm_sha256}} func_table["sha3256"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 25000, StorageCost: 0, PtrS: dvm_sha3256}} func_table["keccak256"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 25000, StorageCost: 0, PtrS: dvm_keccak256}} func_table["hex"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 10000, StorageCost: 0, PtrS: dvm_hex}} func_table["hexdecode"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 10000, StorageCost: 0, PtrS: dvm_hexdecode}} - func_table["min"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 5000, StorageCost: 0, PtrU: dvm_min}} - func_table["max"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 5000, StorageCost: 0, PtrU: dvm_max}} + func_table["min"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 5000, StorageCost: 0, Ptr: dvm_min}} + func_table["max"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 5000, StorageCost: 0, Ptr: dvm_max}} func_table["strlen"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 20000, StorageCost: 0, PtrU: dvm_strlen}} func_table["substr"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 20000, StorageCost: 0, PtrS: dvm_substr}} + func_table["tolower"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 10000, StorageCost: 0, PtrS: dvm_tolower}} + func_table["toupper"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 10000, StorageCost: 0, PtrS: dvm_toupper}} + func_table["subfield"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 10000, StorageCost: 0, PtrS: dvm_subfield}} func_table["panic"] = []func_data{func_data{Range: semver.MustParseRange(">=0.0.0"), ComputeCost: 10000, StorageCost: 0, PtrU: dvm_panic}} } @@ -103,6 +118,8 @@ func (dvm *DVM_Interpreter) Handle_Internal_Function(expr *ast.CallExpr, func_na dvm.State.ConsumeGas(f.ComputeCost) if f.PtrU != nil { return f.PtrU(dvm, expr) + } else if f.PtrL != nil { + return f.PtrL(dvm, expr) } else if f.PtrS != nil { return f.PtrS(dvm, expr) } else { @@ -119,12 +136,18 @@ func (dvm *DVM_Interpreter) Handle_Internal_Function(expr *ast.CallExpr, func_na // the load/store functions are sandboxed and thus cannot affect any other SC storage // loads a variable from store func (dvm *DVM_Interpreter) Load(key Variable) interface{} { + return dvm.SCLoad(dvm.State.Chain_inputs.SCID, key) +} + +func (dvm *DVM_Interpreter) SCLoad(scid crypto.Hash, key Variable) interface{} { var found uint64 - result := dvm.State.Store.Load(DataKey{SCID: dvm.State.Chain_inputs.SCID, Key: key}, &found) + result := dvm.State.Store.Load(DataKey{SCID: scid, Key: key}, &found) switch result.Type { case Uint64: return result.ValueUint64 + case Uint256: + return &result.ValueUint256 case String: return result.ValueString @@ -135,8 +158,12 @@ func (dvm *DVM_Interpreter) Load(key Variable) interface{} { // whether a variable exists in store or not func (dvm *DVM_Interpreter) Exists(key Variable) uint64 { + return dvm.SCExists(dvm.State.Chain_inputs.SCID, key) +} + +func (dvm *DVM_Interpreter) SCExists(scid crypto.Hash, key Variable) uint64 { var found uint64 - dvm.State.Store.Load(DataKey{SCID: dvm.State.Chain_inputs.SCID, Key: key}, &found) + dvm.State.Store.Load(DataKey{SCID: scid, Key: key}, &found) return found } @@ -153,6 +180,8 @@ func convertdatatovariable(datai interface{}) Variable { switch k := datai.(type) { case uint64: return Variable{Type: Uint64, ValueUint64: k} + case *uint256.Int: + return Variable{Type: Uint256, ValueUint256: *k} case string: return Variable{Type: String, ValueString: k} default: @@ -178,11 +207,46 @@ func dvm_version(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result return true, uint64(1) } +func dvm_scload(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { + checkargscount(2, len(expr.Args)) // check number of arguments + scid := dvm.eval(expr.Args[0]) + key := dvm.eval(expr.Args[1]) + + if _, ok := scid.(string); !ok { + panic("asset must be valid string") + } + if len(scid.(string)) != 32 { + panic("asset must be valid string of 32 byte length") + } + + var asset crypto.Hash + copy(asset[:], ([]byte(scid.(string)))) + + return true, dvm.SCLoad(asset, convertdatatovariable(key)) +} + +func dvm_scexists(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + checkargscount(2, len(expr.Args)) // check number of arguments + scid := dvm.eval(expr.Args[0]) + key := dvm.eval(expr.Args[1]) + + if _, ok := scid.(string); !ok { + panic("asset must be valid string") + } + if len(scid.(string)) != 32 { + panic("asset must be valid string of 32 byte length") + } + + var asset crypto.Hash + copy(asset[:], ([]byte(scid.(string)))) + + return true, dvm.SCExists(asset, convertdatatovariable(key)) +} + func dvm_load(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { checkargscount(1, len(expr.Args)) // check number of arguments key := dvm.eval(expr.Args[0]) return true, dvm.Load(convertdatatovariable(key)) - } func dvm_exists(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { @@ -226,6 +290,8 @@ func dvm_mapget(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result if v.Type == Uint64 { return true, v.ValueUint64 + } else if v.Type == Uint256 { + return true, &v.ValueUint256 } else if v.Type == String { return true, v.ValueString } else { @@ -460,12 +526,143 @@ func dvm_itoa(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result st asset_eval := dvm.eval(expr.Args[0]) - if _, ok := asset_eval.(uint64); !ok { + switch v := asset_eval.(type) { + case uint64: + result = fmt.Sprintf("%d", v) + case *uint256.Int: + result = v.String() + default: panic("itoa argument must be valid uint64") } - return true, fmt.Sprintf("%d", asset_eval.(uint64)) + return true, result + +} + +func dvm_uint64(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + checkargscount(1, len(expr.Args)) // check number of arguments + + asset_eval := dvm.eval(expr.Args[0]) + + var z uint64 + + switch v := asset_eval.(type) { + case uint64: + z = v + case *uint256.Int: + z = v.Uint64() + case string: + i, err := strconv.ParseUint(v, 0, 64) + if err != nil { + i = 0 + } + z = i + } + + return true, z +} + +func dvm_uint256(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result *uint256.Int) { + checkargscount(1, len(expr.Args)) // check number of arguments + + asset_eval := dvm.eval(expr.Args[0]) + + z := uint256.NewInt(0) + + switch v := asset_eval.(type) { + case uint64: + z.SetUint64(v) + case *uint256.Int: + z.Set(v) + case string: + t := new(big.Int) + var t_valid bool + + if t, t_valid = t.SetString(v, 0); t_valid { + z, _ = uint256.FromBig(t); + } + } + + return true, z +} + +func dvm_ismainnet(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { + checkargscount(0, len(expr.Args)) // check number of arguments + + mainnet := uint64(0) + if globals.IsMainnet() { + mainnet = uint64(1) + } + + return true, mainnet +} + +func dvm_sqrt(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result *uint256.Int) { + checkargscount(1, len(expr.Args)) // check number of arguments + + e := dvm.eval(expr.Args[0]) + + switch v := e.(type) { + case uint64: + e = dvm.castToUint256(v) + case string: + panic("sqrt argument must be valid int") + } + + y := e.(*uint256.Int) + x := uint256.NewInt(0) + z := uint256.NewInt(1) + tmp := uint256.NewInt(0) + + if y.IsZero() { + return true, y + } + if y.Eq(z) { + return true, z + } + + z.SetUint64(0) + if y.GtUint64(3) { + z = y.Clone() + x.Rsh(y, 1) + x.AddUint64(x, 1) + } + + for z.Gt(x) { + z = x.Clone() + tmp.Div(y, x) + tmp.Add(tmp, x) + x.Rsh(tmp, 1) + } + + return true, z +} + +func dvm_pow(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result *uint256.Int) { + checkargscount(2, len(expr.Args)) // check number of arguments + + e1 := dvm.eval(expr.Args[0]) + e2 := dvm.eval(expr.Args[1]) + + switch v1 := e1.(type) { + case uint64: + e1 = dvm.castToUint256(v1) + case string: + panic("pow arguments must be valid int") + } + + switch v2 := e2.(type) { + case uint64: + e2 = dvm.castToUint256(v2) + case string: + panic("pow arguments must be valid int") + } + + x := e1.(*uint256.Int) + y := e2.(*uint256.Int) + + return true, x.Exp(x, y) } func dvm_atoi(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { @@ -528,6 +725,50 @@ func dvm_substr(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result return true, substr(input_eval.(string), offset_eval.(uint64), length_eval.(uint64)) } +func dvm_tolower(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result string) { + checkargscount(1, len(expr.Args)) // check number of arguments + input_eval := dvm.eval(expr.Args[0]) + if _, ok := input_eval.(string); !ok { + panic("input argument must be valid string") + } + + return true, strings.ToLower(input_eval.(string)) +} + +func dvm_toupper(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result string) { + checkargscount(1, len(expr.Args)) // check number of arguments + input_eval := dvm.eval(expr.Args[0]) + if _, ok := input_eval.(string); !ok { + panic("input argument must be valid string") + } + + return true, strings.ToUpper(input_eval.(string)) +} + +func dvm_subfield(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result string) { + checkargscount(3, len(expr.Args)) // check number of arguments + input_eval := dvm.eval(expr.Args[0]) + if _, ok := input_eval.(string); !ok { + panic("input argument must be valid string") + } + separator_eval := dvm.eval(expr.Args[1]) + if _, ok := separator_eval.(string); !ok { + panic("input argument must be valid string") + } + field_eval := dvm.eval(expr.Args[2]) + if _, ok := field_eval.(uint64); !ok { + panic("input argument must be valid uint64") + } + + res := "" + s := strings.Split(input_eval.(string), separator_eval.(string)) + if field_eval.(uint64) < uint64(len(s)) { + res = s[field_eval.(uint64)] + } + + return true, res +} + func dvm_sha256(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result string) { checkargscount(1, len(expr.Args)) // check number of arguments input_eval := dvm.eval(expr.Args[0]) @@ -585,41 +826,46 @@ func dvm_hexdecode(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, resu } } -func dvm_min(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { +func isUint(exp interface{}) (bool) { + return (fmt.Sprintf("%T", exp) == "uint64" || fmt.Sprintf("%T", exp) == "*uint256.Int") +} + +func dvm_min(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { checkargscount(2, len(expr.Args)) // check number of arguments a1 := dvm.eval(expr.Args[0]) - if _, ok := a1.(uint64); !ok { - panic("input argument must be uint64") - } - a2 := dvm.eval(expr.Args[1]) - if _, ok := a1.(uint64); !ok { - panic("input argument must be uint64") + + if !isUint(a1) || !isUint(a2) { + panic("input arguments must be integers") } - if a1.(uint64) < a2.(uint64) { - return true, a1.(uint64) + a1 = dvm.castToUint256(a1) + a2 = dvm.castToUint256(a2) + + if a1.(*uint256.Int).Lt(a2.(*uint256.Int)) { + return true, a1 } - return true, a2.(uint64) + return true, a2 } -func dvm_max(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { +func dvm_max(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result interface{}) { checkargscount(2, len(expr.Args)) // check number of arguments - a1 := dvm.eval(expr.Args[0]) - if _, ok := a1.(uint64); !ok { - panic("input argument must be uint64") - } + a1 := dvm.eval(expr.Args[0]) a2 := dvm.eval(expr.Args[1]) - if _, ok := a1.(uint64); !ok { - panic("input argument must be uint64") + + if !isUint(a1) || !isUint(a2) { + panic("input arguments must be integers") } - if a1.(uint64) > a2.(uint64) { - return true, a1.(uint64) + a1 = dvm.castToUint256(a1) + a2 = dvm.castToUint256(a2) + + if a1.(*uint256.Int).Gt(a2.(*uint256.Int)) { + return true, a1 } - return true, a2.(uint64) + return true, a2 } func dvm_panic(dvm *DVM_Interpreter, expr *ast.CallExpr) (handled bool, result uint64) { diff --git a/dvm/dvm_functions_test.go b/dvm/dvm_functions_test.go index 41d20bce..f977bcee 100644 --- a/dvm/dvm_functions_test.go +++ b/dvm/dvm_functions_test.go @@ -22,6 +22,7 @@ import "testing" import "encoding/hex" import "github.com/deroproject/derohe/cryptography/crypto" +import "github.com/holiman/uint256" // ensure 100% coverage of functions execution var execution_tests_functions = []struct { @@ -32,6 +33,188 @@ var execution_tests_functions = []struct { Eerr error // execute error result Variable // execution result }{ + { + "valid function testing MAP*() with 256bit key", + `Function TestRun(a1 Uint64, a2 Uint256) Uint64 + 20 DIM v1, v2 AS Uint256 + 30 MAPSTORE(a2, a1) + 40 LET v1 = a2 + 50 IF MAPEXISTS(v1) THEN GOTO 100 + 60 RETURN 1 + 100 IF MAPGET(v1) == a1 THEN GOTO 200 + 110 RETUN 1 + 200 DIM str AS String + 210 LET str = ITOA(v1) + 230 LET v2 = UINT256(str) + 240 IF MAPGET(v2) == a1 THEN GOTO 300 + 250 RETURN 1 + 300 MAPDELETE(v2) + 310 IF !MAPEXISTS(v1) THEN GOTO 500 + 320 RETURN 1 + 500 RETURN 99 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "987654321", "a2": "987654320"}, + nil, + Variable{Type: Uint64, ValueUint64: uint64(99)}, + }, + { + "valid function testing MAP*() with 64bit key", + `Function TestRun(a1 Uint64, a2 Uint256) Uint64 + 20 DIM v1, v2 AS Uint64 + 25 DIM v3 as Uint256 + 30 MAPSTORE(a1, a2) + 40 LET v1 = a1 + 50 IF MAPEXISTS(v1) THEN GOTO 100 + 60 RETURN 1 + 100 IF MAPGET(v1) == a2 THEN GOTO 200 + 110 RETUN 1 + 200 DIM str AS String + 210 LET str = ITOA(a2) + 230 LET v3 = UINT256(str) + 240 IF MAPGET(v1) == v3 THEN GOTO 300 + 250 RETURN 1 + 300 MAPDELETE(v1) + 310 IF !MAPEXISTS(v1) THEN GOTO 500 + 320 RETURN 1 + 500 RETURN 99 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "987654321", "a2": "987654320"}, + nil, + Variable{Type: Uint64, ValueUint64: uint64(99)}, + }, + { + "valid function testing MAX() ", + `Function TestRun(a1 Uint64, a2 Uint256) Uint64 + 10 IF MAX(a1, a2) == a1 THEN GOTO 50 + 20 RETURN 1 + 50 RETURN 99 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "987654321", "a2": "987654320"}, + nil, + Variable{Type: Uint64, ValueUint64: uint64(99)}, + }, + { + "valid function testing MIN() ", + `Function TestRun(a1 Uint64, a2 Uint256) Uint64 + 10 IF MIN(a1, a2) == a2 THEN GOTO 50 + 20 RETURN 1 + 50 RETURN 99 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "987654321", "a2": "987654320"}, + nil, + Variable{Type: Uint64, ValueUint64: uint64(99)}, + }, + { + "valid function testing ITOA() ", + `Function TestRun(a1 Uint64, a2 Uint256) Uint64 + 10 IF ITOA(a1) == "987654321" THEN GOTO 50 + 20 RETURN 1 + 50 IF ITOA(a2) == "0x3ade68b1" THEN GOTO 90 + 60 RETURN 1 + 90 RETURN 99 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "987654321", "a2": "987654321"}, + nil, + Variable{Type: Uint64, ValueUint64: uint64(99)}, + }, + { + "valid function testing UINT64() ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 dim s3, s4 as Uint256 + 30 LET s1 = 987654321 + 40 LET s3 = 987654321 + 50 IF s1 == UINT64(s3) THEN GOTO 100 + 60 return 0 + 100 LET s2 = UINT64("987654321") + 110 IF s2*s2 == s3*UINT64(s3) THEN GOTO 200 + 120 RETURN 1 + 200 RETURN 99 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint64, ValueUint64: uint64(99)}, + }, + { + "valid function testing UINT256() ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 dim s3, s4 as Uint256 + 30 LET s1 = 123456 + 40 LET s3 = UINT256("123456") + 50 IF s1 == s3 THEN GOTO 100 + 60 return 0 + 100 LET s2 = UINT256("0xffffffffffffffff") + 110 IF (UINT256(s1)*s2) == (UINT256(123456) * UINT256("0xffffffffffffffff")) THEN GOTO 200 + 120 RETURN 1 + 200 RETURN 99 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint64, ValueUint64: uint64(99)}, + }, + { + "valid function testing SQRT(Uint64) ", + `Function TestRun(a1 Uint64) Uint64 + 10 RETURN SQRT(a1) + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1046529"}, + nil, + Variable{Type: Uint64, ValueUint64: uint64(1023)}, + }, + { + "valid function testing SQRT(Uint256) ", + `Function TestRun(a1 Uint256) Uint256 + 10 DIM square AS Uint256 + 20 LET square = a1 * a1 + 30 IF SQRT(square) == a1 THEN GOTO 100 + 40 RETURN 1 + 100 RETURN 99 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "109876543210"}, + nil, + Variable{Type: Uint256, ValueUint256: *uint256.NewInt(99)}, + }, + { + "valid function testing POW(Uint64) ", + `Function TestRun(a1 Uint64, a2 Uint64) Uint64 + 10 RETURN POW(a1, a2) + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "33"}, + nil, + Variable{Type: Uint64, ValueUint64: uint64(8589934592)}, + }, + { + "valid function testing POW(Uint256) ", + `Function TestRun(a1 Uint256, a2 Uint256) Uint256 + 10 RETURN POW(a1, a2) + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "255"}, + nil, + Variable{Type: Uint256, ValueUint256: *(uint256.NewInt(0).Exp(uint256.NewInt(2), uint256.NewInt(255)))}, + }, { "valid function testing BLOCK_HEIGHT() ", `Function TestRun(a1 Uint64,a2 Uint64) Uint64 @@ -219,6 +402,56 @@ var execution_tests_functions = []struct { nil, Variable{Type: String, ValueString: string("")}, }, + { + "tolower()", + `Function TestRun(input String) String + 30 return tolower(input) + End Function`, + "TestRun", + map[string]interface{}{"input": string("A0b1C2d3E5f6")}, + nil, + Variable{Type: String, ValueString: string("a0b1c2d3e5f6")}, + }, + { + "toupper()", + `Function TestRun(input String) String + 30 return toupper(input) + End Function`, + "TestRun", + map[string]interface{}{"input": string("A0b1C2d3E5f6")}, + nil, + Variable{Type: String, ValueString: string("A0B1C2D3E5F6")}, + }, + { + "subfield()", + `Function TestRun(input String) String + 30 return subfield(input, ":", 3) + End Function`, + "TestRun", + map[string]interface{}{"input": string("This::is:a:test")}, + nil, + Variable{Type: String, ValueString: string("a")}, + }, + { + "subfield()", + `Function TestRun(input String) String + 30 return subfield(input, ":", 5) + End Function`, + "TestRun", + map[string]interface{}{"input": string("This::is:a:test")}, + nil, + Variable{Type: String, ValueString: string("")}, + }, + { + "subfield()", + `Function TestRun(input String) String + 30 return subfield(input, ":", 1) + End Function`, + "TestRun", + map[string]interface{}{"input": string("This::is:a:test")}, + nil, + Variable{Type: String, ValueString: string("")}, + }, { "mapget()", `Function TestRun(input String) String diff --git a/dvm/dvm_store.go b/dvm/dvm_store.go index 0466172f..04e5f0a9 100644 --- a/dvm/dvm_store.go +++ b/dvm/dvm_store.go @@ -19,6 +19,7 @@ package dvm import "fmt" import "encoding/binary" import "github.com/deroproject/derohe/cryptography/crypto" +import "github.com/holiman/uint256" // this package exports an interface which is used by blockchain to persist/query data @@ -194,6 +195,9 @@ func (v Variable) Length() (length int64) { length += int64(done) + 1 case String: length = int64(len([]byte(v.ValueString)) + 1) + case Uint256: + buf := (&v.ValueUint256).Bytes() + length = int64(len(buf)) + 1 default: panic("unknown variable type not implemented") } @@ -211,6 +215,9 @@ func (v Variable) MarshalBinary() (data []byte, err error) { data = append(data, buf[:done]...) case String: data = append(data, ([]byte(v.ValueString))...) // string + case Uint256: + data = (&v.ValueUint256).Bytes() + default: panic("unknown variable type not implemented2") } @@ -245,6 +252,10 @@ func (v *Variable) UnmarshalBinary(buf []byte) (err error) { v.Type = String v.ValueString = string(buf[:len(buf)-1]) return nil + case Uint256: + v.Type = Uint256 + v.ValueUint256 = *uint256.NewInt(0).SetBytes(buf[:len(buf)-1]) + return nil default: panic("unknown variable type not implemented3") diff --git a/dvm/sc.go b/dvm/sc.go index 977dee4b..81aa07a1 100644 --- a/dvm/sc.go +++ b/dvm/sc.go @@ -28,6 +28,7 @@ import "github.com/deroproject/derohe/config" import "github.com/deroproject/derohe/rpc" import "github.com/deroproject/derohe/globals" import "github.com/deroproject/graviton" +import "github.com/holiman/uint256" //import "github.com/deroproject/derohe/transaction" @@ -88,6 +89,11 @@ type Tree_Wrapper struct { Tree *graviton.Tree Entries map[string][]byte Transfere []TransferExternal + // save for external r/o reference + Id crypto.Hash + CacheL1 map[crypto.Hash]*graviton.Tree + CacheL2 map[crypto.Hash]*graviton.Tree + Ss *graviton.Snapshot } func (t *Tree_Wrapper) Get(key []byte) ([]byte, error) { @@ -98,6 +104,26 @@ func (t *Tree_Wrapper) Get(key []byte) ([]byte, error) { } } +func (t *Tree_Wrapper) GetExternal(scid crypto.Hash, key []byte) (result []byte, err error) { + // check local cache + scTree, ok := t.CacheL1[scid] + + if !ok { + // check prior transactions + if scTree, ok = t.CacheL2[scid]; !ok { + // finally, load from snapshot + if scTree, err = t.Ss.GetTree(string(scid[:])); err == nil { + // store in local cache + t.CacheL1[scid] = scTree + } else { + return + } + } + } + + return scTree.Get(key) +} + func (t *Tree_Wrapper) Put(key []byte, value []byte) error { t.Entries[string(key)] = append([]byte{}, value...) return nil @@ -106,13 +132,13 @@ func (t *Tree_Wrapper) Put(key []byte, value []byte) error { // checks cache and returns a wrapped tree if possible func Wrapped_tree(cache map[crypto.Hash]*graviton.Tree, ss *graviton.Snapshot, id crypto.Hash) *Tree_Wrapper { if cached_tree, ok := cache[id]; ok { // tree is in cache return it - return &Tree_Wrapper{Tree: cached_tree, Entries: map[string][]byte{}} + return &Tree_Wrapper{Tree: cached_tree, Entries: map[string][]byte{}, Id: id, CacheL1: map[crypto.Hash]*graviton.Tree{}, CacheL2: cache, Ss: ss} } if tree, err := ss.GetTree(string(id[:])); err != nil { panic(err) } else { - return &Tree_Wrapper{Tree: tree, Entries: map[string][]byte{}} + return &Tree_Wrapper{Tree: tree, Entries: map[string][]byte{}, Id: id, CacheL1: map[crypto.Hash]*graviton.Tree{}, CacheL2: cache, Ss: ss} } } @@ -220,10 +246,16 @@ func Execute_sc_function(w_sc_tree *Tree_Wrapper, data_tree *Tree_Wrapper, scid switch { case p.Type == Uint64 && p.Name == "value": params[p.Name] = fmt.Sprintf("%d", state.Assets[zerohash]) // overide value + case p.Type == Uint64 && SCDATA.Has(p.Name, rpc.DataUint64): params[p.Name] = fmt.Sprintf("%d", SCDATA.Value(p.Name, rpc.DataUint64).(uint64)) + + case p.Type == Uint256 && SCDATA.Has(p.Name, rpc.DataUint256): + params[p.Name] = SCDATA.Value(p.Name, rpc.DataUint256).(*uint256.Int).String() + case p.Type == String && SCDATA.Has(p.Name, rpc.DataString): params[p.Name] = SCDATA.Value(p.Name, rpc.DataString).(string) + case p.Type == String && SCDATA.Has(p.Name, rpc.DataHash): h := SCDATA.Value(p.Name, rpc.DataHash).(crypto.Hash) params[p.Name] = string(h[:]) @@ -275,7 +307,22 @@ func Execute_sc_function(w_sc_tree *Tree_Wrapper, data_tree *Tree_Wrapper, scid return } - if err == nil && result.Type == Uint64 && result.ValueUint64 == 0 { // confirm the changes + var result_code string + var result_valid bool = false + + switch result.Type { + case Uint64: + result_valid = (result.ValueUint64 == 0) + result_code = fmt.Sprintf("%d", result.ValueUint64) + case Uint256: + result_valid = (result.ValueUint256.IsZero()) + result_code = fmt.Sprintf("%d", result.ValueUint256) + case String: + result_valid = (result.ValueString == "") + result_code = result.ValueString + } + + if (result_valid) { for k, v := range tx_store.RawKeys { StoreSCValue(data_tree, scid, []byte(k), v) @@ -283,7 +330,7 @@ func Execute_sc_function(w_sc_tree *Tree_Wrapper, data_tree *Tree_Wrapper, scid } data_tree.Transfere = append(data_tree.Transfere, tx_store.Transfers[scid].TransferE...) } else { // discard all changes, since we never write to store immediately, they are purged, however we need to return any value associated - err = fmt.Errorf("Discarded knowingly") + err = fmt.Errorf("Discarded knowingly [" + result_code + "]") return } @@ -320,8 +367,15 @@ func ReadSC(w_sc_tree *Tree_Wrapper, data_tree *Tree_Wrapper, scid crypto.Hash) func LoadSCValue(data_tree *Tree_Wrapper, scid crypto.Hash, key []byte) (v Variable, found bool) { //fmt.Printf("loading fromdb %s %s \n", scid, key) + var object_data []byte + var err error + + if scid == data_tree.Id { + object_data, err = data_tree.Get(key[:]) + } else { + object_data, err = data_tree.GetExternal(scid, key[:]) + } - object_data, err := data_tree.Get(key[:]) if err != nil { return v, false } @@ -365,6 +419,8 @@ func ReadSCValue(data_tree *Tree_Wrapper, scid crypto.Hash, key interface{}) (va switch k := key.(type) { case uint64: keybytes = DataKey{Key: Variable{Type: Uint64, ValueUint64: k}}.MarshalBinaryPanic() + case *uint256.Int: + keybytes = DataKey{Key: Variable{Type: Uint64, ValueUint256: *k}}.MarshalBinaryPanic() case string: keybytes = DataKey{Key: Variable{Type: String, ValueString: k}}.MarshalBinaryPanic() //case int64: @@ -379,6 +435,8 @@ func ReadSCValue(data_tree *Tree_Wrapper, scid crypto.Hash, key interface{}) (va switch value_var.Type { case Uint64: value = value_var.ValueUint64 + case Uint256: + value = &value_var.ValueUint256 case String: value = value_var.ValueString default: diff --git a/dvm/simulator.go b/dvm/simulator.go index 702e2e26..6dc49277 100644 --- a/dvm/simulator.go +++ b/dvm/simulator.go @@ -251,8 +251,6 @@ func ErrorRevert(ss *graviton.Snapshot, cache map[crypto.Hash]*graviton.Tree, ba switch scid_asset { case zeroscid: // main dero balance, handle it curbtree = balance_tree - case scid: // this scid balance, handle it - curbtree = cache[scid] default: // any other asset scid var ok bool if curbtree, ok = cache[scid_asset]; !ok { diff --git a/rpc/rpc.go b/rpc/rpc.go index 461fdfa9..2bd53380 100644 --- a/rpc/rpc.go +++ b/rpc/rpc.go @@ -4,9 +4,11 @@ import "fmt" import "time" import "sort" import "encoding/json" +import "math/big" import "github.com/fxamacker/cbor/v2" import "github.com/deroproject/derohe/cryptography/crypto" +import "github.com/holiman/uint256" // this package defines interfaces and necessary glue code Digital Network, it exposes and provides encrypted RPC calls over DERO chain @@ -49,6 +51,7 @@ const ( DataString DataType = "S" DataInt64 = "I" DataUint64 = "U" + DataUint256 = "L" DataFloat64 = "F" DataHash = "H" // a 256 bit hash (basically sha256 of 32 bytes long) DataAddress = "A" // dero address represented in 33 bytes @@ -63,6 +66,8 @@ func (d DataType) String() string { return "int64" case DataUint64: return "uint64" + case DataUint256: + return "uint256" case DataFloat64: return "float64" case DataHash: @@ -95,6 +100,8 @@ func (arg Argument) String() string { return fmt.Sprintf("Name:%s Type:%s Value:'%d'", arg.Name, arg.DataType, arg.Value) case DataUint64: return fmt.Sprintf("Name:%s Type:%s Value:'%d'", arg.Name, arg.DataType, arg.Value) + case DataUint256: + return fmt.Sprintf("Name:%s Type:%s Value:'%s'", arg.Name, arg.DataType, arg.Value.(*uint256.Int).String()) case DataFloat64: return fmt.Sprintf("Name:%s Type:%s Value:'%f'", arg.Name, arg.DataType, arg.Value) case DataHash: @@ -194,6 +201,8 @@ func (args Arguments) MarshalBinary() (data []byte, err error) { localmap[arg.Name+string(arg.DataType)] = v case uint64: localmap[arg.Name+string(arg.DataType)] = v + case *uint256.Int: + localmap[arg.Name+string(arg.DataType)] = v.Bytes() case float64: localmap[arg.Name+string(arg.DataType)] = v case crypto.Hash: @@ -243,6 +252,13 @@ func (args *Arguments) UnmarshalBinary(data []byte) (err error) { } else { return fmt.Errorf("%+v has invalid data type %T\n", arg, v) } + case DataUint256: + if value, ok := v.([]byte); ok { + z := uint256.NewInt(0) + arg.Value = z.SetBytes(value) + } else { + return fmt.Errorf("%+v has invalid data type %T\n", arg, v) + } case DataFloat64: if value, ok := v.(float64); ok { arg.Value = value @@ -319,6 +335,10 @@ func (args Arguments) Validate_Arguments() error { if _, ok := arg.Value.(uint64); !ok { return fmt.Errorf("'%s' value should be of type uint64", arg.Name) } + case DataUint256: + if _, ok := arg.Value.(*uint256.Int); !ok { + return fmt.Errorf("'%s' value should be of type uint256", arg.Name) + } case DataFloat64: if _, ok := arg.Value.(float64); !ok { return fmt.Errorf("'%s' value should be of type float64", arg.Name) @@ -400,6 +420,15 @@ func (a *Argument) UnmarshalJSON(b []byte) (err error) { a.Value = x return } + case DataUint256: + t := new(big.Int) + var t_valid bool + if t, t_valid = t.SetString(string(raw.Value), 0); t_valid { + a.Value, _ = uint256.FromBig(t); + return + } else { + err = fmt.Errorf("cannot convert data type %s", raw.DataType) + } case DataFloat64: var x float64 if err = json.Unmarshal(raw.Value, &x); err == nil {