github.com/onflow/flow-go@v0.33.17/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 "github.com/onflow/cadence/encoding/ccf" 13 jsoncdc "github.com/onflow/cadence/encoding/json" 14 "github.com/onflow/cadence/runtime" 15 "github.com/onflow/cadence/runtime/common" 16 cadenceErrors "github.com/onflow/cadence/runtime/errors" 17 "github.com/onflow/cadence/runtime/tests/utils" 18 "github.com/stretchr/testify/assert" 19 mockery "github.com/stretchr/testify/mock" 20 "github.com/stretchr/testify/require" 21 22 "github.com/onflow/flow-go/crypto" 23 "github.com/onflow/flow-go/engine/execution/testutil" 24 exeUtils "github.com/onflow/flow-go/engine/execution/utils" 25 "github.com/onflow/flow-go/fvm" 26 fvmCrypto "github.com/onflow/flow-go/fvm/crypto" 27 "github.com/onflow/flow-go/fvm/environment" 28 "github.com/onflow/flow-go/fvm/errors" 29 "github.com/onflow/flow-go/fvm/evm/stdlib" 30 "github.com/onflow/flow-go/fvm/evm/types" 31 "github.com/onflow/flow-go/fvm/meter" 32 reusableRuntime "github.com/onflow/flow-go/fvm/runtime" 33 "github.com/onflow/flow-go/fvm/storage/snapshot" 34 "github.com/onflow/flow-go/fvm/storage/snapshot/mock" 35 "github.com/onflow/flow-go/fvm/storage/testutils" 36 "github.com/onflow/flow-go/fvm/systemcontracts" 37 "github.com/onflow/flow-go/model/flow" 38 "github.com/onflow/flow-go/utils/unittest" 39 ) 40 41 // from 18.8.2022 42 var mainnetExecutionEffortWeights = meter.ExecutionEffortWeights{ 43 common.ComputationKindStatement: 1569, 44 common.ComputationKindLoop: 1569, 45 common.ComputationKindFunctionInvocation: 1569, 46 environment.ComputationKindGetValue: 808, 47 environment.ComputationKindCreateAccount: 2837670, 48 environment.ComputationKindSetValue: 765, 49 } 50 51 type vmTest struct { 52 bootstrapOptions []fvm.BootstrapProcedureOption 53 contextOptions []fvm.Option 54 } 55 56 func newVMTest() vmTest { 57 return vmTest{} 58 } 59 60 func (vmt vmTest) withBootstrapProcedureOptions(opts ...fvm.BootstrapProcedureOption) vmTest { 61 vmt.bootstrapOptions = append(vmt.bootstrapOptions, opts...) 62 return vmt 63 } 64 65 func (vmt vmTest) withContextOptions(opts ...fvm.Option) vmTest { 66 vmt.contextOptions = append(vmt.contextOptions, opts...) 67 return vmt 68 } 69 70 func createChainAndVm(chainID flow.ChainID) (flow.Chain, fvm.VM) { 71 return chainID.Chain(), fvm.NewVirtualMachine() 72 } 73 74 func (vmt vmTest) run( 75 f func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree), 76 ) func(t *testing.T) { 77 return func(t *testing.T) { 78 baseOpts := []fvm.Option{ 79 // default chain is Testnet 80 fvm.WithChain(flow.Testnet.Chain()), 81 } 82 83 opts := append(baseOpts, vmt.contextOptions...) 84 ctx := fvm.NewContext(opts...) 85 86 chain := ctx.Chain 87 vm := fvm.NewVirtualMachine() 88 89 snapshotTree := snapshot.NewSnapshotTree(nil) 90 91 baseBootstrapOpts := []fvm.BootstrapProcedureOption{ 92 fvm.WithInitialTokenSupply(unittest.GenesisTokenSupply), 93 } 94 95 bootstrapOpts := append(baseBootstrapOpts, vmt.bootstrapOptions...) 96 97 executionSnapshot, _, err := vm.Run( 98 ctx, 99 fvm.Bootstrap(unittest.ServiceAccountPublicKey, bootstrapOpts...), 100 snapshotTree) 101 require.NoError(t, err) 102 103 snapshotTree = snapshotTree.Append(executionSnapshot) 104 105 f(t, vm, chain, ctx, snapshotTree) 106 } 107 } 108 109 // bootstrapWith executes the bootstrap procedure and the custom bootstrap function 110 // and returns a prepared bootstrappedVmTest with all the state needed 111 func (vmt vmTest) bootstrapWith( 112 bootstrap func(vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) (snapshot.SnapshotTree, error), 113 ) (bootstrappedVmTest, error) { 114 115 baseOpts := []fvm.Option{ 116 // default chain is Testnet 117 fvm.WithChain(flow.Testnet.Chain()), 118 } 119 120 opts := append(baseOpts, vmt.contextOptions...) 121 ctx := fvm.NewContext(opts...) 122 123 chain := ctx.Chain 124 vm := fvm.NewVirtualMachine() 125 126 snapshotTree := snapshot.NewSnapshotTree(nil) 127 128 baseBootstrapOpts := []fvm.BootstrapProcedureOption{ 129 fvm.WithInitialTokenSupply(unittest.GenesisTokenSupply), 130 } 131 132 bootstrapOpts := append(baseBootstrapOpts, vmt.bootstrapOptions...) 133 134 executionSnapshot, _, err := vm.Run( 135 ctx, 136 fvm.Bootstrap(unittest.ServiceAccountPublicKey, bootstrapOpts...), 137 snapshotTree) 138 if err != nil { 139 return bootstrappedVmTest{}, err 140 } 141 142 snapshotTree = snapshotTree.Append(executionSnapshot) 143 144 snapshotTree, err = bootstrap(vm, chain, ctx, snapshotTree) 145 if err != nil { 146 return bootstrappedVmTest{}, err 147 } 148 149 return bootstrappedVmTest{chain, ctx, snapshotTree}, nil 150 } 151 152 type bootstrappedVmTest struct { 153 chain flow.Chain 154 ctx fvm.Context 155 snapshotTree snapshot.SnapshotTree 156 } 157 158 // run Runs a test from the bootstrapped state, without changing the bootstrapped state 159 func (vmt bootstrappedVmTest) run( 160 f func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree), 161 ) func(t *testing.T) { 162 return func(t *testing.T) { 163 f(t, fvm.NewVirtualMachine(), vmt.chain, vmt.ctx, vmt.snapshotTree) 164 } 165 } 166 167 func TestHashing(t *testing.T) { 168 169 t.Parallel() 170 171 chain, vm := createChainAndVm(flow.Mainnet) 172 173 ctx := fvm.NewContext( 174 fvm.WithChain(chain), 175 fvm.WithCadenceLogging(true), 176 ) 177 178 snapshotTree := testutil.RootBootstrappedLedger(vm, ctx) 179 180 hashScript := func(hashName string) []byte { 181 return []byte(fmt.Sprintf( 182 ` 183 import Crypto 184 185 pub fun main(data: [UInt8]): [UInt8] { 186 return Crypto.hash(data, algorithm: HashAlgorithm.%s) 187 } 188 `, hashName)) 189 } 190 hashWithTagScript := func(hashName string) []byte { 191 return []byte(fmt.Sprintf( 192 ` 193 import Crypto 194 195 pub fun main(data: [UInt8], tag: String): [UInt8] { 196 return Crypto.hashWithTag(data, tag: tag, algorithm: HashAlgorithm.%s) 197 } 198 `, hashName)) 199 } 200 201 data := []byte("some random message") 202 encodedBytes := make([]cadence.Value, len(data)) 203 for i := range encodedBytes { 204 encodedBytes[i] = cadence.NewUInt8(data[i]) 205 } 206 cadenceData := jsoncdc.MustEncode(cadence.NewArray(encodedBytes)) 207 208 // ===== Test Cases ===== 209 cases := []struct { 210 Algo runtime.HashAlgorithm 211 WithTag bool 212 Tag string 213 Check func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) 214 }{ 215 { 216 Algo: runtime.HashAlgorithmSHA2_256, 217 WithTag: false, 218 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 219 require.NoError(t, scriptErr) 220 require.NoError(t, executionErr) 221 require.Equal(t, "68fb87dfba69b956f4ba98b748a75a604f99b38a4f2740290037957f7e830da8", result) 222 }, 223 }, 224 { 225 Algo: runtime.HashAlgorithmSHA2_384, 226 WithTag: false, 227 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 228 require.NoError(t, scriptErr) 229 require.NoError(t, executionErr) 230 require.Equal(t, "a9b3e62ab9b2a33020e015f245b82e063afd1398211326408bc8fc31c2c15859594b0aee263fbb02f6d8b5065ad49df2", result) 231 }, 232 }, 233 { 234 Algo: runtime.HashAlgorithmSHA3_256, 235 WithTag: false, 236 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 237 require.NoError(t, scriptErr) 238 require.NoError(t, executionErr) 239 require.Equal(t, "38effea5ab9082a2cb0dc9adfafaf88523e8f3ce74bfbeac85ffc719cc2c4677", result) 240 }, 241 }, 242 { 243 Algo: runtime.HashAlgorithmSHA3_384, 244 WithTag: false, 245 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 246 require.NoError(t, scriptErr) 247 require.NoError(t, executionErr) 248 require.Equal(t, "f41e8de9af0c1f46fc56d5a776f1bd500530879a85f3b904821810295927e13a54f3e936dddb84669021052eb12966c3", result) 249 }, 250 }, 251 { 252 Algo: runtime.HashAlgorithmKECCAK_256, 253 WithTag: false, 254 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 255 require.NoError(t, scriptErr) 256 require.NoError(t, executionErr) 257 require.Equal(t, "1d5ced4738dd4e0bb4628dad7a7b59b8e339a75ece97a4ad004773a49ed7b5bc", result) 258 }, 259 }, 260 { 261 Algo: runtime.HashAlgorithmKECCAK_256, 262 WithTag: true, 263 Tag: "some_tag", 264 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 265 require.NoError(t, scriptErr) 266 require.NoError(t, executionErr) 267 require.Equal(t, "8454ec77f76b229a473770c91e3ea6e7e852416d747805215d15d53bdc56ce5f", result) 268 }, 269 }, 270 { 271 Algo: runtime.HashAlgorithmSHA2_256, 272 WithTag: true, 273 Tag: "some_tag", 274 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 275 require.NoError(t, scriptErr) 276 require.NoError(t, executionErr) 277 require.Equal(t, "4e07609b9a856a5e10703d1dba73be34d9ca0f4e780859d66983f41d746ec8b2", result) 278 }, 279 }, 280 { 281 Algo: runtime.HashAlgorithmSHA2_384, 282 WithTag: true, 283 Tag: "some_tag", 284 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 285 require.NoError(t, scriptErr) 286 require.NoError(t, executionErr) 287 require.Equal(t, "f9bd89e15f341a225656944dc8b3c405e66a0f97838ad44c9803164c911e677aea7ad4e24486fba3f803d83ed1ccfce5", result) 288 }, 289 }, 290 { 291 Algo: runtime.HashAlgorithmSHA3_256, 292 WithTag: true, 293 Tag: "some_tag", 294 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 295 require.NoError(t, scriptErr) 296 require.NoError(t, executionErr) 297 require.Equal(t, "f59e2ccc9d7f008a96948a31573670d9976a4a161601ab1cd1d2da019779a0f6", result) 298 }, 299 }, 300 { 301 Algo: runtime.HashAlgorithmSHA3_384, 302 WithTag: true, 303 Tag: "some_tag", 304 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 305 require.NoError(t, scriptErr) 306 require.NoError(t, executionErr) 307 require.Equal(t, "e7875eafdb53327faeace8478d1650c6547d04fb4fb42f34509ad64bde0267bea7e1b3af8fda3ef9d9c9327dd4e97a96", result) 308 }, 309 }, 310 { 311 Algo: runtime.HashAlgorithmKMAC128_BLS_BLS12_381, 312 WithTag: false, 313 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 314 require.NoError(t, scriptErr) 315 require.NoError(t, executionErr) 316 require.Equal(t, "44dc46111abacfe2bb4a04cea4805aad03f84e4849f138cc3ed431478472b185548628e96d0c963b21ebaf17132d73fc13031eb82d5f4cbe3b6047ff54d20e8d663904373d73348b97ce18305ebc56114cb7e7394e486684007f78aa59abc5d0a8f6bae6bd186db32528af80857cd12112ce6960be29c96074df9c4aaed5b0e6", result) 317 }, 318 }, 319 { 320 Algo: runtime.HashAlgorithmKMAC128_BLS_BLS12_381, 321 WithTag: true, 322 Tag: "some_tag", 323 Check: func(t *testing.T, result string, scriptErr errors.CodedError, executionErr error) { 324 require.NoError(t, scriptErr) 325 require.NoError(t, executionErr) 326 require.Equal(t, "de7d9aa24274fa12c98cce5c09eea0634108ead2e91828b9a9a450e878088393e3e63eb4b19834f579ce215b00a9915919b67a71dab1112560319e6e1e5e9ad0fb670e8a09d586508c84547cee7ddbe8c9362c996846154865eb271bdc4523dbcdbdae5a77391fb54374f37534c8bb2281589cb2e3d62742596cdad7e4f9f35c", result) 327 }, 328 }, 329 } 330 // ====================== 331 332 for i, c := range cases { 333 t.Run(fmt.Sprintf("case %d: %s with tag: %v", i, c.Algo, c.WithTag), func(t *testing.T) { 334 code := hashScript(c.Algo.Name()) 335 if c.WithTag { 336 code = hashWithTagScript(c.Algo.Name()) 337 } 338 339 script := fvm.Script(code) 340 341 if c.WithTag { 342 script = script.WithArguments( 343 cadenceData, 344 jsoncdc.MustEncode(cadence.String(c.Tag)), 345 ) 346 } else { 347 script = script.WithArguments( 348 cadenceData, 349 ) 350 } 351 352 _, output, err := vm.Run(ctx, script, snapshotTree) 353 require.NoError(t, err) 354 355 byteResult := make([]byte, 0) 356 if err == nil && output.Err == nil { 357 cadenceArray := output.Value.(cadence.Array) 358 for _, value := range cadenceArray.Values { 359 byteResult = append(byteResult, value.(cadence.UInt8).ToGoValue().(uint8)) 360 } 361 } 362 363 c.Check(t, hex.EncodeToString(byteResult), output.Err, err) 364 }) 365 } 366 367 hashAlgos := []runtime.HashAlgorithm{ 368 runtime.HashAlgorithmSHA2_256, 369 runtime.HashAlgorithmSHA3_256, 370 runtime.HashAlgorithmSHA2_384, 371 runtime.HashAlgorithmSHA3_384, 372 runtime.HashAlgorithmKMAC128_BLS_BLS12_381, 373 runtime.HashAlgorithmKECCAK_256, 374 } 375 376 for i, algo := range hashAlgos { 377 t.Run(fmt.Sprintf("compare hash results without tag %v: %v", i, algo), func(t *testing.T) { 378 code := hashWithTagScript(algo.Name()) 379 script := fvm.Script(code) 380 script = script.WithArguments( 381 cadenceData, 382 jsoncdc.MustEncode(cadence.String("")), 383 ) 384 _, output, err := vm.Run(ctx, script, snapshotTree) 385 require.NoError(t, err) 386 require.NoError(t, output.Err) 387 388 result1 := make([]byte, 0) 389 cadenceArray := output.Value.(cadence.Array) 390 for _, value := range cadenceArray.Values { 391 result1 = append(result1, value.(cadence.UInt8).ToGoValue().(uint8)) 392 } 393 394 code = hashScript(algo.Name()) 395 script = fvm.Script(code) 396 script = script.WithArguments( 397 cadenceData, 398 ) 399 _, output, err = vm.Run(ctx, script, snapshotTree) 400 require.NoError(t, err) 401 require.NoError(t, output.Err) 402 403 result2 := make([]byte, 0) 404 cadenceArray = output.Value.(cadence.Array) 405 for _, value := range cadenceArray.Values { 406 result2 = append(result2, value.(cadence.UInt8).ToGoValue().(uint8)) 407 } 408 409 result3, err := fvmCrypto.HashWithTag(fvmCrypto.RuntimeToCryptoHashingAlgorithm(algo), "", data) 410 require.NoError(t, err) 411 412 require.Equal(t, result1, result2) 413 require.Equal(t, result1, result3) 414 }) 415 } 416 } 417 418 func TestWithServiceAccount(t *testing.T) { 419 420 t.Parallel() 421 422 chain, vm := createChainAndVm(flow.Mainnet) 423 424 ctxA := fvm.NewContext( 425 fvm.WithChain(chain), 426 fvm.WithAuthorizationChecksEnabled(false), 427 fvm.WithSequenceNumberCheckAndIncrementEnabled(false), 428 ) 429 430 snapshotTree := snapshot.NewSnapshotTree(nil) 431 432 txBody := flow.NewTransactionBody(). 433 SetScript([]byte(`transaction { prepare(signer: AuthAccount) { AuthAccount(payer: signer) } }`)). 434 AddAuthorizer(chain.ServiceAddress()) 435 436 t.Run("With service account enabled", func(t *testing.T) { 437 executionSnapshot, output, err := vm.Run( 438 ctxA, 439 fvm.Transaction(txBody, 0), 440 snapshotTree) 441 require.NoError(t, err) 442 443 // transaction should fail on non-bootstrapped ledger 444 require.Error(t, output.Err) 445 446 snapshotTree = snapshotTree.Append(executionSnapshot) 447 }) 448 449 t.Run("With service account disabled", func(t *testing.T) { 450 ctxB := fvm.NewContextFromParent( 451 ctxA, 452 fvm.WithServiceAccount(false)) 453 454 _, output, err := vm.Run( 455 ctxB, 456 fvm.Transaction(txBody, 0), 457 snapshotTree) 458 require.NoError(t, err) 459 460 // transaction should succeed on non-bootstrapped ledger 461 require.NoError(t, output.Err) 462 }) 463 } 464 465 func TestEventLimits(t *testing.T) { 466 chain, vm := createChainAndVm(flow.Mainnet) 467 468 ctx := fvm.NewContext( 469 fvm.WithChain(chain), 470 fvm.WithAuthorizationChecksEnabled(false), 471 fvm.WithSequenceNumberCheckAndIncrementEnabled(false), 472 ) 473 474 snapshotTree := testutil.RootBootstrappedLedger(vm, ctx) 475 476 testContract := ` 477 access(all) contract TestContract { 478 access(all) event LargeEvent(value: Int256, str: String, list: [UInt256], dic: {String: String}) 479 access(all) fun EmitEvent() { 480 var s: Int256 = 1024102410241024 481 var i = 0 482 483 while i < 20 { 484 emit LargeEvent(value: s, str: s.toString(), list:[], dic:{s.toString():s.toString()}) 485 i = i + 1 486 } 487 } 488 } 489 ` 490 491 deployingContractScriptTemplate := ` 492 transaction { 493 prepare(signer: AuthAccount) { 494 let code = "%s".decodeHex() 495 signer.contracts.add( 496 name: "TestContract", 497 code: code 498 ) 499 } 500 } 501 ` 502 503 ctx = fvm.NewContextFromParent( 504 ctx, 505 fvm.WithEventCollectionSizeLimit(2)) 506 507 txBody := flow.NewTransactionBody(). 508 SetScript([]byte(fmt.Sprintf(deployingContractScriptTemplate, hex.EncodeToString([]byte(testContract))))). 509 SetPayer(chain.ServiceAddress()). 510 AddAuthorizer(chain.ServiceAddress()) 511 512 executionSnapshot, output, err := vm.Run( 513 ctx, 514 fvm.Transaction(txBody, 0), 515 snapshotTree) 516 require.NoError(t, err) 517 require.NoError(t, output.Err) 518 519 snapshotTree = snapshotTree.Append(executionSnapshot) 520 521 txBody = flow.NewTransactionBody(). 522 SetScript([]byte(fmt.Sprintf(` 523 import TestContract from 0x%s 524 transaction { 525 prepare(acct: AuthAccount) {} 526 execute { 527 TestContract.EmitEvent() 528 } 529 }`, chain.ServiceAddress()))). 530 AddAuthorizer(chain.ServiceAddress()) 531 532 t.Run("With limits", func(t *testing.T) { 533 txBody.Payer = unittest.RandomAddressFixture() 534 535 executionSnapshot, output, err := vm.Run( 536 ctx, 537 fvm.Transaction(txBody, 0), 538 snapshotTree) 539 require.NoError(t, err) 540 541 // transaction should fail due to event size limit 542 require.Error(t, output.Err) 543 544 snapshotTree = snapshotTree.Append(executionSnapshot) 545 }) 546 547 t.Run("With service account as payer", func(t *testing.T) { 548 txBody.Payer = chain.ServiceAddress() 549 550 _, output, err := vm.Run( 551 ctx, 552 fvm.Transaction(txBody, 0), 553 snapshotTree) 554 require.NoError(t, err) 555 556 unittest.EnsureEventsIndexSeq(t, output.Events, chain.ChainID()) 557 558 // transaction should not fail due to event size limit 559 require.NoError(t, output.Err) 560 }) 561 } 562 563 // TestHappyPathSigning checks that a signing a transaction with `Sign` doesn't produce an error. 564 // Transaction verification tests are in `TestVerifySignatureFromTransaction`. 565 func TestHappyPathTransactionSigning(t *testing.T) { 566 567 newVMTest().run( 568 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) { 569 // Create an account private key. 570 privateKey, err := testutil.GenerateAccountPrivateKey() 571 require.NoError(t, err) 572 573 // Bootstrap a ledger, creating accounts with the provided private 574 // keys and the root account. 575 snapshotTree, accounts, err := testutil.CreateAccounts( 576 vm, 577 snapshotTree, 578 []flow.AccountPrivateKey{privateKey}, 579 chain) 580 require.NoError(t, err) 581 582 txBody := flow.NewTransactionBody(). 583 SetScript([]byte(`transaction(){}`)) 584 585 txBody.SetProposalKey(accounts[0], 0, 0) 586 txBody.SetPayer(accounts[0]) 587 588 hasher, err := exeUtils.NewHasher(privateKey.HashAlgo) 589 require.NoError(t, err) 590 591 sig, err := txBody.Sign(txBody.EnvelopeMessage(), privateKey.PrivateKey, hasher) 592 require.NoError(t, err) 593 txBody.AddEnvelopeSignature(accounts[0], 0, sig) 594 595 _, output, err := vm.Run( 596 ctx, 597 fvm.Transaction(txBody, 0), 598 snapshotTree) 599 require.NoError(t, err) 600 require.NoError(t, output.Err) 601 }, 602 ) 603 } 604 605 func TestTransactionFeeDeduction(t *testing.T) { 606 getBalance := func(vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree, address flow.Address) uint64 { 607 sc := systemcontracts.SystemContractsForChain(chain.ChainID()) 608 code := []byte(fmt.Sprintf( 609 ` 610 import FungibleToken from 0x%s 611 import FlowToken from 0x%s 612 613 pub fun main(account: Address): UFix64 { 614 let acct = getAccount(account) 615 let vaultRef = acct.getCapability(/public/flowTokenBalance) 616 .borrow<&FlowToken.Vault{FungibleToken.Balance}>() 617 ?? panic("Could not borrow Balance reference to the Vault") 618 619 return vaultRef.balance 620 } 621 `, 622 sc.FungibleToken.Address.Hex(), 623 sc.FlowToken.Address.Hex(), 624 )) 625 script := fvm.Script(code).WithArguments( 626 jsoncdc.MustEncode(cadence.NewAddress(address)), 627 ) 628 629 _, output, err := vm.Run(ctx, script, snapshotTree) 630 require.NoError(t, err) 631 require.NoError(t, output.Err) 632 return output.Value.ToGoValue().(uint64) 633 } 634 635 type testCase struct { 636 name string 637 fundWith uint64 638 tryToTransfer uint64 639 gasLimit uint64 640 checkResult func(t *testing.T, balanceBefore uint64, balanceAfter uint64, output fvm.ProcedureOutput) 641 } 642 643 txFees := uint64(1_000) // 0.00001 644 fundingAmount := uint64(100_000_000) // 1.0 645 transferAmount := uint64(123_456) 646 minimumStorageReservation := fvm.DefaultMinimumStorageReservation.ToGoValue().(uint64) 647 648 chain := flow.Testnet.Chain() 649 sc := systemcontracts.SystemContractsForChain(chain.ChainID()) 650 depositedEvent := fmt.Sprintf("A.%s.FlowToken.TokensDeposited", sc.FlowToken.Address) 651 withdrawnEvent := fmt.Sprintf("A.%s.FlowToken.TokensWithdrawn", sc.FlowToken.Address) 652 feesDeductedEvent := fmt.Sprintf("A.%s.FlowFees.FeesDeducted", sc.FlowFees.Address) 653 654 testCases := []testCase{ 655 { 656 name: "Transaction fees are deducted", 657 fundWith: fundingAmount, 658 tryToTransfer: 0, 659 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, output fvm.ProcedureOutput) { 660 require.NoError(t, output.Err) 661 require.Equal(t, txFees, balanceBefore-balanceAfter) 662 }, 663 }, 664 { 665 name: "Transaction fee deduction emits events", 666 fundWith: fundingAmount, 667 tryToTransfer: 0, 668 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, output fvm.ProcedureOutput) { 669 require.NoError(t, output.Err) 670 671 var deposits []flow.Event 672 var withdraws []flow.Event 673 674 chain := flow.Testnet.Chain() 675 for _, e := range output.Events { 676 if string(e.Type) == depositedEvent { 677 deposits = append(deposits, e) 678 } 679 if string(e.Type) == withdrawnEvent { 680 withdraws = append(withdraws, e) 681 } 682 } 683 684 unittest.EnsureEventsIndexSeq(t, output.Events, chain.ChainID()) 685 require.Len(t, deposits, 2) 686 require.Len(t, withdraws, 2) 687 }, 688 }, 689 { 690 name: "Transaction fees are deducted and tx is applied", 691 fundWith: fundingAmount, 692 tryToTransfer: transferAmount, 693 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, output fvm.ProcedureOutput) { 694 require.NoError(t, output.Err) 695 require.Equal(t, txFees+transferAmount, balanceBefore-balanceAfter) 696 }, 697 }, 698 { 699 name: "Transaction fees are deducted and fee deduction is emitted", 700 fundWith: fundingAmount, 701 tryToTransfer: transferAmount, 702 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, output fvm.ProcedureOutput) { 703 require.NoError(t, output.Err) 704 chain := flow.Testnet.Chain() 705 706 var feeDeduction flow.Event // fee deduction event 707 for _, e := range output.Events { 708 if string(e.Type) == feesDeductedEvent { 709 feeDeduction = e 710 break 711 } 712 } 713 unittest.EnsureEventsIndexSeq(t, output.Events, chain.ChainID()) 714 require.NotEmpty(t, feeDeduction.Payload) 715 716 payload, err := ccf.Decode(nil, feeDeduction.Payload) 717 require.NoError(t, err) 718 719 event := payload.(cadence.Event) 720 721 var actualTXFees any 722 var actualInclusionEffort any 723 var actualExecutionEffort any 724 for i, f := range event.EventType.Fields { 725 switch f.Identifier { 726 case "amount": 727 actualTXFees = event.Fields[i].ToGoValue() 728 case "executionEffort": 729 actualExecutionEffort = event.Fields[i].ToGoValue() 730 case "inclusionEffort": 731 actualInclusionEffort = event.Fields[i].ToGoValue() 732 } 733 } 734 735 require.Equal(t, txFees, actualTXFees) 736 // Inclusion effort should be equivalent to 1.0 UFix64 737 require.Equal(t, uint64(100_000_000), actualInclusionEffort) 738 // Execution effort should be non-0 739 require.Greater(t, actualExecutionEffort, uint64(0)) 740 741 }, 742 }, 743 { 744 name: "If just enough balance, fees are deducted", 745 fundWith: txFees + transferAmount, 746 tryToTransfer: transferAmount, 747 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, output fvm.ProcedureOutput) { 748 require.NoError(t, output.Err) 749 require.Equal(t, uint64(0), balanceAfter) 750 }, 751 }, 752 { 753 // this is an edge case that is not applicable to any network. 754 // If storage limits were on this would fail due to storage limits 755 name: "If not enough balance, transaction succeeds and fees are deducted to 0", 756 fundWith: txFees, 757 tryToTransfer: 1, 758 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, output fvm.ProcedureOutput) { 759 require.NoError(t, output.Err) 760 require.Equal(t, uint64(0), balanceAfter) 761 }, 762 }, 763 { 764 name: "If tx fails, fees are deducted", 765 fundWith: fundingAmount, 766 tryToTransfer: 2 * fundingAmount, 767 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, output fvm.ProcedureOutput) { 768 require.Error(t, output.Err) 769 require.Equal(t, fundingAmount-txFees, balanceAfter) 770 }, 771 }, 772 { 773 name: "If tx fails, fee deduction events are emitted", 774 fundWith: fundingAmount, 775 tryToTransfer: 2 * fundingAmount, 776 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, output fvm.ProcedureOutput) { 777 require.Error(t, output.Err) 778 779 var deposits []flow.Event 780 var withdraws []flow.Event 781 782 chain := flow.Testnet.Chain() 783 784 for _, e := range output.Events { 785 if string(e.Type) == depositedEvent { 786 deposits = append(deposits, e) 787 } 788 if string(e.Type) == withdrawnEvent { 789 withdraws = append(withdraws, e) 790 } 791 } 792 793 unittest.EnsureEventsIndexSeq(t, output.Events, chain.ChainID()) 794 require.Len(t, deposits, 1) 795 require.Len(t, withdraws, 1) 796 }, 797 }, 798 { 799 name: "If tx fails because of gas limit reached, fee deduction events are emitted", 800 fundWith: txFees + transferAmount, 801 tryToTransfer: transferAmount, 802 gasLimit: uint64(2), 803 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, output fvm.ProcedureOutput) { 804 require.ErrorContains(t, output.Err, "computation exceeds limit (2)") 805 806 var deposits []flow.Event 807 var withdraws []flow.Event 808 809 chain := flow.Testnet.Chain() 810 811 for _, e := range output.Events { 812 if string(e.Type) == depositedEvent { 813 deposits = append(deposits, e) 814 } 815 if string(e.Type) == withdrawnEvent { 816 withdraws = append(withdraws, e) 817 } 818 } 819 820 unittest.EnsureEventsIndexSeq(t, output.Events, chain.ChainID()) 821 require.Len(t, deposits, 1) 822 require.Len(t, withdraws, 1) 823 }, 824 }, 825 } 826 827 testCasesWithStorageEnabled := []testCase{ 828 { 829 name: "Transaction fees are deducted", 830 fundWith: fundingAmount, 831 tryToTransfer: 0, 832 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, output fvm.ProcedureOutput) { 833 require.NoError(t, output.Err) 834 require.Equal(t, txFees, balanceBefore-balanceAfter) 835 }, 836 }, 837 { 838 name: "Transaction fee deduction emits events", 839 fundWith: fundingAmount, 840 tryToTransfer: 0, 841 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, output fvm.ProcedureOutput) { 842 require.NoError(t, output.Err) 843 844 var deposits []flow.Event 845 var withdraws []flow.Event 846 847 chain := flow.Testnet.Chain() 848 849 for _, e := range output.Events { 850 if string(e.Type) == depositedEvent { 851 deposits = append(deposits, e) 852 } 853 if string(e.Type) == withdrawnEvent { 854 withdraws = append(withdraws, e) 855 } 856 } 857 858 unittest.EnsureEventsIndexSeq(t, output.Events, chain.ChainID()) 859 require.Len(t, deposits, 2) 860 require.Len(t, withdraws, 2) 861 }, 862 }, 863 { 864 name: "Transaction fees are deducted and tx is applied", 865 fundWith: fundingAmount, 866 tryToTransfer: transferAmount, 867 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, output fvm.ProcedureOutput) { 868 require.NoError(t, output.Err) 869 require.Equal(t, txFees+transferAmount, balanceBefore-balanceAfter) 870 }, 871 }, 872 { 873 name: "If just enough balance, fees are deducted", 874 fundWith: txFees + transferAmount, 875 tryToTransfer: transferAmount, 876 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, output fvm.ProcedureOutput) { 877 require.NoError(t, output.Err) 878 require.Equal(t, minimumStorageReservation, balanceAfter) 879 }, 880 }, 881 { 882 name: "If tx fails, fees are deducted", 883 fundWith: fundingAmount, 884 tryToTransfer: 2 * fundingAmount, 885 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, output fvm.ProcedureOutput) { 886 require.Error(t, output.Err) 887 require.Equal(t, fundingAmount-txFees+minimumStorageReservation, balanceAfter) 888 }, 889 }, 890 { 891 name: "If tx fails, fee deduction events are emitted", 892 fundWith: fundingAmount, 893 tryToTransfer: 2 * fundingAmount, 894 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, output fvm.ProcedureOutput) { 895 require.Error(t, output.Err) 896 897 var deposits []flow.Event 898 var withdraws []flow.Event 899 900 chain := flow.Testnet.Chain() 901 902 for _, e := range output.Events { 903 if string(e.Type) == depositedEvent { 904 deposits = append(deposits, e) 905 } 906 if string(e.Type) == withdrawnEvent { 907 withdraws = append(withdraws, e) 908 } 909 } 910 911 unittest.EnsureEventsIndexSeq(t, output.Events, chain.ChainID()) 912 require.Len(t, deposits, 1) 913 require.Len(t, withdraws, 1) 914 }, 915 }, 916 { 917 name: "If balance at minimum, transaction fails, fees are deducted and fee deduction events are emitted", 918 fundWith: 0, 919 tryToTransfer: 0, 920 checkResult: func(t *testing.T, balanceBefore uint64, balanceAfter uint64, output fvm.ProcedureOutput) { 921 require.Error(t, output.Err) 922 require.Equal(t, minimumStorageReservation-txFees, balanceAfter) 923 924 var deposits []flow.Event 925 var withdraws []flow.Event 926 927 chain := flow.Testnet.Chain() 928 929 for _, e := range output.Events { 930 if string(e.Type) == depositedEvent { 931 deposits = append(deposits, e) 932 } 933 if string(e.Type) == withdrawnEvent { 934 withdraws = append(withdraws, e) 935 } 936 } 937 938 unittest.EnsureEventsIndexSeq(t, output.Events, chain.ChainID()) 939 require.Len(t, deposits, 1) 940 require.Len(t, withdraws, 1) 941 }, 942 }, 943 } 944 945 runTx := func(tc testCase) func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) { 946 return func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) { 947 // ==== Create an account ==== 948 privateKey, txBody := testutil.CreateAccountCreationTransaction(t, chain) 949 950 err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) 951 require.NoError(t, err) 952 953 executionSnapshot, output, err := vm.Run( 954 ctx, 955 fvm.Transaction(txBody, 0), 956 snapshotTree) 957 require.NoError(t, err) 958 require.NoError(t, output.Err) 959 960 snapshotTree = snapshotTree.Append(executionSnapshot) 961 962 require.Len(t, output.Events, 10) 963 unittest.EnsureEventsIndexSeq(t, output.Events, chain.ChainID()) 964 965 accountCreatedEvents := filterAccountCreatedEvents(output.Events) 966 967 require.Len(t, accountCreatedEvents, 1) 968 969 // read the address of the account created (e.g. "0x01" and convert it to flow.address) 970 data, err := ccf.Decode(nil, accountCreatedEvents[0].Payload) 971 require.NoError(t, err) 972 address := flow.ConvertAddress( 973 data.(cadence.Event).Fields[0].(cadence.Address)) 974 975 // ==== Transfer tokens to new account ==== 976 txBody = transferTokensTx(chain). 977 AddAuthorizer(chain.ServiceAddress()). 978 AddArgument(jsoncdc.MustEncode(cadence.UFix64(tc.fundWith))). 979 AddArgument(jsoncdc.MustEncode(cadence.NewAddress(address))) 980 981 txBody.SetProposalKey(chain.ServiceAddress(), 0, 1) 982 txBody.SetPayer(chain.ServiceAddress()) 983 984 err = testutil.SignEnvelope( 985 txBody, 986 chain.ServiceAddress(), 987 unittest.ServiceAccountPrivateKey, 988 ) 989 require.NoError(t, err) 990 991 executionSnapshot, output, err = vm.Run( 992 ctx, 993 fvm.Transaction(txBody, 0), 994 snapshotTree) 995 require.NoError(t, err) 996 require.NoError(t, output.Err) 997 998 snapshotTree = snapshotTree.Append(executionSnapshot) 999 1000 balanceBefore := getBalance(vm, chain, ctx, snapshotTree, address) 1001 1002 // ==== Transfer tokens from new account ==== 1003 1004 txBody = transferTokensTx(chain). 1005 AddAuthorizer(address). 1006 AddArgument(jsoncdc.MustEncode(cadence.UFix64(tc.tryToTransfer))). 1007 AddArgument(jsoncdc.MustEncode(cadence.NewAddress(chain.ServiceAddress()))) 1008 1009 txBody.SetProposalKey(address, 0, 0) 1010 txBody.SetPayer(address) 1011 1012 if tc.gasLimit == 0 { 1013 txBody.SetGasLimit(fvm.DefaultComputationLimit) 1014 } else { 1015 txBody.SetGasLimit(tc.gasLimit) 1016 } 1017 1018 err = testutil.SignEnvelope( 1019 txBody, 1020 address, 1021 privateKey, 1022 ) 1023 require.NoError(t, err) 1024 1025 executionSnapshot, output, err = vm.Run( 1026 ctx, 1027 fvm.Transaction(txBody, 0), 1028 snapshotTree) 1029 require.NoError(t, err) 1030 1031 snapshotTree = snapshotTree.Append(executionSnapshot) 1032 1033 balanceAfter := getBalance(vm, chain, ctx, snapshotTree, address) 1034 1035 tc.checkResult( 1036 t, 1037 balanceBefore, 1038 balanceAfter, 1039 output, 1040 ) 1041 } 1042 } 1043 1044 for i, tc := range testCases { 1045 t.Run(fmt.Sprintf("Transaction Fees %d: %s", i, tc.name), newVMTest().withBootstrapProcedureOptions( 1046 fvm.WithTransactionFee(fvm.DefaultTransactionFees), 1047 fvm.WithExecutionMemoryLimit(math.MaxUint64), 1048 fvm.WithExecutionEffortWeights(mainnetExecutionEffortWeights), 1049 fvm.WithExecutionMemoryWeights(meter.DefaultMemoryWeights), 1050 ).withContextOptions( 1051 fvm.WithTransactionFeesEnabled(true), 1052 fvm.WithChain(chain), 1053 ).run( 1054 runTx(tc)), 1055 ) 1056 } 1057 1058 for i, tc := range testCasesWithStorageEnabled { 1059 t.Run(fmt.Sprintf("Transaction Fees with storage %d: %s", i, tc.name), newVMTest().withBootstrapProcedureOptions( 1060 fvm.WithTransactionFee(fvm.DefaultTransactionFees), 1061 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 1062 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 1063 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 1064 fvm.WithExecutionMemoryLimit(math.MaxUint64), 1065 fvm.WithExecutionEffortWeights(mainnetExecutionEffortWeights), 1066 fvm.WithExecutionMemoryWeights(meter.DefaultMemoryWeights), 1067 ).withContextOptions( 1068 fvm.WithTransactionFeesEnabled(true), 1069 fvm.WithAccountStorageLimit(true), 1070 fvm.WithChain(chain), 1071 ).run( 1072 runTx(tc)), 1073 ) 1074 } 1075 } 1076 1077 func TestSettingExecutionWeights(t *testing.T) { 1078 1079 t.Run("transaction should fail with high weights", newVMTest().withBootstrapProcedureOptions( 1080 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 1081 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 1082 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 1083 fvm.WithExecutionEffortWeights( 1084 meter.ExecutionEffortWeights{ 1085 common.ComputationKindLoop: 100_000 << meter.MeterExecutionInternalPrecisionBytes, 1086 }, 1087 ), 1088 ).run( 1089 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) { 1090 1091 txBody := flow.NewTransactionBody(). 1092 SetScript([]byte(` 1093 transaction { 1094 prepare(signer: AuthAccount) { 1095 var a = 0 1096 while a < 100 { 1097 a = a + 1 1098 } 1099 } 1100 } 1101 `)). 1102 SetProposalKey(chain.ServiceAddress(), 0, 0). 1103 AddAuthorizer(chain.ServiceAddress()). 1104 SetPayer(chain.ServiceAddress()) 1105 1106 err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) 1107 require.NoError(t, err) 1108 1109 _, output, err := vm.Run( 1110 ctx, 1111 fvm.Transaction(txBody, 0), 1112 snapshotTree) 1113 require.NoError(t, err) 1114 1115 require.True(t, errors.IsComputationLimitExceededError(output.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, snapshotTree snapshot.SnapshotTree) { 1138 // Create an account private key. 1139 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 1140 require.NoError(t, err) 1141 1142 // Bootstrap a ledger, creating accounts with the provided private 1143 // keys and the root account. 1144 snapshotTree, accounts, err := testutil.CreateAccounts( 1145 vm, 1146 snapshotTree, 1147 privateKeys, 1148 chain) 1149 require.NoError(t, err) 1150 1151 txBody := flow.NewTransactionBody(). 1152 SetScript([]byte(` 1153 transaction { 1154 prepare(signer: AuthAccount) { 1155 var a = 1 1156 } 1157 } 1158 `)). 1159 SetProposalKey(accounts[0], 0, 0). 1160 AddAuthorizer(accounts[0]). 1161 SetPayer(accounts[0]) 1162 1163 err = testutil.SignTransaction(txBody, accounts[0], privateKeys[0], 0) 1164 require.NoError(t, err) 1165 1166 _, output, err := vm.Run( 1167 ctx, 1168 fvm.Transaction(txBody, 0), 1169 snapshotTree) 1170 require.NoError(t, err) 1171 require.Greater(t, output.MemoryEstimate, uint64(highWeight)) 1172 1173 require.True(t, errors.IsMemoryLimitExceededError(output.Err)) 1174 }, 1175 )) 1176 1177 t.Run("service account transactions should not fail with high memory weights", newVMTest().withBootstrapProcedureOptions( 1178 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 1179 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 1180 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 1181 fvm.WithExecutionMemoryWeights( 1182 memoryWeights, 1183 ), 1184 ).withContextOptions( 1185 fvm.WithMemoryLimit(10_000_000_000), 1186 ).run( 1187 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) { 1188 1189 txBody := flow.NewTransactionBody(). 1190 SetScript([]byte(` 1191 transaction { 1192 prepare(signer: AuthAccount) { 1193 var a = 1 1194 } 1195 } 1196 `)). 1197 SetProposalKey(chain.ServiceAddress(), 0, 0). 1198 AddAuthorizer(chain.ServiceAddress()). 1199 SetPayer(chain.ServiceAddress()) 1200 1201 err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) 1202 require.NoError(t, err) 1203 1204 _, output, err := vm.Run( 1205 ctx, 1206 fvm.Transaction(txBody, 0), 1207 snapshotTree) 1208 require.NoError(t, err) 1209 require.Greater(t, output.MemoryEstimate, uint64(highWeight)) 1210 1211 require.NoError(t, output.Err) 1212 }, 1213 )) 1214 1215 memoryWeights = make(map[common.MemoryKind]uint64) 1216 for k, v := range meter.DefaultMemoryWeights { 1217 memoryWeights[k] = v 1218 } 1219 memoryWeights[common.MemoryKindBreakStatement] = 1_000_000 1220 t.Run("transaction should fail with low memory limit (set in the state)", newVMTest().withBootstrapProcedureOptions( 1221 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 1222 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 1223 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 1224 fvm.WithExecutionMemoryLimit( 1225 100_000_000, 1226 ), 1227 fvm.WithExecutionMemoryWeights( 1228 memoryWeights, 1229 ), 1230 ).run( 1231 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) { 1232 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 1233 require.NoError(t, err) 1234 1235 snapshotTree, accounts, err := testutil.CreateAccounts( 1236 vm, 1237 snapshotTree, 1238 privateKeys, 1239 chain) 1240 require.NoError(t, err) 1241 1242 // This transaction is specially designed to use a lot of breaks 1243 // as the weight for breaks is much higher than usual. 1244 // putting a `while true {break}` in a loop does not use the same amount of memory. 1245 txBody := flow.NewTransactionBody(). 1246 SetScript([]byte(` 1247 transaction { 1248 prepare(signer: AuthAccount) { 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 while true {break};while true {break};while true {break};while true {break};while true {break}; 1258 while true {break};while true {break};while true {break};while true {break};while true {break}; 1259 while true {break};while true {break};while true {break};while true {break};while true {break}; 1260 while true {break};while true {break};while true {break};while true {break};while true {break}; 1261 while true {break};while true {break};while true {break};while true {break};while true {break}; 1262 while true {break};while true {break};while true {break};while true {break};while true {break}; 1263 while true {break};while true {break};while true {break};while true {break};while true {break}; 1264 while true {break};while true {break};while true {break};while true {break};while true {break}; 1265 while true {break};while true {break};while true {break};while true {break};while true {break}; 1266 while true {break};while true {break};while true {break};while true {break};while true {break}; 1267 while true {break};while true {break};while true {break};while true {break};while true {break}; 1268 while true {break};while true {break};while true {break};while true {break};while true {break}; 1269 } 1270 } 1271 `)) 1272 1273 err = testutil.SignTransaction(txBody, accounts[0], privateKeys[0], 0) 1274 require.NoError(t, err) 1275 1276 _, output, err := vm.Run( 1277 ctx, 1278 fvm.Transaction(txBody, 0), 1279 snapshotTree) 1280 require.NoError(t, err) 1281 // There are 100 breaks and each break uses 1_000_000 memory 1282 require.Greater(t, output.MemoryEstimate, uint64(100_000_000)) 1283 1284 require.True(t, errors.IsMemoryLimitExceededError(output.Err)) 1285 }, 1286 )) 1287 1288 t.Run("transaction should fail if create account weight is high", newVMTest().withBootstrapProcedureOptions( 1289 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 1290 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 1291 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 1292 fvm.WithExecutionEffortWeights( 1293 meter.ExecutionEffortWeights{ 1294 environment.ComputationKindCreateAccount: (fvm.DefaultComputationLimit + 1) << meter.MeterExecutionInternalPrecisionBytes, 1295 }, 1296 ), 1297 ).run( 1298 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) { 1299 txBody := flow.NewTransactionBody(). 1300 SetScript([]byte(` 1301 transaction { 1302 prepare(signer: AuthAccount) { 1303 AuthAccount(payer: signer) 1304 } 1305 } 1306 `)). 1307 SetProposalKey(chain.ServiceAddress(), 0, 0). 1308 AddAuthorizer(chain.ServiceAddress()). 1309 SetPayer(chain.ServiceAddress()) 1310 1311 err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) 1312 require.NoError(t, err) 1313 1314 _, output, err := vm.Run( 1315 ctx, 1316 fvm.Transaction(txBody, 0), 1317 snapshotTree) 1318 require.NoError(t, err) 1319 1320 require.True(t, errors.IsComputationLimitExceededError(output.Err)) 1321 }, 1322 )) 1323 1324 t.Run("transaction should fail if create account weight is high", newVMTest().withBootstrapProcedureOptions( 1325 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 1326 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 1327 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 1328 fvm.WithExecutionEffortWeights( 1329 meter.ExecutionEffortWeights{ 1330 environment.ComputationKindCreateAccount: 100_000_000 << meter.MeterExecutionInternalPrecisionBytes, 1331 }, 1332 ), 1333 ).run( 1334 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) { 1335 1336 txBody := flow.NewTransactionBody(). 1337 SetScript([]byte(` 1338 transaction { 1339 prepare(signer: AuthAccount) { 1340 AuthAccount(payer: signer) 1341 } 1342 } 1343 `)). 1344 SetProposalKey(chain.ServiceAddress(), 0, 0). 1345 AddAuthorizer(chain.ServiceAddress()). 1346 SetPayer(chain.ServiceAddress()) 1347 1348 err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) 1349 require.NoError(t, err) 1350 1351 _, output, err := vm.Run( 1352 ctx, 1353 fvm.Transaction(txBody, 0), 1354 snapshotTree) 1355 require.NoError(t, err) 1356 1357 require.True(t, errors.IsComputationLimitExceededError(output.Err)) 1358 }, 1359 )) 1360 1361 t.Run("transaction should fail if create account weight is high", newVMTest().withBootstrapProcedureOptions( 1362 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 1363 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 1364 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 1365 fvm.WithExecutionEffortWeights( 1366 meter.ExecutionEffortWeights{ 1367 environment.ComputationKindCreateAccount: 100_000_000 << meter.MeterExecutionInternalPrecisionBytes, 1368 }, 1369 ), 1370 ).run( 1371 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) { 1372 txBody := flow.NewTransactionBody(). 1373 SetScript([]byte(` 1374 transaction { 1375 prepare(signer: AuthAccount) { 1376 AuthAccount(payer: signer) 1377 } 1378 } 1379 `)). 1380 SetProposalKey(chain.ServiceAddress(), 0, 0). 1381 AddAuthorizer(chain.ServiceAddress()). 1382 SetPayer(chain.ServiceAddress()) 1383 1384 err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) 1385 require.NoError(t, err) 1386 1387 _, output, err := vm.Run( 1388 ctx, 1389 fvm.Transaction(txBody, 0), 1390 snapshotTree) 1391 require.NoError(t, err) 1392 1393 require.True(t, errors.IsComputationLimitExceededError(output.Err)) 1394 }, 1395 )) 1396 1397 t.Run("transaction should not use up more computation that the transaction body itself", newVMTest().withBootstrapProcedureOptions( 1398 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 1399 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 1400 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 1401 fvm.WithTransactionFee(fvm.DefaultTransactionFees), 1402 fvm.WithExecutionEffortWeights( 1403 meter.ExecutionEffortWeights{ 1404 common.ComputationKindStatement: 0, 1405 common.ComputationKindLoop: 1 << meter.MeterExecutionInternalPrecisionBytes, 1406 common.ComputationKindFunctionInvocation: 0, 1407 }, 1408 ), 1409 ).withContextOptions( 1410 fvm.WithAccountStorageLimit(true), 1411 fvm.WithTransactionFeesEnabled(true), 1412 fvm.WithMemoryLimit(math.MaxUint64), 1413 ).run( 1414 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) { 1415 // Use the maximum amount of computation so that the transaction still passes. 1416 loops := uint64(997) 1417 maxExecutionEffort := uint64(997) 1418 txBody := flow.NewTransactionBody(). 1419 SetScript([]byte(fmt.Sprintf(` 1420 transaction() {prepare(signer: AuthAccount){var i=0; while i < %d {i = i +1 } } execute{}} 1421 `, loops))). 1422 SetProposalKey(chain.ServiceAddress(), 0, 0). 1423 AddAuthorizer(chain.ServiceAddress()). 1424 SetPayer(chain.ServiceAddress()). 1425 SetGasLimit(maxExecutionEffort) 1426 1427 err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) 1428 require.NoError(t, err) 1429 1430 executionSnapshot, output, err := vm.Run( 1431 ctx, 1432 fvm.Transaction(txBody, 0), 1433 snapshotTree) 1434 require.NoError(t, err) 1435 require.NoError(t, output.Err) 1436 1437 snapshotTree = snapshotTree.Append(executionSnapshot) 1438 1439 // expected used is number of loops. 1440 require.Equal(t, loops, output.ComputationUsed) 1441 1442 // increasing the number of loops should fail the transaction. 1443 loops = loops + 1 1444 txBody = flow.NewTransactionBody(). 1445 SetScript([]byte(fmt.Sprintf(` 1446 transaction() {prepare(signer: AuthAccount){var i=0; while i < %d {i = i +1 } } execute{}} 1447 `, loops))). 1448 SetProposalKey(chain.ServiceAddress(), 0, 1). 1449 AddAuthorizer(chain.ServiceAddress()). 1450 SetPayer(chain.ServiceAddress()). 1451 SetGasLimit(maxExecutionEffort) 1452 1453 err = testutil.SignTransactionAsServiceAccount(txBody, 1, chain) 1454 require.NoError(t, err) 1455 1456 _, output, err = vm.Run( 1457 ctx, 1458 fvm.Transaction(txBody, 0), 1459 snapshotTree) 1460 require.NoError(t, err) 1461 1462 require.ErrorContains(t, output.Err, "computation exceeds limit (997)") 1463 // computation used should the actual computation used. 1464 require.Equal(t, loops, output.ComputationUsed) 1465 1466 for _, event := range output.Events { 1467 // the fee deduction event should only contain the max gas worth of execution effort. 1468 if strings.Contains(string(event.Type), "FlowFees.FeesDeducted") { 1469 v, err := ccf.Decode(nil, event.Payload) 1470 require.NoError(t, err) 1471 1472 ev := v.(cadence.Event) 1473 var actualExecutionEffort any 1474 for i, f := range ev.Type().(*cadence.EventType).Fields { 1475 if f.Identifier == "executionEffort" { 1476 actualExecutionEffort = ev.Fields[i].ToGoValue() 1477 } 1478 } 1479 1480 require.Equal( 1481 t, 1482 maxExecutionEffort, 1483 actualExecutionEffort) 1484 } 1485 } 1486 unittest.EnsureEventsIndexSeq(t, output.Events, chain.ChainID()) 1487 }, 1488 )) 1489 } 1490 1491 func TestStorageUsed(t *testing.T) { 1492 t.Parallel() 1493 1494 chain, vm := createChainAndVm(flow.Testnet) 1495 1496 ctx := fvm.NewContext( 1497 fvm.WithChain(chain), 1498 fvm.WithCadenceLogging(true), 1499 ) 1500 1501 code := []byte(` 1502 pub fun main(): UInt64 { 1503 1504 var addresses: [Address]= [ 1505 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 1506 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 1507 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 1508 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 1509 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 1510 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 1511 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 1512 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 1513 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 1514 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 1515 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731, 0x2a3c4c2581cef731 1516 ] 1517 1518 var storageUsed: UInt64 = 0 1519 for address in addresses { 1520 let account = getAccount(address) 1521 storageUsed = account.storageUsed 1522 } 1523 1524 return storageUsed 1525 } 1526 `) 1527 1528 address, err := hex.DecodeString("2a3c4c2581cef731") 1529 require.NoError(t, err) 1530 1531 accountStatusId := flow.AccountStatusRegisterID( 1532 flow.BytesToAddress(address)) 1533 1534 status := environment.NewAccountStatus() 1535 status.SetStorageUsed(5) 1536 1537 _, output, err := vm.Run( 1538 ctx, 1539 fvm.Script(code), 1540 snapshot.MapStorageSnapshot{ 1541 accountStatusId: status.ToBytes(), 1542 }) 1543 require.NoError(t, err) 1544 1545 require.Equal(t, cadence.NewUInt64(5), output.Value) 1546 } 1547 1548 func TestEnforcingComputationLimit(t *testing.T) { 1549 t.Parallel() 1550 1551 chain, vm := createChainAndVm(flow.Testnet) 1552 1553 const computationLimit = 5 1554 1555 type test struct { 1556 name string 1557 code string 1558 payerIsServAcc bool 1559 ok bool 1560 expCompUsed uint64 1561 } 1562 1563 tests := []test{ 1564 { 1565 name: "infinite while loop", 1566 code: ` 1567 while true {} 1568 `, 1569 payerIsServAcc: false, 1570 ok: false, 1571 expCompUsed: computationLimit + 1, 1572 }, 1573 { 1574 name: "limited while loop", 1575 code: ` 1576 var i = 0 1577 while i < 5 { 1578 i = i + 1 1579 } 1580 `, 1581 payerIsServAcc: false, 1582 ok: false, 1583 expCompUsed: computationLimit + 1, 1584 }, 1585 { 1586 name: "too many for-in loop iterations", 1587 code: ` 1588 for i in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] {} 1589 `, 1590 payerIsServAcc: false, 1591 ok: false, 1592 expCompUsed: computationLimit + 1, 1593 }, 1594 { 1595 name: "too many for-in loop iterations", 1596 code: ` 1597 for i in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] {} 1598 `, 1599 payerIsServAcc: true, 1600 ok: true, 1601 expCompUsed: 11, 1602 }, 1603 { 1604 name: "some for-in loop iterations", 1605 code: ` 1606 for i in [1, 2, 3, 4] {} 1607 `, 1608 payerIsServAcc: false, 1609 ok: true, 1610 expCompUsed: 5, 1611 }, 1612 } 1613 1614 for _, test := range tests { 1615 1616 t.Run(test.name, func(t *testing.T) { 1617 ctx := fvm.NewContext( 1618 fvm.WithChain(chain), 1619 fvm.WithAuthorizationChecksEnabled(false), 1620 fvm.WithSequenceNumberCheckAndIncrementEnabled(false), 1621 ) 1622 1623 script := []byte( 1624 fmt.Sprintf( 1625 ` 1626 transaction { 1627 prepare() { 1628 %s 1629 } 1630 } 1631 `, 1632 test.code, 1633 ), 1634 ) 1635 1636 txBody := flow.NewTransactionBody(). 1637 SetScript(script). 1638 SetGasLimit(computationLimit) 1639 1640 if test.payerIsServAcc { 1641 txBody.SetPayer(chain.ServiceAddress()). 1642 SetGasLimit(0) 1643 } 1644 tx := fvm.Transaction(txBody, 0) 1645 1646 _, output, err := vm.Run(ctx, tx, nil) 1647 require.NoError(t, err) 1648 require.Equal(t, test.expCompUsed, output.ComputationUsed) 1649 if test.ok { 1650 require.NoError(t, output.Err) 1651 } else { 1652 require.Error(t, output.Err) 1653 } 1654 1655 }) 1656 } 1657 } 1658 1659 func TestStorageCapacity(t *testing.T) { 1660 t.Run("Storage capacity updates on FLOW transfer", newVMTest(). 1661 withContextOptions( 1662 fvm.WithAuthorizationChecksEnabled(false), 1663 fvm.WithSequenceNumberCheckAndIncrementEnabled(false), 1664 fvm.WithCadenceLogging(true), 1665 ). 1666 withBootstrapProcedureOptions( 1667 fvm.WithStorageMBPerFLOW(10_0000_0000), 1668 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 1669 ). 1670 run(func( 1671 t *testing.T, 1672 vm fvm.VM, 1673 chain flow.Chain, 1674 ctx fvm.Context, 1675 snapshotTree snapshot.SnapshotTree, 1676 ) { 1677 service := chain.ServiceAddress() 1678 snapshotTree, signer := createAccount( 1679 t, 1680 vm, 1681 chain, 1682 ctx, 1683 snapshotTree) 1684 snapshotTree, target := createAccount( 1685 t, 1686 vm, 1687 chain, 1688 ctx, 1689 snapshotTree) 1690 1691 // Transfer FLOW from service account to test accounts 1692 1693 transferTxBody := transferTokensTx(chain). 1694 AddAuthorizer(service). 1695 AddArgument(jsoncdc.MustEncode(cadence.UFix64(1_000_000))). 1696 AddArgument(jsoncdc.MustEncode(cadence.NewAddress(signer))). 1697 SetProposalKey(service, 0, 0). 1698 SetPayer(service) 1699 1700 executionSnapshot, output, err := vm.Run( 1701 ctx, 1702 fvm.Transaction(transferTxBody, 0), 1703 snapshotTree) 1704 require.NoError(t, err) 1705 require.NoError(t, output.Err) 1706 1707 snapshotTree = snapshotTree.Append(executionSnapshot) 1708 1709 transferTxBody = transferTokensTx(chain). 1710 AddAuthorizer(service). 1711 AddArgument(jsoncdc.MustEncode(cadence.UFix64(1_000_000))). 1712 AddArgument(jsoncdc.MustEncode(cadence.NewAddress(target))). 1713 SetProposalKey(service, 0, 0). 1714 SetPayer(service) 1715 1716 executionSnapshot, output, err = vm.Run( 1717 ctx, 1718 fvm.Transaction(transferTxBody, 0), 1719 snapshotTree) 1720 require.NoError(t, err) 1721 require.NoError(t, output.Err) 1722 1723 snapshotTree = snapshotTree.Append(executionSnapshot) 1724 1725 // Perform test 1726 sc := systemcontracts.SystemContractsForChain(chain.ChainID()) 1727 1728 txBody := flow.NewTransactionBody(). 1729 SetScript([]byte(fmt.Sprintf( 1730 ` 1731 import FungibleToken from 0x%s 1732 import FlowToken from 0x%s 1733 1734 transaction(target: Address) { 1735 prepare(signer: AuthAccount) { 1736 let receiverRef = getAccount(target) 1737 .getCapability(/public/flowTokenReceiver) 1738 .borrow<&{FungibleToken.Receiver}>() 1739 ?? panic("Could not borrow receiver reference to the recipient''s Vault") 1740 1741 let vaultRef = signer 1742 .borrow<&{FungibleToken.Provider}>(from: /storage/flowTokenVault) 1743 ?? panic("Could not borrow reference to the owner''s Vault!") 1744 1745 var cap0: UInt64 = signer.storageCapacity 1746 1747 receiverRef.deposit(from: <- vaultRef.withdraw(amount: 0.0000001)) 1748 1749 var cap1: UInt64 = signer.storageCapacity 1750 1751 log(cap0 - cap1) 1752 } 1753 }`, 1754 sc.FungibleToken.Address.Hex(), 1755 sc.FlowToken.Address.Hex(), 1756 ))). 1757 AddArgument(jsoncdc.MustEncode(cadence.NewAddress(target))). 1758 AddAuthorizer(signer) 1759 1760 _, output, err = vm.Run( 1761 ctx, 1762 fvm.Transaction(txBody, 0), 1763 snapshotTree) 1764 require.NoError(t, err) 1765 require.NoError(t, output.Err) 1766 1767 require.Len(t, output.Logs, 1) 1768 require.Equal(t, output.Logs[0], "1") 1769 }), 1770 ) 1771 } 1772 1773 func TestScriptContractMutationsFailure(t *testing.T) { 1774 t.Parallel() 1775 1776 t.Run("contract additions are not committed", 1777 newVMTest().run( 1778 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) { 1779 // Create an account private key. 1780 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 1781 require.NoError(t, err) 1782 1783 // Bootstrap a ledger, creating accounts with the provided 1784 // private keys and the root account. 1785 snapshotTree, accounts, err := testutil.CreateAccounts( 1786 vm, 1787 snapshotTree, 1788 privateKeys, 1789 chain) 1790 require.NoError(t, err) 1791 account := accounts[0] 1792 address := cadence.NewAddress(account) 1793 1794 scriptCtx := fvm.NewContextFromParent(ctx) 1795 1796 contract := "pub contract Foo {}" 1797 1798 script := fvm.Script([]byte(fmt.Sprintf(` 1799 pub fun main(account: Address) { 1800 let acc = getAuthAccount(account) 1801 acc.contracts.add(name: "Foo", code: "%s".decodeHex()) 1802 }`, hex.EncodeToString([]byte(contract))), 1803 )).WithArguments( 1804 jsoncdc.MustEncode(address), 1805 ) 1806 1807 _, output, err := vm.Run(scriptCtx, script, snapshotTree) 1808 require.NoError(t, err) 1809 require.Error(t, output.Err) 1810 require.True(t, errors.IsCadenceRuntimeError(output.Err)) 1811 // modifications to contracts are not supported in scripts 1812 require.True(t, errors.IsOperationNotSupportedError(output.Err)) 1813 }, 1814 ), 1815 ) 1816 1817 t.Run("contract removals are not committed", 1818 newVMTest().run( 1819 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) { 1820 // Create an account private key. 1821 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 1822 privateKey := privateKeys[0] 1823 require.NoError(t, err) 1824 1825 // Bootstrap a ledger, creating accounts with the provided 1826 // private keys and the root account. 1827 snapshotTree, accounts, err := testutil.CreateAccounts( 1828 vm, 1829 snapshotTree, 1830 privateKeys, 1831 chain) 1832 require.NoError(t, err) 1833 account := accounts[0] 1834 address := cadence.NewAddress(account) 1835 1836 subCtx := fvm.NewContextFromParent(ctx) 1837 1838 contract := "pub contract Foo {}" 1839 1840 txBody := flow.NewTransactionBody().SetScript([]byte(fmt.Sprintf(` 1841 transaction { 1842 prepare(signer: AuthAccount, service: AuthAccount) { 1843 signer.contracts.add(name: "Foo", code: "%s".decodeHex()) 1844 } 1845 } 1846 `, hex.EncodeToString([]byte(contract))))). 1847 AddAuthorizer(account). 1848 AddAuthorizer(chain.ServiceAddress()). 1849 SetPayer(chain.ServiceAddress()). 1850 SetProposalKey(chain.ServiceAddress(), 0, 0) 1851 1852 _ = testutil.SignPayload(txBody, account, privateKey) 1853 _ = testutil.SignEnvelope( 1854 txBody, 1855 chain.ServiceAddress(), 1856 unittest.ServiceAccountPrivateKey) 1857 1858 executionSnapshot, output, err := vm.Run( 1859 subCtx, 1860 fvm.Transaction(txBody, 0), 1861 snapshotTree) 1862 require.NoError(t, err) 1863 require.NoError(t, output.Err) 1864 1865 snapshotTree = snapshotTree.Append(executionSnapshot) 1866 1867 script := fvm.Script([]byte(` 1868 pub fun main(account: Address) { 1869 let acc = getAuthAccount(account) 1870 let n = acc.contracts.names[0] 1871 acc.contracts.remove(name: n) 1872 }`, 1873 )).WithArguments( 1874 jsoncdc.MustEncode(address), 1875 ) 1876 1877 _, output, err = vm.Run(subCtx, script, snapshotTree) 1878 require.NoError(t, err) 1879 require.Error(t, output.Err) 1880 require.True(t, errors.IsCadenceRuntimeError(output.Err)) 1881 // modifications to contracts are not supported in scripts 1882 require.True(t, errors.IsOperationNotSupportedError(output.Err)) 1883 }, 1884 ), 1885 ) 1886 1887 t.Run("contract updates are not committed", 1888 newVMTest().run( 1889 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) { 1890 // Create an account private key. 1891 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 1892 privateKey := privateKeys[0] 1893 require.NoError(t, err) 1894 1895 // Bootstrap a ledger, creating accounts with the provided 1896 // private keys and the root account. 1897 snapshotTree, accounts, err := testutil.CreateAccounts( 1898 vm, 1899 snapshotTree, 1900 privateKeys, 1901 chain) 1902 require.NoError(t, err) 1903 account := accounts[0] 1904 address := cadence.NewAddress(account) 1905 1906 subCtx := fvm.NewContextFromParent(ctx) 1907 1908 contract := "pub contract Foo {}" 1909 1910 txBody := flow.NewTransactionBody().SetScript([]byte(fmt.Sprintf(` 1911 transaction { 1912 prepare(signer: AuthAccount, service: AuthAccount) { 1913 signer.contracts.add(name: "Foo", code: "%s".decodeHex()) 1914 } 1915 } 1916 `, hex.EncodeToString([]byte(contract))))). 1917 AddAuthorizer(account). 1918 AddAuthorizer(chain.ServiceAddress()). 1919 SetPayer(chain.ServiceAddress()). 1920 SetProposalKey(chain.ServiceAddress(), 0, 0) 1921 1922 _ = testutil.SignPayload(txBody, account, privateKey) 1923 _ = testutil.SignEnvelope( 1924 txBody, 1925 chain.ServiceAddress(), 1926 unittest.ServiceAccountPrivateKey) 1927 1928 executionSnapshot, output, err := vm.Run( 1929 subCtx, 1930 fvm.Transaction(txBody, 0), 1931 snapshotTree) 1932 require.NoError(t, err) 1933 require.NoError(t, output.Err) 1934 1935 snapshotTree = snapshotTree.Append(executionSnapshot) 1936 1937 script := fvm.Script([]byte(fmt.Sprintf(` 1938 pub fun main(account: Address) { 1939 let acc = getAuthAccount(account) 1940 let n = acc.contracts.names[0] 1941 acc.contracts.update__experimental(name: n, code: "%s".decodeHex()) 1942 }`, hex.EncodeToString([]byte(contract))))).WithArguments( 1943 jsoncdc.MustEncode(address), 1944 ) 1945 1946 _, output, err = vm.Run(subCtx, script, snapshotTree) 1947 require.NoError(t, err) 1948 require.Error(t, output.Err) 1949 require.True(t, errors.IsCadenceRuntimeError(output.Err)) 1950 // modifications to contracts are not supported in scripts 1951 require.True(t, errors.IsOperationNotSupportedError(output.Err)) 1952 }, 1953 ), 1954 ) 1955 } 1956 1957 func TestScriptAccountKeyMutationsFailure(t *testing.T) { 1958 t.Parallel() 1959 1960 t.Run("Account key additions are not committed", 1961 newVMTest().run( 1962 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) { 1963 // Create an account private key. 1964 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 1965 require.NoError(t, err) 1966 1967 // Bootstrap a ledger, creating accounts with the provided 1968 // private keys and the root account. 1969 snapshotTree, accounts, err := testutil.CreateAccounts( 1970 vm, 1971 snapshotTree, 1972 privateKeys, 1973 chain) 1974 require.NoError(t, err) 1975 account := accounts[0] 1976 address := cadence.NewAddress(account) 1977 1978 scriptCtx := fvm.NewContextFromParent(ctx) 1979 1980 seed := make([]byte, crypto.KeyGenSeedMinLen) 1981 _, _ = rand.Read(seed) 1982 1983 privateKey, _ := crypto.GeneratePrivateKey(crypto.ECDSAP256, seed) 1984 1985 script := fvm.Script([]byte(` 1986 pub fun main(account: Address, k: [UInt8]) { 1987 let acc = getAuthAccount(account) 1988 acc.addPublicKey(k) 1989 }`, 1990 )).WithArguments( 1991 jsoncdc.MustEncode(address), 1992 jsoncdc.MustEncode(testutil.BytesToCadenceArray( 1993 privateKey.PublicKey().Encode(), 1994 )), 1995 ) 1996 1997 _, output, err := vm.Run(scriptCtx, script, snapshotTree) 1998 require.NoError(t, err) 1999 require.Error(t, output.Err) 2000 require.True(t, errors.IsCadenceRuntimeError(output.Err)) 2001 // modifications to public keys are not supported in scripts 2002 require.True(t, errors.IsOperationNotSupportedError(output.Err)) 2003 }, 2004 ), 2005 ) 2006 2007 t.Run("Account key removals are not committed", 2008 newVMTest().run( 2009 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) { 2010 // Create an account private key. 2011 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 2012 require.NoError(t, err) 2013 2014 // Bootstrap a ledger, creating accounts with the provided 2015 // private keys and the root account. 2016 snapshotTree, accounts, err := testutil.CreateAccounts( 2017 vm, 2018 snapshotTree, 2019 privateKeys, 2020 chain) 2021 require.NoError(t, err) 2022 account := accounts[0] 2023 address := cadence.NewAddress(account) 2024 2025 scriptCtx := fvm.NewContextFromParent(ctx) 2026 2027 script := fvm.Script([]byte(` 2028 pub fun main(account: Address) { 2029 let acc = getAuthAccount(account) 2030 acc.removePublicKey(0) 2031 }`, 2032 )).WithArguments( 2033 jsoncdc.MustEncode(address), 2034 ) 2035 2036 _, output, err := vm.Run(scriptCtx, script, snapshotTree) 2037 require.NoError(t, err) 2038 require.Error(t, output.Err) 2039 require.True(t, errors.IsCadenceRuntimeError(output.Err)) 2040 // modifications to public keys are not supported in scripts 2041 require.True(t, errors.IsOperationNotSupportedError(output.Err)) 2042 }, 2043 ), 2044 ) 2045 } 2046 2047 func TestScriptExecutionLimit(t *testing.T) { 2048 2049 t.Parallel() 2050 2051 script := fvm.Script([]byte(` 2052 pub fun main() { 2053 var s: Int256 = 1024102410241024 2054 var i: Int256 = 0 2055 var a: Int256 = 7 2056 var b: Int256 = 5 2057 var c: Int256 = 2 2058 2059 while i < 150000 { 2060 s = s * a 2061 s = s / b 2062 s = s / c 2063 i = i + 1 2064 } 2065 } 2066 `)) 2067 2068 bootstrapProcedureOptions := []fvm.BootstrapProcedureOption{ 2069 fvm.WithTransactionFee(fvm.DefaultTransactionFees), 2070 fvm.WithExecutionMemoryLimit(math.MaxUint32), 2071 fvm.WithExecutionEffortWeights(map[common.ComputationKind]uint64{ 2072 common.ComputationKindStatement: 1569, 2073 common.ComputationKindLoop: 1569, 2074 common.ComputationKindFunctionInvocation: 1569, 2075 environment.ComputationKindGetValue: 808, 2076 environment.ComputationKindCreateAccount: 2837670, 2077 environment.ComputationKindSetValue: 765, 2078 }), 2079 fvm.WithExecutionMemoryWeights(meter.DefaultMemoryWeights), 2080 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 2081 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 2082 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 2083 } 2084 2085 t.Run("Exceeding computation limit", 2086 newVMTest().withBootstrapProcedureOptions( 2087 bootstrapProcedureOptions..., 2088 ).withContextOptions( 2089 fvm.WithTransactionFeesEnabled(true), 2090 fvm.WithAccountStorageLimit(true), 2091 fvm.WithComputationLimit(10000), 2092 ).run( 2093 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) { 2094 scriptCtx := fvm.NewContextFromParent(ctx) 2095 2096 _, output, err := vm.Run(scriptCtx, script, snapshotTree) 2097 require.NoError(t, err) 2098 require.Error(t, output.Err) 2099 require.True(t, errors.IsComputationLimitExceededError(output.Err)) 2100 require.ErrorContains(t, output.Err, "computation exceeds limit (10000)") 2101 require.GreaterOrEqual(t, output.ComputationUsed, uint64(10000)) 2102 require.GreaterOrEqual(t, output.MemoryEstimate, uint64(548020260)) 2103 }, 2104 ), 2105 ) 2106 2107 t.Run("Sufficient computation limit", 2108 newVMTest().withBootstrapProcedureOptions( 2109 bootstrapProcedureOptions..., 2110 ).withContextOptions( 2111 fvm.WithTransactionFeesEnabled(true), 2112 fvm.WithAccountStorageLimit(true), 2113 fvm.WithComputationLimit(20000), 2114 ).run( 2115 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) { 2116 scriptCtx := fvm.NewContextFromParent(ctx) 2117 2118 _, output, err := vm.Run(scriptCtx, script, snapshotTree) 2119 require.NoError(t, err) 2120 require.NoError(t, output.Err) 2121 require.GreaterOrEqual(t, output.ComputationUsed, uint64(17955)) 2122 require.GreaterOrEqual(t, output.MemoryEstimate, uint64(984017413)) 2123 }, 2124 ), 2125 ) 2126 } 2127 2128 func TestInteractionLimit(t *testing.T) { 2129 type testCase struct { 2130 name string 2131 interactionLimit uint64 2132 require func(t *testing.T, output fvm.ProcedureOutput) 2133 } 2134 2135 testCases := []testCase{ 2136 { 2137 name: "high limit succeeds", 2138 interactionLimit: math.MaxUint64, 2139 require: func(t *testing.T, output fvm.ProcedureOutput) { 2140 require.NoError(t, output.Err) 2141 require.Len(t, output.Events, 5) 2142 }, 2143 }, 2144 { 2145 name: "default limit succeeds", 2146 interactionLimit: fvm.DefaultMaxInteractionSize, 2147 require: func(t *testing.T, output fvm.ProcedureOutput) { 2148 require.NoError(t, output.Err) 2149 require.Len(t, output.Events, 5) 2150 unittest.EnsureEventsIndexSeq(t, output.Events, flow.Testnet.Chain().ChainID()) 2151 }, 2152 }, 2153 { 2154 name: "low limit succeeds", 2155 interactionLimit: 170000, 2156 require: func(t *testing.T, output fvm.ProcedureOutput) { 2157 require.NoError(t, output.Err) 2158 require.Len(t, output.Events, 5) 2159 unittest.EnsureEventsIndexSeq(t, output.Events, flow.Testnet.Chain().ChainID()) 2160 }, 2161 }, 2162 { 2163 name: "even lower low limit fails, and has only 3 events", 2164 interactionLimit: 5000, 2165 require: func(t *testing.T, output fvm.ProcedureOutput) { 2166 require.Error(t, output.Err) 2167 require.Len(t, output.Events, 3) 2168 unittest.EnsureEventsIndexSeq(t, output.Events, flow.Testnet.Chain().ChainID()) 2169 }, 2170 }, 2171 } 2172 2173 // === setup === 2174 // setup an address with some funds 2175 var privateKey flow.AccountPrivateKey 2176 var address flow.Address 2177 vmt, err := newVMTest().withBootstrapProcedureOptions( 2178 fvm.WithTransactionFee(fvm.DefaultTransactionFees), 2179 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 2180 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 2181 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 2182 fvm.WithExecutionMemoryLimit(math.MaxUint64), 2183 ).withContextOptions( 2184 fvm.WithTransactionFeesEnabled(true), 2185 fvm.WithAccountStorageLimit(true), 2186 ).bootstrapWith( 2187 func(vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) (snapshot.SnapshotTree, error) { 2188 // ==== Create an account ==== 2189 var txBody *flow.TransactionBody 2190 privateKey, txBody = testutil.CreateAccountCreationTransaction(t, chain) 2191 2192 err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) 2193 if err != nil { 2194 return snapshotTree, err 2195 } 2196 2197 executionSnapshot, output, err := vm.Run( 2198 ctx, 2199 fvm.Transaction(txBody, 0), 2200 snapshotTree) 2201 if err != nil { 2202 return snapshotTree, err 2203 } 2204 2205 snapshotTree = snapshotTree.Append(executionSnapshot) 2206 2207 if output.Err != nil { 2208 return snapshotTree, output.Err 2209 } 2210 2211 accountCreatedEvents := filterAccountCreatedEvents(output.Events) 2212 2213 // read the address of the account created (e.g. "0x01" and convert it to flow.address) 2214 data, err := ccf.Decode(nil, accountCreatedEvents[0].Payload) 2215 if err != nil { 2216 return snapshotTree, err 2217 } 2218 address = flow.ConvertAddress( 2219 data.(cadence.Event).Fields[0].(cadence.Address)) 2220 2221 // ==== Transfer tokens to new account ==== 2222 txBody = transferTokensTx(chain). 2223 AddAuthorizer(chain.ServiceAddress()). 2224 AddArgument(jsoncdc.MustEncode(cadence.UFix64(1_000_000))). 2225 AddArgument(jsoncdc.MustEncode(cadence.NewAddress(address))) 2226 2227 txBody.SetProposalKey(chain.ServiceAddress(), 0, 1) 2228 txBody.SetPayer(chain.ServiceAddress()) 2229 2230 err = testutil.SignEnvelope( 2231 txBody, 2232 chain.ServiceAddress(), 2233 unittest.ServiceAccountPrivateKey, 2234 ) 2235 if err != nil { 2236 return snapshotTree, err 2237 } 2238 2239 executionSnapshot, output, err = vm.Run( 2240 ctx, 2241 fvm.Transaction(txBody, 0), 2242 snapshotTree) 2243 if err != nil { 2244 return snapshotTree, err 2245 } 2246 2247 return snapshotTree.Append(executionSnapshot), output.Err 2248 }, 2249 ) 2250 require.NoError(t, err) 2251 2252 for _, tc := range testCases { 2253 t.Run(tc.name, vmt.run( 2254 func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) { 2255 // ==== Transfer funds with lowe interaction limit ==== 2256 txBody := transferTokensTx(chain). 2257 AddAuthorizer(address). 2258 AddArgument(jsoncdc.MustEncode(cadence.UFix64(1))). 2259 AddArgument(jsoncdc.MustEncode(cadence.NewAddress(chain.ServiceAddress()))) 2260 2261 txBody.SetProposalKey(address, 0, 0) 2262 txBody.SetPayer(address) 2263 2264 hasher, err := exeUtils.NewHasher(privateKey.HashAlgo) 2265 require.NoError(t, err) 2266 2267 sig, err := txBody.Sign(txBody.EnvelopeMessage(), privateKey.PrivateKey, hasher) 2268 require.NoError(t, err) 2269 txBody.AddEnvelopeSignature(address, 0, sig) 2270 2271 // ==== IMPORTANT LINE ==== 2272 ctx.MaxStateInteractionSize = tc.interactionLimit 2273 2274 _, output, err := vm.Run( 2275 ctx, 2276 fvm.Transaction(txBody, 0), 2277 snapshotTree) 2278 require.NoError(t, err) 2279 tc.require(t, output) 2280 }), 2281 ) 2282 } 2283 } 2284 2285 func TestAuthAccountCapabilities(t *testing.T) { 2286 2287 t.Parallel() 2288 2289 t.Run("transaction", func(t *testing.T) { 2290 2291 t.Parallel() 2292 2293 test := func(t *testing.T, allowAccountLinking bool) { 2294 newVMTest(). 2295 withBootstrapProcedureOptions(). 2296 withContextOptions( 2297 fvm.WithReusableCadenceRuntimePool( 2298 reusableRuntime.NewReusableCadenceRuntimePool( 2299 1, 2300 runtime.Config{ 2301 AccountLinkingEnabled: true, 2302 }, 2303 ), 2304 ), 2305 ). 2306 run( 2307 func( 2308 t *testing.T, 2309 vm fvm.VM, 2310 chain flow.Chain, 2311 ctx fvm.Context, 2312 snapshotTree snapshot.SnapshotTree, 2313 ) { 2314 // Create an account private key. 2315 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 2316 privateKey := privateKeys[0] 2317 require.NoError(t, err) 2318 2319 // Bootstrap a ledger, creating accounts with the 2320 // provided private keys and the root account. 2321 snapshotTree, accounts, err := testutil.CreateAccounts( 2322 vm, 2323 snapshotTree, 2324 privateKeys, 2325 chain) 2326 require.NoError(t, err) 2327 account := accounts[0] 2328 2329 var pragma string 2330 if allowAccountLinking { 2331 pragma = "#allowAccountLinking" 2332 } 2333 2334 code := fmt.Sprintf( 2335 ` 2336 %s 2337 transaction { 2338 prepare(acct: AuthAccount) { 2339 acct.linkAccount(/private/foo) 2340 } 2341 } 2342 `, 2343 pragma, 2344 ) 2345 2346 txBody := flow.NewTransactionBody(). 2347 SetScript([]byte(code)). 2348 AddAuthorizer(account). 2349 SetPayer(chain.ServiceAddress()). 2350 SetProposalKey(chain.ServiceAddress(), 0, 0) 2351 2352 _ = testutil.SignPayload(txBody, account, privateKey) 2353 _ = testutil.SignEnvelope( 2354 txBody, 2355 chain.ServiceAddress(), 2356 unittest.ServiceAccountPrivateKey) 2357 2358 _, output, err := vm.Run( 2359 ctx, 2360 fvm.Transaction(txBody, 0), 2361 snapshotTree) 2362 require.NoError(t, err) 2363 if allowAccountLinking { 2364 require.NoError(t, output.Err) 2365 } else { 2366 require.Error(t, output.Err) 2367 } 2368 }, 2369 )(t) 2370 } 2371 2372 t.Run("account linking allowed", func(t *testing.T) { 2373 test(t, true) 2374 }) 2375 2376 t.Run("account linking disallowed", func(t *testing.T) { 2377 test(t, false) 2378 }) 2379 }) 2380 2381 t.Run("contract", func(t *testing.T) { 2382 2383 t.Parallel() 2384 2385 test := func(t *testing.T, allowAccountLinking bool) { 2386 newVMTest(). 2387 withBootstrapProcedureOptions(). 2388 withContextOptions( 2389 fvm.WithReusableCadenceRuntimePool( 2390 reusableRuntime.NewReusableCadenceRuntimePool( 2391 1, 2392 runtime.Config{ 2393 AccountLinkingEnabled: true, 2394 }, 2395 ), 2396 ), 2397 fvm.WithContractDeploymentRestricted(false), 2398 ). 2399 run( 2400 func( 2401 t *testing.T, 2402 vm fvm.VM, 2403 chain flow.Chain, 2404 ctx fvm.Context, 2405 snapshotTree snapshot.SnapshotTree, 2406 ) { 2407 // Create two private keys 2408 privateKeys, err := testutil.GenerateAccountPrivateKeys(2) 2409 require.NoError(t, err) 2410 2411 // Bootstrap a ledger, creating accounts with the provided private keys and the root account. 2412 snapshotTree, accounts, err := testutil.CreateAccounts( 2413 vm, 2414 snapshotTree, 2415 privateKeys, 2416 chain) 2417 require.NoError(t, err) 2418 2419 // Deploy contract 2420 contractCode := ` 2421 pub contract AccountLinker { 2422 pub fun link(_ account: AuthAccount) { 2423 account.linkAccount(/private/acct) 2424 } 2425 } 2426 ` 2427 2428 deployingContractScriptTemplate := ` 2429 transaction { 2430 prepare(signer: AuthAccount) { 2431 signer.contracts.add( 2432 name: "AccountLinker", 2433 code: "%s".decodeHex() 2434 ) 2435 } 2436 } 2437 ` 2438 2439 txBody := flow.NewTransactionBody(). 2440 SetScript([]byte(fmt.Sprintf( 2441 deployingContractScriptTemplate, 2442 hex.EncodeToString([]byte(contractCode)), 2443 ))). 2444 SetPayer(chain.ServiceAddress()). 2445 SetProposalKey(chain.ServiceAddress(), 0, 0). 2446 AddAuthorizer(accounts[0]) 2447 _ = testutil.SignPayload(txBody, accounts[0], privateKeys[0]) 2448 _ = testutil.SignEnvelope(txBody, chain.ServiceAddress(), unittest.ServiceAccountPrivateKey) 2449 2450 executionSnapshot, output, err := vm.Run( 2451 ctx, 2452 fvm.Transaction(txBody, 0), 2453 snapshotTree) 2454 require.NoError(t, err) 2455 require.NoError(t, output.Err) 2456 2457 snapshotTree = snapshotTree.Append(executionSnapshot) 2458 2459 // Use contract 2460 2461 var pragma string 2462 if allowAccountLinking { 2463 pragma = "#allowAccountLinking" 2464 } 2465 2466 code := fmt.Sprintf( 2467 ` 2468 %s 2469 import AccountLinker from %s 2470 transaction { 2471 prepare(acct: AuthAccount) { 2472 AccountLinker.link(acct) 2473 } 2474 } 2475 `, 2476 pragma, 2477 accounts[0].HexWithPrefix(), 2478 ) 2479 2480 txBody = flow.NewTransactionBody(). 2481 SetScript([]byte(code)). 2482 AddAuthorizer(accounts[1]). 2483 SetPayer(chain.ServiceAddress()). 2484 SetProposalKey(chain.ServiceAddress(), 0, 1) 2485 2486 _ = testutil.SignPayload(txBody, accounts[1], privateKeys[1]) 2487 _ = testutil.SignEnvelope(txBody, chain.ServiceAddress(), unittest.ServiceAccountPrivateKey) 2488 2489 _, output, err = vm.Run( 2490 ctx, 2491 fvm.Transaction(txBody, 1), 2492 snapshotTree) 2493 require.NoError(t, err) 2494 if allowAccountLinking { 2495 require.NoError(t, output.Err) 2496 2497 require.Len(t, output.Events, 1) 2498 require.Equal( 2499 t, 2500 flow.EventType("flow.AccountLinked"), 2501 output.Events[0].Type) 2502 } else { 2503 require.Error(t, output.Err) 2504 } 2505 }, 2506 )(t) 2507 } 2508 2509 t.Run("account linking allowed", func(t *testing.T) { 2510 test(t, true) 2511 }) 2512 2513 t.Run("account linking disallowed", func(t *testing.T) { 2514 test(t, false) 2515 }) 2516 }) 2517 } 2518 2519 func TestAttachments(t *testing.T) { 2520 test := func(t *testing.T, attachmentsEnabled bool) { 2521 newVMTest(). 2522 withBootstrapProcedureOptions(). 2523 withContextOptions( 2524 fvm.WithReusableCadenceRuntimePool( 2525 reusableRuntime.NewReusableCadenceRuntimePool( 2526 1, 2527 runtime.Config{ 2528 AttachmentsEnabled: attachmentsEnabled, 2529 }, 2530 ), 2531 ), 2532 ). 2533 run( 2534 func( 2535 t *testing.T, 2536 vm fvm.VM, 2537 chain flow.Chain, 2538 ctx fvm.Context, 2539 snapshotTree snapshot.SnapshotTree, 2540 ) { 2541 script := fvm.Script([]byte(` 2542 2543 pub resource R {} 2544 2545 pub attachment A for R {} 2546 2547 pub fun main() { 2548 let r <- create R() 2549 r[A] 2550 destroy r 2551 } 2552 `)) 2553 2554 _, output, err := vm.Run(ctx, script, snapshotTree) 2555 require.NoError(t, err) 2556 2557 if attachmentsEnabled { 2558 require.NoError(t, output.Err) 2559 } else { 2560 require.Error(t, output.Err) 2561 require.ErrorContains( 2562 t, 2563 output.Err, 2564 "attachments are not enabled") 2565 } 2566 }, 2567 )(t) 2568 } 2569 2570 t.Run("attachments enabled", func(t *testing.T) { 2571 test(t, true) 2572 }) 2573 2574 t.Run("attachments disabled", func(t *testing.T) { 2575 test(t, false) 2576 }) 2577 } 2578 2579 func TestCapabilityControllers(t *testing.T) { 2580 test := func(t *testing.T, capabilityControllersEnabled bool) { 2581 newVMTest(). 2582 withBootstrapProcedureOptions(). 2583 withContextOptions( 2584 fvm.WithReusableCadenceRuntimePool( 2585 reusableRuntime.NewReusableCadenceRuntimePool( 2586 1, 2587 runtime.Config{ 2588 CapabilityControllersEnabled: capabilityControllersEnabled, 2589 }, 2590 ), 2591 ), 2592 ). 2593 run(func( 2594 t *testing.T, 2595 vm fvm.VM, 2596 chain flow.Chain, 2597 ctx fvm.Context, 2598 snapshotTree snapshot.SnapshotTree, 2599 ) { 2600 txBody := flow.NewTransactionBody(). 2601 SetScript([]byte(` 2602 transaction { 2603 prepare(signer: AuthAccount) { 2604 let cap = signer.capabilities.storage.issue<&Int>(/storage/foo) 2605 assert(cap.id == 1) 2606 2607 let cap2 = signer.capabilities.storage.issue<&String>(/storage/bar) 2608 assert(cap2.id == 2) 2609 } 2610 } 2611 `)). 2612 SetProposalKey(chain.ServiceAddress(), 0, 0). 2613 AddAuthorizer(chain.ServiceAddress()). 2614 SetPayer(chain.ServiceAddress()) 2615 2616 err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) 2617 require.NoError(t, err) 2618 2619 _, output, err := vm.Run( 2620 ctx, 2621 fvm.Transaction(txBody, 0), 2622 snapshotTree) 2623 require.NoError(t, err) 2624 2625 if capabilityControllersEnabled { 2626 require.NoError(t, output.Err) 2627 } else { 2628 require.Error(t, output.Err) 2629 require.ErrorContains( 2630 t, 2631 output.Err, 2632 "`AuthAccount` has no member `capabilities`") 2633 } 2634 }, 2635 )(t) 2636 } 2637 2638 t.Run("enabled", func(t *testing.T) { 2639 test(t, true) 2640 }) 2641 2642 t.Run("disabled", func(t *testing.T) { 2643 test(t, false) 2644 }) 2645 } 2646 2647 func TestStorageIterationWithBrokenValues(t *testing.T) { 2648 2649 t.Parallel() 2650 2651 newVMTest(). 2652 withBootstrapProcedureOptions(). 2653 withContextOptions( 2654 fvm.WithReusableCadenceRuntimePool( 2655 reusableRuntime.NewReusableCadenceRuntimePool( 2656 1, 2657 runtime.Config{ 2658 AccountLinkingEnabled: true, 2659 }, 2660 ), 2661 ), 2662 fvm.WithContractDeploymentRestricted(false), 2663 ). 2664 run( 2665 func( 2666 t *testing.T, 2667 vm fvm.VM, 2668 chain flow.Chain, 2669 ctx fvm.Context, 2670 snapshotTree snapshot.SnapshotTree, 2671 ) { 2672 // Create a private key 2673 privateKeys, err := testutil.GenerateAccountPrivateKeys(1) 2674 require.NoError(t, err) 2675 2676 // Bootstrap a ledger, creating an account with the provided private key and the root account. 2677 snapshotTree, accounts, err := testutil.CreateAccounts( 2678 vm, 2679 snapshotTree, 2680 privateKeys, 2681 chain, 2682 ) 2683 require.NoError(t, err) 2684 2685 contractA := ` 2686 pub contract A { 2687 pub struct interface Foo{} 2688 } 2689 ` 2690 2691 updatedContractA := ` 2692 pub contract A { 2693 pub struct interface Foo{ 2694 pub fun hello() 2695 } 2696 } 2697 ` 2698 2699 contractB := fmt.Sprintf(` 2700 import A from %s 2701 2702 pub contract B { 2703 pub struct Bar : A.Foo {} 2704 2705 pub struct interface Foo2{} 2706 }`, 2707 accounts[0].HexWithPrefix(), 2708 ) 2709 2710 contractC := fmt.Sprintf(` 2711 import B from %s 2712 import A from %s 2713 2714 pub contract C { 2715 pub struct Bar : A.Foo, B.Foo2 {} 2716 2717 pub struct interface Foo3{} 2718 }`, 2719 accounts[0].HexWithPrefix(), 2720 accounts[0].HexWithPrefix(), 2721 ) 2722 2723 contractD := fmt.Sprintf(` 2724 import C from %s 2725 import B from %s 2726 import A from %s 2727 2728 pub contract D { 2729 pub struct Bar : A.Foo, B.Foo2, C.Foo3 {} 2730 }`, 2731 accounts[0].HexWithPrefix(), 2732 accounts[0].HexWithPrefix(), 2733 accounts[0].HexWithPrefix(), 2734 ) 2735 2736 var sequenceNumber uint64 = 0 2737 2738 runTransaction := func(code []byte) { 2739 txBody := flow.NewTransactionBody(). 2740 SetScript(code). 2741 SetPayer(chain.ServiceAddress()). 2742 SetProposalKey(chain.ServiceAddress(), 0, sequenceNumber). 2743 AddAuthorizer(accounts[0]) 2744 2745 _ = testutil.SignPayload(txBody, accounts[0], privateKeys[0]) 2746 _ = testutil.SignEnvelope(txBody, chain.ServiceAddress(), unittest.ServiceAccountPrivateKey) 2747 2748 executionSnapshot, output, err := vm.Run( 2749 ctx, 2750 fvm.Transaction(txBody, 0), 2751 snapshotTree, 2752 ) 2753 require.NoError(t, err) 2754 require.NoError(t, output.Err) 2755 2756 snapshotTree = snapshotTree.Append(executionSnapshot) 2757 2758 // increment sequence number 2759 sequenceNumber++ 2760 } 2761 2762 // Deploy `A` 2763 runTransaction(utils.DeploymentTransaction( 2764 "A", 2765 []byte(contractA), 2766 )) 2767 2768 // Deploy `B` 2769 runTransaction(utils.DeploymentTransaction( 2770 "B", 2771 []byte(contractB), 2772 )) 2773 2774 // Deploy `C` 2775 runTransaction(utils.DeploymentTransaction( 2776 "C", 2777 []byte(contractC), 2778 )) 2779 2780 // Deploy `D` 2781 runTransaction(utils.DeploymentTransaction( 2782 "D", 2783 []byte(contractD), 2784 )) 2785 2786 // Store values 2787 runTransaction([]byte(fmt.Sprintf( 2788 ` 2789 import D from %s 2790 import C from %s 2791 import B from %s 2792 2793 transaction { 2794 prepare(signer: AuthAccount) { 2795 signer.save("Hello, World!", to: /storage/first) 2796 signer.save(["one", "two", "three"], to: /storage/second) 2797 signer.save(D.Bar(), to: /storage/third) 2798 signer.save(C.Bar(), to: /storage/fourth) 2799 signer.save(B.Bar(), to: /storage/fifth) 2800 2801 signer.link<&String>(/private/a, target:/storage/first) 2802 signer.link<&[String]>(/private/b, target:/storage/second) 2803 signer.link<&D.Bar>(/private/c, target:/storage/third) 2804 signer.link<&C.Bar>(/private/d, target:/storage/fourth) 2805 signer.link<&B.Bar>(/private/e, target:/storage/fifth) 2806 } 2807 }`, 2808 accounts[0].HexWithPrefix(), 2809 accounts[0].HexWithPrefix(), 2810 accounts[0].HexWithPrefix(), 2811 ))) 2812 2813 // Update `A`. `B`, `C` and `D` are now broken. 2814 runTransaction(utils.UpdateTransaction( 2815 "A", 2816 []byte(updatedContractA), 2817 )) 2818 2819 // Iterate stored values 2820 runTransaction([]byte( 2821 ` 2822 transaction { 2823 prepare(account: AuthAccount) { 2824 var total = 0 2825 account.forEachPrivate(fun (path: PrivatePath, type: Type): Bool { 2826 account.getCapability<&AnyStruct>(path).borrow()! 2827 total = total + 1 2828 return true 2829 }) 2830 2831 assert(total == 2, message:"found ".concat(total.toString())) 2832 } 2833 }`, 2834 )) 2835 }, 2836 )(t) 2837 } 2838 2839 func TestEntropyCallOnlyOkIfAllowed(t *testing.T) { 2840 source := testutil.EntropyProviderFixture(nil) 2841 2842 test := func(t *testing.T, allowed bool) { 2843 newVMTest(). 2844 withBootstrapProcedureOptions(). 2845 withContextOptions( 2846 fvm.WithRandomSourceHistoryCallAllowed(allowed), 2847 fvm.WithEntropyProvider(source), 2848 ). 2849 run(func( 2850 t *testing.T, 2851 vm fvm.VM, 2852 chain flow.Chain, 2853 ctx fvm.Context, 2854 snapshotTree snapshot.SnapshotTree, 2855 ) { 2856 txBody := flow.NewTransactionBody(). 2857 SetScript([]byte(` 2858 transaction { 2859 prepare() { 2860 randomSourceHistory() 2861 } 2862 } 2863 `)). 2864 SetProposalKey(chain.ServiceAddress(), 0, 0). 2865 SetPayer(chain.ServiceAddress()) 2866 2867 err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) 2868 require.NoError(t, err) 2869 2870 _, output, err := vm.Run( 2871 ctx, 2872 fvm.Transaction(txBody, 0), 2873 snapshotTree) 2874 require.NoError(t, err) 2875 2876 if allowed { 2877 require.NoError(t, output.Err) 2878 } else { 2879 require.Error(t, output.Err) 2880 require.True(t, errors.HasErrorCode(output.Err, errors.ErrCodeOperationNotSupportedError)) 2881 } 2882 }, 2883 )(t) 2884 } 2885 2886 t.Run("enabled", func(t *testing.T) { 2887 test(t, true) 2888 }) 2889 2890 t.Run("disabled", func(t *testing.T) { 2891 test(t, false) 2892 }) 2893 } 2894 2895 func TestEntropyCallExpectsNoParameters(t *testing.T) { 2896 source := testutil.EntropyProviderFixture(nil) 2897 newVMTest(). 2898 withBootstrapProcedureOptions(). 2899 withContextOptions( 2900 fvm.WithRandomSourceHistoryCallAllowed(true), 2901 fvm.WithEntropyProvider(source), 2902 ). 2903 run(func( 2904 t *testing.T, 2905 vm fvm.VM, 2906 chain flow.Chain, 2907 ctx fvm.Context, 2908 snapshotTree snapshot.SnapshotTree, 2909 ) { 2910 txBody := flow.NewTransactionBody(). 2911 SetScript([]byte(` 2912 transaction { 2913 prepare() { 2914 randomSourceHistory("foo") 2915 } 2916 } 2917 `)). 2918 SetProposalKey(chain.ServiceAddress(), 0, 0). 2919 SetPayer(chain.ServiceAddress()) 2920 2921 err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) 2922 require.NoError(t, err) 2923 2924 _, output, err := vm.Run( 2925 ctx, 2926 fvm.Transaction(txBody, 0), 2927 snapshotTree) 2928 require.NoError(t, err) 2929 2930 require.ErrorContains(t, output.Err, "too many arguments") 2931 }, 2932 )(t) 2933 } 2934 2935 func TestTransientNetworkCoreContractAddresses(t *testing.T) { 2936 2937 // This test ensures that the transient networks have the correct core contract addresses. 2938 newVMTest(). 2939 run( 2940 func( 2941 t *testing.T, 2942 vm fvm.VM, 2943 chain flow.Chain, 2944 ctx fvm.Context, 2945 snapshotTree snapshot.SnapshotTree, 2946 ) { 2947 sc := systemcontracts.SystemContractsForChain(chain.ChainID()) 2948 2949 for _, contract := range sc.All() { 2950 txnState := testutils.NewSimpleTransaction(snapshotTree) 2951 accounts := environment.NewAccounts(txnState) 2952 2953 yes, err := accounts.ContractExists(contract.Name, contract.Address) 2954 require.NoError(t, err) 2955 require.True(t, yes, "contract %s does not exist", contract.Name) 2956 } 2957 }) 2958 } 2959 2960 func TestEVM(t *testing.T) { 2961 t.Run("successful transaction", newVMTest(). 2962 withBootstrapProcedureOptions(fvm.WithSetupEVMEnabled(true)). 2963 withContextOptions( 2964 fvm.WithEVMEnabled(true), 2965 fvm.WithCadenceLogging(true), 2966 ). 2967 run(func( 2968 t *testing.T, 2969 vm fvm.VM, 2970 chain flow.Chain, 2971 ctx fvm.Context, 2972 snapshotTree snapshot.SnapshotTree, 2973 ) { 2974 // generate test address 2975 genArr := make([]cadence.Value, 20) 2976 for i := range genArr { 2977 genArr[i] = cadence.UInt8(i) 2978 } 2979 addrBytes := cadence.NewArray(genArr).WithType(stdlib.EVMAddressBytesCadenceType) 2980 encodedArg, err := jsoncdc.Encode(addrBytes) 2981 require.NoError(t, err) 2982 2983 sc := systemcontracts.SystemContractsForChain(chain.ChainID()) 2984 2985 txBody := flow.NewTransactionBody(). 2986 SetScript([]byte(fmt.Sprintf(` 2987 import EVM from %s 2988 2989 transaction(bytes: [UInt8; 20]) { 2990 execute { 2991 let addr = EVM.EVMAddress(bytes: bytes) 2992 log(addr) 2993 } 2994 } 2995 `, sc.EVMContract.Address.HexWithPrefix()))). 2996 SetProposalKey(chain.ServiceAddress(), 0, 0). 2997 SetPayer(chain.ServiceAddress()). 2998 AddArgument(encodedArg) 2999 3000 err = testutil.SignTransactionAsServiceAccount(txBody, 0, chain) 3001 require.NoError(t, err) 3002 3003 _, output, err := vm.Run( 3004 ctx, 3005 fvm.Transaction(txBody, 0), 3006 snapshotTree) 3007 3008 require.NoError(t, err) 3009 require.NoError(t, output.Err) 3010 require.Len(t, output.Logs, 1) 3011 require.Equal(t, output.Logs[0], fmt.Sprintf( 3012 "A.%s.EVM.EVMAddress(bytes: %s)", 3013 sc.EVMContract.Address, 3014 addrBytes.String(), 3015 )) 3016 }), 3017 ) 3018 3019 // this test makes sure that only ABI encoding/decoding functionality is 3020 // available through the EVM contract, when bootstraped with `WithEVMABIOnly` 3021 t.Run("with ABI only EVM", newVMTest(). 3022 withBootstrapProcedureOptions( 3023 fvm.WithSetupEVMEnabled(true), 3024 fvm.WithEVMABIOnly(true), 3025 ). 3026 withContextOptions( 3027 fvm.WithEVMEnabled(true), 3028 ). 3029 run(func( 3030 t *testing.T, 3031 vm fvm.VM, 3032 chain flow.Chain, 3033 ctx fvm.Context, 3034 snapshotTree snapshot.SnapshotTree, 3035 ) { 3036 txBody := flow.NewTransactionBody(). 3037 SetScript([]byte(fmt.Sprintf(` 3038 import EVM from %s 3039 3040 transaction { 3041 execute { 3042 let data = EVM.encodeABI(["John Doe", UInt64(33), false]) 3043 log(data.length) 3044 assert(data.length == 160) 3045 3046 let acc <- EVM.createBridgedAccount() 3047 destroy acc 3048 } 3049 } 3050 `, chain.ServiceAddress().HexWithPrefix()))). 3051 SetProposalKey(chain.ServiceAddress(), 0, 0). 3052 SetPayer(chain.ServiceAddress()) 3053 3054 err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) 3055 require.NoError(t, err) 3056 3057 _, output, err := vm.Run( 3058 ctx, 3059 fvm.Transaction(txBody, 0), 3060 snapshotTree) 3061 3062 require.NoError(t, err) 3063 require.Error(t, output.Err) 3064 assert.ErrorContains( 3065 t, 3066 output.Err, 3067 "value of type `EVM` has no member `createBridgedAccount`", 3068 ) 3069 }), 3070 ) 3071 3072 // this test makes sure the execution error is correctly handled and returned as a correct type 3073 t.Run("execution reverted", newVMTest(). 3074 withBootstrapProcedureOptions(fvm.WithSetupEVMEnabled(true)). 3075 withContextOptions(fvm.WithEVMEnabled(true)). 3076 run(func( 3077 t *testing.T, 3078 vm fvm.VM, 3079 chain flow.Chain, 3080 ctx fvm.Context, 3081 snapshotTree snapshot.SnapshotTree, 3082 ) { 3083 sc := systemcontracts.SystemContractsForChain(chain.ChainID()) 3084 script := fvm.Script([]byte(fmt.Sprintf(` 3085 import EVM from %s 3086 3087 pub fun main() { 3088 let bal = EVM.Balance(flow: 1.0); 3089 let acc <- EVM.createBridgedAccount(); 3090 // withdraw insufficient balance 3091 destroy acc.withdraw(balance: bal); 3092 destroy acc; 3093 } 3094 `, sc.EVMContract.Address.HexWithPrefix()))) 3095 3096 _, output, err := vm.Run(ctx, script, snapshotTree) 3097 3098 require.NoError(t, err) 3099 require.Error(t, output.Err) 3100 require.True(t, errors.IsEVMError(output.Err)) 3101 3102 // make sure error is not treated as internal error by Cadence 3103 var internal cadenceErrors.InternalError 3104 require.False(t, errors.As(output.Err, &internal)) 3105 }), 3106 ) 3107 3108 // this test makes sure the EVM error is correctly returned as an error and has a correct type 3109 // we have implemented a snapshot wrapper to return an error from the EVM 3110 t.Run("internal evm error handling", newVMTest(). 3111 withBootstrapProcedureOptions(fvm.WithSetupEVMEnabled(true)). 3112 withContextOptions(fvm.WithEVMEnabled(true)). 3113 run(func( 3114 t *testing.T, 3115 vm fvm.VM, 3116 chain flow.Chain, 3117 ctx fvm.Context, 3118 snapshotTree snapshot.SnapshotTree, 3119 ) { 3120 sc := systemcontracts.SystemContractsForChain(chain.ChainID()) 3121 3122 tests := []struct { 3123 err error 3124 errChecker func(error) bool 3125 }{{ 3126 types.ErrNotImplemented, 3127 types.IsAFatalError, 3128 }, { 3129 types.NewStateError(fmt.Errorf("test state error")), 3130 types.IsAStateError, 3131 }} 3132 3133 for _, e := range tests { 3134 // this mock will return an error we provide with the test once it starts to access address allocator registers 3135 // that is done to make sure the error is coming out of EVM execution 3136 errStorage := &mock.StorageSnapshot{} 3137 errStorage. 3138 On("Get", mockery.AnythingOfType("flow.RegisterID")). 3139 Return(func(id flow.RegisterID) (flow.RegisterValue, error) { 3140 if id.Key == "AddressAllocator" { 3141 return nil, e.err 3142 } 3143 return snapshotTree.Get(id) 3144 }) 3145 3146 script := fvm.Script([]byte(fmt.Sprintf(` 3147 import EVM from %s 3148 3149 pub fun main() { 3150 destroy <- EVM.createBridgedAccount(); 3151 } 3152 `, sc.EVMContract.Address.HexWithPrefix()))) 3153 3154 _, output, err := vm.Run(ctx, script, errStorage) 3155 3156 require.NoError(t, output.Err) 3157 require.Error(t, err) 3158 // make sure error it's the right type of error 3159 require.True(t, e.errChecker(err), "error is not of the right type") 3160 } 3161 }), 3162 ) 3163 }