github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/smartcontract/entry.go (about) 1 package smartcontract 2 3 import ( 4 "fmt" 5 6 "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" 7 "github.com/nspcc-dev/neo-go/pkg/io" 8 "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" 9 "github.com/nspcc-dev/neo-go/pkg/util" 10 "github.com/nspcc-dev/neo-go/pkg/vm/emit" 11 "github.com/nspcc-dev/neo-go/pkg/vm/opcode" 12 ) 13 14 // CreateCallAndUnwrapIteratorScript creates a script that calls 'operation' method 15 // of the 'contract' with the specified arguments. This method is expected to return 16 // an iterator that then is traversed (using iterator.Next) with values (iterator.Value) 17 // extracted and added into array. At most maxIteratorResultItems number of items is 18 // processed this way (and this number can't exceed VM limits), the result of the 19 // script is an array containing extracted value elements. This script can be useful 20 // for interactions with RPC server that have iterator sessions disabled. 21 func CreateCallAndUnwrapIteratorScript(contract util.Uint160, operation string, maxIteratorResultItems int, params ...any) ([]byte, error) { 22 script := io.NewBufBinWriter() 23 jmpIfNotOffset, jmpIfMaxReachedOffset := emitCallAndUnwrapIteratorScript(script, contract, operation, maxIteratorResultItems, params...) 24 25 // End of the program: push the result on stack and return. 26 loadResultOffset := script.Len() 27 emit.Opcodes(script.BinWriter, opcode.NIP, // Remove iterator from the 1-st cell of estack 28 opcode.NIP) // Remove maxIteratorResultItems from the 1-st cell of estack, so that only resulting array is left on estack. 29 if err := script.Err; err != nil { 30 return nil, fmt.Errorf("emitting iterator unwrapper script: %w", err) 31 } 32 33 // Fill in JMPIFNOT instruction parameter. 34 bytes := script.Bytes() 35 bytes[jmpIfNotOffset+1] = uint8(loadResultOffset - jmpIfNotOffset) // +1 is for JMPIFNOT itself; offset is relative to JMPIFNOT position. 36 // Fill in jmpIfMaxReachedOffset instruction parameter. 37 bytes[jmpIfMaxReachedOffset+1] = uint8(loadResultOffset - jmpIfMaxReachedOffset) // +1 is for JMPIF itself; offset is relative to JMPIF position. 38 return bytes, nil 39 } 40 41 // CreateCallAndPrefetchIteratorScript creates a script that calls 'operation' method 42 // of the 'contract' with the specified arguments. This method is expected to return 43 // an array of the first iterator items (up to maxIteratorResultItems, which cannot exceed VM limits) 44 // and, optionally, an iterator that then is traversed (using iterator.Next). 45 // The result of the script is an array containing extracted value elements and an iterator, if it can contain more items. 46 // If the iterator is present, it lies on top of the stack. 47 // Note, however, that if an iterator is returned, the number of remaining items can still be 0. 48 // This script should only be used for interactions with RPC server that have iterator sessions enabled. 49 func CreateCallAndPrefetchIteratorScript(contract util.Uint160, operation string, maxIteratorResultItems int, params ...any) ([]byte, error) { 50 script := io.NewBufBinWriter() 51 jmpIfNotOffset, jmpIfMaxReachedOffset := emitCallAndUnwrapIteratorScript(script, contract, operation, maxIteratorResultItems, params...) 52 53 // 1st possibility: jump here when the maximum number of items was reached. 54 retainIteratorOffset := script.Len() 55 emit.Opcodes(script.BinWriter, opcode.ROT, // Put maxIteratorResultItems from the 2-nd cell of estack, to the top 56 opcode.DROP, // ... and then drop it. 57 opcode.SWAP, // Put the iterator on top of the stack. 58 opcode.RET) 59 60 // 2nd possibility: jump here when the iterator has no more items. 61 loadResultOffset := script.Len() 62 emit.Opcodes(script.BinWriter, opcode.ROT, // Put maxIteratorResultItems from the 2-nd cell of estack, to the top 63 opcode.DROP, // ... and then drop it. 64 opcode.NIP) // Drop iterator as the 1-st cell on the stack. 65 if err := script.Err; err != nil { 66 return nil, fmt.Errorf("emitting iterator unwrapper script: %w", err) 67 } 68 69 // Fill in JMPIFNOT instruction parameter. 70 bytes := script.Bytes() 71 bytes[jmpIfNotOffset+1] = uint8(loadResultOffset - jmpIfNotOffset) // +1 is for JMPIFNOT itself; offset is relative to JMPIFNOT position. 72 // Fill in jmpIfMaxReachedOffset instruction parameter. 73 bytes[jmpIfMaxReachedOffset+1] = uint8(retainIteratorOffset - jmpIfMaxReachedOffset) // +1 is for JMPIF itself; offset is relative to JMPIF position. 74 return bytes, nil 75 } 76 77 func emitCallAndUnwrapIteratorScript(script *io.BufBinWriter, contract util.Uint160, operation string, maxIteratorResultItems int, params ...any) (int, int) { 78 emit.Int(script.BinWriter, int64(maxIteratorResultItems)) 79 emit.AppCall(script.BinWriter, contract, operation, callflag.All, params...) // The System.Contract.Call itself, it will push Iterator on estack. 80 emit.Opcodes(script.BinWriter, opcode.NEWARRAY0) // Push new empty array to estack. This array will store iterator's elements. 81 82 // Start the iterator traversal cycle. 83 iteratorTraverseCycleStartOffset := script.Len() 84 emit.Opcodes(script.BinWriter, opcode.OVER) // Load iterator from 1-st cell of estack. 85 emit.Syscall(script.BinWriter, interopnames.SystemIteratorNext) // Call System.Iterator.Next, it will pop the iterator from estack and push `true` or `false` to estack. 86 jmpIfNotOffset := script.Len() 87 emit.Instruction(script.BinWriter, opcode.JMPIFNOT, // Pop boolean value (from the previous step) from estack, if `false`, then iterator has no more items => jump to the end of program. 88 []byte{ 89 0x00, // jump to loadResultOffset, but we'll fill this byte after script creation. 90 }) 91 emit.Opcodes(script.BinWriter, opcode.DUP, // Duplicate the resulting array from 0-th cell of estack and push it to estack. 92 opcode.PUSH2, opcode.PICK) // Pick iterator from the 2-nd cell of estack. 93 emit.Syscall(script.BinWriter, interopnames.SystemIteratorValue) // Call System.Iterator.Value, it will pop the iterator from estack and push its current value to estack. 94 emit.Opcodes(script.BinWriter, opcode.APPEND) // Pop iterator value and the resulting array from estack. Append value to the resulting array. Array is a reference type, thus, value stored at the 1-th cell of local slot will also be updated. 95 emit.Opcodes(script.BinWriter, opcode.DUP, // Duplicate the resulting array from 0-th cell of estack and push it to estack. 96 opcode.SIZE, // Pop array from estack and push its size to estack. 97 opcode.PUSH3, opcode.PICK, // Pick maxIteratorResultItems from the 3-d cell of estack. 98 opcode.GE) // Compare len(arr) and maxIteratorResultItems 99 jmpIfMaxReachedOffset := script.Len() 100 emit.Instruction(script.BinWriter, opcode.JMPIF, // Pop boolean value (from the previous step) from estack, if `false`, then max array elements is reached => jump to the end of program. 101 []byte{ 102 0x00, // jump to loadResultOffset, but we'll fill this byte after script creation. 103 }) 104 jmpOffset := script.Len() 105 emit.Instruction(script.BinWriter, opcode.JMP, // Jump to the start of iterator traverse cycle. 106 []byte{ 107 uint8(iteratorTraverseCycleStartOffset - jmpOffset), // jump to iteratorTraverseCycleStartOffset; offset is relative to JMP position. 108 }) 109 return jmpIfNotOffset, jmpIfMaxReachedOffset 110 } 111 112 // CreateCallScript returns a script that calls contract's method with 113 // the specified parameters. Whatever this method returns remains on the stack. 114 // See also (*Builder).InvokeMethod. 115 func CreateCallScript(contract util.Uint160, method string, params ...any) ([]byte, error) { 116 b := NewBuilder() 117 b.InvokeMethod(contract, method, params...) 118 return b.Script() 119 } 120 121 // CreateCallWithAssertScript returns a script that calls contract's method with 122 // the specified parameters expecting a Boolean value to be return that then is 123 // used for ASSERT. See also (*Builder).InvokeWithAssert. 124 func CreateCallWithAssertScript(contract util.Uint160, method string, params ...any) ([]byte, error) { 125 b := NewBuilder() 126 b.InvokeWithAssert(contract, method, params...) 127 return b.Script() 128 }