monza/internal/bytecode/extract.go
AndrewDanilin aca2229ab7 [#2] Print name of invoked method in transaction
Signed-off-by: AndrewDanilin <andnilin@gmail.com>
2024-07-03 09:36:54 +03:00

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
}
}