github.com/koko1123/flow-go-1@v0.29.6/fvm/fvm_test.go (about) 1 package fvm_test 2 3 import ( 4 "crypto/rand" 5 "encoding/hex" 6 "fmt" 7 "math" 8 "strings" 9 "testing" 10 11 "github.com/onflow/cadence" 12 jsoncdc "github.com/onflow/cadence/encoding/json" 13 "github.com/onflow/cadence/runtime" 14 "github.com/onflow/cadence/runtime/common" 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 18 "github.com/onflow/flow-go/crypto" 19 20 "github.com/koko1123/flow-go-1/engine/execution/testutil" 21 exeUtils "github.com/koko1123/flow-go-1/engine/execution/utils" 22 "github.com/koko1123/flow-go-1/fvm" 23 fvmCrypto "github.com/koko1123/flow-go-1/fvm/crypto" 24 "github.com/koko1123/flow-go-1/fvm/derived" 25 "github.com/koko1123/flow-go-1/fvm/environment" 26 errors "github.com/koko1123/flow-go-1/fvm/errors" 27 "github.com/koko1123/flow-go-1/fvm/meter" 28 "github.com/koko1123/flow-go-1/fvm/state" 29 "github.com/koko1123/flow-go-1/fvm/utils" 30 "github.com/koko1123/flow-go-1/model/flow" 31 "github.com/koko1123/flow-go-1/utils/unittest" 32 ) 33 34 // from 18.8.2022 35 var mainnetExecutionEffortWeights = meter.ExecutionEffortWeights{ 36 common.ComputationKindStatement: 1569, 37 common.ComputationKindLoop: 1569, 38 common.ComputationKindFunctionInvocation: 1569, 39 meter.ComputationKindGetValue: 808, 40 meter.ComputationKindCreateAccount: 2837670, 41 meter.ComputationKindSetValue: 765, 42 } 43 44 type vmTest struct { 45 bootstrapOptions []fvm.BootstrapProcedureOption 46 contextOptions []fvm.Option 47 } 48 49 func newVMTest() vmTest { 50 return vmTest{} 51 } 52 53 func (vmt vmTest) withBootstrapProcedureOptions(opts ...fvm.BootstrapProcedureOption) vmTest { 54 vmt.bootstrapOptions = append(vmt.bootstrapOptions, opts...) 55 return vmt 56 } 57 58 func (vmt vmTest) withContextOptions(opts ...fvm.Option) vmTest { 59 vmt.contextOptions = append(vmt.contextOptions, opts...) 60 return vmt 61 } 62 63 func createChainAndVm(chainID flow.ChainID) (flow.Chain, fvm.VM) { 64 return chainID.Chain(), fvm.NewVirtualMachine() 65 } 66 67 func (vmt vmTest) run( 68 f func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData), 69 ) func(t *testing.T) { 70 return func(t *testing.T) { 71 chain, vm := createChainAndVm(flow.Testnet) 72 derivedBlockData := derived.NewEmptyDerivedBlockData() 73 74 baseOpts := []fvm.Option{ 75 fvm.WithChain(chain), 76 fvm.WithDerivedBlockData(derivedBlockData), 77 } 78 79 opts := append(baseOpts, vmt.contextOptions...) 80 81 ctx := fvm.NewContext(opts...) 82 83 view := utils.NewSimpleView() 84 85 baseBootstrapOpts := []fvm.BootstrapProcedureOption{ 86 fvm.WithInitialTokenSupply(unittest.GenesisTokenSupply), 87 } 88 89 bootstrapOpts := append(baseBootstrapOpts, vmt.bootstrapOptions...) 90 91 err := vm.Run(ctx, fvm.Bootstrap(unittest.ServiceAccountPublicKey, bootstrapOpts...), view) 92 require.NoError(t, err) 93 94 f(t, vm, chain, ctx, view, derivedBlockData) 95 } 96 } 97 98 // bootstrapWith executes the bootstrap procedure and the custom bootstrap function 99 // and returns a prepared bootstrappedVmTest with all the state needed 100 func (vmt vmTest) bootstrapWith( 101 bootstrap func(vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) error, 102 ) (bootstrappedVmTest, error) { 103 chain, vm := createChainAndVm(flow.Testnet) 104 105 baseOpts := []fvm.Option{ 106 fvm.WithChain(chain), 107 } 108 109 opts := append(baseOpts, vmt.contextOptions...) 110 111 ctx := fvm.NewContext(opts...) 112 113 view := utils.NewSimpleView() 114 115 baseBootstrapOpts := []fvm.BootstrapProcedureOption{ 116 fvm.WithInitialTokenSupply(unittest.GenesisTokenSupply), 117 } 118 119 derivedBlockData := derived.NewEmptyDerivedBlockData() 120 121 bootstrapOpts := append(baseBootstrapOpts, vmt.bootstrapOptions...) 122 123 err := vm.Run(ctx, fvm.Bootstrap(unittest.ServiceAccountPublicKey, bootstrapOpts...), view) 124 if err != nil { 125 return bootstrappedVmTest{}, err 126 } 127 128 err = bootstrap(vm, chain, ctx, view, derivedBlockData) 129 if err != nil { 130 return bootstrappedVmTest{}, err 131 } 132 133 return bootstrappedVmTest{chain, ctx, view, derivedBlockData}, nil 134 } 135 136 type bootstrappedVmTest struct { 137 chain flow.Chain 138 ctx fvm.Context 139 view state.View 140 derivedBlockData *derived.DerivedBlockData 141 } 142 143 // run Runs a test from the bootstrapped state, without changing the bootstrapped state 144 func (vmt bootstrappedVmTest) run( 145 f func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData), 146 ) func(t *testing.T) { 147 return func(t *testing.T) { 148 f(t, fvm.NewVirtualMachine(), vmt.chain, vmt.ctx, vmt.view.NewChild(), vmt.derivedBlockData.NewChildDerivedBlockData()) 149 } 150 } 151 152 func TestPrograms(t *testing.T) { 153 154 t.Run( 155 "transaction execution derivedBlockData are committed", 156 newVMTest().run( 157 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) { 158 159 txCtx := fvm.NewContextFromParent(ctx) 160 161 for i := 0; i < 10; i++ { 162 163 script := []byte(fmt.Sprintf(` 164 import FungibleToken from %s 165 166 transaction {} 167 `, 168 fvm.FungibleTokenAddress(chain).HexWithPrefix(), 169 )) 170 171 serviceAddress := chain.ServiceAddress() 172 173 txBody := flow.NewTransactionBody(). 174 SetScript(script). 175 SetProposalKey(serviceAddress, 0, uint64(i)). 176 SetPayer(serviceAddress) 177 178 err := testutil.SignEnvelope( 179 txBody, 180 serviceAddress, 181 unittest.ServiceAccountPrivateKey, 182 ) 183 require.NoError(t, err) 184 185 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 186 187 err = vm.Run(txCtx, tx, view) 188 require.NoError(t, err) 189 190 require.NoError(t, tx.Err) 191 } 192 }, 193 ), 194 ) 195 196 t.Run("script execution derivedBlockData are not committed", 197 newVMTest().withBootstrapProcedureOptions().run( 198 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) { 199 200 scriptCtx := fvm.NewContextFromParent(ctx) 201 202 script := fvm.Script([]byte(fmt.Sprintf(` 203 204 import FungibleToken from %s 205 206 pub fun main() {} 207 `, 208 fvm.FungibleTokenAddress(chain).HexWithPrefix(), 209 ))) 210 211 err := vm.Run(scriptCtx, script, view) 212 require.NoError(t, err) 213 require.NoError(t, script.Err) 214 }, 215 ), 216 ) 217 } 218 219 func TestHashing(t *testing.T) { 220 221 t.Parallel() 222 223 chain, vm := createChainAndVm(flow.Mainnet) 224 225 ctx := fvm.NewContext( 226 fvm.WithChain(chain), 227 fvm.WithCadenceLogging(true), 228 ) 229 230 ledger := testutil.RootBootstrappedLedger(vm, ctx) 231 232 hashScript := func(hashName string) []byte { 233 return []byte(fmt.Sprintf( 234 ` 235 import Crypto 236 237 pub fun main(data: [UInt8]): [UInt8] { 238 return Crypto.hash(data, algorithm: HashAlgorithm.%s) 239 } 240 `, hashName)) 241 } 242 hashWithTagScript := func(hashName string) []byte { 243 return []byte(fmt.Sprintf( 244 ` 245 import Crypto 246 247 pub fun main(data: [UInt8], tag: String): [UInt8] { 248 return Crypto.hashWithTag(data, tag: tag, algorithm: HashAlgorithm.%s) 249 } 250 `, hashName)) 251 } 252 253 data := []byte("some random message") 254 encodedBytes := make([]cadence.Value, len(data)) 255 for i := range encodedBytes { 256 encodedBytes[i] = cadence.NewUInt8(data[i]) 257 } 258 cadenceData := jsoncdc.MustEncode(cadence.NewArray(encodedBytes)) 259 260 // ===== Test Cases ===== 261 cases := []struct { 262 Algo runtime.HashAlgorithm 263 WithTag bool 264 Tag string 265 Check func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) 266 }{ 267 { 268 Algo: runtime.HashAlgorithmSHA2_256, 269 WithTag: false, 270 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 271 require.NoError(t, scriptErr) 272 require.NoError(t, executionErr) 273 require.Equal(t, "68fb87dfba69b956f4ba98b748a75a604f99b38a4f2740290037957f7e830da8", result) 274 }, 275 }, 276 { 277 Algo: runtime.HashAlgorithmSHA2_384, 278 WithTag: false, 279 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 280 require.NoError(t, scriptErr) 281 require.NoError(t, executionErr) 282 require.Equal(t, "a9b3e62ab9b2a33020e015f245b82e063afd1398211326408bc8fc31c2c15859594b0aee263fbb02f6d8b5065ad49df2", result) 283 }, 284 }, 285 { 286 Algo: runtime.HashAlgorithmSHA3_256, 287 WithTag: false, 288 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 289 require.NoError(t, scriptErr) 290 require.NoError(t, executionErr) 291 require.Equal(t, "38effea5ab9082a2cb0dc9adfafaf88523e8f3ce74bfbeac85ffc719cc2c4677", result) 292 }, 293 }, 294 { 295 Algo: runtime.HashAlgorithmSHA3_384, 296 WithTag: false, 297 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 298 require.NoError(t, scriptErr) 299 require.NoError(t, executionErr) 300 require.Equal(t, "f41e8de9af0c1f46fc56d5a776f1bd500530879a85f3b904821810295927e13a54f3e936dddb84669021052eb12966c3", result) 301 }, 302 }, 303 { 304 Algo: runtime.HashAlgorithmKECCAK_256, 305 WithTag: false, 306 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 307 require.NoError(t, scriptErr) 308 require.NoError(t, executionErr) 309 require.Equal(t, "1d5ced4738dd4e0bb4628dad7a7b59b8e339a75ece97a4ad004773a49ed7b5bc", result) 310 }, 311 }, 312 { 313 Algo: runtime.HashAlgorithmKECCAK_256, 314 WithTag: true, 315 Tag: "some_tag", 316 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 317 require.NoError(t, scriptErr) 318 require.NoError(t, executionErr) 319 require.Equal(t, "8454ec77f76b229a473770c91e3ea6e7e852416d747805215d15d53bdc56ce5f", result) 320 }, 321 }, 322 { 323 Algo: runtime.HashAlgorithmSHA2_256, 324 WithTag: true, 325 Tag: "some_tag", 326 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 327 require.NoError(t, scriptErr) 328 require.NoError(t, executionErr) 329 require.Equal(t, "4e07609b9a856a5e10703d1dba73be34d9ca0f4e780859d66983f41d746ec8b2", result) 330 }, 331 }, 332 { 333 Algo: runtime.HashAlgorithmSHA2_384, 334 WithTag: true, 335 Tag: "some_tag", 336 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 337 require.NoError(t, scriptErr) 338 require.NoError(t, executionErr) 339 require.Equal(t, "f9bd89e15f341a225656944dc8b3c405e66a0f97838ad44c9803164c911e677aea7ad4e24486fba3f803d83ed1ccfce5", result) 340 }, 341 }, 342 { 343 Algo: runtime.HashAlgorithmSHA3_256, 344 WithTag: true, 345 Tag: "some_tag", 346 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 347 require.NoError(t, scriptErr) 348 require.NoError(t, executionErr) 349 require.Equal(t, "f59e2ccc9d7f008a96948a31573670d9976a4a161601ab1cd1d2da019779a0f6", result) 350 }, 351 }, 352 { 353 Algo: runtime.HashAlgorithmSHA3_384, 354 WithTag: true, 355 Tag: "some_tag", 356 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 357 require.NoError(t, scriptErr) 358 require.NoError(t, executionErr) 359 require.Equal(t, "e7875eafdb53327faeace8478d1650c6547d04fb4fb42f34509ad64bde0267bea7e1b3af8fda3ef9d9c9327dd4e97a96", result) 360 }, 361 }, 362 { 363 Algo: runtime.HashAlgorithmKMAC128_BLS_BLS12_381, 364 WithTag: false, 365 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 366 require.NoError(t, scriptErr) 367 require.NoError(t, executionErr) 368 require.Equal(t, "44dc46111abacfe2bb4a04cea4805aad03f84e4849f138cc3ed431478472b185548628e96d0c963b21ebaf17132d73fc13031eb82d5f4cbe3b6047ff54d20e8d663904373d73348b97ce18305ebc56114cb7e7394e486684007f78aa59abc5d0a8f6bae6bd186db32528af80857cd12112ce6960be29c96074df9c4aaed5b0e6", result) 369 }, 370 }, 371 { 372 Algo: runtime.HashAlgorithmKMAC128_BLS_BLS12_381, 373 WithTag: true, 374 Tag: "some_tag", 375 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 376 require.NoError(t, scriptErr) 377 require.NoError(t, executionErr) 378 require.Equal(t, "de7d9aa24274fa12c98cce5c09eea0634108ead2e91828b9a9a450e878088393e3e63eb4b19834f579ce215b00a9915919b67a71dab1112560319e6e1e5e9ad0fb670e8a09d586508c84547cee7ddbe8c9362c996846154865eb271bdc4523dbcdbdae5a77391fb54374f37534c8bb2281589cb2e3d62742596cdad7e4f9f35c", result) 379 }, 380 }, 381 } 382 // ====================== 383 384 for i, c := range cases { 385 t.Run(fmt.Sprintf("case %d: %s with tag: %v", i, c.Algo, c.WithTag), func(t *testing.T) { 386 code := hashScript(c.Algo.Name()) 387 if c.WithTag { 388 code = hashWithTagScript(c.Algo.Name()) 389 } 390 391 script := fvm.Script(code) 392 393 if c.WithTag { 394 script = script.WithArguments( 395 cadenceData, 396 jsoncdc.MustEncode(cadence.String(c.Tag)), 397 ) 398 } else { 399 script = script.WithArguments( 400 cadenceData, 401 ) 402 } 403 404 err := vm.Run(ctx, script, ledger) 405 406 byteResult := make([]byte, 0) 407 if err == nil && script.Err == nil { 408 cadenceArray := script.Value.(cadence.Array) 409 for _, value := range cadenceArray.Values { 410 byteResult = append(byteResult, value.(cadence.UInt8).ToGoValue().(uint8)) 411 } 412 } 413 414 c.Check(t, hex.EncodeToString(byteResult), script.Err, err) 415 }) 416 } 417 418 hashAlgos := []runtime.HashAlgorithm{ 419 runtime.HashAlgorithmSHA2_256, 420 runtime.HashAlgorithmSHA3_256, 421 runtime.HashAlgorithmSHA2_384, 422 runtime.HashAlgorithmSHA3_384, 423 runtime.HashAlgorithmKMAC128_BLS_BLS12_381, 424 runtime.HashAlgorithmKECCAK_256, 425 } 426 427 for i, algo := range hashAlgos { 428 t.Run(fmt.Sprintf("compare hash results without tag %v: %v", i, algo), func(t *testing.T) { 429 code := hashWithTagScript(algo.Name()) 430 script := fvm.Script(code) 431 script = script.WithArguments( 432 cadenceData, 433 jsoncdc.MustEncode(cadence.String("")), 434 ) 435 err := vm.Run(ctx, script, ledger) 436 require.NoError(t, err) 437 require.NoError(t, script.Err) 438 439 result1 := make([]byte, 0) 440 cadenceArray := script.Value.(cadence.Array) 441 for _, value := range cadenceArray.Values { 442 result1 = append(result1, value.(cadence.UInt8).ToGoValue().(uint8)) 443 } 444 445 code = hashScript(algo.Name()) 446 script = fvm.Script(code) 447 script = script.WithArguments( 448 cadenceData, 449 ) 450 err = vm.Run(ctx, script, ledger) 451 require.NoError(t, err) 452 require.NoError(t, script.Err) 453 454 result2 := make([]byte, 0) 455 cadenceArray = script.Value.(cadence.Array) 456 for _, value := range cadenceArray.Values { 457 result2 = append(result2, value.(cadence.UInt8).ToGoValue().(uint8)) 458 } 459 460 result3, err := fvmCrypto.HashWithTag(fvmCrypto.RuntimeToCryptoHashingAlgorithm(algo), "", data) 461 require.NoError(t, err) 462 463 require.Equal(t, result1, result2) 464 require.Equal(t, result1, result3) 465 }) 466 } 467 } 468 469 func TestWithServiceAccount(t *testing.T) { 470 471 t.Parallel() 472 473 chain, vm := createChainAndVm(flow.Mainnet) 474 475 ctxA := fvm.NewContext( 476 fvm.WithChain(chain), 477 fvm.WithAuthorizationChecksEnabled(false), 478 fvm.WithSequenceNumberCheckAndIncrementEnabled(false), 479 ) 480 481 view := utils.NewSimpleView() 482 483 txBody := flow.NewTransactionBody(). 484 SetScript([]byte(`transaction { prepare(signer: AuthAccount) { AuthAccount(payer: signer) } }`)). 485 AddAuthorizer(chain.ServiceAddress()) 486 487 t.Run("With service account enabled", func(t *testing.T) { 488 derivedBlockData := derived.NewEmptyDerivedBlockData() 489 ctxB := fvm.NewContextFromParent( 490 ctxA, 491 fvm.WithDerivedBlockData(derivedBlockData)) 492 493 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 494 495 err := vm.Run(ctxB, tx, view) 496 require.NoError(t, err) 497 498 // transaction should fail on non-bootstrapped ledger 499 require.Error(t, tx.Err) 500 }) 501 502 t.Run("With service account disabled", func(t *testing.T) { 503 derivedBlockData := derived.NewEmptyDerivedBlockData() 504 ctxB := fvm.NewContextFromParent( 505 ctxA, 506 fvm.WithServiceAccount(false), 507 fvm.WithDerivedBlockData(derivedBlockData)) 508 509 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 510 511 err := vm.Run(ctxB, tx, view) 512 require.NoError(t, err) 513 514 // transaction should succeed on non-bootstrapped ledger 515 assert.NoError(t, tx.Err) 516 }) 517 } 518 519 func TestEventLimits(t *testing.T) { 520 chain, vm := createChainAndVm(flow.Mainnet) 521 derivedBlockData := derived.NewEmptyDerivedBlockData() 522 523 ctx := fvm.NewContext( 524 fvm.WithChain(chain), 525 fvm.WithAuthorizationChecksEnabled(false), 526 fvm.WithSequenceNumberCheckAndIncrementEnabled(false), 527 fvm.WithDerivedBlockData(derivedBlockData), 528 ) 529 530 ledger := testutil.RootBootstrappedLedger(vm, ctx) 531 532 testContract := ` 533 access(all) contract TestContract { 534 access(all) event LargeEvent(value: Int256, str: String, list: [UInt256], dic: {String: String}) 535 access(all) fun EmitEvent() { 536 var s: Int256 = 1024102410241024 537 var i = 0 538 539 while i < 20 { 540 emit LargeEvent(value: s, str: s.toString(), list:[], dic:{s.toString():s.toString()}) 541 i = i + 1 542 } 543 } 544 } 545 ` 546 547 deployingContractScriptTemplate := ` 548 transaction { 549 prepare(signer: AuthAccount) { 550 let code = "%s".decodeHex() 551 signer.contracts.add( 552 name: "TestContract", 553 code: code 554 ) 555 } 556 } 557 ` 558 559 ctx = fvm.NewContextFromParent( 560 ctx, 561 fvm.WithEventCollectionSizeLimit(2)) 562 563 txBody := flow.NewTransactionBody(). 564 SetScript([]byte(fmt.Sprintf(deployingContractScriptTemplate, hex.EncodeToString([]byte(testContract))))). 565 SetPayer(chain.ServiceAddress()). 566 AddAuthorizer(chain.ServiceAddress()) 567 568 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 569 err := vm.Run(ctx, tx, ledger) 570 require.NoError(t, err) 571 572 txBody = flow.NewTransactionBody(). 573 SetScript([]byte(fmt.Sprintf(` 574 import TestContract from 0x%s 575 transaction { 576 prepare(acct: AuthAccount) {} 577 execute { 578 TestContract.EmitEvent() 579 } 580 }`, chain.ServiceAddress()))). 581 AddAuthorizer(chain.ServiceAddress()) 582 583 t.Run("With limits", func(t *testing.T) { 584 txBody.Payer = unittest.RandomAddressFixture() 585 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 586 err := vm.Run(ctx, tx, ledger) 587 require.NoError(t, err) 588 589 // transaction should fail due to event size limit 590 assert.Error(t, tx.Err) 591 }) 592 593 t.Run("With service account as payer", func(t *testing.T) { 594 txBody.Payer = chain.ServiceAddress() 595 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 596 err := vm.Run(ctx, tx, ledger) 597 require.NoError(t, err) 598 599 unittest.EnsureEventsIndexSeq(t, tx.Events, chain.ChainID()) 600 601 // transaction should not fail due to event size limit 602 assert.NoError(t, tx.Err) 603 }) 604 } 605 606 // TestHappyPathSigning checks that a signing a transaction with `Sign` doesn't produce an error. 607 // Transaction verification tests are in `TestVerifySignatureFromTransaction`. 608 func TestHappyPathTransactionSigning(t *testing.T) { 609 610 newVMTest().run( 611 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) { 612 // Create an account private key. 613 privateKey, err := testutil.GenerateAccountPrivateKey() 614 require.NoError(t, err) 615 616 // Bootstrap a ledger, creating accounts with the provided private keys and the root account. 617 accounts, err := testutil.CreateAccounts(vm, view, derivedBlockData, []flow.AccountPrivateKey{privateKey}, chain) 618 require.NoError(t, err) 619 620 txBody := flow.NewTransactionBody(). 621 SetScript([]byte(`transaction(){}`)) 622 623 txBody.SetProposalKey(accounts[0], 0, 0) 624 txBody.SetPayer(accounts[0]) 625 626 hasher, err := exeUtils.NewHasher(privateKey.HashAlgo) 627 require.NoError(t, err) 628 629 sig, err := txBody.Sign(txBody.EnvelopeMessage(), privateKey.PrivateKey, hasher) 630 require.NoError(t, err) 631 txBody.AddEnvelopeSignature(accounts[0], 0, sig) 632 633 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 634 635 err = vm.Run(ctx, tx, view) 636 require.NoError(t, err) 637 require.NoError(t, tx.Err) 638 }, 639 ) 640 } 641 642 func TestTransactionFeeDeduction(t *testing.T) { 643 getBalance := func(vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, address flow.Address) uint64 { 644 645 code := []byte(fmt.Sprintf(` 646 import FungibleToken from 0x%s 647 import FlowToken from 0x%s 648 649 pub fun main(account: Address): UFix64 { 650 let acct = getAccount(account) 651 let vaultRef = acct.getCapability(/public/flowTokenBalance) 652 .borrow<&FlowToken.Vault{FungibleToken.Balance}>() 653 ?? panic("Could not borrow Balance reference to the Vault") 654 655 return vaultRef.balance 656 } 657 `, fvm.FungibleTokenAddress(chain), fvm.FlowTokenAddress(chain))) 658 script := fvm.Script(code).WithArguments( 659 jsoncdc.MustEncode(cadence.NewAddress(address)), 660 ) 661 662 err := vm.Run(ctx, script, view) 663 require.NoError(t, err) 664 require.NoError(t, script.Err) 665 return script.Value.ToGoValue().(uint64) 666 } 667 668 type testCase struct { 669 name string 670 fundWith uint64 671 tryToTransfer uint64 672 gasLimit uint64 673 checkResult func(t *testing.T, balanceBefore uint64, balanceAfter uint64, tx *fvm.TransactionProcedure) 674 } 675 676 txFees := uint64(1_000) // 0.00001 677 fundingAmount := uint64(100_000_000) // 1.0 678 transferAmount := uint64(123_456) 679 minimumStorageReservation := fvm.DefaultMinimumStorageReservation.ToGoValue().(uint64) 680 681 testCases := []testCase{ 682 { 683 name: "Transaction fees are deducted", 684 fundWith: fundingAmount, 685 tryToTransfer: 0, 686 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, tx *fvm.TransactionProcedure) { 687 require.NoError(t, tx.Err) 688 require.Equal(t, txFees, balanceBefore-balanceAfter) 689 }, 690 }, 691 { 692 name: "Transaction fee deduction emits events", 693 fundWith: fundingAmount, 694 tryToTransfer: 0, 695 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, tx *fvm.TransactionProcedure) { 696 require.NoError(t, tx.Err) 697 698 var deposits []flow.Event 699 var withdraws []flow.Event 700 701 chain := flow.Testnet.Chain() 702 for _, e := range tx.Events { 703 if string(e.Type) == fmt.Sprintf("A.%s.FlowToken.TokensDeposited", fvm.FlowTokenAddress(chain)) { 704 deposits = append(deposits, e) 705 } 706 if string(e.Type) == fmt.Sprintf("A.%s.FlowToken.TokensWithdrawn", fvm.FlowTokenAddress(chain)) { 707 withdraws = append(withdraws, e) 708 } 709 } 710 711 unittest.EnsureEventsIndexSeq(t, tx.Events, chain.ChainID()) 712 require.Len(t, deposits, 2) 713 require.Len(t, withdraws, 2) 714 }, 715 }, 716 { 717 name: "Transaction fees are deducted and tx is applied", 718 fundWith: fundingAmount, 719 tryToTransfer: transferAmount, 720 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, tx *fvm.TransactionProcedure) { 721 require.NoError(t, tx.Err) 722 require.Equal(t, txFees+transferAmount, balanceBefore-balanceAfter) 723 }, 724 }, 725 { 726 name: "Transaction fees are deducted and fee deduction is emitted", 727 fundWith: fundingAmount, 728 tryToTransfer: transferAmount, 729 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, tx *fvm.TransactionProcedure) { 730 require.NoError(t, tx.Err) 731 chain := flow.Testnet.Chain() 732 733 var feeDeduction flow.Event // fee deduction event 734 for _, e := range tx.Events { 735 if string(e.Type) == fmt.Sprintf("A.%s.FlowFees.FeesDeducted", environment.FlowFeesAddress(chain)) { 736 feeDeduction = e 737 break 738 } 739 } 740 unittest.EnsureEventsIndexSeq(t, tx.Events, chain.ChainID()) 741 require.NotEmpty(t, feeDeduction.Payload) 742 743 payload, err := jsoncdc.Decode(nil, feeDeduction.Payload) 744 require.NoError(t, err) 745 746 event := payload.(cadence.Event) 747 748 require.Equal(t, txFees, event.Fields[0].ToGoValue()) 749 // Inclusion effort should be equivalent to 1.0 UFix64 750 require.Equal(t, uint64(100_000_000), event.Fields[1].ToGoValue()) 751 // Execution effort should be non-0 752 require.Greater(t, event.Fields[2].ToGoValue(), uint64(0)) 753 754 }, 755 }, 756 { 757 name: "If just enough balance, fees are deducted", 758 fundWith: txFees + transferAmount, 759 tryToTransfer: transferAmount, 760 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, tx *fvm.TransactionProcedure) { 761 require.NoError(t, tx.Err) 762 require.Equal(t, uint64(0), balanceAfter) 763 }, 764 }, 765 { 766 // this is an edge case that is not applicable to any network. 767 // If storage limits were on this would fail due to storage limits 768 name: "If not enough balance, transaction succeeds and fees are deducted to 0", 769 fundWith: txFees, 770 tryToTransfer: 1, 771 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, tx *fvm.TransactionProcedure) { 772 require.NoError(t, tx.Err) 773 require.Equal(t, uint64(0), balanceAfter) 774 }, 775 }, 776 { 777 name: "If tx fails, fees are deducted", 778 fundWith: fundingAmount, 779 tryToTransfer: 2 * fundingAmount, 780 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, tx *fvm.TransactionProcedure) { 781 require.Error(t, tx.Err) 782 require.Equal(t, fundingAmount-txFees, balanceAfter) 783 }, 784 }, 785 { 786 name: "If tx fails, fee deduction events are emitted", 787 fundWith: fundingAmount, 788 tryToTransfer: 2 * fundingAmount, 789 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, tx *fvm.TransactionProcedure) { 790 require.Error(t, tx.Err) 791 792 var deposits []flow.Event 793 var withdraws []flow.Event 794 795 chain := flow.Testnet.Chain() 796 797 for _, e := range tx.Events { 798 if string(e.Type) == fmt.Sprintf("A.%s.FlowToken.TokensDeposited", fvm.FlowTokenAddress(chain)) { 799 deposits = append(deposits, e) 800 } 801 if string(e.Type) == fmt.Sprintf("A.%s.FlowToken.TokensWithdrawn", fvm.FlowTokenAddress(chain)) { 802 withdraws = append(withdraws, e) 803 } 804 } 805 806 unittest.EnsureEventsIndexSeq(t, tx.Events, chain.ChainID()) 807 require.Len(t, deposits, 1) 808 require.Len(t, withdraws, 1) 809 }, 810 }, 811 { 812 name: "If tx fails because of gas limit reached, fee deduction events are emitted", 813 fundWith: txFees + transferAmount, 814 tryToTransfer: transferAmount, 815 gasLimit: uint64(2), 816 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, tx *fvm.TransactionProcedure) { 817 require.ErrorContains(t, tx.Err, "computation exceeds limit (2)") 818 819 var deposits []flow.Event 820 var withdraws []flow.Event 821 822 chain := flow.Testnet.Chain() 823 824 for _, e := range tx.Events { 825 if string(e.Type) == fmt.Sprintf("A.%s.FlowToken.TokensDeposited", fvm.FlowTokenAddress(chain)) { 826 deposits = append(deposits, e) 827 } 828 if string(e.Type) == fmt.Sprintf("A.%s.FlowToken.TokensWithdrawn", fvm.FlowTokenAddress(chain)) { 829 withdraws = append(withdraws, e) 830 } 831 } 832 833 unittest.EnsureEventsIndexSeq(t, tx.Events, chain.ChainID()) 834 require.Len(t, deposits, 1) 835 require.Len(t, withdraws, 1) 836 }, 837 }, 838 } 839 840 testCasesWithStorageEnabled := []testCase{ 841 { 842 name: "Transaction fees are deducted", 843 fundWith: fundingAmount, 844 tryToTransfer: 0, 845 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, tx *fvm.TransactionProcedure) { 846 require.NoError(t, tx.Err) 847 require.Equal(t, txFees, balanceBefore-balanceAfter) 848 }, 849 }, 850 { 851 name: "Transaction fee deduction emits events", 852 fundWith: fundingAmount, 853 tryToTransfer: 0, 854 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, tx *fvm.TransactionProcedure) { 855 require.NoError(t, tx.Err) 856 857 var deposits []flow.Event 858 var withdraws []flow.Event 859 860 chain := flow.Testnet.Chain() 861 862 for _, e := range tx.Events { 863 if string(e.Type) == fmt.Sprintf("A.%s.FlowToken.TokensDeposited", fvm.FlowTokenAddress(chain)) { 864 deposits = append(deposits, e) 865 } 866 if string(e.Type) == fmt.Sprintf("A.%s.FlowToken.TokensWithdrawn", fvm.FlowTokenAddress(chain)) { 867 withdraws = append(withdraws, e) 868 } 869 } 870 871 unittest.EnsureEventsIndexSeq(t, tx.Events, chain.ChainID()) 872 require.Len(t, deposits, 2) 873 require.Len(t, withdraws, 2) 874 }, 875 }, 876 { 877 name: "Transaction fees are deducted and tx is applied", 878 fundWith: fundingAmount, 879 tryToTransfer: transferAmount, 880 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, tx *fvm.TransactionProcedure) { 881 require.NoError(t, tx.Err) 882 require.Equal(t, txFees+transferAmount, balanceBefore-balanceAfter) 883 }, 884 }, 885 { 886 name: "If just enough balance, fees are deducted", 887 fundWith: txFees + transferAmount, 888 tryToTransfer: transferAmount, 889 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, tx *fvm.TransactionProcedure) { 890 require.NoError(t, tx.Err) 891 require.Equal(t, minimumStorageReservation, balanceAfter) 892 }, 893 }, 894 { 895 name: "If tx fails, fees are deducted", 896 fundWith: fundingAmount, 897 tryToTransfer: 2 * fundingAmount, 898 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, tx *fvm.TransactionProcedure) { 899 require.Error(t, tx.Err) 900 require.Equal(t, fundingAmount-txFees+minimumStorageReservation, balanceAfter) 901 }, 902 }, 903 { 904 name: "If tx fails, fee deduction events are emitted", 905 fundWith: fundingAmount, 906 tryToTransfer: 2 * fundingAmount, 907 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, tx *fvm.TransactionProcedure) { 908 require.Error(t, tx.Err) 909 910 var deposits []flow.Event 911 var withdraws []flow.Event 912 913 chain := flow.Testnet.Chain() 914 915 for _, e := range tx.Events { 916 if string(e.Type) == fmt.Sprintf("A.%s.FlowToken.TokensDeposited", fvm.FlowTokenAddress(chain)) { 917 deposits = append(deposits, e) 918 } 919 if string(e.Type) == fmt.Sprintf("A.%s.FlowToken.TokensWithdrawn", fvm.FlowTokenAddress(chain)) { 920 withdraws = append(withdraws, e) 921 } 922 } 923 924 unittest.EnsureEventsIndexSeq(t, tx.Events, chain.ChainID()) 925 require.Len(t, deposits, 1) 926 require.Len(t, withdraws, 1) 927 }, 928 }, 929 { 930 name: "If balance at minimum, transaction fails, fees are deducted and fee deduction events are emitted", 931 fundWith: 0, 932 tryToTransfer: 0, 933 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, tx *fvm.TransactionProcedure) { 934 require.Error(t, tx.Err) 935 require.Equal(t, minimumStorageReservation-txFees, balanceAfter) 936 937 var deposits []flow.Event 938 var withdraws []flow.Event 939 940 chain := flow.Testnet.Chain() 941 942 for _, e := range tx.Events { 943 if string(e.Type) == fmt.Sprintf("A.%s.FlowToken.TokensDeposited", fvm.FlowTokenAddress(chain)) { 944 deposits = append(deposits, e) 945 } 946 if string(e.Type) == fmt.Sprintf("A.%s.FlowToken.TokensWithdrawn", fvm.FlowTokenAddress(chain)) { 947 withdraws = append(withdraws, e) 948 } 949 } 950 951 unittest.EnsureEventsIndexSeq(t, tx.Events, chain.ChainID()) 952 require.Len(t, deposits, 1) 953 require.Len(t, withdraws, 1) 954 }, 955 }, 956 } 957 958 runTx := func(tc testCase) func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) { 959 return func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) { 960 // ==== Create an account ==== 961 privateKey, txBody := testutil.CreateAccountCreationTransaction(t, chain) 962 963 err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) 964 require.NoError(t, err) 965 966 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 967 968 err = vm.Run(ctx, tx, view) 969 require.NoError(t, err) 970 971 assert.NoError(t, tx.Err) 972 973 assert.Len(t, tx.Events, 10) 974 unittest.EnsureEventsIndexSeq(t, tx.Events, chain.ChainID()) 975 976 accountCreatedEvents := filterAccountCreatedEvents(tx.Events) 977 978 require.Len(t, accountCreatedEvents, 1) 979 980 // read the address of the account created (e.g. "0x01" and convert it to flow.address) 981 data, err := jsoncdc.Decode(nil, accountCreatedEvents[0].Payload) 982 require.NoError(t, err) 983 address := flow.Address(data.(cadence.Event).Fields[0].(cadence.Address)) 984 985 // ==== Transfer tokens to new account ==== 986 txBody = transferTokensTx(chain). 987 AddAuthorizer(chain.ServiceAddress()). 988 AddArgument(jsoncdc.MustEncode(cadence.UFix64(tc.fundWith))). 989 AddArgument(jsoncdc.MustEncode(cadence.NewAddress(address))) 990 991 txBody.SetProposalKey(chain.ServiceAddress(), 0, 1) 992 txBody.SetPayer(chain.ServiceAddress()) 993 994 err = testutil.SignEnvelope( 995 txBody, 996 chain.ServiceAddress(), 997 unittest.ServiceAccountPrivateKey, 998 ) 999 require.NoError(t, err) 1000 1001 tx = fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 1002 1003 err = vm.Run(ctx, tx, view) 1004 require.NoError(t, err) 1005 require.NoError(t, tx.Err) 1006 1007 balanceBefore := getBalance(vm, chain, ctx, view, address) 1008 1009 // ==== Transfer tokens from new account ==== 1010 1011 txBody = transferTokensTx(chain). 1012 AddAuthorizer(address). 1013 AddArgument(jsoncdc.MustEncode(cadence.UFix64(tc.tryToTransfer))). 1014 AddArgument(jsoncdc.MustEncode(cadence.NewAddress(chain.ServiceAddress()))) 1015 1016 txBody.SetProposalKey(address, 0, 0) 1017 txBody.SetPayer(address) 1018 1019 if tc.gasLimit == 0 { 1020 txBody.SetGasLimit(fvm.DefaultComputationLimit) 1021 } else { 1022 txBody.SetGasLimit(tc.gasLimit) 1023 } 1024 1025 err = testutil.SignEnvelope( 1026 txBody, 1027 address, 1028 privateKey, 1029 ) 1030 require.NoError(t, err) 1031 1032 tx = fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 1033 1034 err = vm.Run(ctx, tx, view) 1035 require.NoError(t, err) 1036 1037 balanceAfter := getBalance(vm, chain, ctx, view, address) 1038 1039 tc.checkResult( 1040 t, 1041 balanceBefore, 1042 balanceAfter, 1043 tx, 1044 ) 1045 } 1046 } 1047 1048 for i, tc := range testCases { 1049 t.Run(fmt.Sprintf("Transaction Fees %d: %s", i, tc.name), newVMTest().withBootstrapProcedureOptions( 1050 fvm.WithTransactionFee(fvm.DefaultTransactionFees), 1051 fvm.WithExecutionMemoryLimit(math.MaxUint64), 1052 fvm.WithExecutionEffortWeights(mainnetExecutionEffortWeights), 1053 fvm.WithExecutionMemoryWeights(meter.DefaultMemoryWeights), 1054 ).withContextOptions( 1055 fvm.WithTransactionFeesEnabled(true), 1056 ).run( 1057 runTx(tc)), 1058 ) 1059 } 1060 1061 for i, tc := range testCasesWithStorageEnabled { 1062 t.Run(fmt.Sprintf("Transaction Fees with storage %d: %s", i, tc.name), newVMTest().withBootstrapProcedureOptions( 1063 fvm.WithTransactionFee(fvm.DefaultTransactionFees), 1064 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 1065 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 1066 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 1067 fvm.WithExecutionMemoryLimit(math.MaxUint64), 1068 fvm.WithExecutionEffortWeights(mainnetExecutionEffortWeights), 1069 fvm.WithExecutionMemoryWeights(meter.DefaultMemoryWeights), 1070 ).withContextOptions( 1071 fvm.WithTransactionFeesEnabled(true), 1072 fvm.WithAccountStorageLimit(true), 1073 ).run( 1074 runTx(tc)), 1075 ) 1076 } 1077 } 1078 1079 func TestSettingExecutionWeights(t *testing.T) { 1080 1081 t.Run("transaction should fail with high weights", newVMTest().withBootstrapProcedureOptions( 1082 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 1083 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 1084 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 1085 fvm.WithExecutionEffortWeights( 1086 meter.ExecutionEffortWeights{ 1087 common.ComputationKindLoop: 100_000 << meter.MeterExecutionInternalPrecisionBytes, 1088 }, 1089 ), 1090 ).run( 1091 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) { 1092 1093 txBody := flow.NewTransactionBody(). 1094 SetScript([]byte(` 1095 transaction { 1096 prepare(signer: AuthAccount) { 1097 var a = 0 1098 while a < 100 { 1099 a = a + 1 1100 } 1101 } 1102 } 1103 `)). 1104 SetProposalKey(chain.ServiceAddress(), 0, 0). 1105 AddAuthorizer(chain.ServiceAddress()). 1106 SetPayer(chain.ServiceAddress()) 1107 1108 err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) 1109 require.NoError(t, err) 1110 1111 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 1112 err = vm.Run(ctx, tx, view) 1113 require.NoError(t, err) 1114 1115 assert.True(t, errors.IsComputationLimitExceededError(tx.Err)) 1116 }, 1117 )) 1118 1119 memoryWeights := make(map[common.MemoryKind]uint64) 1120 for k, v := range meter.DefaultMemoryWeights { 1121 memoryWeights[k] = v 1122 } 1123 1124 const highWeight = 20_000_000_000 1125 memoryWeights[common.MemoryKindIntegerExpression] = highWeight 1126 1127 t.Run("normal transactions should fail with high memory weights", newVMTest().withBootstrapProcedureOptions( 1128 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 1129 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 1130 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 1131 fvm.WithExecutionMemoryWeights( 1132 memoryWeights, 1133 ), 1134 ).withContextOptions( 1135 fvm.WithMemoryLimit(10_000_000_000), 1136 ).run( 1137 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) { 1138 1139 // Create an account private key. 1140 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 1141 require.NoError(t, err) 1142 1143 // Bootstrap a ledger, creating accounts with the provided private keys and the root account. 1144 accounts, err := testutil.CreateAccounts(vm, view, derivedBlockData, privateKeys, chain) 1145 require.NoError(t, err) 1146 1147 txBody := flow.NewTransactionBody(). 1148 SetScript([]byte(` 1149 transaction { 1150 prepare(signer: AuthAccount) { 1151 var a = 1 1152 } 1153 } 1154 `)). 1155 SetProposalKey(accounts[0], 0, 0). 1156 AddAuthorizer(accounts[0]). 1157 SetPayer(accounts[0]) 1158 1159 err = testutil.SignTransaction(txBody, accounts[0], privateKeys[0], 0) 1160 require.NoError(t, err) 1161 1162 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 1163 err = vm.Run(ctx, tx, view) 1164 require.NoError(t, err) 1165 require.Greater(t, tx.MemoryEstimate, uint64(highWeight)) 1166 1167 assert.True(t, errors.IsMemoryLimitExceededError(tx.Err)) 1168 }, 1169 )) 1170 1171 t.Run("service account transactions should not fail with high memory weights", newVMTest().withBootstrapProcedureOptions( 1172 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 1173 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 1174 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 1175 fvm.WithExecutionMemoryWeights( 1176 memoryWeights, 1177 ), 1178 ).withContextOptions( 1179 fvm.WithMemoryLimit(10_000_000_000), 1180 ).run( 1181 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) { 1182 1183 txBody := flow.NewTransactionBody(). 1184 SetScript([]byte(` 1185 transaction { 1186 prepare(signer: AuthAccount) { 1187 var a = 1 1188 } 1189 } 1190 `)). 1191 SetProposalKey(chain.ServiceAddress(), 0, 0). 1192 AddAuthorizer(chain.ServiceAddress()). 1193 SetPayer(chain.ServiceAddress()) 1194 1195 err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) 1196 require.NoError(t, err) 1197 1198 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 1199 err = vm.Run(ctx, tx, view) 1200 require.NoError(t, err) 1201 require.Greater(t, tx.MemoryEstimate, uint64(highWeight)) 1202 1203 require.NoError(t, tx.Err) 1204 }, 1205 )) 1206 1207 memoryWeights = make(map[common.MemoryKind]uint64) 1208 for k, v := range meter.DefaultMemoryWeights { 1209 memoryWeights[k] = v 1210 } 1211 memoryWeights[common.MemoryKindBreakStatement] = 1_000_000 1212 t.Run("transaction should fail with low memory limit (set in the state)", newVMTest().withBootstrapProcedureOptions( 1213 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 1214 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 1215 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 1216 fvm.WithExecutionMemoryLimit( 1217 100_000_000, 1218 ), 1219 fvm.WithExecutionMemoryWeights( 1220 memoryWeights, 1221 ), 1222 ).run( 1223 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) { 1224 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 1225 require.NoError(t, err) 1226 1227 accounts, err := testutil.CreateAccounts(vm, view, derivedBlockData, privateKeys, chain) 1228 require.NoError(t, err) 1229 1230 // This transaction is specially designed to use a lot of breaks 1231 // as the weight for breaks is much higher than usual. 1232 // putting a `while true {break}` in a loop does not use the same amount of memory. 1233 txBody := flow.NewTransactionBody(). 1234 SetScript([]byte(` 1235 transaction { 1236 prepare(signer: AuthAccount) { 1237 while true {break};while true {break};while true {break};while true {break};while true {break}; 1238 while true {break};while true {break};while true {break};while true {break};while true {break}; 1239 while true {break};while true {break};while true {break};while true {break};while true {break}; 1240 while true {break};while true {break};while true {break};while true {break};while true {break}; 1241 while true {break};while true {break};while true {break};while true {break};while true {break}; 1242 while true {break};while true {break};while true {break};while true {break};while true {break}; 1243 while true {break};while true {break};while true {break};while true {break};while true {break}; 1244 while true {break};while true {break};while true {break};while true {break};while true {break}; 1245 while true {break};while true {break};while true {break};while true {break};while true {break}; 1246 while true {break};while true {break};while true {break};while true {break};while true {break}; 1247 while true {break};while true {break};while true {break};while true {break};while true {break}; 1248 while true {break};while true {break};while true {break};while true {break};while true {break}; 1249 while true {break};while true {break};while true {break};while true {break};while true {break}; 1250 while true {break};while true {break};while true {break};while true {break};while true {break}; 1251 while true {break};while true {break};while true {break};while true {break};while true {break}; 1252 while true {break};while true {break};while true {break};while true {break};while true {break}; 1253 while true {break};while true {break};while true {break};while true {break};while true {break}; 1254 while true {break};while true {break};while true {break};while true {break};while true {break}; 1255 while true {break};while true {break};while true {break};while true {break};while true {break}; 1256 while true {break};while true {break};while true {break};while true {break};while true {break}; 1257 } 1258 } 1259 `)) 1260 1261 err = testutil.SignTransaction(txBody, accounts[0], privateKeys[0], 0) 1262 require.NoError(t, err) 1263 1264 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 1265 err = vm.Run(ctx, tx, view) 1266 require.NoError(t, err) 1267 // There are 100 breaks and each break uses 1_000_000 memory 1268 require.Greater(t, tx.MemoryEstimate, uint64(100_000_000)) 1269 1270 assert.True(t, errors.IsMemoryLimitExceededError(tx.Err)) 1271 }, 1272 )) 1273 1274 t.Run("transaction should fail if create account weight is high", newVMTest().withBootstrapProcedureOptions( 1275 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 1276 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 1277 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 1278 fvm.WithExecutionEffortWeights( 1279 meter.ExecutionEffortWeights{ 1280 environment.ComputationKindCreateAccount: (fvm.DefaultComputationLimit + 1) << meter.MeterExecutionInternalPrecisionBytes, 1281 }, 1282 ), 1283 ).run( 1284 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) { 1285 txBody := flow.NewTransactionBody(). 1286 SetScript([]byte(` 1287 transaction { 1288 prepare(signer: AuthAccount) { 1289 AuthAccount(payer: signer) 1290 } 1291 } 1292 `)). 1293 SetProposalKey(chain.ServiceAddress(), 0, 0). 1294 AddAuthorizer(chain.ServiceAddress()). 1295 SetPayer(chain.ServiceAddress()) 1296 1297 err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) 1298 require.NoError(t, err) 1299 1300 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 1301 err = vm.Run(ctx, tx, view) 1302 require.NoError(t, err) 1303 1304 assert.True(t, errors.IsComputationLimitExceededError(tx.Err)) 1305 }, 1306 )) 1307 1308 t.Run("transaction should fail if create account weight is high", newVMTest().withBootstrapProcedureOptions( 1309 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 1310 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 1311 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 1312 fvm.WithExecutionEffortWeights( 1313 meter.ExecutionEffortWeights{ 1314 environment.ComputationKindCreateAccount: 100_000_000 << meter.MeterExecutionInternalPrecisionBytes, 1315 }, 1316 ), 1317 ).run( 1318 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) { 1319 1320 txBody := flow.NewTransactionBody(). 1321 SetScript([]byte(` 1322 transaction { 1323 prepare(signer: AuthAccount) { 1324 AuthAccount(payer: signer) 1325 } 1326 } 1327 `)). 1328 SetProposalKey(chain.ServiceAddress(), 0, 0). 1329 AddAuthorizer(chain.ServiceAddress()). 1330 SetPayer(chain.ServiceAddress()) 1331 1332 err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) 1333 require.NoError(t, err) 1334 1335 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 1336 err = vm.Run(ctx, tx, view) 1337 require.NoError(t, err) 1338 1339 assert.True(t, errors.IsComputationLimitExceededError(tx.Err)) 1340 }, 1341 )) 1342 1343 t.Run("transaction should fail if create account weight is high", newVMTest().withBootstrapProcedureOptions( 1344 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 1345 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 1346 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 1347 fvm.WithExecutionEffortWeights( 1348 meter.ExecutionEffortWeights{ 1349 environment.ComputationKindCreateAccount: 100_000_000 << meter.MeterExecutionInternalPrecisionBytes, 1350 }, 1351 ), 1352 ).run( 1353 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) { 1354 txBody := flow.NewTransactionBody(). 1355 SetScript([]byte(` 1356 transaction { 1357 prepare(signer: AuthAccount) { 1358 AuthAccount(payer: signer) 1359 } 1360 } 1361 `)). 1362 SetProposalKey(chain.ServiceAddress(), 0, 0). 1363 AddAuthorizer(chain.ServiceAddress()). 1364 SetPayer(chain.ServiceAddress()) 1365 1366 err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) 1367 require.NoError(t, err) 1368 1369 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 1370 err = vm.Run(ctx, tx, view) 1371 require.NoError(t, err) 1372 1373 assert.True(t, errors.IsComputationLimitExceededError(tx.Err)) 1374 }, 1375 )) 1376 1377 t.Run("transaction should not use up more computation that the transaction body itself", newVMTest().withBootstrapProcedureOptions( 1378 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 1379 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 1380 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 1381 fvm.WithTransactionFee(fvm.DefaultTransactionFees), 1382 fvm.WithExecutionEffortWeights( 1383 meter.ExecutionEffortWeights{ 1384 common.ComputationKindStatement: 0, 1385 common.ComputationKindLoop: 1 << meter.MeterExecutionInternalPrecisionBytes, 1386 common.ComputationKindFunctionInvocation: 0, 1387 }, 1388 ), 1389 ).withContextOptions( 1390 fvm.WithAccountStorageLimit(true), 1391 fvm.WithTransactionFeesEnabled(true), 1392 fvm.WithMemoryLimit(math.MaxUint64), 1393 ).run( 1394 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) { 1395 // Use the maximum amount of computation so that the transaction still passes. 1396 loops := uint64(997) 1397 maxExecutionEffort := uint64(997) 1398 txBody := flow.NewTransactionBody(). 1399 SetScript([]byte(fmt.Sprintf(` 1400 transaction() {prepare(signer: AuthAccount){var i=0; while i < %d {i = i +1 } } execute{}} 1401 `, loops))). 1402 SetProposalKey(chain.ServiceAddress(), 0, 0). 1403 AddAuthorizer(chain.ServiceAddress()). 1404 SetPayer(chain.ServiceAddress()). 1405 SetGasLimit(maxExecutionEffort) 1406 1407 err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) 1408 require.NoError(t, err) 1409 1410 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 1411 err = vm.Run(ctx, tx, view) 1412 require.NoError(t, err) 1413 require.NoError(t, tx.Err) 1414 1415 // expected used is number of loops. 1416 assert.Equal(t, loops, tx.ComputationUsed) 1417 1418 // increasing the number of loops should fail the transaction. 1419 loops = loops + 1 1420 txBody = flow.NewTransactionBody(). 1421 SetScript([]byte(fmt.Sprintf(` 1422 transaction() {prepare(signer: AuthAccount){var i=0; while i < %d {i = i +1 } } execute{}} 1423 `, loops))). 1424 SetProposalKey(chain.ServiceAddress(), 0, 1). 1425 AddAuthorizer(chain.ServiceAddress()). 1426 SetPayer(chain.ServiceAddress()). 1427 SetGasLimit(maxExecutionEffort) 1428 1429 err = testutil.SignTransactionAsServiceAccount(txBody, 1, chain) 1430 require.NoError(t, err) 1431 1432 tx = fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 1433 err = vm.Run(ctx, tx, view) 1434 require.NoError(t, err) 1435 1436 require.ErrorContains(t, tx.Err, "computation exceeds limit (997)") 1437 // computation used should the actual computation used. 1438 assert.Equal(t, loops, tx.ComputationUsed) 1439 1440 for _, event := range tx.Events { 1441 // the fee deduction event should only contain the max gas worth of execution effort. 1442 if strings.Contains(string(event.Type), "FlowFees.FeesDeducted") { 1443 ev, err := jsoncdc.Decode(nil, event.Payload) 1444 require.NoError(t, err) 1445 assert.Equal(t, maxExecutionEffort, ev.(cadence.Event).Fields[2].ToGoValue().(uint64)) 1446 } 1447 } 1448 unittest.EnsureEventsIndexSeq(t, tx.Events, chain.ChainID()) 1449 }, 1450 )) 1451 } 1452 1453 func TestStorageUsed(t *testing.T) { 1454 t.Parallel() 1455 1456 chain, vm := createChainAndVm(flow.Testnet) 1457 1458 ctx := fvm.NewContext( 1459 fvm.WithChain(chain), 1460 fvm.WithCadenceLogging(true), 1461 ) 1462 1463 code := []byte(` 1464 pub fun main(): UInt64 { 1465 1466 var addresses: [Address]= [ 1467 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 1468 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 1469 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 1470 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 1471 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 1472 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 1473 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 1474 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 1475 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 1476 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 1477 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731 1478 ] 1479 1480 var storageUsed: UInt64 = 0 1481 for address in addresses { 1482 let account = getAccount(address) 1483 storageUsed = account.storageUsed 1484 } 1485 1486 return storageUsed 1487 } 1488 `) 1489 1490 address, err := hex.DecodeString("2a3c4c2581cef731") 1491 require.NoError(t, err) 1492 1493 simpleView := utils.NewSimpleView() 1494 status := environment.NewAccountStatus() 1495 status.SetStorageUsed(5) 1496 err = simpleView.Set(string(address), state.AccountStatusKey, status.ToBytes()) 1497 require.NoError(t, err) 1498 1499 script := fvm.Script(code) 1500 1501 err = vm.Run(ctx, script, simpleView) 1502 require.NoError(t, err) 1503 1504 assert.Equal(t, cadence.NewUInt64(5), script.Value) 1505 } 1506 1507 func TestEnforcingComputationLimit(t *testing.T) { 1508 t.Parallel() 1509 1510 chain, vm := createChainAndVm(flow.Testnet) 1511 simpleView := utils.NewSimpleView() 1512 1513 const computationLimit = 5 1514 1515 type test struct { 1516 name string 1517 code string 1518 payerIsServAcc bool 1519 ok bool 1520 expCompUsed uint64 1521 } 1522 1523 tests := []test{ 1524 { 1525 name: "infinite while loop", 1526 code: ` 1527 while true {} 1528 `, 1529 payerIsServAcc: false, 1530 ok: false, 1531 expCompUsed: computationLimit + 1, 1532 }, 1533 { 1534 name: "limited while loop", 1535 code: ` 1536 var i = 0 1537 while i < 5 { 1538 i = i + 1 1539 } 1540 `, 1541 payerIsServAcc: false, 1542 ok: false, 1543 expCompUsed: computationLimit + 1, 1544 }, 1545 { 1546 name: "too many for-in loop iterations", 1547 code: ` 1548 for i in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] {} 1549 `, 1550 payerIsServAcc: false, 1551 ok: false, 1552 expCompUsed: computationLimit + 1, 1553 }, 1554 { 1555 name: "too many for-in loop iterations", 1556 code: ` 1557 for i in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] {} 1558 `, 1559 payerIsServAcc: true, 1560 ok: true, 1561 expCompUsed: 11, 1562 }, 1563 { 1564 name: "some for-in loop iterations", 1565 code: ` 1566 for i in [1, 2, 3, 4] {} 1567 `, 1568 payerIsServAcc: false, 1569 ok: true, 1570 expCompUsed: 5, 1571 }, 1572 } 1573 1574 for _, test := range tests { 1575 1576 t.Run(test.name, func(t *testing.T) { 1577 derivedBlockData := derived.NewEmptyDerivedBlockData() 1578 1579 ctx := fvm.NewContext( 1580 fvm.WithChain(chain), 1581 fvm.WithAuthorizationChecksEnabled(false), 1582 fvm.WithSequenceNumberCheckAndIncrementEnabled(false), 1583 fvm.WithDerivedBlockData(derivedBlockData), 1584 ) 1585 1586 script := []byte( 1587 fmt.Sprintf( 1588 ` 1589 transaction { 1590 prepare() { 1591 %s 1592 } 1593 } 1594 `, 1595 test.code, 1596 ), 1597 ) 1598 1599 txBody := flow.NewTransactionBody(). 1600 SetScript(script). 1601 SetGasLimit(computationLimit) 1602 1603 if test.payerIsServAcc { 1604 txBody.SetPayer(chain.ServiceAddress()). 1605 SetGasLimit(0) 1606 } 1607 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 1608 1609 err := vm.Run(ctx, tx, simpleView) 1610 require.NoError(t, err) 1611 require.Equal(t, test.expCompUsed, tx.ComputationUsed) 1612 if test.ok { 1613 require.NoError(t, tx.Err) 1614 } else { 1615 require.Error(t, tx.Err) 1616 } 1617 1618 }) 1619 } 1620 } 1621 1622 func TestStorageCapacity(t *testing.T) { 1623 t.Run("Storage capacity updates on FLOW transfer", newVMTest(). 1624 withContextOptions( 1625 fvm.WithAuthorizationChecksEnabled(false), 1626 fvm.WithSequenceNumberCheckAndIncrementEnabled(false), 1627 fvm.WithCadenceLogging(true), 1628 ). 1629 withBootstrapProcedureOptions( 1630 fvm.WithStorageMBPerFLOW(10_0000_0000), 1631 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 1632 ). 1633 run(func( 1634 t *testing.T, 1635 vm fvm.VM, 1636 chain flow.Chain, 1637 ctx fvm.Context, 1638 view state.View, 1639 derivedBlockData *derived.DerivedBlockData, 1640 ) { 1641 service := chain.ServiceAddress() 1642 signer := createAccount(t, vm, chain, ctx, view, derivedBlockData) 1643 target := createAccount(t, vm, chain, ctx, view, derivedBlockData) 1644 1645 // Transfer FLOW from service account to test accounts 1646 1647 transferTxBody := transferTokensTx(chain). 1648 AddAuthorizer(service). 1649 AddArgument(jsoncdc.MustEncode(cadence.UFix64(1_000_000))). 1650 AddArgument(jsoncdc.MustEncode(cadence.NewAddress(signer))). 1651 SetProposalKey(service, 0, 0). 1652 SetPayer(service) 1653 tx := fvm.Transaction(transferTxBody, derivedBlockData.NextTxIndexForTestingOnly()) 1654 err := vm.Run(ctx, tx, view) 1655 require.NoError(t, err) 1656 require.NoError(t, tx.Err) 1657 1658 transferTxBody = transferTokensTx(chain). 1659 AddAuthorizer(service). 1660 AddArgument(jsoncdc.MustEncode(cadence.UFix64(1_000_000))). 1661 AddArgument(jsoncdc.MustEncode(cadence.NewAddress(target))). 1662 SetProposalKey(service, 0, 0). 1663 SetPayer(service) 1664 tx = fvm.Transaction(transferTxBody, derivedBlockData.NextTxIndexForTestingOnly()) 1665 err = vm.Run(ctx, tx, view) 1666 require.NoError(t, err) 1667 require.NoError(t, tx.Err) 1668 1669 // Perform test 1670 1671 txBody := flow.NewTransactionBody(). 1672 SetScript([]byte(fmt.Sprintf(` 1673 import FungibleToken from 0x%s 1674 import FlowToken from 0x%s 1675 1676 transaction(target: Address) { 1677 prepare(signer: AuthAccount) { 1678 let receiverRef = getAccount(target) 1679 .getCapability(/public/flowTokenReceiver) 1680 .borrow<&{FungibleToken.Receiver}>() 1681 ?? panic("Could not borrow receiver reference to the recipient''s Vault") 1682 1683 let vaultRef = signer 1684 .borrow<&{FungibleToken.Provider}>(from: /storage/flowTokenVault) 1685 ?? panic("Could not borrow reference to the owner''s Vault!") 1686 1687 var cap0: UInt64 = signer.storageCapacity 1688 1689 receiverRef.deposit(from: <- vaultRef.withdraw(amount: 0.0000001)) 1690 1691 var cap1: UInt64 = signer.storageCapacity 1692 1693 log(cap0 - cap1) 1694 } 1695 }`, 1696 fvm.FungibleTokenAddress(chain), 1697 fvm.FlowTokenAddress(chain), 1698 ))). 1699 AddArgument(jsoncdc.MustEncode(cadence.NewAddress(target))). 1700 AddAuthorizer(signer) 1701 1702 tx = fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 1703 1704 err = vm.Run(ctx, tx, view) 1705 require.NoError(t, err) 1706 require.NoError(t, tx.Err) 1707 1708 require.Len(t, tx.Logs, 1) 1709 assert.Equal(t, tx.Logs[0], "1") 1710 }), 1711 ) 1712 } 1713 1714 func TestScriptContractMutationsFailure(t *testing.T) { 1715 t.Parallel() 1716 1717 t.Run("contract additions are not committed", 1718 newVMTest().run( 1719 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) { 1720 1721 // Create an account private key. 1722 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 1723 require.NoError(t, err) 1724 1725 // Bootstrap a ledger, creating accounts with the provided private keys and the root account. 1726 accounts, err := testutil.CreateAccounts(vm, view, derivedBlockData, privateKeys, chain) 1727 require.NoError(t, err) 1728 account := accounts[0] 1729 address := cadence.NewAddress(account) 1730 1731 scriptCtx := fvm.NewContextFromParent(ctx) 1732 1733 contract := "pub contract Foo {}" 1734 1735 script := fvm.Script([]byte(fmt.Sprintf(` 1736 pub fun main(account: Address) { 1737 let acc = getAuthAccount(account) 1738 acc.contracts.add(name: "Foo", code: "%s".decodeHex()) 1739 }`, hex.EncodeToString([]byte(contract))), 1740 )).WithArguments( 1741 jsoncdc.MustEncode(address), 1742 ) 1743 1744 err = vm.Run(scriptCtx, script, view) 1745 require.NoError(t, err) 1746 require.Error(t, script.Err) 1747 require.True(t, errors.IsCadenceRuntimeError(script.Err)) 1748 // modifications to contracts are not supported in scripts 1749 require.True(t, errors.IsOperationNotSupportedError(script.Err)) 1750 }, 1751 ), 1752 ) 1753 1754 t.Run("contract removals are not committed", 1755 newVMTest().run( 1756 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) { 1757 1758 // Create an account private key. 1759 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 1760 privateKey := privateKeys[0] 1761 require.NoError(t, err) 1762 1763 // Bootstrap a ledger, creating accounts with the provided private keys and the root account. 1764 accounts, err := testutil.CreateAccounts(vm, view, derivedBlockData, privateKeys, chain) 1765 require.NoError(t, err) 1766 account := accounts[0] 1767 address := cadence.NewAddress(account) 1768 1769 subCtx := fvm.NewContextFromParent(ctx) 1770 1771 contract := "pub contract Foo {}" 1772 1773 txBody := flow.NewTransactionBody().SetScript([]byte(fmt.Sprintf(` 1774 transaction { 1775 prepare(signer: AuthAccount, service: AuthAccount) { 1776 signer.contracts.add(name: "Foo", code: "%s".decodeHex()) 1777 } 1778 } 1779 `, hex.EncodeToString([]byte(contract))))). 1780 AddAuthorizer(account). 1781 AddAuthorizer(chain.ServiceAddress()). 1782 SetPayer(chain.ServiceAddress()). 1783 SetProposalKey(chain.ServiceAddress(), 0, 0) 1784 1785 _ = testutil.SignPayload(txBody, account, privateKey) 1786 _ = testutil.SignEnvelope(txBody, chain.ServiceAddress(), unittest.ServiceAccountPrivateKey) 1787 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 1788 err = vm.Run(subCtx, tx, view) 1789 require.NoError(t, err) 1790 require.NoError(t, tx.Err) 1791 1792 script := fvm.Script([]byte(` 1793 pub fun main(account: Address) { 1794 let acc = getAuthAccount(account) 1795 let n = acc.contracts.names[0] 1796 acc.contracts.remove(name: n) 1797 }`, 1798 )).WithArguments( 1799 jsoncdc.MustEncode(address), 1800 ) 1801 1802 err = vm.Run(subCtx, script, view) 1803 require.NoError(t, err) 1804 require.Error(t, script.Err) 1805 require.True(t, errors.IsCadenceRuntimeError(script.Err)) 1806 // modifications to contracts are not supported in scripts 1807 require.True(t, errors.IsOperationNotSupportedError(script.Err)) 1808 }, 1809 ), 1810 ) 1811 1812 t.Run("contract updates are not committed", 1813 newVMTest().run( 1814 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) { 1815 1816 // Create an account private key. 1817 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 1818 privateKey := privateKeys[0] 1819 require.NoError(t, err) 1820 1821 // Bootstrap a ledger, creating accounts with the provided private keys and the root account. 1822 accounts, err := testutil.CreateAccounts(vm, view, derivedBlockData, privateKeys, chain) 1823 require.NoError(t, err) 1824 account := accounts[0] 1825 address := cadence.NewAddress(account) 1826 1827 subCtx := fvm.NewContextFromParent(ctx) 1828 1829 contract := "pub contract Foo {}" 1830 1831 txBody := flow.NewTransactionBody().SetScript([]byte(fmt.Sprintf(` 1832 transaction { 1833 prepare(signer: AuthAccount, service: AuthAccount) { 1834 signer.contracts.add(name: "Foo", code: "%s".decodeHex()) 1835 } 1836 } 1837 `, hex.EncodeToString([]byte(contract))))). 1838 AddAuthorizer(account). 1839 AddAuthorizer(chain.ServiceAddress()). 1840 SetPayer(chain.ServiceAddress()). 1841 SetProposalKey(chain.ServiceAddress(), 0, 0) 1842 1843 _ = testutil.SignPayload(txBody, account, privateKey) 1844 _ = testutil.SignEnvelope(txBody, chain.ServiceAddress(), unittest.ServiceAccountPrivateKey) 1845 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 1846 err = vm.Run(subCtx, tx, view) 1847 require.NoError(t, err) 1848 require.NoError(t, tx.Err) 1849 1850 script := fvm.Script([]byte(fmt.Sprintf(` 1851 pub fun main(account: Address) { 1852 let acc = getAuthAccount(account) 1853 let n = acc.contracts.names[0] 1854 acc.contracts.update__experimental(name: n, code: "%s".decodeHex()) 1855 }`, hex.EncodeToString([]byte(contract))))).WithArguments( 1856 jsoncdc.MustEncode(address), 1857 ) 1858 1859 err = vm.Run(subCtx, script, view) 1860 require.NoError(t, err) 1861 require.Error(t, script.Err) 1862 require.True(t, errors.IsCadenceRuntimeError(script.Err)) 1863 // modifications to contracts are not supported in scripts 1864 require.True(t, errors.IsOperationNotSupportedError(script.Err)) 1865 }, 1866 ), 1867 ) 1868 } 1869 1870 func TestScriptAccountKeyMutationsFailure(t *testing.T) { 1871 t.Parallel() 1872 1873 t.Run("Account key additions are not committed", 1874 newVMTest().run( 1875 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) { 1876 1877 // Create an account private key. 1878 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 1879 require.NoError(t, err) 1880 1881 // Bootstrap a ledger, creating accounts with the provided private keys and the root account. 1882 accounts, err := testutil.CreateAccounts(vm, view, derivedBlockData, privateKeys, chain) 1883 require.NoError(t, err) 1884 account := accounts[0] 1885 address := cadence.NewAddress(account) 1886 1887 scriptCtx := fvm.NewContextFromParent(ctx) 1888 1889 seed := make([]byte, crypto.KeyGenSeedMinLenECDSAP256) 1890 _, _ = rand.Read(seed) 1891 1892 privateKey, _ := crypto.GeneratePrivateKey(crypto.ECDSAP256, seed) 1893 1894 script := fvm.Script([]byte(` 1895 pub fun main(account: Address, k: [UInt8]) { 1896 let acc = getAuthAccount(account) 1897 acc.addPublicKey(k) 1898 }`, 1899 )).WithArguments( 1900 jsoncdc.MustEncode(address), 1901 jsoncdc.MustEncode(testutil.BytesToCadenceArray( 1902 privateKey.PublicKey().Encode(), 1903 )), 1904 ) 1905 1906 err = vm.Run(scriptCtx, script, view) 1907 require.NoError(t, err) 1908 require.Error(t, script.Err) 1909 require.True(t, errors.IsCadenceRuntimeError(script.Err)) 1910 // modifications to public keys are not supported in scripts 1911 require.True(t, errors.IsOperationNotSupportedError(script.Err)) 1912 }, 1913 ), 1914 ) 1915 1916 t.Run("Account key removals are not committed", 1917 newVMTest().run( 1918 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) { 1919 1920 // Create an account private key. 1921 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 1922 require.NoError(t, err) 1923 1924 // Bootstrap a ledger, creating accounts with the provided private keys and the root account. 1925 accounts, err := testutil.CreateAccounts(vm, view, derivedBlockData, privateKeys, chain) 1926 require.NoError(t, err) 1927 account := accounts[0] 1928 address := cadence.NewAddress(account) 1929 1930 scriptCtx := fvm.NewContextFromParent(ctx) 1931 1932 script := fvm.Script([]byte(` 1933 pub fun main(account: Address) { 1934 let acc = getAuthAccount(account) 1935 acc.removePublicKey(0) 1936 }`, 1937 )).WithArguments( 1938 jsoncdc.MustEncode(address), 1939 ) 1940 1941 err = vm.Run(scriptCtx, script, view) 1942 require.NoError(t, err) 1943 require.Error(t, script.Err) 1944 require.True(t, errors.IsCadenceRuntimeError(script.Err)) 1945 // modifications to public keys are not supported in scripts 1946 require.True(t, errors.IsOperationNotSupportedError(script.Err)) 1947 }, 1948 ), 1949 ) 1950 } 1951 1952 func TestInteractionLimit(t *testing.T) { 1953 type testCase struct { 1954 name string 1955 interactionLimit uint64 1956 require func(t *testing.T, tx *fvm.TransactionProcedure) 1957 } 1958 1959 testCases := []testCase{ 1960 { 1961 name: "high limit succeeds", 1962 interactionLimit: math.MaxUint64, 1963 require: func(t *testing.T, tx *fvm.TransactionProcedure) { 1964 require.NoError(t, tx.Err) 1965 require.Len(t, tx.Events, 5) 1966 }, 1967 }, 1968 { 1969 name: "default limit succeeds", 1970 interactionLimit: fvm.DefaultMaxInteractionSize, 1971 require: func(t *testing.T, tx *fvm.TransactionProcedure) { 1972 require.NoError(t, tx.Err) 1973 require.Len(t, tx.Events, 5) 1974 unittest.EnsureEventsIndexSeq(t, tx.Events, flow.Testnet.Chain().ChainID()) 1975 }, 1976 }, 1977 { 1978 name: "low limit succeeds", 1979 interactionLimit: 170000, 1980 require: func(t *testing.T, tx *fvm.TransactionProcedure) { 1981 require.NoError(t, tx.Err) 1982 require.Len(t, tx.Events, 5) 1983 unittest.EnsureEventsIndexSeq(t, tx.Events, flow.Testnet.Chain().ChainID()) 1984 }, 1985 }, 1986 { 1987 name: "even lower low limit fails, and has only 3 events", 1988 interactionLimit: 10000, 1989 require: func(t *testing.T, tx *fvm.TransactionProcedure) { 1990 require.Error(t, tx.Err) 1991 require.Len(t, tx.Events, 3) 1992 unittest.EnsureEventsIndexSeq(t, tx.Events, flow.Testnet.Chain().ChainID()) 1993 }, 1994 }, 1995 } 1996 1997 // === setup === 1998 // setup an address with some funds 1999 var privateKey flow.AccountPrivateKey 2000 var address flow.Address 2001 vmt, err := newVMTest().withBootstrapProcedureOptions( 2002 fvm.WithTransactionFee(fvm.DefaultTransactionFees), 2003 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 2004 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 2005 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 2006 fvm.WithExecutionMemoryLimit(math.MaxUint64), 2007 ).withContextOptions( 2008 fvm.WithTransactionFeesEnabled(true), 2009 fvm.WithAccountStorageLimit(true), 2010 ).bootstrapWith( 2011 func(vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) error { 2012 // ==== Create an account ==== 2013 var txBody *flow.TransactionBody 2014 privateKey, txBody = testutil.CreateAccountCreationTransaction(t, chain) 2015 2016 err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) 2017 if err != nil { 2018 return err 2019 } 2020 2021 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 2022 2023 err = vm.Run(ctx, tx, view) 2024 if err != nil { 2025 return err 2026 } 2027 if tx.Err != nil { 2028 return tx.Err 2029 } 2030 2031 accountCreatedEvents := filterAccountCreatedEvents(tx.Events) 2032 2033 // read the address of the account created (e.g. "0x01" and convert it to flow.address) 2034 data, err := jsoncdc.Decode(nil, accountCreatedEvents[0].Payload) 2035 if err != nil { 2036 return err 2037 } 2038 address = flow.Address(data.(cadence.Event).Fields[0].(cadence.Address)) 2039 2040 // ==== Transfer tokens to new account ==== 2041 txBody = transferTokensTx(chain). 2042 AddAuthorizer(chain.ServiceAddress()). 2043 AddArgument(jsoncdc.MustEncode(cadence.UFix64(1_000_000))). 2044 AddArgument(jsoncdc.MustEncode(cadence.NewAddress(address))) 2045 2046 txBody.SetProposalKey(chain.ServiceAddress(), 0, 1) 2047 txBody.SetPayer(chain.ServiceAddress()) 2048 2049 err = testutil.SignEnvelope( 2050 txBody, 2051 chain.ServiceAddress(), 2052 unittest.ServiceAccountPrivateKey, 2053 ) 2054 if err != nil { 2055 return err 2056 } 2057 2058 tx = fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 2059 2060 err = vm.Run(ctx, tx, view) 2061 if err != nil { 2062 return err 2063 } 2064 if tx.Err != nil { 2065 return tx.Err 2066 } 2067 return nil 2068 }, 2069 ) 2070 require.NoError(t, err) 2071 2072 for _, tc := range testCases { 2073 t.Run(tc.name, vmt.run( 2074 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, view state.View, derivedBlockData *derived.DerivedBlockData) { 2075 // ==== Transfer funds with lowe interaction limit ==== 2076 txBody := transferTokensTx(chain). 2077 AddAuthorizer(address). 2078 AddArgument(jsoncdc.MustEncode(cadence.UFix64(1))). 2079 AddArgument(jsoncdc.MustEncode(cadence.NewAddress(chain.ServiceAddress()))) 2080 2081 txBody.SetProposalKey(address, 0, 0) 2082 txBody.SetPayer(address) 2083 2084 hasher, err := exeUtils.NewHasher(privateKey.HashAlgo) 2085 require.NoError(t, err) 2086 2087 sig, err := txBody.Sign(txBody.EnvelopeMessage(), privateKey.PrivateKey, hasher) 2088 require.NoError(t, err) 2089 txBody.AddEnvelopeSignature(address, 0, sig) 2090 2091 tx := fvm.Transaction(txBody, derivedBlockData.NextTxIndexForTestingOnly()) 2092 2093 // ==== IMPORTANT LINE ==== 2094 ctx.MaxStateInteractionSize = tc.interactionLimit 2095 2096 err = vm.Run(ctx, tx, view) 2097 require.NoError(t, err) 2098 tc.require(t, tx) 2099 }), 2100 ) 2101 } 2102 }