github.com/koko1123/flow-go-1@v0.29.6/fvm/fvm_fuzz_test.go (about) 1 package fvm_test 2 3 import ( 4 "fmt" 5 "math" 6 "testing" 7 8 "github.com/stretchr/testify/require" 9 10 "github.com/onflow/cadence" 11 jsoncdc "github.com/onflow/cadence/encoding/json" 12 13 "github.com/koko1123/flow-go-1/engine/execution/testutil" 14 "github.com/koko1123/flow-go-1/fvm" 15 "github.com/koko1123/flow-go-1/fvm/derived" 16 "github.com/koko1123/flow-go-1/fvm/environment" 17 "github.com/koko1123/flow-go-1/fvm/errors" 18 "github.com/koko1123/flow-go-1/fvm/meter" 19 "github.com/koko1123/flow-go-1/fvm/state" 20 "github.com/koko1123/flow-go-1/model/flow" 21 "github.com/koko1123/flow-go-1/utils/unittest" 22 ) 23 24 func FuzzTransactionComputationLimit(f *testing.F) { 25 // setup initial state 26 vmt, tctx := bootstrapFuzzStateAndTxContext(f) 27 28 f.Add(uint64(0), uint64(0), uint64(0), uint(0)) 29 f.Add(uint64(5), uint64(0), uint64(0), uint(0)) 30 f.Fuzz(func(t *testing.T, computationLimit uint64, memoryLimit uint64, interactionLimit uint64, transactionType uint) { 31 computationLimit %= flow.DefaultMaxTransactionGasLimit 32 transactionType %= uint(len(fuzzTransactionTypes)) 33 34 tt := fuzzTransactionTypes[transactionType] 35 36 vmt.run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) { 37 // create the transaction 38 txBody := tt.createTxBody(t, tctx) 39 // set the computation limit 40 txBody.SetGasLimit(computationLimit) 41 42 // sign the transaction 43 err := testutil.SignEnvelope( 44 txBody, 45 tctx.address, 46 tctx.privateKey, 47 ) 48 require.NoError(t, err) 49 50 // set the memory limit 51 ctx.MemoryLimit = memoryLimit 52 // set the interaction limit 53 ctx.MaxStateInteractionSize = interactionLimit 54 // run the transaction 55 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 56 57 require.NotPanics(t, func() { 58 err = vm.Run(ctx, tx, view) 59 }, "Transaction should never result in a panic.") 60 require.NoError(t, err, "Transaction should never result in an error.") 61 62 // check if results are expected 63 tt.require(t, tctx, fuzzResults{ 64 tx: tx, 65 }) 66 })(t) 67 }) 68 } 69 70 type fuzzResults struct { 71 tx *fvm.TransactionProcedure 72 } 73 74 type transactionTypeContext struct { 75 address flow.Address 76 addressFunds uint64 77 privateKey flow.AccountPrivateKey 78 chain flow.Chain 79 } 80 81 type transactionType struct { 82 createTxBody func(t *testing.T, tctx transactionTypeContext) *flow.TransactionBody 83 require func(t *testing.T, tctx transactionTypeContext, results fuzzResults) 84 } 85 86 var fuzzTransactionTypes = []transactionType{ 87 { 88 // Token transfer of 0 tokens. 89 // should succeed if no limits are hit. 90 // fees should be deducted no matter what. 91 createTxBody: func(t *testing.T, tctx transactionTypeContext) *flow.TransactionBody { 92 txBody := transferTokensTx(tctx.chain). 93 AddAuthorizer(tctx.address). 94 AddArgument(jsoncdc.MustEncode(cadence.UFix64(0))). // 0 value transferred 95 AddArgument(jsoncdc.MustEncode(cadence.NewAddress(tctx.chain.ServiceAddress()))) 96 97 txBody.SetProposalKey(tctx.address, 0, 0) 98 txBody.SetPayer(tctx.address) 99 return txBody 100 }, 101 require: func(t *testing.T, tctx transactionTypeContext, results fuzzResults) { 102 // if there is an error, it should be computation exceeded 103 if results.tx.Err != nil { 104 require.Len(t, results.tx.Events, 3) 105 unittest.EnsureEventsIndexSeq(t, results.tx.Events, tctx.chain.ChainID()) 106 codes := []errors.ErrorCode{ 107 errors.ErrCodeComputationLimitExceededError, 108 errors.ErrCodeCadenceRunTimeError, 109 errors.ErrCodeLedgerInteractionLimitExceededError, 110 } 111 require.Contains(t, codes, results.tx.Err.Code(), results.tx.Err.Error()) 112 } 113 114 // fees should be deducted no matter the input 115 fees, deducted := getDeductedFees(t, tctx, results) 116 require.True(t, deducted, "Fees should be deducted.") 117 require.GreaterOrEqual(t, fees.ToGoValue().(uint64), fuzzTestsInclusionFees) 118 unittest.EnsureEventsIndexSeq(t, results.tx.Events, tctx.chain.ChainID()) 119 }, 120 }, 121 { 122 // Token transfer of too many tokens. 123 // Should never succeed. 124 // fees should be deducted no matter what. 125 createTxBody: func(t *testing.T, tctx transactionTypeContext) *flow.TransactionBody { 126 txBody := transferTokensTx(tctx.chain). 127 AddAuthorizer(tctx.address). 128 AddArgument(jsoncdc.MustEncode(cadence.UFix64(2 * tctx.addressFunds))). // too much value transferred 129 AddArgument(jsoncdc.MustEncode(cadence.NewAddress(tctx.chain.ServiceAddress()))) 130 131 txBody.SetProposalKey(tctx.address, 0, 0) 132 txBody.SetPayer(tctx.address) 133 return txBody 134 }, 135 require: func(t *testing.T, tctx transactionTypeContext, results fuzzResults) { 136 require.Error(t, results.tx.Err) 137 require.Len(t, results.tx.Events, 3) 138 unittest.EnsureEventsIndexSeq(t, results.tx.Events, tctx.chain.ChainID()) 139 codes := []errors.ErrorCode{ 140 errors.ErrCodeComputationLimitExceededError, 141 errors.ErrCodeCadenceRunTimeError, // because of the failed transfer 142 errors.ErrCodeLedgerInteractionLimitExceededError, 143 } 144 require.Contains(t, codes, results.tx.Err.Code(), results.tx.Err.Error()) 145 146 // fees should be deducted no matter the input 147 fees, deducted := getDeductedFees(t, tctx, results) 148 require.True(t, deducted, "Fees should be deducted.") 149 require.GreaterOrEqual(t, fees.ToGoValue().(uint64), fuzzTestsInclusionFees) 150 unittest.EnsureEventsIndexSeq(t, results.tx.Events, tctx.chain.ChainID()) 151 }, 152 }, 153 { 154 // Transaction that calls panic. 155 // Should never succeed. 156 // fees should be deducted no matter what. 157 createTxBody: func(t *testing.T, tctx transactionTypeContext) *flow.TransactionBody { 158 // empty transaction 159 txBody := flow.NewTransactionBody().SetScript([]byte("transaction(){prepare(){};execute{panic(\"some panic\")}}")) 160 txBody.SetProposalKey(tctx.address, 0, 0) 161 txBody.SetPayer(tctx.address) 162 return txBody 163 }, 164 require: func(t *testing.T, tctx transactionTypeContext, results fuzzResults) { 165 require.Error(t, results.tx.Err) 166 require.Len(t, results.tx.Events, 3) 167 unittest.EnsureEventsIndexSeq(t, results.tx.Events, tctx.chain.ChainID()) 168 codes := []errors.ErrorCode{ 169 errors.ErrCodeComputationLimitExceededError, 170 errors.ErrCodeCadenceRunTimeError, // because of the panic 171 errors.ErrCodeLedgerInteractionLimitExceededError, 172 } 173 require.Contains(t, codes, results.tx.Err.Code(), results.tx.Err.Error()) 174 175 // fees should be deducted no matter the input 176 fees, deducted := getDeductedFees(t, tctx, results) 177 require.True(t, deducted, "Fees should be deducted.") 178 require.GreaterOrEqual(t, fees.ToGoValue().(uint64), fuzzTestsInclusionFees) 179 unittest.EnsureEventsIndexSeq(t, results.tx.Events, tctx.chain.ChainID()) 180 }, 181 }, 182 { 183 createTxBody: func(t *testing.T, tctx transactionTypeContext) *flow.TransactionBody { 184 // create account 185 txBody := flow.NewTransactionBody().SetScript(createAccountScript). 186 AddAuthorizer(tctx.address) 187 txBody.SetProposalKey(tctx.address, 0, 0) 188 txBody.SetPayer(tctx.address) 189 return txBody 190 }, 191 require: func(t *testing.T, tctx transactionTypeContext, results fuzzResults) { 192 // if there is an error, it should be computation exceeded 193 if results.tx.Err != nil { 194 require.Len(t, results.tx.Events, 3) 195 unittest.EnsureEventsIndexSeq(t, results.tx.Events, tctx.chain.ChainID()) 196 codes := []errors.ErrorCode{ 197 errors.ErrCodeComputationLimitExceededError, 198 errors.ErrCodeCadenceRunTimeError, 199 errors.ErrCodeLedgerInteractionLimitExceededError, 200 } 201 require.Contains(t, codes, results.tx.Err.Code(), results.tx.Err.Error()) 202 } 203 204 // fees should be deducted no matter the input 205 fees, deducted := getDeductedFees(t, tctx, results) 206 require.True(t, deducted, "Fees should be deducted.") 207 require.GreaterOrEqual(t, fees.ToGoValue().(uint64), fuzzTestsInclusionFees) 208 unittest.EnsureEventsIndexSeq(t, results.tx.Events, tctx.chain.ChainID()) 209 }, 210 }, 211 } 212 213 const fuzzTestsInclusionFees = uint64(1_000) 214 215 // checks fee deduction happened and returns the amount of funds deducted 216 func getDeductedFees(tb testing.TB, tctx transactionTypeContext, results fuzzResults) (fees cadence.UFix64, deducted bool) { 217 tb.Helper() 218 219 var ok bool 220 var feesDeductedEvent cadence.Event 221 for _, e := range results.tx.Events { 222 if string(e.Type) == fmt.Sprintf("A.%s.FlowFees.FeesDeducted", environment.FlowFeesAddress(tctx.chain)) { 223 data, err := jsoncdc.Decode(nil, e.Payload) 224 require.NoError(tb, err) 225 feesDeductedEvent, ok = data.(cadence.Event) 226 require.True(tb, ok, "Event payload should be of type cadence event.") 227 } 228 } 229 if feesDeductedEvent.Type() == nil { 230 return 0, false 231 } 232 fees, ok = feesDeductedEvent.Fields[0].(cadence.UFix64) 233 require.True(tb, ok, "FeesDeducted[0] event should be of type cadence.UFix64.") 234 return fees, true 235 } 236 237 func bootstrapFuzzStateAndTxContext(tb testing.TB) (bootstrappedVmTest, transactionTypeContext) { 238 tb.Helper() 239 240 addressFunds := uint64(1_000_000_000) 241 var privateKey flow.AccountPrivateKey 242 var address flow.Address 243 bootstrappedVMTest, err := newVMTest().withBootstrapProcedureOptions( 244 fvm.WithTransactionFee(fvm.DefaultTransactionFees), 245 fvm.WithExecutionMemoryLimit(math.MaxUint32), 246 fvm.WithExecutionEffortWeights(mainnetExecutionEffortWeights), 247 fvm.WithExecutionMemoryWeights(meter.DefaultMemoryWeights), 248 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 249 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 250 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 251 ).withContextOptions( 252 fvm.WithTransactionFeesEnabled(true), 253 fvm.WithAccountStorageLimit(true), 254 ).bootstrapWith(func(vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) error { 255 // ==== Create an account ==== 256 var txBody *flow.TransactionBody 257 privateKey, txBody = testutil.CreateAccountCreationTransaction(tb, chain) 258 259 err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) 260 if err != nil { 261 return err 262 } 263 264 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 265 266 err = vm.Run(ctx, tx, view) 267 268 require.NoError(tb, err) 269 accountCreatedEvents := filterAccountCreatedEvents(tx.Events) 270 271 // read the address of the account created (e.g. "0x01" and convert it to flow.address) 272 data, err := jsoncdc.Decode(nil, accountCreatedEvents[0].Payload) 273 require.NoError(tb, err) 274 275 address = flow.Address(data.(cadence.Event).Fields[0].(cadence.Address)) 276 277 // ==== Transfer tokens to new account ==== 278 txBody = transferTokensTx(chain). 279 AddAuthorizer(chain.ServiceAddress()). 280 AddArgument(jsoncdc.MustEncode(cadence.UFix64(1_000_000_000))). // 10 FLOW 281 AddArgument(jsoncdc.MustEncode(cadence.NewAddress(address))) 282 283 txBody.SetProposalKey(chain.ServiceAddress(), 0, 1) 284 txBody.SetPayer(chain.ServiceAddress()) 285 286 err = testutil.SignEnvelope( 287 txBody, 288 chain.ServiceAddress(), 289 unittest.ServiceAccountPrivateKey, 290 ) 291 require.NoError(tb, err) 292 293 tx = fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 294 295 err = vm.Run(ctx, tx, view) 296 if err != nil { 297 return err 298 } 299 300 return tx.Err 301 }) 302 require.NoError(tb, err) 303 304 return bootstrappedVMTest, 305 transactionTypeContext{ 306 address: address, 307 addressFunds: addressFunds, 308 privateKey: privateKey, 309 chain: bootstrappedVMTest.chain, 310 } 311 }