github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/smartcontract/builder.go (about)

     1  package smartcontract
     2  
     3  import (
     4  	"github.com/nspcc-dev/neo-go/pkg/io"
     5  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
     6  	"github.com/nspcc-dev/neo-go/pkg/util"
     7  	"github.com/nspcc-dev/neo-go/pkg/vm/emit"
     8  	"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
     9  )
    10  
    11  // Builder is used to create arbitrary scripts from the set of methods it provides.
    12  // Each method emits some set of opcodes performing an action and (in most cases)
    13  // returning a result. These chunks of code can be composed together to perform
    14  // several actions in the same script (and therefore in the same transaction), but
    15  // the end result (in terms of state changes and/or resulting items) of the script
    16  // totally depends on what it contains and that's the responsibility of the Builder
    17  // user. Builder is mostly used to create transaction scripts (also known as
    18  // "entry scripts"), so the set of methods it exposes is tailored to this model
    19  // of use and any calls emitted don't limit flags in any way (always use
    20  // callflag.All).
    21  //
    22  // When using this API keep in mind that the resulting script can't be larger than
    23  // 64K (transaction.MaxScriptLength) to be used as a transaction entry script and
    24  // it can't have more than 2048 elements on the stack. Technically, this limits
    25  // the number of calls that can be made to a lesser value because invocations use
    26  // the same stack too (the exact number depends on methods and parameters).
    27  //
    28  // This API is not (and won't be) suitable to create complex scripts that use
    29  // returned values as parameters to other calls or perform loops or do any other
    30  // things that can be done in NeoVM. This hardly can be expressed in an API like
    31  // this, so if you need more than that and if you're ready to work with bare
    32  // NeoVM instructions please refer to [emit] and [opcode] packages.
    33  type Builder struct {
    34  	bw *io.BufBinWriter
    35  }
    36  
    37  // NewBuilder creates a new Builder instance.
    38  func NewBuilder() *Builder {
    39  	return &Builder{bw: io.NewBufBinWriter()}
    40  }
    41  
    42  // InvokeMethod is the most generic contract method invoker, the code it produces
    43  // packs all of the arguments given into an array and calls some method of the
    44  // contract. It accepts as parameters everything that emit.Array accepts. The
    45  // correctness of this invocation (number and type of parameters) is out of scope
    46  // of this method, as well as return value, if contract's method returns something
    47  // this value just remains on the execution stack.
    48  func (b *Builder) InvokeMethod(contract util.Uint160, method string, params ...any) {
    49  	emit.AppCall(b.bw.BinWriter, contract, method, callflag.All, params...)
    50  }
    51  
    52  // Assert emits an ASSERT opcode that expects a Boolean value to be on the stack,
    53  // checks if it's true and aborts the transaction if it's not.
    54  func (b *Builder) Assert() {
    55  	emit.Opcodes(b.bw.BinWriter, opcode.ASSERT)
    56  }
    57  
    58  // InvokeWithAssert emits an invocation of the method (see InvokeMethod) with
    59  // an ASSERT after the invocation. The presumption is that the method called
    60  // returns a Boolean value signalling the success or failure of the operation.
    61  // This pattern is pretty common, NEP-11 or NEP-17 'transfer' methods do exactly
    62  // that as well as NEO's 'vote'. The ASSERT then allow to simplify transaction
    63  // status checking, if action is successful then transaction is successful as
    64  // well, if it went wrong than whole transaction fails (ends with vmstate.FAULT).
    65  func (b *Builder) InvokeWithAssert(contract util.Uint160, method string, params ...any) {
    66  	b.InvokeMethod(contract, method, params...)
    67  	b.Assert()
    68  }
    69  
    70  // Len returns the current length of the script. It's useful to perform script
    71  // length checks (wrt transaction.MaxScriptLength limit) while building the
    72  // script.
    73  func (b *Builder) Len() int {
    74  	return b.bw.Len()
    75  }
    76  
    77  // Script return current script, you can't use Builder after invoking this method
    78  // unless you Reset it.
    79  func (b *Builder) Script() ([]byte, error) {
    80  	err := b.bw.Err
    81  	return b.bw.Bytes(), err
    82  }
    83  
    84  // Reset resets the Builder, allowing to reuse the same script buffer (but
    85  // previous script will be overwritten there).
    86  func (b *Builder) Reset() {
    87  	b.bw.Reset()
    88  }