github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/fvm/transactionStorageLimiter_test.go (about)

     1  package fvm_test
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/onflow/cadence"
     7  	"github.com/stretchr/testify/mock"
     8  	"github.com/stretchr/testify/require"
     9  
    10  	"github.com/onflow/flow-go/fvm"
    11  	"github.com/onflow/flow-go/fvm/environment"
    12  	fvmmock "github.com/onflow/flow-go/fvm/environment/mock"
    13  	"github.com/onflow/flow-go/fvm/errors"
    14  	"github.com/onflow/flow-go/fvm/storage/snapshot"
    15  	"github.com/onflow/flow-go/fvm/systemcontracts"
    16  	"github.com/onflow/flow-go/fvm/tracing"
    17  	"github.com/onflow/flow-go/model/flow"
    18  )
    19  
    20  func TestTransactionStorageLimiter(t *testing.T) {
    21  	owner := flow.HexToAddress("1")
    22  	executionSnapshot := &snapshot.ExecutionSnapshot{
    23  		WriteSet: map[flow.RegisterID]flow.RegisterValue{
    24  			flow.NewRegisterID(owner, "a"): flow.RegisterValue("foo"),
    25  			flow.NewRegisterID(owner, "b"): flow.RegisterValue("bar"),
    26  		},
    27  	}
    28  
    29  	ctx := fvm.Context{
    30  		EnvironmentParams: environment.EnvironmentParams{
    31  			Chain: flow.Emulator.Chain(),
    32  		},
    33  	}
    34  
    35  	t.Run("capacity > storage -> OK", func(t *testing.T) {
    36  		env := &fvmmock.Environment{}
    37  		env.On("LimitAccountStorage").Return(true)
    38  		env.On("StartChildSpan", mock.Anything).Return(
    39  			tracing.NewMockTracerSpan())
    40  		env.On("GetStorageUsed", mock.Anything).Return(uint64(99), nil)
    41  		env.On("AccountsStorageCapacity", mock.Anything, mock.Anything, mock.Anything).Return(
    42  			cadence.NewArray([]cadence.Value{
    43  				bytesToUFix64(100),
    44  			}),
    45  			nil,
    46  		)
    47  
    48  		d := &fvm.TransactionStorageLimiter{}
    49  		err := d.CheckStorageLimits(ctx, env, executionSnapshot, flow.EmptyAddress, 0)
    50  		require.NoError(t, err, "Transaction with higher capacity than storage used should work")
    51  	})
    52  	t.Run("capacity = storage -> OK", func(t *testing.T) {
    53  		env := &fvmmock.Environment{}
    54  		env.On("LimitAccountStorage").Return(true)
    55  		env.On("StartChildSpan", mock.Anything).Return(
    56  			tracing.NewMockTracerSpan())
    57  		env.On("GetStorageUsed", mock.Anything).Return(uint64(100), nil)
    58  		env.On("AccountsStorageCapacity", mock.Anything, mock.Anything, mock.Anything).Return(
    59  			cadence.NewArray([]cadence.Value{
    60  				bytesToUFix64(100),
    61  			}),
    62  			nil,
    63  		)
    64  
    65  		d := &fvm.TransactionStorageLimiter{}
    66  		err := d.CheckStorageLimits(ctx, env, executionSnapshot, flow.EmptyAddress, 0)
    67  		require.NoError(t, err, "Transaction with equal capacity than storage used should work")
    68  	})
    69  	t.Run("capacity = storage -> OK (dedup payer)", func(t *testing.T) {
    70  		env := &fvmmock.Environment{}
    71  		env.On("LimitAccountStorage").Return(true)
    72  		env.On("StartChildSpan", mock.Anything).Return(
    73  			tracing.NewMockTracerSpan())
    74  		env.On("GetStorageUsed", mock.Anything).Return(uint64(100), nil)
    75  		env.On("AccountsStorageCapacity", mock.Anything, mock.Anything, mock.Anything).Return(
    76  			cadence.NewArray([]cadence.Value{
    77  				bytesToUFix64(100),
    78  			}),
    79  			nil,
    80  		)
    81  
    82  		d := &fvm.TransactionStorageLimiter{}
    83  		err := d.CheckStorageLimits(ctx, env, executionSnapshot, owner, 0)
    84  		require.NoError(t, err, "Transaction with equal capacity than storage used should work")
    85  	})
    86  	t.Run("capacity < storage -> Not OK", func(t *testing.T) {
    87  		env := &fvmmock.Environment{}
    88  		env.On("LimitAccountStorage").Return(true)
    89  		env.On("StartChildSpan", mock.Anything).Return(
    90  			tracing.NewMockTracerSpan())
    91  		env.On("GetStorageUsed", mock.Anything).Return(uint64(101), nil)
    92  		env.On("AccountsStorageCapacity", mock.Anything, mock.Anything, mock.Anything).Return(
    93  			cadence.NewArray([]cadence.Value{
    94  				bytesToUFix64(100),
    95  			}),
    96  			nil,
    97  		)
    98  
    99  		d := &fvm.TransactionStorageLimiter{}
   100  		err := d.CheckStorageLimits(ctx, env, executionSnapshot, flow.EmptyAddress, 0)
   101  		require.Error(t, err, "Transaction with lower capacity than storage used should fail")
   102  	})
   103  	t.Run("capacity > storage -> OK (payer not updated)", func(t *testing.T) {
   104  		env := &fvmmock.Environment{}
   105  		env.On("LimitAccountStorage").Return(true)
   106  		env.On("StartChildSpan", mock.Anything).Return(
   107  			tracing.NewMockTracerSpan())
   108  		env.On("GetStorageUsed", mock.Anything).Return(uint64(99), nil)
   109  		env.On("AccountsStorageCapacity", mock.Anything, mock.Anything, mock.Anything).Return(
   110  			cadence.NewArray([]cadence.Value{
   111  				bytesToUFix64(100),
   112  			}),
   113  			nil,
   114  		)
   115  
   116  		executionSnapshot = &snapshot.ExecutionSnapshot{}
   117  
   118  		d := &fvm.TransactionStorageLimiter{}
   119  		err := d.CheckStorageLimits(ctx, env, executionSnapshot, owner, 1)
   120  		require.NoError(t, err, "Transaction with higher capacity than storage used should work")
   121  	})
   122  	t.Run("capacity < storage -> Not OK (payer not updated)", func(t *testing.T) {
   123  		env := &fvmmock.Environment{}
   124  		env.On("LimitAccountStorage").Return(true)
   125  		env.On("StartChildSpan", mock.Anything).Return(
   126  			tracing.NewMockTracerSpan())
   127  		env.On("GetStorageUsed", mock.Anything).Return(uint64(101), nil)
   128  		env.On("AccountsStorageCapacity", mock.Anything, mock.Anything, mock.Anything).Return(
   129  			cadence.NewArray([]cadence.Value{
   130  				bytesToUFix64(100),
   131  			}),
   132  			nil,
   133  		)
   134  
   135  		executionSnapshot = &snapshot.ExecutionSnapshot{}
   136  
   137  		d := &fvm.TransactionStorageLimiter{}
   138  		err := d.CheckStorageLimits(ctx, env, executionSnapshot, owner, 1000)
   139  		require.Error(t, err, "Transaction with lower capacity than storage used should fail")
   140  	})
   141  	t.Run("if ctx LimitAccountStorage false-> OK", func(t *testing.T) {
   142  		env := &fvmmock.Environment{}
   143  		env.On("LimitAccountStorage").Return(false)
   144  		env.On("StartChildSpan", mock.Anything).Return(
   145  			tracing.NewMockTracerSpan())
   146  		env.On("GetStorageCapacity", mock.Anything).Return(uint64(100), nil)
   147  		env.On("GetStorageUsed", mock.Anything).Return(uint64(101), nil)
   148  		env.On("AccountsStorageCapacity", mock.Anything, mock.Anything, mock.Anything).Return(
   149  			cadence.NewArray([]cadence.Value{
   150  				bytesToUFix64(100),
   151  			}),
   152  			nil,
   153  		)
   154  
   155  		d := &fvm.TransactionStorageLimiter{}
   156  		err := d.CheckStorageLimits(ctx, env, executionSnapshot, flow.EmptyAddress, 0)
   157  		require.NoError(t, err, "Transaction with higher capacity than storage used should work")
   158  	})
   159  	t.Run(
   160  		"non existing accounts or any other errors on fetching storage used -> Not OK",
   161  		func(t *testing.T) {
   162  			env := &fvmmock.Environment{}
   163  			env.On("LimitAccountStorage").Return(true)
   164  			env.On("StartChildSpan", mock.Anything).Return(
   165  				tracing.NewMockTracerSpan())
   166  			env.On("GetStorageUsed", mock.Anything).
   167  				Return(uint64(0), errors.NewAccountNotFoundError(owner))
   168  			env.On("AccountsStorageCapacity", mock.Anything, mock.Anything, mock.Anything).Return(
   169  				cadence.NewArray([]cadence.Value{
   170  					bytesToUFix64(100),
   171  				}),
   172  				nil,
   173  			)
   174  
   175  			d := &fvm.TransactionStorageLimiter{}
   176  			err := d.CheckStorageLimits(ctx, env, executionSnapshot, flow.EmptyAddress, 0)
   177  			require.Error(
   178  				t,
   179  				err,
   180  				"check storage used on non existing account (not general registers) should fail",
   181  			)
   182  		},
   183  	)
   184  	t.Run("special account is skipped", func(t *testing.T) {
   185  		sc := systemcontracts.SystemContractsForChain(ctx.Chain.ChainID())
   186  		evm := sc.EVMStorage.Address
   187  
   188  		executionSnapshot := &snapshot.ExecutionSnapshot{
   189  			WriteSet: map[flow.RegisterID]flow.RegisterValue{
   190  				flow.NewRegisterID(evm, "a"): flow.RegisterValue("foo"),
   191  			},
   192  		}
   193  
   194  		env := &fvmmock.Environment{}
   195  		env.On("LimitAccountStorage").Return(true)
   196  		env.On("StartChildSpan", mock.Anything).Return(
   197  			tracing.NewMockTracerSpan())
   198  		env.On("GetStorageUsed", mock.Anything).
   199  			Return(uint64(0), errors.NewAccountNotFoundError(owner))
   200  		env.On("AccountsStorageCapacity", mock.Anything, mock.Anything, mock.Anything).
   201  			Run(func(args mock.Arguments) {
   202  				require.Len(t, args.Get(0).([]flow.Address), 0)
   203  			}).
   204  			Return(
   205  				// since the special account is skipped, the resulting array from AccountsStorageCapacity should be empty
   206  				cadence.NewArray([]cadence.Value{}),
   207  				nil,
   208  			)
   209  
   210  		d := &fvm.TransactionStorageLimiter{}
   211  
   212  		// if EVM is disabled don't skip the storage check
   213  		err := d.CheckStorageLimits(ctx, env, executionSnapshot, flow.EmptyAddress, 0)
   214  		require.Error(t, err)
   215  
   216  		// if EVM is enabled skip the storage check
   217  		ctx := fvm.NewContextFromParent(ctx, fvm.WithEVMEnabled(true))
   218  		err = d.CheckStorageLimits(ctx, env, executionSnapshot, flow.EmptyAddress, 0)
   219  		require.NoError(t, err)
   220  	})
   221  }
   222  
   223  func bytesToUFix64(b uint64) cadence.Value {
   224  	return cadence.UFix64(b * 100)
   225  }