github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/interop/storage/basic.go (about)

     1  package storage
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/nspcc-dev/neo-go/pkg/config/limits"
     8  	"github.com/nspcc-dev/neo-go/pkg/core/interop"
     9  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    10  )
    11  
    12  var (
    13  	// ErrGasLimitExceeded is returned from interops when there is not enough
    14  	// GAS left in the execution context to complete the action.
    15  	ErrGasLimitExceeded   = errors.New("gas limit exceeded")
    16  	errFindInvalidOptions = errors.New("invalid Find options")
    17  )
    18  
    19  // Context contains contract ID and read/write flag, it's used as
    20  // a context for storage manipulation functions.
    21  type Context struct {
    22  	ID       int32
    23  	ReadOnly bool
    24  }
    25  
    26  // storageDelete deletes stored key-value pair.
    27  func Delete(ic *interop.Context) error {
    28  	stcInterface := ic.VM.Estack().Pop().Value()
    29  	stc, ok := stcInterface.(*Context)
    30  	if !ok {
    31  		return fmt.Errorf("%T is not a storage.Context", stcInterface)
    32  	}
    33  	if stc.ReadOnly {
    34  		return errors.New("storage.Context is read only")
    35  	}
    36  	key := ic.VM.Estack().Pop().Bytes()
    37  	ic.DAO.DeleteStorageItem(stc.ID, key)
    38  	return nil
    39  }
    40  
    41  // Get returns stored key-value pair.
    42  func Get(ic *interop.Context) error {
    43  	stcInterface := ic.VM.Estack().Pop().Value()
    44  	stc, ok := stcInterface.(*Context)
    45  	if !ok {
    46  		return fmt.Errorf("%T is not a storage.Context", stcInterface)
    47  	}
    48  	key := ic.VM.Estack().Pop().Bytes()
    49  	si := ic.DAO.GetStorageItem(stc.ID, key)
    50  	if si != nil {
    51  		ic.VM.Estack().PushItem(stackitem.NewByteArray([]byte(si)))
    52  	} else {
    53  		ic.VM.Estack().PushItem(stackitem.Null{})
    54  	}
    55  	return nil
    56  }
    57  
    58  // GetContext returns storage context for the currently executing contract.
    59  func GetContext(ic *interop.Context) error {
    60  	return getContextInternal(ic, false)
    61  }
    62  
    63  // GetReadOnlyContext returns read-only storage context for the currently executing contract.
    64  func GetReadOnlyContext(ic *interop.Context) error {
    65  	return getContextInternal(ic, true)
    66  }
    67  
    68  // getContextInternal is internal version of storageGetContext and
    69  // storageGetReadOnlyContext which allows to specify ReadOnly context flag.
    70  func getContextInternal(ic *interop.Context, isReadOnly bool) error {
    71  	contract, err := ic.GetContract(ic.VM.GetCurrentScriptHash())
    72  	if err != nil {
    73  		return fmt.Errorf("storage context can not be retrieved in dynamic scripts: %w", err)
    74  	}
    75  	sc := &Context{
    76  		ID:       contract.ID,
    77  		ReadOnly: isReadOnly,
    78  	}
    79  	ic.VM.Estack().PushItem(stackitem.NewInterop(sc))
    80  	return nil
    81  }
    82  
    83  func putWithContext(ic *interop.Context, stc *Context, key []byte, value []byte) error {
    84  	if len(key) > limits.MaxStorageKeyLen {
    85  		return errors.New("key is too big")
    86  	}
    87  	if len(value) > limits.MaxStorageValueLen {
    88  		return errors.New("value is too big")
    89  	}
    90  	if stc.ReadOnly {
    91  		return errors.New("storage.Context is read only")
    92  	}
    93  	si := ic.DAO.GetStorageItem(stc.ID, key)
    94  	sizeInc := len(value)
    95  	if si == nil {
    96  		sizeInc = len(key) + len(value)
    97  	} else if len(value) != 0 {
    98  		if len(value) <= len(si) {
    99  			sizeInc = (len(value)-1)/4 + 1
   100  		} else if len(si) != 0 {
   101  			sizeInc = (len(si)-1)/4 + 1 + len(value) - len(si)
   102  		}
   103  	}
   104  	if !ic.VM.AddGas(int64(sizeInc) * ic.BaseStorageFee()) {
   105  		return ErrGasLimitExceeded
   106  	}
   107  	ic.DAO.PutStorageItem(stc.ID, key, value)
   108  	return nil
   109  }
   110  
   111  // Put puts key-value pair into the storage.
   112  func Put(ic *interop.Context) error {
   113  	stcInterface := ic.VM.Estack().Pop().Value()
   114  	stc, ok := stcInterface.(*Context)
   115  	if !ok {
   116  		return fmt.Errorf("%T is not a storage.Context", stcInterface)
   117  	}
   118  	key := ic.VM.Estack().Pop().Bytes()
   119  	value := ic.VM.Estack().Pop().Bytes()
   120  	return putWithContext(ic, stc, key, value)
   121  }
   122  
   123  // ContextAsReadOnly sets given context to read-only mode.
   124  func ContextAsReadOnly(ic *interop.Context) error {
   125  	stcInterface := ic.VM.Estack().Pop().Value()
   126  	stc, ok := stcInterface.(*Context)
   127  	if !ok {
   128  		return fmt.Errorf("%T is not a storage.Context", stcInterface)
   129  	}
   130  	if !stc.ReadOnly {
   131  		stx := &Context{
   132  			ID:       stc.ID,
   133  			ReadOnly: true,
   134  		}
   135  		stc = stx
   136  	}
   137  	ic.VM.Estack().PushItem(stackitem.NewInterop(stc))
   138  	return nil
   139  }