package vm import ( "fmt" "reflect" "runtime" "strings" "testing" "github.com/bytom/vapor/testutil" ) func TestStackOps(t *testing.T) { type testStruct struct { op Op startVM *virtualMachine wantErr error wantVM *virtualMachine } cases := []testStruct{{ op: OP_TOALTSTACK, startVM: &virtualMachine{ runLimit: 50000, dataStack: [][]byte{{1}}, }, wantVM: &virtualMachine{ runLimit: 49998, dataStack: [][]byte{}, altStack: [][]byte{{1}}, }, }, { op: OP_FROMALTSTACK, startVM: &virtualMachine{ runLimit: 50000, altStack: [][]byte{{1}}, }, wantVM: &virtualMachine{ runLimit: 49998, altStack: [][]byte{}, dataStack: [][]byte{{1}}, }, }, { op: OP_FROMALTSTACK, startVM: &virtualMachine{ runLimit: 50000, altStack: [][]byte{}, }, wantErr: ErrAltStackUnderflow, }, { op: OP_2DROP, startVM: &virtualMachine{ runLimit: 50000, dataStack: [][]byte{{1}, {1}}, }, wantVM: &virtualMachine{ runLimit: 50016, dataStack: [][]byte{}, }, }, { op: OP_2DUP, startVM: &virtualMachine{ runLimit: 50000, dataStack: [][]byte{{2}, {1}}, }, wantVM: &virtualMachine{ runLimit: 49980, dataStack: [][]byte{{2}, {1}, {2}, {1}}, }, }, { op: OP_3DUP, startVM: &virtualMachine{ runLimit: 50000, dataStack: [][]byte{{3}, {2}, {1}}, }, wantVM: &virtualMachine{ runLimit: 49970, dataStack: [][]byte{{3}, {2}, {1}, {3}, {2}, {1}}, }, }, { op: OP_2OVER, startVM: &virtualMachine{ runLimit: 50000, dataStack: [][]byte{{4}, {3}, {2}, {1}}, }, wantVM: &virtualMachine{ runLimit: 49980, dataStack: [][]byte{{4}, {3}, {2}, {1}, {4}, {3}}, }, }, { op: OP_2OVER, startVM: &virtualMachine{ runLimit: 2, dataStack: [][]byte{{4}, {3}, {2}, {1}}, }, wantErr: ErrRunLimitExceeded, }, { op: OP_2ROT, startVM: &virtualMachine{ runLimit: 50000, dataStack: [][]byte{{6}, {5}, {4}, {3}, {2}, {1}}, }, wantVM: &virtualMachine{ runLimit: 49998, dataStack: [][]byte{{4}, {3}, {2}, {1}, {6}, {5}}, }, }, { op: OP_2SWAP, startVM: &virtualMachine{ runLimit: 50000, dataStack: [][]byte{{4}, {3}, {2}, {1}}, }, wantVM: &virtualMachine{ runLimit: 49998, dataStack: [][]byte{{2}, {1}, {4}, {3}}, }, }, { op: OP_IFDUP, startVM: &virtualMachine{ runLimit: 50000, dataStack: [][]byte{{1}}, }, wantVM: &virtualMachine{ runLimit: 49990, dataStack: [][]byte{{1}, {1}}, }, }, { op: OP_IFDUP, startVM: &virtualMachine{ runLimit: 50000, dataStack: [][]byte{{}}, }, wantVM: &virtualMachine{ runLimit: 49999, dataStack: [][]byte{{}}, }, }, { op: OP_IFDUP, startVM: &virtualMachine{ runLimit: 1, dataStack: [][]byte{{1}}, }, wantErr: ErrRunLimitExceeded, }, { op: OP_DEPTH, startVM: &virtualMachine{ runLimit: 50000, dataStack: [][]byte{{1}}, }, wantVM: &virtualMachine{ runLimit: 49990, dataStack: [][]byte{{1}, {1}}, }, }, { op: OP_DEPTH, startVM: &virtualMachine{ runLimit: 1, dataStack: [][]byte{{1}}, }, wantErr: ErrRunLimitExceeded, }, { op: OP_DROP, startVM: &virtualMachine{ runLimit: 50000, dataStack: [][]byte{{1}}, }, wantVM: &virtualMachine{ runLimit: 50008, dataStack: [][]byte{}, }, }, { op: OP_DUP, startVM: &virtualMachine{ runLimit: 50000, dataStack: [][]byte{{1}}, }, wantVM: &virtualMachine{ runLimit: 49990, dataStack: [][]byte{{1}, {1}}, }, }, { op: OP_DUP, startVM: &virtualMachine{ runLimit: 1, dataStack: [][]byte{{1}}, }, wantErr: ErrRunLimitExceeded, }, { op: OP_NIP, startVM: &virtualMachine{ runLimit: 50000, dataStack: [][]byte{{2}, {1}}, }, wantVM: &virtualMachine{ runLimit: 50008, dataStack: [][]byte{{1}}, }, }, { op: OP_OVER, startVM: &virtualMachine{ runLimit: 50000, dataStack: [][]byte{{2}, {1}}, }, wantVM: &virtualMachine{ runLimit: 49990, dataStack: [][]byte{{2}, {1}, {2}}, }, }, { op: OP_OVER, startVM: &virtualMachine{ runLimit: 1, dataStack: [][]byte{{2}, {1}}, }, wantErr: ErrRunLimitExceeded, }, { op: OP_PICK, startVM: &virtualMachine{ runLimit: 50000, dataStack: [][]byte{{3}, {2}, {1}, {2}}, }, wantVM: &virtualMachine{ runLimit: 49998, dataStack: [][]byte{{3}, {2}, {1}, {3}}, }, }, { op: OP_PICK, startVM: &virtualMachine{ runLimit: 2, dataStack: [][]byte{{0xff, 0xff}, {2}, {1}, {2}}, }, wantErr: ErrRunLimitExceeded, }, { op: OP_ROLL, startVM: &virtualMachine{ runLimit: 50000, dataStack: [][]byte{{3}, {2}, {1}, {2}}, }, wantVM: &virtualMachine{ runLimit: 50007, dataStack: [][]byte{{2}, {1}, {3}}, }, }, { op: OP_ROT, startVM: &virtualMachine{ runLimit: 50000, dataStack: [][]byte{{3}, {2}, {1}}, }, wantVM: &virtualMachine{ runLimit: 49998, dataStack: [][]byte{{2}, {1}, {3}}, }, }, { op: OP_SWAP, startVM: &virtualMachine{ runLimit: 50000, dataStack: [][]byte{{2}, {1}}, }, wantVM: &virtualMachine{ runLimit: 49999, dataStack: [][]byte{{1}, {2}}, }, }, { op: OP_TUCK, startVM: &virtualMachine{ runLimit: 50000, dataStack: [][]byte{{2}, {1}}, }, wantVM: &virtualMachine{ runLimit: 49990, dataStack: [][]byte{{1}, {2}, {1}}, }, }, { op: OP_TUCK, startVM: &virtualMachine{ runLimit: 1, dataStack: [][]byte{{2}, {1}}, }, wantErr: ErrRunLimitExceeded, }} stackops := []Op{ OP_DEPTH, OP_FROMALTSTACK, OP_TOALTSTACK, OP_2DROP, OP_2DUP, OP_3DUP, OP_2OVER, OP_2ROT, OP_2SWAP, OP_IFDUP, OP_DROP, OP_DUP, OP_NIP, OP_OVER, OP_PICK, OP_ROLL, OP_ROT, OP_SWAP, OP_TUCK, } for _, op := range stackops { cases = append(cases, testStruct{ op: op, startVM: &virtualMachine{runLimit: 0}, wantErr: ErrRunLimitExceeded, }) } for i, c := range cases { err := ops[c.op].fn(c.startVM) if err != c.wantErr { t.Errorf("case %d, op %s: got err = %v want %v", i, ops[c.op].name, err, c.wantErr) continue } if c.wantErr != nil { continue } if !testutil.DeepEqual(c.startVM, c.wantVM) { t.Errorf("case %d, op %s: unexpected vm result\n\tgot: %+v\n\twant: %+v\n", i, ops[c.op].name, c.startVM, c.wantVM) } } } func TestStackUnderflow(t *testing.T) { cases := []struct { narg int // number of stack items required op func(*virtualMachine) error }{ // bitwise {1, opInvert}, {2, opAnd}, {2, opOr}, {2, opXor}, {2, opEqual}, {2, opEqualVerify}, // control {1, opVerify}, {3, opCheckPredicate}, {1, opJumpIf}, // crypto {1, opSha256}, {1, opSha3}, {3, opCheckSig}, {3, opCheckMultiSig}, // special, see also TestCryptoOps // introspection {5, opCheckOutput}, // numeric {1, op1Add}, {1, op1Sub}, {1, op2Mul}, {1, op2Div}, {1, opNegate}, {1, opAbs}, {1, opNot}, {1, op0NotEqual}, {2, opAdd}, {2, opSub}, {2, opMul}, {2, opDiv}, {2, opMod}, {2, opLshift}, {2, opRshift}, {2, opBoolAnd}, {2, opBoolOr}, {2, opNumEqual}, {2, opNumEqualVerify}, {2, opNumNotEqual}, {2, opLessThan}, {2, opGreaterThan}, {2, opLessThanOrEqual}, {2, opGreaterThanOrEqual}, {2, opMin}, {2, opMax}, {3, opWithin}, // splice {2, opCat}, {3, opSubstr}, {2, opLeft}, {2, opRight}, {1, opSize}, {2, opCatpushdata}, // stack {1, opToAltStack}, {2, op2Drop}, {2, op2Dup}, {3, op3Dup}, {4, op2Over}, {6, op2Rot}, {4, op2Swap}, {1, opIfDup}, {1, opDrop}, {1, opDup}, {2, opNip}, {2, opOver}, {2, opPick}, // TODO(kr): special; check data-dependent # of pops {2, opRoll}, // TODO(kr): special; check data-dependent # of pops {3, opRot}, {2, opSwap}, {2, opTuck}, } for _, test := range cases { t.Run(funcName(test.op), func(t *testing.T) { for i := 0; i < test.narg; i++ { t.Run(fmt.Sprintf("%d args", i), func(t *testing.T) { vm := &virtualMachine{ runLimit: 50000, dataStack: make([][]byte, i), } err := test.op(vm) if err != ErrDataStackUnderflow { t.Errorf("err = %v, want ErrStackUnderflow", err) } }) } }) } } func funcName(f interface{}) string { v := reflect.ValueOf(f) if v.Kind() != reflect.Func { return "" } s := runtime.FuncForPC(v.Pointer()).Name() return s[strings.LastIndex(s, ".")+1:] }