forked from TrueCloudLab/monza
146 lines
3.7 KiB
Go
146 lines
3.7 KiB
Go
package bytecode
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
|
)
|
|
|
|
type SyscallParameters struct {
|
|
Contract string `json:"contract"`
|
|
Method string `json:"method"`
|
|
}
|
|
|
|
// ExtractCalls Extract parameters of SYSCALL opcode from bytecode of transaction
|
|
func ExtractCalls(script []byte) ([]SyscallParameters, error) {
|
|
syscallParams := make([]SyscallParameters, 0)
|
|
|
|
offsets := make([]int, 0)
|
|
for i := 0; i < len(script); {
|
|
offsets = append(offsets, i)
|
|
|
|
if opcode.Opcode(script[i]) == opcode.SYSCALL {
|
|
// Current opcode is SYSCALL
|
|
// here we parse two previous instruction,
|
|
// slice offsets contains positions of these two instructions
|
|
//
|
|
// PUSHDATA1/2/4 <Len in bytes of name> <Name of method>
|
|
// PUSHDATA1 <Len in bytes of address> <Address of contract>
|
|
// SYSCALL System.Contract.Call
|
|
|
|
offset1 := offsets[len(offsets)-3]
|
|
offset2 := offsets[len(offsets)-2]
|
|
|
|
offset1++
|
|
var n int
|
|
switch opcode.Opcode(script[offset1-1]) {
|
|
case opcode.PUSHDATA1:
|
|
n = parseInt(offset1, 1, script)
|
|
offset1 += 1
|
|
case opcode.PUSHDATA2:
|
|
n = parseInt(offset1, 2, script)
|
|
offset1 += 2
|
|
case opcode.PUSHDATA4:
|
|
n = parseInt(offset1, 4, script)
|
|
offset1 += 4
|
|
}
|
|
src := script[offset1 : offset1+n]
|
|
|
|
nameOfMethod, err := hex.DecodeString(hex.EncodeToString(src))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to convert bytes to UTF-8 string: %w", err)
|
|
}
|
|
|
|
offset2++
|
|
var contractAddress string
|
|
switch opcode.Opcode(script[offset2-1]) {
|
|
case opcode.PUSHDATA1:
|
|
n = parseInt(offset2, 1, script)
|
|
if n != 20 {
|
|
return nil, errors.New("invalid bytecode: address should be 20-byte value")
|
|
}
|
|
offset2++
|
|
u, err := util.Uint160DecodeBytesBE(script[offset2 : offset2+20])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode bytes BE to Uint160: %w", err)
|
|
}
|
|
contractAddress = address.Uint160ToString(u)
|
|
default:
|
|
return nil, errors.New("invalid bytecode: address should be 20-byte value")
|
|
}
|
|
|
|
syscallParams = append(syscallParams, SyscallParameters{
|
|
Contract: contractAddress,
|
|
Method: string(nameOfMethod),
|
|
})
|
|
}
|
|
|
|
// Skip n bytes (params of current opcode) to next opcode
|
|
err := skipBytes(&i, script)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s%w", "error while parsing bytecode:", err)
|
|
}
|
|
}
|
|
|
|
return syscallParams, nil
|
|
}
|
|
|
|
func skipBytes(i *int, script []byte) error {
|
|
*i++
|
|
switch opcode.Opcode(script[*i-1]) {
|
|
case opcode.PUSHINT8:
|
|
*i += 1
|
|
case opcode.PUSHINT16:
|
|
*i += 2
|
|
case opcode.PUSHINT32:
|
|
*i += 4
|
|
case opcode.PUSHINT64:
|
|
*i += 8
|
|
case opcode.PUSHINT128:
|
|
*i += 16
|
|
case opcode.PUSHINT256:
|
|
*i += 32
|
|
case opcode.PUSHDATA1:
|
|
n := parseInt(*i, 1, script)
|
|
*i += n + 1
|
|
case opcode.PUSHDATA2:
|
|
n := parseInt(*i, 2, script)
|
|
*i += n + 2
|
|
case opcode.PUSHDATA4:
|
|
n := parseInt(*i, 4, script)
|
|
*i += n + 4
|
|
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE,
|
|
opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE,
|
|
opcode.CALL, opcode.ISTYPE, opcode.CONVERT, opcode.NEWARRAYT,
|
|
opcode.ENDTRY:
|
|
*i += 1
|
|
case opcode.INITSLOT, opcode.TRY, opcode.CALLT:
|
|
*i += 2
|
|
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL,
|
|
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
|
|
opcode.ENDTRYL,
|
|
opcode.CALLL, opcode.SYSCALL, opcode.PUSHA:
|
|
*i += 4
|
|
case opcode.TRYL:
|
|
*i += 8
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parseInt(i int, len int, b []byte) int {
|
|
switch len {
|
|
case 1:
|
|
return int(b[i])
|
|
case 2:
|
|
return int(binary.LittleEndian.Uint16(b[i : i+len]))
|
|
case 4:
|
|
return int(binary.LittleEndian.Uint32(b[i : i+len]))
|
|
default:
|
|
return 0
|
|
}
|
|
}
|