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 }