Merge pull request #907 from nspcc-dev/feature/compound

vm: implement Array/Struct/Map opcodes
This commit is contained in:
Roman Khimov 2020-04-27 12:10:40 +03:00 committed by GitHub
commit aa8d4d2f57
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 447 additions and 204 deletions

View file

@ -398,7 +398,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
ast.Walk(c, n.High)
} else {
emit.Opcode(c.prog.BinWriter, opcode.OVER)
emit.Opcode(c.prog.BinWriter, opcode.ARRAYSIZE)
emit.Opcode(c.prog.BinWriter, opcode.SIZE)
}
emit.Opcode(c.prog.BinWriter, opcode.OVER)
@ -858,7 +858,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
ast.Walk(c, n.X)
emit.Opcode(c.prog.BinWriter, opcode.ARRAYSIZE)
emit.Opcode(c.prog.BinWriter, opcode.SIZE)
emit.Opcode(c.prog.BinWriter, opcode.PUSH0)
c.pushStackLabel(label, 2)
@ -1030,13 +1030,7 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) {
switch name {
case "len":
arg := expr.Args[0]
typ := c.typeInfo.Types[arg].Type
if isStringType(typ) {
emit.Opcode(c.prog.BinWriter, opcode.SIZE)
} else {
emit.Opcode(c.prog.BinWriter, opcode.ARRAYSIZE)
}
case "append":
arg := expr.Args[0]
typ := c.typeInfo.Types[arg].Type

View file

@ -28,34 +28,34 @@ func TestInvocationScriptCreationGood(t *testing.T) {
script: "0c023432676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{}}},
script: "10c10c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
script: "10c00c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.ByteArrayType, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
script: "0c1450befd26fdf6e4d957c11e078b24ebce6291456f11c10c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
script: "0c1450befd26fdf6e4d957c11e078b24ebce6291456f11c00c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.SignatureType, Value: Param{Type: StringT, Value: "4edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f"}}}}}},
script: "0c404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f11c10c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
script: "0c404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f11c00c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.StringType, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
script: "0c283530626566643236666466366534643935376331316530373862323465626365363239313435366611c10c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
script: "0c283530626566643236666466366534643935376331316530373862323465626365363239313435366611c00c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.Hash160Type, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
script: "0c146f459162ceeb248b071ec157d9e4f6fd26fdbe5011c10c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
script: "0c146f459162ceeb248b071ec157d9e4f6fd26fdbe5011c00c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.Hash256Type, Value: Param{Type: StringT, Value: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}}}}}},
script: "0c20e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6011c10c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
script: "0c20e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6011c00c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.PublicKeyType, Value: Param{Type: StringT, Value: "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1"}}}}}},
script: "0c2103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c111c10c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
script: "0c2103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c111c00c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.IntegerType, Value: Param{Type: NumberT, Value: 42}}}}}},
script: "002a11c10c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
script: "002a11c00c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.BoolType, Value: Param{Type: StringT, Value: "true"}}}}}},
script: "1111c10c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
script: "1111c00c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}, {
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.BoolType, Value: Param{Type: StringT, Value: "false"}}}}}},
script: "1011c10c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
script: "1011c00c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
}}
for _, ps := range paramScripts {
script, err := CreateFunctionInvocationScript(contract, ps.ps)

View file

@ -48,18 +48,18 @@ type rpcTestCase struct {
check func(t *testing.T, e *executor, result interface{})
}
const testContractHash = "6b9e88be61028590ebbb1296cbee09beed4ae75d"
const testContractHash = "7858559a8405f4e4e1834fcff274ddf4f1b8f407"
var rpcTestCases = map[string][]rpcTestCase{
"getapplicationlog": {
{
name: "positive",
params: `["4459d8050e0abb0a42a9ec5a84dee1c5f8c449b11859265ef45a47c5ea369bc5"]`,
params: `["4108062977676178e8453a8ef84a702e01bb35af8a65c7529d04704fcb5f1e0e"]`,
result: func(e *executor) interface{} { return &result.ApplicationLog{} },
check: func(t *testing.T, e *executor, acc interface{}) {
res, ok := acc.(*result.ApplicationLog)
require.True(t, ok)
expectedTxHash, err := util.Uint256DecodeStringLE("4459d8050e0abb0a42a9ec5a84dee1c5f8c449b11859265ef45a47c5ea369bc5")
expectedTxHash, err := util.Uint256DecodeStringLE("4108062977676178e8453a8ef84a702e01bb35af8a65c7529d04704fcb5f1e0e")
require.NoError(t, err)
assert.Equal(t, expectedTxHash, res.TxHash)
assert.Equal(t, 1, len(res.Executions))

Binary file not shown.

Binary file not shown.

View file

@ -101,7 +101,7 @@ func (c *Context) Next() (opcode.Opcode, []byte, error) {
}
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE,
opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE,
opcode.CALL:
opcode.CALL, opcode.ISTYPE, opcode.NEWARRAYT:
numtoread = 1
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL,
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
@ -186,6 +186,9 @@ func (c *Context) TryInteger() (*big.Int, error) {
return nil, errors.New("can't convert Context to Integer")
}
// Type implements StackItem interface.
func (c *Context) Type() StackItemType { panic("Context cannot appear on evaluation stack") }
// Equals implements StackItem interface.
func (c *Context) Equals(s StackItem) bool {
return c == s

View file

@ -98,7 +98,6 @@ const (
SUBSTR Opcode = 0x7F
LEFT Opcode = 0x80
RIGHT Opcode = 0x81
SIZE Opcode = 0x82
// Bitwise logic
INVERT Opcode = 0x83
@ -144,20 +143,27 @@ const (
CHECKMULTISIG Opcode = 0xAE
// Advanced data structures (arrays, structures, maps)
ARRAYSIZE Opcode = 0xC0
PACK Opcode = 0xC1
UNPACK Opcode = 0xC2
PICKITEM Opcode = 0xC3
SETITEM Opcode = 0xC4
NEWARRAY Opcode = 0xC5
PACK Opcode = 0xC0
UNPACK Opcode = 0xC1
NEWARRAY0 Opcode = 0xC2
NEWARRAY Opcode = 0xC3
NEWARRAYT Opcode = 0xC4
NEWSTRUCT0 Opcode = 0xC5
NEWSTRUCT Opcode = 0xC6
NEWMAP Opcode = 0xC7
APPEND Opcode = 0xC8
REVERSE Opcode = 0xC9
REMOVE Opcode = 0xCA
NEWMAP Opcode = 0xC8
SIZE Opcode = 0xCA
HASKEY Opcode = 0xCB
KEYS Opcode = 0xCC
VALUES Opcode = 0xCD
PICKITEM Opcode = 0xCE
APPEND Opcode = 0xCF
SETITEM Opcode = 0xD0
REVERSEITEMS Opcode = 0xD1
REMOVE Opcode = 0xD2
CLEARITEMS Opcode = 0xD3
// Types
ISTYPE Opcode = 0xD9
// Exceptions
THROW Opcode = 0xF0

View file

@ -85,7 +85,6 @@ func _() {
_ = x[SUBSTR-127]
_ = x[LEFT-128]
_ = x[RIGHT-129]
_ = x[SIZE-130]
_ = x[INVERT-131]
_ = x[AND-132]
_ = x[OR-133]
@ -123,25 +122,30 @@ func _() {
_ = x[CHECKSIG-172]
_ = x[VERIFY-173]
_ = x[CHECKMULTISIG-174]
_ = x[ARRAYSIZE-192]
_ = x[PACK-193]
_ = x[UNPACK-194]
_ = x[PICKITEM-195]
_ = x[SETITEM-196]
_ = x[NEWARRAY-197]
_ = x[PACK-192]
_ = x[UNPACK-193]
_ = x[NEWARRAY0-194]
_ = x[NEWARRAY-195]
_ = x[NEWARRAYT-196]
_ = x[NEWSTRUCT0-197]
_ = x[NEWSTRUCT-198]
_ = x[NEWMAP-199]
_ = x[APPEND-200]
_ = x[REVERSE-201]
_ = x[REMOVE-202]
_ = x[NEWMAP-200]
_ = x[SIZE-202]
_ = x[HASKEY-203]
_ = x[KEYS-204]
_ = x[VALUES-205]
_ = x[PICKITEM-206]
_ = x[APPEND-207]
_ = x[SETITEM-208]
_ = x[REVERSEITEMS-209]
_ = x[REMOVE-210]
_ = x[CLEARITEMS-211]
_ = x[ISTYPE-217]
_ = x[THROW-240]
_ = x[THROWIFNOT-241]
}
const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMP_LJMPIFJMPIF_LJMPIFNOTJMPIFNOT_LJMPEQJMPEQ_LJMPNEJMPNE_LJMPGTJMPGT_LJMPGEJMPGE_LJMPLTJMPLT_LJMPLEJMPLE_LCALLCALL_LOLDPUSH1RETAPPCALLSYSCALLTAILCALLDUPFROMALTSTACKTOALTSTACKFROMALTSTACKXDROPISNULLXSWAPXTUCKDEPTHDROPDUPNIPOVERPICKROLLROTSWAPTUCKCATSUBSTRLEFTRIGHTSIZEINVERTANDORXOREQUALINCDECSIGNNEGATEABSNOTNZADDSUBMULDIVMODSHLSHRBOOLANDBOOLORNUMEQUALNUMNOTEQUALLTGTLTEGTEMINMAXWITHINSHA1SHA256HASH160HASH256CHECKSIGVERIFYCHECKMULTISIGARRAYSIZEPACKUNPACKPICKITEMSETITEMNEWARRAYNEWSTRUCTNEWMAPAPPENDREVERSEREMOVEHASKEYKEYSVALUESTHROWTHROWIFNOT"
const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPLJMPIFJMPIFLJMPIFNOTJMPIFNOTLJMPEQJMPEQLJMPNEJMPNELJMPGTJMPGTLJMPGEJMPGELJMPLTJMPLTLJMPLEJMPLELCALLCALLLOLDPUSH1RETAPPCALLSYSCALLTAILCALLDUPFROMALTSTACKTOALTSTACKFROMALTSTACKXDROPISNULLXSWAPXTUCKDEPTHDROPDUPNIPOVERPICKROLLROTSWAPTUCKCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALINCDECSIGNNEGATEABSNOTNZADDSUBMULDIVMODSHLSHRBOOLANDBOOLORNUMEQUALNUMNOTEQUALLTGTLTEGTEMINMAXWITHINSHA1SHA256HASH160HASH256CHECKSIGVERIFYCHECKMULTISIGPACKUNPACKNEWARRAY0NEWARRAYNEWARRAYTNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISTYPETHROWTHROWIFNOT"
var _Opcode_map = map[Opcode]string{
0: _Opcode_name[0:8],
@ -174,105 +178,109 @@ var _Opcode_map = map[Opcode]string{
32: _Opcode_name[182:188],
33: _Opcode_name[188:191],
34: _Opcode_name[191:194],
35: _Opcode_name[194:199],
36: _Opcode_name[199:204],
37: _Opcode_name[204:211],
38: _Opcode_name[211:219],
39: _Opcode_name[219:229],
40: _Opcode_name[229:234],
41: _Opcode_name[234:241],
42: _Opcode_name[241:246],
43: _Opcode_name[246:253],
44: _Opcode_name[253:258],
45: _Opcode_name[258:265],
46: _Opcode_name[265:270],
47: _Opcode_name[270:277],
48: _Opcode_name[277:282],
49: _Opcode_name[282:289],
50: _Opcode_name[289:294],
51: _Opcode_name[294:301],
52: _Opcode_name[301:305],
53: _Opcode_name[305:311],
81: _Opcode_name[311:319],
102: _Opcode_name[319:322],
103: _Opcode_name[322:329],
104: _Opcode_name[329:336],
105: _Opcode_name[336:344],
106: _Opcode_name[344:359],
107: _Opcode_name[359:369],
108: _Opcode_name[369:381],
109: _Opcode_name[381:386],
112: _Opcode_name[386:392],
114: _Opcode_name[392:397],
115: _Opcode_name[397:402],
116: _Opcode_name[402:407],
117: _Opcode_name[407:411],
118: _Opcode_name[411:414],
119: _Opcode_name[414:417],
120: _Opcode_name[417:421],
121: _Opcode_name[421:425],
122: _Opcode_name[425:429],
123: _Opcode_name[429:432],
124: _Opcode_name[432:436],
125: _Opcode_name[436:440],
126: _Opcode_name[440:443],
127: _Opcode_name[443:449],
128: _Opcode_name[449:453],
129: _Opcode_name[453:458],
130: _Opcode_name[458:462],
131: _Opcode_name[462:468],
132: _Opcode_name[468:471],
133: _Opcode_name[471:473],
134: _Opcode_name[473:476],
135: _Opcode_name[476:481],
139: _Opcode_name[481:484],
140: _Opcode_name[484:487],
141: _Opcode_name[487:491],
143: _Opcode_name[491:497],
144: _Opcode_name[497:500],
145: _Opcode_name[500:503],
146: _Opcode_name[503:505],
147: _Opcode_name[505:508],
148: _Opcode_name[508:511],
149: _Opcode_name[511:514],
150: _Opcode_name[514:517],
151: _Opcode_name[517:520],
152: _Opcode_name[520:523],
153: _Opcode_name[523:526],
154: _Opcode_name[526:533],
155: _Opcode_name[533:539],
156: _Opcode_name[539:547],
158: _Opcode_name[547:558],
159: _Opcode_name[558:560],
160: _Opcode_name[560:562],
161: _Opcode_name[562:565],
162: _Opcode_name[565:568],
163: _Opcode_name[568:571],
164: _Opcode_name[571:574],
165: _Opcode_name[574:580],
167: _Opcode_name[580:584],
168: _Opcode_name[584:590],
169: _Opcode_name[590:597],
170: _Opcode_name[597:604],
172: _Opcode_name[604:612],
173: _Opcode_name[612:618],
174: _Opcode_name[618:631],
192: _Opcode_name[631:640],
193: _Opcode_name[640:644],
194: _Opcode_name[644:650],
195: _Opcode_name[650:658],
196: _Opcode_name[658:665],
197: _Opcode_name[665:673],
198: _Opcode_name[673:682],
199: _Opcode_name[682:688],
200: _Opcode_name[688:694],
201: _Opcode_name[694:701],
202: _Opcode_name[701:707],
203: _Opcode_name[707:713],
204: _Opcode_name[713:717],
205: _Opcode_name[717:723],
240: _Opcode_name[723:728],
241: _Opcode_name[728:738],
35: _Opcode_name[194:198],
36: _Opcode_name[198:203],
37: _Opcode_name[203:209],
38: _Opcode_name[209:217],
39: _Opcode_name[217:226],
40: _Opcode_name[226:231],
41: _Opcode_name[231:237],
42: _Opcode_name[237:242],
43: _Opcode_name[242:248],
44: _Opcode_name[248:253],
45: _Opcode_name[253:259],
46: _Opcode_name[259:264],
47: _Opcode_name[264:270],
48: _Opcode_name[270:275],
49: _Opcode_name[275:281],
50: _Opcode_name[281:286],
51: _Opcode_name[286:292],
52: _Opcode_name[292:296],
53: _Opcode_name[296:301],
81: _Opcode_name[301:309],
102: _Opcode_name[309:312],
103: _Opcode_name[312:319],
104: _Opcode_name[319:326],
105: _Opcode_name[326:334],
106: _Opcode_name[334:349],
107: _Opcode_name[349:359],
108: _Opcode_name[359:371],
109: _Opcode_name[371:376],
112: _Opcode_name[376:382],
114: _Opcode_name[382:387],
115: _Opcode_name[387:392],
116: _Opcode_name[392:397],
117: _Opcode_name[397:401],
118: _Opcode_name[401:404],
119: _Opcode_name[404:407],
120: _Opcode_name[407:411],
121: _Opcode_name[411:415],
122: _Opcode_name[415:419],
123: _Opcode_name[419:422],
124: _Opcode_name[422:426],
125: _Opcode_name[426:430],
126: _Opcode_name[430:433],
127: _Opcode_name[433:439],
128: _Opcode_name[439:443],
129: _Opcode_name[443:448],
131: _Opcode_name[448:454],
132: _Opcode_name[454:457],
133: _Opcode_name[457:459],
134: _Opcode_name[459:462],
135: _Opcode_name[462:467],
139: _Opcode_name[467:470],
140: _Opcode_name[470:473],
141: _Opcode_name[473:477],
143: _Opcode_name[477:483],
144: _Opcode_name[483:486],
145: _Opcode_name[486:489],
146: _Opcode_name[489:491],
147: _Opcode_name[491:494],
148: _Opcode_name[494:497],
149: _Opcode_name[497:500],
150: _Opcode_name[500:503],
151: _Opcode_name[503:506],
152: _Opcode_name[506:509],
153: _Opcode_name[509:512],
154: _Opcode_name[512:519],
155: _Opcode_name[519:525],
156: _Opcode_name[525:533],
158: _Opcode_name[533:544],
159: _Opcode_name[544:546],
160: _Opcode_name[546:548],
161: _Opcode_name[548:551],
162: _Opcode_name[551:554],
163: _Opcode_name[554:557],
164: _Opcode_name[557:560],
165: _Opcode_name[560:566],
167: _Opcode_name[566:570],
168: _Opcode_name[570:576],
169: _Opcode_name[576:583],
170: _Opcode_name[583:590],
172: _Opcode_name[590:598],
173: _Opcode_name[598:604],
174: _Opcode_name[604:617],
192: _Opcode_name[617:621],
193: _Opcode_name[621:627],
194: _Opcode_name[627:636],
195: _Opcode_name[636:644],
196: _Opcode_name[644:653],
197: _Opcode_name[653:663],
198: _Opcode_name[663:672],
200: _Opcode_name[672:678],
202: _Opcode_name[678:682],
203: _Opcode_name[682:688],
204: _Opcode_name[688:692],
205: _Opcode_name[692:698],
206: _Opcode_name[698:706],
207: _Opcode_name[706:712],
208: _Opcode_name[712:719],
209: _Opcode_name[719:731],
210: _Opcode_name[731:737],
211: _Opcode_name[737:747],
217: _Opcode_name[747:753],
240: _Opcode_name[753:758],
241: _Opcode_name[758:768],
}
func (i Opcode) String() string {

View file

@ -7,17 +7,61 @@ import (
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
)
type stackItemType byte
// StackItemType represents type of the stack item.
type StackItemType byte
// This block defines all known stack item types.
const (
byteArrayT stackItemType = 0x00
booleanT stackItemType = 0x01
integerT stackItemType = 0x02
arrayT stackItemType = 0x80
structT stackItemType = 0x81
mapT stackItemType = 0x82
AnyT StackItemType = 0x00
PointerT StackItemType = 0x10
BooleanT StackItemType = 0x20
IntegerT StackItemType = 0x21
ByteArrayT StackItemType = 0x28
BufferT StackItemType = 0x30
ArrayT StackItemType = 0x40
StructT StackItemType = 0x41
MapT StackItemType = 0x48
InteropT StackItemType = 0x60
)
// String implements fmt.Stringer interface.
func (t StackItemType) String() string {
switch t {
case AnyT:
return "Any"
case PointerT:
return "Pointer"
case BooleanT:
return "Boolean"
case IntegerT:
return "Integer"
case ByteArrayT:
return "ByteArray"
case BufferT:
return "Buffer"
case ArrayT:
return "Array"
case StructT:
return "Struct"
case MapT:
return "Map"
case InteropT:
return "Interop"
default:
return "INVALID"
}
}
// IsValid checks if s is a well defined stack item type.
func (t StackItemType) IsValid() bool {
switch t {
case AnyT, PointerT, BooleanT, IntegerT, ByteArrayT, BufferT, ArrayT, StructT, MapT, InteropT:
return true
default:
return false
}
}
// SerializeItem encodes given StackItem into the byte slice.
func SerializeItem(item StackItem) ([]byte, error) {
w := io.NewBufBinWriter()
@ -43,13 +87,13 @@ func serializeItemTo(item StackItem, w *io.BinWriter, seen map[StackItem]bool) {
switch t := item.(type) {
case *ByteArrayItem:
w.WriteBytes([]byte{byte(byteArrayT)})
w.WriteBytes([]byte{byte(ByteArrayT)})
w.WriteVarBytes(t.value)
case *BoolItem:
w.WriteBytes([]byte{byte(booleanT)})
w.WriteBytes([]byte{byte(BooleanT)})
w.WriteBool(t.value)
case *BigIntegerItem:
w.WriteBytes([]byte{byte(integerT)})
w.WriteBytes([]byte{byte(IntegerT)})
w.WriteVarBytes(emit.IntToBytes(t.value))
case *InteropItem:
w.Err = errors.New("interop item can't be serialized")
@ -58,9 +102,9 @@ func serializeItemTo(item StackItem, w *io.BinWriter, seen map[StackItem]bool) {
_, isArray := t.(*ArrayItem)
if isArray {
w.WriteBytes([]byte{byte(arrayT)})
w.WriteBytes([]byte{byte(ArrayT)})
} else {
w.WriteBytes([]byte{byte(structT)})
w.WriteBytes([]byte{byte(StructT)})
}
arr := t.Value().([]StackItem)
@ -71,7 +115,7 @@ func serializeItemTo(item StackItem, w *io.BinWriter, seen map[StackItem]bool) {
case *MapItem:
seen[item] = true
w.WriteBytes([]byte{byte(mapT)})
w.WriteBytes([]byte{byte(MapT)})
w.WriteVarUint(uint64(len(t.value)))
for i := range t.value {
serializeItemTo(t.value[i].Key, w, seen)
@ -100,31 +144,31 @@ func DecodeBinaryStackItem(r *io.BinReader) StackItem {
return nil
}
switch stackItemType(t) {
case byteArrayT:
switch StackItemType(t) {
case ByteArrayT:
data := r.ReadVarBytes()
return NewByteArrayItem(data)
case booleanT:
case BooleanT:
var b = r.ReadBool()
return NewBoolItem(b)
case integerT:
case IntegerT:
data := r.ReadVarBytes()
num := emit.BytesToInt(data)
return &BigIntegerItem{
value: num,
}
case arrayT, structT:
case ArrayT, StructT:
size := int(r.ReadVarUint())
arr := make([]StackItem, size)
for i := 0; i < size; i++ {
arr[i] = DecodeBinaryStackItem(r)
}
if stackItemType(t) == arrayT {
if StackItemType(t) == ArrayT {
return &ArrayItem{value: arr}
}
return &StructItem{value: arr}
case mapT:
case MapT:
size := int(r.ReadVarUint())
m := NewMapItem()
for i := 0; i < size; i++ {

View file

@ -28,6 +28,8 @@ type StackItem interface {
Equals(s StackItem) bool
// ToContractParameter converts StackItem to smartcontract.Parameter
ToContractParameter(map[StackItem]bool) smartcontract.Parameter
// Type returns stack item type.
Type() StackItemType
}
func makeStackItem(v interface{}) StackItem {
@ -177,6 +179,9 @@ func (i *StructItem) ToContractParameter(seen map[StackItem]bool) smartcontract.
}
}
// Type implements StackItem interface.
func (i *StructItem) Type() StackItemType { return StructT }
// Clone returns a Struct with all Struct fields copied by value.
// Array fields are still copied by reference.
func (i *StructItem) Clone() *StructItem {
@ -235,6 +240,9 @@ func (i NullItem) ToContractParameter(map[StackItem]bool) smartcontract.Paramete
}
}
// Type implements StackItem interface.
func (i NullItem) Type() StackItemType { return AnyT }
// BigIntegerItem represents a big integer on the stack.
type BigIntegerItem struct {
value *big.Int
@ -300,6 +308,9 @@ func (i *BigIntegerItem) ToContractParameter(map[StackItem]bool) smartcontract.P
}
}
// Type implements StackItem interface.
func (i *BigIntegerItem) Type() StackItemType { return IntegerT }
// MarshalJSON implements the json.Marshaler interface.
func (i *BigIntegerItem) MarshalJSON() ([]byte, error) {
return json.Marshal(i.value)
@ -380,6 +391,9 @@ func (i *BoolItem) ToContractParameter(map[StackItem]bool) smartcontract.Paramet
}
}
// Type implements StackItem interface.
func (i *BoolItem) Type() StackItemType { return BooleanT }
// ByteArrayItem represents a byte array on the stack.
type ByteArrayItem struct {
value []byte
@ -442,6 +456,9 @@ func (i *ByteArrayItem) ToContractParameter(map[StackItem]bool) smartcontract.Pa
}
}
// Type implements StackItem interface.
func (i *ByteArrayItem) Type() StackItemType { return ByteArrayT }
// ArrayItem represents a new ArrayItem object.
type ArrayItem struct {
value []StackItem
@ -506,6 +523,9 @@ func (i *ArrayItem) ToContractParameter(seen map[StackItem]bool) smartcontract.P
}
}
// Type implements StackItem interface.
func (i *ArrayItem) Type() StackItemType { return ArrayT }
// MapElement is a key-value pair of StackItems.
type MapElement struct {
Key StackItem
@ -591,6 +611,9 @@ func (i *MapItem) ToContractParameter(seen map[StackItem]bool) smartcontract.Par
}
}
// Type implements StackItem interface.
func (i *MapItem) Type() StackItemType { return MapT }
// Add adds key-value pair to the map.
func (i *MapItem) Add(key, value StackItem) {
if !isValidMapKey(key) {
@ -678,6 +701,9 @@ func (i *InteropItem) ToContractParameter(map[StackItem]bool) smartcontract.Para
}
}
// Type implements StackItem interface.
func (i *InteropItem) Type() StackItemType { return InteropT }
// MarshalJSON implements the json.Marshaler interface.
func (i *InteropItem) MarshalJSON() ([]byte, error) {
return json.Marshal(i.value)

View file

@ -565,6 +565,10 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
res := v.estack.Pop().value.Equals(NullItem{})
v.estack.PushVal(res)
case opcode.ISTYPE:
res := v.estack.Pop().Item()
v.estack.PushVal(res.Type() == StackItemType(parameter[0]))
// Stack operations.
case opcode.TOALTSTACK:
v.astack.Push(v.estack.Pop())
@ -911,8 +915,11 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
x := v.estack.Pop().BigInt()
v.estack.PushVal(x.Cmp(big.NewInt(0)) != 0)
// Object operations.
case opcode.NEWARRAY:
// Object operations
case opcode.NEWARRAY0:
v.estack.PushVal(&ArrayItem{[]StackItem{}})
case opcode.NEWARRAY, opcode.NEWARRAYT:
item := v.estack.Pop()
switch t := item.value.(type) {
case *StructItem:
@ -926,10 +933,17 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
if n > MaxArraySize {
panic("too long array")
}
items := makeArrayOfFalses(int(n))
typ := BooleanT
if op == opcode.NEWARRAYT {
typ = StackItemType(parameter[0])
}
items := makeArrayOfType(int(n), typ)
v.estack.PushVal(&ArrayItem{items})
}
case opcode.NEWSTRUCT0:
v.estack.PushVal(&StructItem{[]StackItem{}})
case opcode.NEWSTRUCT:
item := v.estack.Pop()
switch t := item.value.(type) {
@ -944,7 +958,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
if n > MaxArraySize {
panic("too long struct")
}
items := makeArrayOfFalses(int(n))
items := makeArrayOfType(int(n), BooleanT)
v.estack.PushVal(&StructItem{items})
}
@ -1058,7 +1072,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
panic(fmt.Sprintf("SETITEM: invalid item type %s", t))
}
case opcode.REVERSE:
case opcode.REVERSEITEMS:
a := v.estack.Pop().Array()
if len(a) > 1 {
for i, j := 0, len(a)-1; i <= j; i, j = i+1, j-1 {
@ -1100,7 +1114,29 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
panic("REMOVE: invalid type")
}
case opcode.ARRAYSIZE:
case opcode.CLEARITEMS:
elem := v.estack.Pop()
switch t := elem.value.(type) {
case *ArrayItem:
for _, item := range t.value {
v.estack.updateSizeRemove(item)
}
t.value = t.value[:0]
case *StructItem:
for _, item := range t.value {
v.estack.updateSizeRemove(item)
}
t.value = t.value[:0]
case *MapItem:
for i := range t.value {
v.estack.updateSizeRemove(t.value[i].Value)
}
t.value = t.value[:0]
default:
panic("CLEARITEMS: invalid type")
}
case opcode.SIZE:
elem := v.estack.Pop()
// Cause there is no native (byte) item type here, hence we need to check
// the type of the item for array size operations.
@ -1113,11 +1149,6 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
v.estack.PushVal(len(elem.Bytes()))
}
case opcode.SIZE:
elem := v.estack.Pop()
arr := elem.Bytes()
v.estack.PushVal(len(arr))
case opcode.JMP, opcode.JMPL, opcode.JMPIF, opcode.JMPIFL, opcode.JMPIFNOT, opcode.JMPIFNOTL,
opcode.JMPEQ, opcode.JMPEQL, opcode.JMPNE, opcode.JMPNEL,
opcode.JMPGT, opcode.JMPGTL, opcode.JMPGE, opcode.JMPGEL,
@ -1465,10 +1496,22 @@ func cloneIfStruct(item StackItem) StackItem {
}
}
func makeArrayOfFalses(n int) []StackItem {
func makeArrayOfType(n int, typ StackItemType) []StackItem {
if !typ.IsValid() {
panic(fmt.Sprintf("invalid stack item type: %d", typ))
}
items := make([]StackItem, n)
for i := range items {
items[i] = &BoolItem{false}
switch typ {
case BooleanT:
items[i] = NewBoolItem(false)
case IntegerT:
items[i] = NewBigIntegerItem(big.NewInt(0))
case ByteArrayT:
items[i] = NewByteArrayItem([]byte{})
default:
items[i] = NullItem{}
}
}
return items
}

View file

@ -216,6 +216,46 @@ func TestISNULL(t *testing.T) {
})
}
func testISTYPE(t *testing.T, result bool, typ StackItemType, item StackItem) {
prog := []byte{byte(opcode.ISTYPE), byte(typ)}
v := load(prog)
v.estack.PushVal(item)
runVM(t, v)
require.Equal(t, 1, v.estack.Len())
require.Equal(t, result, v.estack.Pop().Bool())
}
func TestISTYPE(t *testing.T) {
t.Run("Integer", func(t *testing.T) {
testISTYPE(t, true, IntegerT, NewBigIntegerItem(big.NewInt(42)))
testISTYPE(t, false, IntegerT, NewByteArrayItem([]byte{}))
})
t.Run("Boolean", func(t *testing.T) {
testISTYPE(t, true, BooleanT, NewBoolItem(true))
testISTYPE(t, false, BooleanT, NewByteArrayItem([]byte{}))
})
t.Run("ByteArray", func(t *testing.T) {
testISTYPE(t, true, ByteArrayT, NewByteArrayItem([]byte{}))
testISTYPE(t, false, ByteArrayT, NewBigIntegerItem(big.NewInt(42)))
})
t.Run("Array", func(t *testing.T) {
testISTYPE(t, true, ArrayT, NewArrayItem([]StackItem{}))
testISTYPE(t, false, ArrayT, NewByteArrayItem([]byte{}))
})
t.Run("Struct", func(t *testing.T) {
testISTYPE(t, true, StructT, NewStructItem([]StackItem{}))
testISTYPE(t, false, StructT, NewByteArrayItem([]byte{}))
})
t.Run("Map", func(t *testing.T) {
testISTYPE(t, true, MapT, NewMapItem())
testISTYPE(t, false, MapT, NewByteArrayItem([]byte{}))
})
t.Run("Interop", func(t *testing.T) {
testISTYPE(t, true, InteropT, NewInteropItem(42))
testISTYPE(t, false, InteropT, NewByteArrayItem([]byte{}))
})
}
// appendBigStruct returns a program which:
// 1. pushes size Structs on stack
// 2. packs them into a new struct
@ -620,7 +660,7 @@ func TestSerializeArray(t *testing.T) {
func TestSerializeArrayBad(t *testing.T) {
vm := load(getSerializeProg())
item := NewArrayItem(makeArrayOfFalses(2))
item := NewArrayItem(makeArrayOfType(2, BooleanT))
item.value[1] = item
vm.estack.Push(&Element{value: item})
@ -685,7 +725,7 @@ func TestSerializeMap(t *testing.T) {
}
func TestSerializeMapCompat(t *testing.T) {
resHex := "820100036b6579000576616c7565"
resHex := "480128036b6579280576616c7565"
res, err := hex.DecodeString(resHex)
require.NoError(t, err)
@ -1252,6 +1292,22 @@ func TestDECBigResult(t *testing.T) {
checkVMFailed(t, vm)
}
func TestNEWARRAY0(t *testing.T) {
prog := makeProgram(opcode.NEWARRAY0)
v := load(prog)
runVM(t, v)
require.Equal(t, 1, v.estack.Len())
require.Equal(t, &ArrayItem{[]StackItem{}}, v.estack.Pop().value)
}
func TestNEWSTRUCT0(t *testing.T) {
prog := makeProgram(opcode.NEWSTRUCT0)
v := load(prog)
runVM(t, v)
require.Equal(t, 1, v.estack.Len())
require.Equal(t, &StructItem{[]StackItem{}}, v.estack.Pop().value)
}
func TestNEWARRAYInteger(t *testing.T) {
prog := makeProgram(opcode.NEWARRAY)
vm := load(prog)
@ -1281,7 +1337,7 @@ func testNEWARRAYIssue437(t *testing.T, i1, i2 opcode.Opcode, appended bool) {
vm := load(prog)
vm.Run()
arr := makeArrayOfFalses(4)
arr := makeArrayOfType(4, BooleanT)
arr[2] = makeStackItem(3)
arr[3] = makeStackItem(4)
if appended {
@ -1323,6 +1379,32 @@ func TestNEWARRAYByteArray(t *testing.T) {
assert.Equal(t, &ArrayItem{[]StackItem{}}, vm.estack.Pop().value)
}
func testNEWARRAYT(t *testing.T, typ StackItemType, item StackItem) {
prog := makeProgram(opcode.NEWARRAYT, opcode.Opcode(typ), opcode.PUSH0, opcode.PICKITEM)
v := load(prog)
v.estack.PushVal(1)
if item == nil {
checkVMFailed(t, v)
return
}
runVM(t, v)
require.Equal(t, 1, v.estack.Len())
require.Equal(t, item, v.estack.Pop().Item())
}
func TestNEWARRAYT(t *testing.T) {
testCases := map[StackItemType]StackItem{
BooleanT: NewBoolItem(false),
IntegerT: NewBigIntegerItem(big.NewInt(0)),
ByteArrayT: NewByteArrayItem([]byte{}),
ArrayT: NullItem{},
0xFF: nil,
}
for typ, item := range testCases {
t.Run(typ.String(), func(t *testing.T) { testNEWARRAYT(t, typ, item) })
}
}
func TestNEWARRAYBadSize(t *testing.T) {
prog := makeProgram(opcode.NEWARRAY)
vm := load(prog)
@ -1581,8 +1663,8 @@ func TestSIZEBool(t *testing.T) {
assert.Equal(t, makeStackItem(1), vm.estack.Pop().value)
}
func TestARRAYSIZEArray(t *testing.T) {
prog := makeProgram(opcode.ARRAYSIZE)
func TestSIZEArray(t *testing.T) {
prog := makeProgram(opcode.SIZE)
vm := load(prog)
vm.estack.PushVal([]StackItem{
makeStackItem(1),
@ -1593,8 +1675,8 @@ func TestARRAYSIZEArray(t *testing.T) {
assert.Equal(t, makeStackItem(2), vm.estack.Pop().value)
}
func TestARRAYSIZEMap(t *testing.T) {
prog := makeProgram(opcode.ARRAYSIZE)
func TestSIZEMap(t *testing.T) {
prog := makeProgram(opcode.SIZE)
vm := load(prog)
m := NewMapItem()
@ -2534,19 +2616,19 @@ func TestUNPACKGood(t *testing.T) {
assert.Equal(t, int64(1), vm.estack.Peek(len(elements)+1).BigInt().Int64())
}
func TestREVERSEBadNotArray(t *testing.T) {
prog := makeProgram(opcode.REVERSE)
func TestREVERSEITEMSBadNotArray(t *testing.T) {
prog := makeProgram(opcode.REVERSEITEMS)
vm := load(prog)
vm.estack.PushVal(1)
checkVMFailed(t, vm)
}
func testREVERSEIssue437(t *testing.T, i1, i2 opcode.Opcode, reversed bool) {
func testREVERSEITEMSIssue437(t *testing.T, i1, i2 opcode.Opcode, reversed bool) {
prog := makeProgram(
opcode.PUSH0, i1,
opcode.DUP, opcode.PUSH1, opcode.APPEND,
opcode.DUP, opcode.PUSH2, opcode.APPEND,
opcode.DUP, i2, opcode.REVERSE)
opcode.DUP, i2, opcode.REVERSEITEMS)
vm := load(prog)
vm.Run()
@ -2567,15 +2649,15 @@ func testREVERSEIssue437(t *testing.T, i1, i2 opcode.Opcode, reversed bool) {
}
}
func TestREVERSEIssue437(t *testing.T) {
t.Run("Array+Array", func(t *testing.T) { testREVERSEIssue437(t, opcode.NEWARRAY, opcode.NEWARRAY, true) })
t.Run("Struct+Struct", func(t *testing.T) { testREVERSEIssue437(t, opcode.NEWSTRUCT, opcode.NEWSTRUCT, true) })
t.Run("Array+Struct", func(t *testing.T) { testREVERSEIssue437(t, opcode.NEWARRAY, opcode.NEWSTRUCT, false) })
t.Run("Struct+Array", func(t *testing.T) { testREVERSEIssue437(t, opcode.NEWSTRUCT, opcode.NEWARRAY, false) })
func TestREVERSEITEMSIssue437(t *testing.T) {
t.Run("Array+Array", func(t *testing.T) { testREVERSEITEMSIssue437(t, opcode.NEWARRAY, opcode.NEWARRAY, true) })
t.Run("Struct+Struct", func(t *testing.T) { testREVERSEITEMSIssue437(t, opcode.NEWSTRUCT, opcode.NEWSTRUCT, true) })
t.Run("Array+Struct", func(t *testing.T) { testREVERSEITEMSIssue437(t, opcode.NEWARRAY, opcode.NEWSTRUCT, false) })
t.Run("Struct+Array", func(t *testing.T) { testREVERSEITEMSIssue437(t, opcode.NEWSTRUCT, opcode.NEWARRAY, false) })
}
func TestREVERSEGoodOneElem(t *testing.T) {
prog := makeProgram(opcode.DUP, opcode.REVERSE)
func TestREVERSEITEMSGoodOneElem(t *testing.T) {
prog := makeProgram(opcode.DUP, opcode.REVERSEITEMS)
elements := []int{22}
vm := load(prog)
vm.estack.PushVal(1)
@ -2588,13 +2670,13 @@ func TestREVERSEGoodOneElem(t *testing.T) {
assert.Equal(t, int64(elements[0]), e.Int64())
}
func TestREVERSEGoodStruct(t *testing.T) {
func TestREVERSEITEMSGoodStruct(t *testing.T) {
eodd := []int{22, 34, 42, 55, 81}
even := []int{22, 34, 42, 55, 81, 99}
eall := [][]int{eodd, even}
for _, elements := range eall {
prog := makeProgram(opcode.DUP, opcode.REVERSE)
prog := makeProgram(opcode.DUP, opcode.REVERSEITEMS)
vm := load(prog)
vm.estack.PushVal(1)
@ -2616,13 +2698,13 @@ func TestREVERSEGoodStruct(t *testing.T) {
}
}
func TestREVERSEGood(t *testing.T) {
func TestREVERSEITEMSGood(t *testing.T) {
eodd := []int{22, 34, 42, 55, 81}
even := []int{22, 34, 42, 55, 81, 99}
eall := [][]int{eodd, even}
for _, elements := range eall {
prog := makeProgram(opcode.DUP, opcode.REVERSE)
prog := makeProgram(opcode.DUP, opcode.REVERSEITEMS)
vm := load(prog)
vm.estack.PushVal(1)
vm.estack.PushVal(elements)
@ -2697,6 +2779,43 @@ func TestREMOVEMap(t *testing.T) {
assert.Equal(t, makeStackItem(false), vm.estack.Pop().value)
}
func testCLEARITEMS(t *testing.T, item StackItem) {
prog := makeProgram(opcode.DUP, opcode.DUP, opcode.CLEARITEMS, opcode.SIZE)
v := load(prog)
v.estack.PushVal(item)
runVM(t, v)
require.Equal(t, 2, v.estack.Len())
require.EqualValues(t, 2, v.size) // empty collection + it's size
require.EqualValues(t, 0, v.estack.Pop().BigInt().Int64())
}
func TestCLEARITEMS(t *testing.T) {
arr := []StackItem{NewBigIntegerItem(big.NewInt(1)), NewByteArrayItem([]byte{1})}
m := NewMapItem()
m.Add(NewBigIntegerItem(big.NewInt(1)), NewByteArrayItem([]byte{}))
m.Add(NewByteArrayItem([]byte{42}), NewBigIntegerItem(big.NewInt(2)))
testCases := map[string]StackItem{
"empty Array": NewArrayItem([]StackItem{}),
"filled Array": NewArrayItem(arr),
"empty Struct": NewStructItem([]StackItem{}),
"filled Struct": NewStructItem(arr),
"empty Map": NewMapItem(),
"filled Map": m,
}
for name, item := range testCases {
t.Run(name, func(t *testing.T) { testCLEARITEMS(t, item) })
}
t.Run("Integer", func(t *testing.T) {
prog := makeProgram(opcode.CLEARITEMS)
v := load(prog)
v.estack.PushVal(1)
checkVMFailed(t, v)
})
}
func TestSWAPGood(t *testing.T) {
prog := makeProgram(opcode.SWAP)
vm := load(prog)