2018-02-15 15:35:49 +00:00
package compiler
import (
2020-04-02 12:38:53 +00:00
"encoding/json"
2020-08-10 15:23:45 +00:00
"errors"
2018-02-19 09:24:28 +00:00
"fmt"
2020-07-28 07:59:21 +00:00
"go/ast"
2018-02-15 15:35:49 +00:00
"go/parser"
2021-12-02 14:44:53 +00:00
"go/token"
2020-07-28 15:40:41 +00:00
"go/types"
2018-02-15 15:35:49 +00:00
"io"
"os"
2021-11-17 11:14:22 +00:00
"path/filepath"
2024-08-26 16:16:17 +00:00
"slices"
2018-02-19 09:24:28 +00:00
"strings"
2018-02-15 15:35:49 +00:00
2022-09-29 12:13:29 +00:00
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
2022-02-11 12:16:15 +00:00
"github.com/nspcc-dev/neo-go/pkg/smartcontract/binding"
2020-08-11 08:21:54 +00:00
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
2020-11-24 10:38:24 +00:00
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest/standard"
2020-06-25 16:21:49 +00:00
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
2023-05-23 11:17:39 +00:00
"github.com/nspcc-dev/neo-go/pkg/smartcontract/rpcbinding"
2021-06-25 07:54:00 +00:00
"github.com/nspcc-dev/neo-go/pkg/util"
2021-12-02 14:44:53 +00:00
"golang.org/x/tools/go/packages"
2021-09-25 10:09:55 +00:00
"gopkg.in/yaml.v3"
2018-02-15 15:35:49 +00:00
)
2020-06-25 16:21:49 +00:00
const fileExt = "nef"
2018-02-15 15:35:49 +00:00
2022-04-20 18:30:09 +00:00
// Options contains all the parameters that affect the behavior of the compiler.
2018-02-19 09:24:28 +00:00
type Options struct {
2020-06-25 16:21:49 +00:00
// The extension of the output file default set to .nef
2018-02-19 09:24:28 +00:00
Ext string
2018-02-15 15:35:49 +00:00
2018-02-19 09:24:28 +00:00
// The name of the output file.
Outfile string
2018-02-15 15:35:49 +00:00
2020-04-02 12:38:53 +00:00
// The name of the output for debug info.
DebugInfo string
compiler: add ability to generate .abi.json file
A part of integration with NEO Blockchain Toolkit (see #902). To be
able to deploy smart-contract compiled with neo-go compiler via NEO
Express, we have to generate additional .abi.json file. This file
contains the following information:
- hash of the compiled contract
- smart-contract metadata (title, description, version, author,
email, has-storage, has-dynamic-invoke, is-payable)
- smart-contract entry point
- functions
- events
However, this .abi.json file is slightly different from the one,
described in manifest.go, so we have to add auxilaury stractures for
json marshalling. The .abi.json format used by NEO-Express is described
[here](https://github.com/neo-project/neo-devpack-dotnet/blob/master/src/Neo.Compiler.MSIL/FuncExport.cs#L66).
2020-04-28 16:39:01 +00:00
2020-06-25 13:10:08 +00:00
// The name of the output for contract manifest file.
ManifestFile string
compiler: add ability to generate .abi.json file
A part of integration with NEO Blockchain Toolkit (see #902). To be
able to deploy smart-contract compiled with neo-go compiler via NEO
Express, we have to generate additional .abi.json file. This file
contains the following information:
- hash of the compiled contract
- smart-contract metadata (title, description, version, author,
email, has-storage, has-dynamic-invoke, is-payable)
- smart-contract entry point
- functions
- events
However, this .abi.json file is slightly different from the one,
described in manifest.go, so we have to add auxilaury stractures for
json marshalling. The .abi.json format used by NEO-Express is described
[here](https://github.com/neo-project/neo-devpack-dotnet/blob/master/src/Neo.Compiler.MSIL/FuncExport.cs#L66).
2020-04-28 16:39:01 +00:00
2020-11-24 13:36:35 +00:00
// NoEventsCheck specifies if events emitted by contract needs to be present in manifest.
// This setting has effect only if manifest is emitted.
NoEventsCheck bool
2020-11-24 10:38:24 +00:00
// NoStandardCheck specifies if supported standards compliance needs to be checked.
// This setting has effect only if manifest is emitted.
NoStandardCheck bool
2021-06-24 15:36:40 +00:00
// NoPermissionsCheck specifies if permissions in YAML config need to be checked
// against invocations performed by the contract.
// This setting has effect only if manifest is emitted.
NoPermissionsCheck bool
2023-05-08 18:58:28 +00:00
// GuessEventTypes specifies if types of runtime notifications need to be guessed
// from the usage context. These types are used for RPC binding generation only and
// can be defined for events with name known at the compilation time and without
// variadic args usages. If some type is specified via config file, then the config's
// one is preferable. Currently, event's parameter type is defined from the first
// occurrence of event call.
GuessEventTypes bool
2022-04-20 18:30:09 +00:00
// Name is a contract's name to be written to manifest.
2020-11-20 08:02:58 +00:00
Name string
2022-04-20 18:30:09 +00:00
// SourceURL is a contract's source URL to be written to manifest.
2021-09-23 21:19:37 +00:00
SourceURL string
2023-05-08 18:58:28 +00:00
// Runtime notifications declared in the contract configuration file.
ContractEvents [ ] HybridEvent
// DeclaredNamedTypes is the set of named types that were declared in the
// contract configuration type and are the part of manifest events.
DeclaredNamedTypes map [ string ] binding . ExtendedType
2020-08-11 08:21:54 +00:00
2020-08-04 09:55:36 +00:00
// The list of standards supported by the contract.
ContractSupportedStandards [ ] string
2020-12-10 14:55:52 +00:00
2022-04-20 18:30:09 +00:00
// SafeMethods contains a list of methods which will be marked as safe in manifest.
2020-12-10 14:55:52 +00:00
SafeMethods [ ] string
2021-06-02 07:50:17 +00:00
2022-04-20 18:30:09 +00:00
// Overloads contains mapping from the compiled method name to the name emitted in manifest.
2021-10-16 11:10:17 +00:00
// It can be used to provide method overloads as Go doesn't have such capability.
Overloads map [ string ] string
2021-06-02 07:50:17 +00:00
// Permissions is a list of permissions for every contract method.
Permissions [ ] manifest . Permission
2022-02-11 12:16:15 +00:00
// BindingsFile contains configuration for smart-contract bindings generator.
BindingsFile string
2018-02-15 15:35:49 +00:00
}
2023-05-08 18:58:28 +00:00
// HybridEvent represents the description of event emitted by the contract squashed
// with extended event's parameters description. We have it as a separate type for
// the user's convenience. It is applied for the smart contract configuration file
// only.
type HybridEvent struct {
Name string ` json:"name" `
Parameters [ ] HybridParameter ` json:"parameters" `
}
// HybridParameter contains the manifest's event parameter description united with
// the extended type description for this parameter. It is applied for the smart
// contract configuration file only.
type HybridParameter struct {
manifest . Parameter ` yaml:",inline" `
ExtendedType * binding . ExtendedType ` yaml:"extendedtype,omitempty" `
}
2018-02-25 12:26:56 +00:00
type buildInfo struct {
2021-12-02 14:44:53 +00:00
config * packages . Config
program [ ] * packages . Package
options * Options
2018-02-25 12:26:56 +00:00
}
2020-08-05 08:14:43 +00:00
// ForEachPackage executes fn on each package used in the current program
// in the order they should be initialized.
2021-12-02 14:44:53 +00:00
func ( c * codegen ) ForEachPackage ( fn func ( * packages . Package ) ) {
for _ , pkgPath := range c . packages {
p := c . packageCache [ pkgPath ]
c . typeInfo = p . TypesInfo
c . currPkg = p
fn ( p )
2020-08-05 08:14:43 +00:00
}
}
2022-04-20 18:30:09 +00:00
// ForEachFile executes fn on each file used in the current program.
2020-08-05 08:14:43 +00:00
func ( c * codegen ) ForEachFile ( fn func ( * ast . File , * types . Package ) ) {
2021-12-02 14:44:53 +00:00
c . ForEachPackage ( func ( pkg * packages . Package ) {
for _ , f := range pkg . Syntax {
c . fillImportMap ( f , pkg )
fn ( f , pkg . Types )
2020-07-28 07:59:21 +00:00
}
2020-08-05 08:14:43 +00:00
} )
2020-07-28 07:59:21 +00:00
}
2020-07-28 16:35:41 +00:00
// fillImportMap fills import map for f.
2021-12-02 14:44:53 +00:00
func ( c * codegen ) fillImportMap ( f * ast . File , pkg * packages . Package ) {
c . importMap = map [ string ] string { "" : pkg . PkgPath }
2020-07-28 16:35:41 +00:00
for _ , imp := range f . Imports {
// We need to load find package metadata because
// name specified in `package ...` decl, can be in
// conflict with package path.
pkgPath := strings . Trim ( imp . Path . Value , ` " ` )
2021-12-02 14:44:53 +00:00
realPkg := pkg . Imports [ pkgPath ]
name := realPkg . Name
2020-07-28 16:35:41 +00:00
if imp . Name != nil {
name = imp . Name . Name
}
2021-12-02 14:44:53 +00:00
c . importMap [ name ] = realPkg . PkgPath
2020-07-28 16:35:41 +00:00
}
}
2023-04-03 10:34:24 +00:00
func getBuildInfo ( name string , src any ) ( * buildInfo , error ) {
2021-12-02 14:44:53 +00:00
dir , err := filepath . Abs ( name )
if err != nil {
return nil , err
}
absName := dir
singleFile := strings . HasSuffix ( absName , ".go" )
if singleFile {
dir = filepath . Dir ( dir )
}
conf := & packages . Config {
Mode : packages . NeedName |
packages . NeedImports |
packages . NeedDeps |
packages . NeedTypes |
packages . NeedSyntax |
packages . NeedTypesInfo ,
Fset : token . NewFileSet ( ) ,
Dir : dir ,
Overlay : make ( map [ string ] [ ] byte ) ,
}
var names [ ] string
2020-08-10 15:23:45 +00:00
if src != nil {
2021-12-02 14:44:53 +00:00
var buf [ ] byte
var err error
switch s := src . ( type ) {
case string :
buf = [ ] byte ( s )
case io . Reader :
2022-02-22 16:27:32 +00:00
buf , err = io . ReadAll ( s )
2021-12-02 14:44:53 +00:00
if err != nil {
return nil , err
}
default :
panic ( fmt . Sprintf ( "unsupported src type: %T" , s ) )
2020-08-10 15:23:45 +00:00
}
2022-02-24 10:39:39 +00:00
names = append ( names , name )
2021-12-02 14:44:53 +00:00
conf . Overlay [ absName ] = buf
2020-08-10 15:23:45 +00:00
} else {
if strings . HasSuffix ( name , ".go" ) {
2021-12-02 14:44:53 +00:00
names = append ( names , "file=" + absName )
2020-08-10 15:23:45 +00:00
} else {
2021-12-02 14:44:53 +00:00
names = append ( names , "pattern=" + absName )
2020-08-10 15:23:45 +00:00
}
2018-02-15 15:35:49 +00:00
}
2021-12-02 14:44:53 +00:00
conf . ParseFile = func ( fset * token . FileSet , filename string , src [ ] byte ) ( * ast . File , error ) {
// When compiling a single file we can or can not load other files from the same package.
2022-04-20 18:30:09 +00:00
// Here we chose the latter which is consistent with `go run` behavior.
2021-12-02 14:44:53 +00:00
// Other dependencies should still be processed.
if singleFile && filepath . Dir ( filename ) == filepath . Dir ( absName ) && filename != absName {
return nil , nil
}
const mode = parser . AllErrors
return parser . ParseFile ( fset , filename , src , mode )
}
prog , err := packages . Load ( conf , names ... )
2018-02-15 15:35:49 +00:00
if err != nil {
2018-02-19 09:24:28 +00:00
return nil , err
2018-02-15 15:35:49 +00:00
}
2021-12-02 14:44:53 +00:00
for _ , p := range prog {
if len ( p . Errors ) != 0 {
return nil , p . Errors [ 0 ]
}
}
2020-03-31 13:16:32 +00:00
return & buildInfo {
2021-12-02 14:44:53 +00:00
config : conf ,
program : prog ,
2020-03-31 13:16:32 +00:00
} , nil
}
2022-12-07 13:51:03 +00:00
// Compile compiles a Go program into a bytecode that can run on the Neo virtual machine.
2020-08-10 15:23:45 +00:00
// If `r != nil`, `name` is interpreted as a filename, and `r` as file contents.
2022-04-20 18:30:09 +00:00
// Otherwise `name` is either a file name or a name of the directory containing source files.
2020-08-10 10:06:06 +00:00
func Compile ( name string , r io . Reader ) ( [ ] byte , error ) {
2021-07-26 12:34:07 +00:00
f , _ , err := CompileWithOptions ( name , r , nil )
2020-03-31 13:16:32 +00:00
if err != nil {
return nil , err
2018-02-24 09:06:48 +00:00
}
2021-12-08 19:33:03 +00:00
return f . Script , nil
2020-04-02 12:38:53 +00:00
}
2022-04-20 18:30:09 +00:00
// CompileWithOptions compiles a Go program into bytecode with the provided compiler options.
2021-12-08 19:33:03 +00:00
func CompileWithOptions ( name string , r io . Reader , o * Options ) ( * nef . File , * DebugInfo , error ) {
2020-08-10 10:06:06 +00:00
ctx , err := getBuildInfo ( name , r )
2018-02-19 09:24:28 +00:00
if err != nil {
2020-04-02 12:38:53 +00:00
return nil , nil , err
2018-02-15 15:35:49 +00:00
}
2021-06-04 08:47:52 +00:00
ctx . options = o
2021-12-08 19:33:03 +00:00
return codeGen ( ctx )
2018-02-15 15:35:49 +00:00
}
2020-06-25 16:21:49 +00:00
// CompileAndSave will compile and save the file to disk in the NEF format.
2019-11-22 14:16:52 +00:00
func CompileAndSave ( src string , o * Options ) ( [ ] byte , error ) {
2018-03-29 06:24:45 +00:00
o . Outfile = strings . TrimSuffix ( o . Outfile , fmt . Sprintf ( ".%s" , fileExt ) )
2018-02-19 09:24:28 +00:00
if len ( o . Outfile ) == 0 {
2020-08-10 15:23:45 +00:00
if strings . HasSuffix ( src , ".go" ) {
o . Outfile = strings . TrimSuffix ( src , ".go" )
} else {
o . Outfile = "out"
}
2018-02-15 15:35:49 +00:00
}
2018-02-19 09:24:28 +00:00
if len ( o . Ext ) == 0 {
o . Ext = fileExt
2018-02-15 15:35:49 +00:00
}
2021-12-08 19:33:03 +00:00
f , di , err := CompileWithOptions ( src , nil , o )
2018-02-19 09:24:28 +00:00
if err != nil {
2020-08-06 16:09:57 +00:00
return nil , fmt . Errorf ( "error while trying to compile smart contract file: %w" , err )
2018-02-15 15:35:49 +00:00
}
2021-09-23 21:19:37 +00:00
if o . SourceURL != "" {
if len ( o . SourceURL ) > nef . MaxSourceURLLength {
return nil , errors . New ( "too long source URL" )
}
f . Source = o . SourceURL
f . Checksum = f . CalculateChecksum ( )
}
2020-06-25 16:21:49 +00:00
bytes , err := f . Bytes ( )
if err != nil {
2020-08-06 16:09:57 +00:00
return nil , fmt . Errorf ( "error while serializing .nef file: %w" , err )
2020-06-25 16:21:49 +00:00
}
2018-02-19 09:24:28 +00:00
out := fmt . Sprintf ( "%s.%s" , o . Outfile , o . Ext )
2022-02-22 16:27:32 +00:00
err = os . WriteFile ( out , bytes , os . ModePerm )
2020-06-26 10:40:19 +00:00
if err != nil {
2021-12-08 19:33:03 +00:00
return f . Script , err
2020-04-02 12:38:53 +00:00
}
2022-02-11 12:16:15 +00:00
if o . DebugInfo == "" && o . ManifestFile == "" && o . BindingsFile == "" {
2021-12-08 19:33:03 +00:00
return f . Script , nil
2020-06-26 10:40:19 +00:00
}
2020-06-30 11:21:58 +00:00
if o . DebugInfo != "" {
2020-08-12 14:39:41 +00:00
di . Events = make ( [ ] EventDebugInfo , len ( o . ContractEvents ) )
for i , e := range o . ContractEvents {
params := make ( [ ] DebugParam , len ( e . Parameters ) )
for j , p := range e . Parameters {
params [ j ] = DebugParam {
Name : p . Name ,
Type : p . Type . String ( ) ,
}
}
di . Events [ i ] = EventDebugInfo {
2020-08-12 14:39:41 +00:00
ID : e . Name ,
// DebugInfo event name should be at the format {namespace},{name}
// but we don't provide namespace via .yml config
Name : "," + e . Name ,
2020-08-12 14:39:41 +00:00
Parameters : params ,
}
}
2020-06-30 11:21:58 +00:00
data , err := json . Marshal ( di )
if err != nil {
2021-12-08 19:33:03 +00:00
return f . Script , err
2020-06-30 11:21:58 +00:00
}
2022-02-22 16:27:32 +00:00
if err := os . WriteFile ( o . DebugInfo , data , os . ModePerm ) ; err != nil {
2021-12-08 19:33:03 +00:00
return f . Script , err
2020-06-30 11:21:58 +00:00
}
}
2022-02-11 12:16:15 +00:00
if o . BindingsFile != "" {
cfg := binding . NewConfig ( )
cfg . Package = di . MainPkg
for _ , m := range di . Methods {
2022-11-14 14:11:54 +00:00
if ! m . IsExported {
continue
}
2022-02-11 12:16:15 +00:00
for _ , p := range m . Parameters {
2022-12-01 18:05:54 +00:00
pname := m . Name . Name + "." + p . Name
2022-02-11 12:16:15 +00:00
if p . RealType . TypeName != "" {
2022-12-01 18:05:54 +00:00
cfg . Overrides [ pname ] = p . RealType
}
if p . ExtendedType != nil {
cfg . Types [ pname ] = * p . ExtendedType
2022-02-11 12:16:15 +00:00
}
}
if m . ReturnTypeReal . TypeName != "" {
cfg . Overrides [ m . Name . Name ] = m . ReturnTypeReal
}
2022-12-01 18:05:54 +00:00
if m . ReturnTypeExtended != nil {
cfg . Types [ m . Name . Name ] = * m . ReturnTypeExtended
}
}
if len ( di . NamedTypes ) > 0 {
cfg . NamedTypes = di . NamedTypes
2022-02-11 12:16:15 +00:00
}
2023-05-08 18:58:28 +00:00
for name , et := range o . DeclaredNamedTypes {
2023-05-29 15:50:36 +00:00
if _ , ok := cfg . NamedTypes [ name ] ; ok {
return nil , fmt . Errorf ( "configured declared named type intersects with the contract's one: `%s`" , name )
}
2023-05-08 18:58:28 +00:00
cfg . NamedTypes [ name ] = et
}
for _ , e := range o . ContractEvents {
2023-05-23 11:17:39 +00:00
eStructName := rpcbinding . ToEventBindingName ( e . Name )
2023-05-08 18:58:28 +00:00
for _ , p := range e . Parameters {
2023-05-23 11:17:39 +00:00
pStructName := rpcbinding . ToParameterBindingName ( p . Name )
2023-05-08 18:58:28 +00:00
if p . ExtendedType != nil {
2023-05-23 11:17:39 +00:00
pName := eStructName + "." + pStructName
2023-05-08 18:58:28 +00:00
cfg . Types [ pName ] = * p . ExtendedType
2023-05-24 08:52:14 +00:00
}
2023-05-08 18:58:28 +00:00
}
}
if o . GuessEventTypes {
if len ( di . EmittedEvents ) > 0 {
2023-11-21 15:41:58 +00:00
var keys = make ( [ ] string , 0 , len ( di . EmittedEvents ) )
for k := range di . EmittedEvents {
keys = append ( keys , k )
}
2024-08-26 16:16:17 +00:00
slices . Sort ( keys )
2023-11-21 15:41:58 +00:00
for _ , eventName := range keys {
var (
eventUsages = di . EmittedEvents [ eventName ]
manifestEvent HybridEvent
)
2023-05-25 16:17:49 +00:00
for _ , e := range o . ContractEvents {
if e . Name == eventName {
manifestEvent = e
break
}
}
if len ( manifestEvent . Name ) == 0 {
return nil , fmt . Errorf ( "inconsistent usages of event `%s`: not declared in the contract config" , eventName )
}
exampleUsage := eventUsages [ 0 ]
for _ , usage := range eventUsages {
if len ( usage . Params ) != len ( manifestEvent . Parameters ) {
return nil , fmt . Errorf ( "inconsistent usages of event `%s` against config: number of params mismatch: %d vs %d" , eventName , len ( exampleUsage . Params ) , len ( manifestEvent . Parameters ) )
}
for i , actual := range usage . Params {
mParam := manifestEvent . Parameters [ i ]
// TODO: see the TestCompile_GuessEventTypes, "SC parameter type mismatch" section,
// do we want to compare with actual.RealType? The conversion code is emitted by the
// compiler for it, so we expect the parameter to be of the proper type.
if ! ( mParam . Type == smartcontract . AnyType || actual . TypeSC == mParam . Type ) {
return nil , fmt . Errorf ( "inconsistent usages of event `%s` against config: SC type of param #%d mismatch: %s vs %s" , eventName , i , actual . TypeSC , mParam . Type )
}
expected := exampleUsage . Params [ i ]
if ! actual . ExtendedType . Equals ( expected . ExtendedType ) {
return nil , fmt . Errorf ( "inconsistent usages of event `%s`: extended type of param #%d mismatch" , eventName , i )
}
}
}
2023-05-23 11:17:39 +00:00
eBindingName := rpcbinding . ToEventBindingName ( eventName )
2023-05-25 16:17:49 +00:00
for _ , p := range exampleUsage . Params {
2023-05-23 11:17:39 +00:00
pBindingName := rpcbinding . ToParameterBindingName ( p . Name )
pname := eBindingName + "." + pBindingName
2023-05-08 18:58:28 +00:00
if p . RealType . TypeName != "" {
if _ , ok := cfg . Overrides [ pname ] ; ! ok {
cfg . Overrides [ pname ] = p . RealType
}
}
if p . ExtendedType != nil {
2023-11-21 15:26:35 +00:00
typeName := p . ExtendedType . Name
if extType , ok := exampleUsage . ExtTypes [ typeName ] ; ok {
for _ , ok := cfg . NamedTypes [ typeName ] ; ok ; _ , ok = cfg . NamedTypes [ typeName ] {
typeName = typeName + "X"
}
extType . Name = typeName
p . ExtendedType . Name = typeName
cfg . NamedTypes [ typeName ] = extType
}
2023-05-08 18:58:28 +00:00
if _ , ok := cfg . Types [ pname ] ; ! ok {
cfg . Types [ pname ] = * p . ExtendedType
}
}
2023-05-24 08:52:14 +00:00
}
}
}
}
2022-02-11 12:16:15 +00:00
data , err := yaml . Marshal ( & cfg )
if err != nil {
return nil , fmt . Errorf ( "can't marshal bindings configuration: %w" , err )
}
2022-02-22 16:27:32 +00:00
err = os . WriteFile ( o . BindingsFile , data , os . ModePerm )
2022-02-11 12:16:15 +00:00
if err != nil {
return nil , fmt . Errorf ( "can't write bindings configuration: %w" , err )
}
}
2020-06-30 11:21:58 +00:00
if o . ManifestFile != "" {
2021-02-19 10:48:45 +00:00
m , err := CreateManifest ( di , o )
2020-06-30 11:21:58 +00:00
if err != nil {
2021-12-08 19:33:03 +00:00
return f . Script , err
2020-11-24 13:36:35 +00:00
}
2020-06-30 11:21:58 +00:00
mData , err := json . Marshal ( m )
if err != nil {
2021-12-08 19:33:03 +00:00
return f . Script , fmt . Errorf ( "failed to marshal manifest to JSON: %w" , err )
2020-06-30 11:21:58 +00:00
}
2022-02-22 16:27:32 +00:00
return f . Script , os . WriteFile ( o . ManifestFile , mData , os . ModePerm )
2020-06-30 11:21:58 +00:00
}
2021-12-08 19:33:03 +00:00
return f . Script , nil
2018-02-15 15:35:49 +00:00
}
2021-02-19 10:48:45 +00:00
// CreateManifest creates manifest and checks that is is valid.
func CreateManifest ( di * DebugInfo , o * Options ) ( * manifest . Manifest , error ) {
m , err := di . ConvertToManifest ( o )
if err != nil {
return m , fmt . Errorf ( "failed to convert debug info to manifest: %w" , err )
}
2021-10-06 12:18:57 +00:00
for _ , name := range o . SafeMethods {
if m . ABI . GetMethod ( name , - 1 ) == nil {
return m , fmt . Errorf ( "method %s is marked as safe but missing from manifest" , name )
}
}
2023-11-22 17:15:43 +00:00
err = m . IsValid ( util . Uint160 { } , true ) // Check as much as possible without hash.
2022-07-14 12:36:21 +00:00
if err != nil {
return m , fmt . Errorf ( "manifest is invalid: %w" , err )
}
2021-02-19 10:48:45 +00:00
if ! o . NoStandardCheck {
if err := standard . CheckABI ( m , o . ContractSupportedStandards ... ) ; err != nil {
return m , err
}
if m . ABI . GetMethod ( manifest . MethodOnNEP11Payment , - 1 ) != nil {
if err := standard . CheckABI ( m , manifest . NEP11Payable ) ; err != nil {
return m , err
}
}
if m . ABI . GetMethod ( manifest . MethodOnNEP17Payment , - 1 ) != nil {
if err := standard . CheckABI ( m , manifest . NEP17Payable ) ; err != nil {
return m , err
}
}
}
if ! o . NoEventsCheck {
for name := range di . EmittedEvents {
2023-05-24 08:52:14 +00:00
expected := m . ABI . GetEvent ( name )
if expected == nil {
2021-02-19 10:48:45 +00:00
return nil , fmt . Errorf ( "event '%s' is emitted but not specified in manifest" , name )
}
2023-05-24 08:52:14 +00:00
for _ , emitted := range di . EmittedEvents [ name ] {
if len ( emitted . Params ) != len ( expected . Parameters ) {
2021-02-19 10:48:45 +00:00
return nil , fmt . Errorf ( "event '%s' should have %d parameters but has %d" ,
2023-05-24 08:52:14 +00:00
name , len ( expected . Parameters ) , len ( emitted . Params ) )
2021-02-19 10:48:45 +00:00
}
2023-05-24 08:52:14 +00:00
for j := range expected . Parameters {
if expected . Parameters [ j ] . Type == smartcontract . AnyType {
2022-09-29 12:13:29 +00:00
continue
}
2023-05-24 08:52:14 +00:00
expectedT := expected . Parameters [ j ] . Type
if emitted . Params [ j ] . TypeSC != expectedT {
2021-02-19 10:48:45 +00:00
return nil , fmt . Errorf ( "event '%s' should have '%s' as type of %d parameter, " +
2023-05-24 08:52:14 +00:00
"got: %s" , name , expectedT , j + 1 , emitted . Params [ j ] . TypeSC )
2021-02-19 10:48:45 +00:00
}
}
}
}
}
2021-06-24 15:36:40 +00:00
if ! o . NoPermissionsCheck {
// We can't perform full check for 2 reasons:
// 1. Contract hash may not be available at compile time.
// 2. Permission may be specified for a group of contracts by public key.
// Thus only basic checks are performed.
for h , methods := range di . InvokedContracts {
2021-06-25 07:54:00 +00:00
knownHash := ! h . Equals ( util . Uint160 { } )
2021-06-24 15:36:40 +00:00
methodLoop :
for _ , m := range methods {
for _ , p := range o . Permissions {
// Group or wildcard permission is ok to try.
2021-06-25 07:54:00 +00:00
if knownHash && p . Contract . Type == manifest . PermissionHash && ! p . Contract . Hash ( ) . Equals ( h ) {
2021-06-24 15:36:40 +00:00
continue
}
if p . Methods . Contains ( m ) {
continue methodLoop
}
}
2021-06-25 07:54:00 +00:00
if knownHash {
return nil , fmt . Errorf ( "method '%s' of contract %s is invoked but" +
" corresponding permission is missing" , m , h . StringLE ( ) )
}
return nil , fmt . Errorf ( "method '%s' is invoked but" +
" corresponding permission is missing" , m )
2021-06-24 15:36:40 +00:00
}
}
}
2021-02-19 10:48:45 +00:00
return m , nil
}