diff --git a/runtime/runtime.go b/runtime/runtime.go index 24ff4b76c1..edcadaa53e 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -1383,7 +1383,14 @@ func (r *interpreterRuntime) newAuthAccountContractsChangeFunction( AddressLocation: addressValue[:], Name: nameArgument, } - checker, err := r.ParseAndCheckProgram(code, runtimeInterface, location) + + // NOTE: do NOT use the cache! + + const useCache = false + + functions := r.standardLibraryFunctions(runtimeInterface, runtimeStorage) + + checker, err := r.parseAndCheckProgram(code, runtimeInterface, location, functions, nil, useCache) if err != nil { panic(fmt.Errorf("invalid contract: %w", err)) } diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index 58a6607d89..7cb4fd5002 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -4401,7 +4401,7 @@ func TestRuntimeExternalError(t *testing.T) { ) } -func TestRuntimeUpdateCodeCaching(t *testing.T) { +func TestRuntimeDeployCodeCaching(t *testing.T) { t.Parallel() @@ -4430,7 +4430,117 @@ func TestRuntimeUpdateCodeCaching(t *testing.T) { } ` - createAccountScript := []byte(` + createAccountTx := []byte(` + transaction { + prepare(signer: AuthAccount) { + AuthAccount(payer: signer) + } + } + `) + + deployTx := utils.DeploymentTransaction("HelloWorld", []byte(helloWorldContract)) + + runtime := NewInterpreterRuntime() + + accountCodes := map[string][]byte{} + var events []cadence.Event + + cachedPrograms := map[LocationID]*ast.Program{} + + var accountCounter uint8 = 0 + + var signerAddresses []Address + + runtimeInterface := &testRuntimeInterface{ + createAccount: func(payer Address) (address Address, err error) { + accountCounter++ + return Address{accountCounter}, nil + }, + getCode: func(location Location) (bytes []byte, err error) { + key := string(location.(AddressLocation).ID()) + return accountCodes[key], nil + }, + cacheProgram: func(location Location, program *ast.Program) error { + cachedPrograms[location.ID()] = program + return nil + }, + getCachedProgram: func(location Location) (*ast.Program, error) { + return cachedPrograms[location.ID()], nil + }, + storage: newTestStorage(nil, nil), + getSigningAccounts: func() []Address { + return signerAddresses + }, + resolveLocation: singleIdentifierLocationResolver(t), + getAccountContractCode: func(address Address, _ string) (code []byte, err error) { + key := string(AddressLocation(address[:]).ID()) + return accountCodes[key], nil + }, + updateAccountContractCode: func(address Address, _ string, code []byte) error { + key := string(AddressLocation(address[:]).ID()) + accountCodes[key] = code + return nil + }, + emitEvent: func(event cadence.Event) { + events = append(events, event) + }, + } + + nextTransactionLocation := newTransactionLocationGenerator() + + // create the account + + signerAddresses = []Address{{accountCounter}} + + err := runtime.ExecuteTransaction(createAccountTx, nil, runtimeInterface, nextTransactionLocation()) + require.NoError(t, err) + + // deploy the contract + + signerAddresses = []Address{{accountCounter}} + + err = runtime.ExecuteTransaction(deployTx, nil, runtimeInterface, nextTransactionLocation()) + require.NoError(t, err) + + // call the hello function + + callTx := []byte(fmt.Sprintf(callHelloTxTemplate, Address{accountCounter})) + + err = runtime.ExecuteTransaction(callTx, nil, runtimeInterface, nextTransactionLocation()) + require.NoError(t, err) +} + +func TestRuntimeUpdateCodeCaching(t *testing.T) { + + t.Parallel() + + const helloWorldContract1 = ` + pub contract HelloWorld { + + pub fun hello(): String { + return "1" + } + } + ` + + const helloWorldContract2 = ` + pub contract HelloWorld { + + pub fun hello(): String { + return "2" + } + } + ` + + const callHelloScriptTemplate = ` + import HelloWorld from 0x%s + + pub fun main(): String { + return HelloWorld.hello() + } + ` + + createAccountTx := []byte(` transaction { prepare(signer: AuthAccount) { AuthAccount(payer: signer) @@ -4438,7 +4548,8 @@ func TestRuntimeUpdateCodeCaching(t *testing.T) { } `) - updateCodeScript := utils.DeploymentTransaction("HelloWorld", []byte(helloWorldContract)) + deployTx := utils.DeploymentTransaction("HelloWorld", []byte(helloWorldContract1)) + updateTx := utils.UpdateTransaction("HelloWorld", []byte(helloWorldContract2)) runtime := NewInterpreterRuntime() @@ -4451,6 +4562,8 @@ func TestRuntimeUpdateCodeCaching(t *testing.T) { var signerAddresses []Address + var cacheHits []string + runtimeInterface := &testRuntimeInterface{ createAccount: func(payer Address) (address Address, err error) { accountCounter++ @@ -4465,6 +4578,7 @@ func TestRuntimeUpdateCodeCaching(t *testing.T) { return nil }, getCachedProgram: func(location Location) (*ast.Program, error) { + cacheHits = append(cacheHits, string(location.ID())) return cachedPrograms[location.ID()], nil }, storage: newTestStorage(nil, nil), @@ -4488,20 +4602,44 @@ func TestRuntimeUpdateCodeCaching(t *testing.T) { nextTransactionLocation := newTransactionLocationGenerator() + // create the account + signerAddresses = []Address{{accountCounter}} - err := runtime.ExecuteTransaction(createAccountScript, nil, runtimeInterface, nextTransactionLocation()) + err := runtime.ExecuteTransaction(createAccountTx, nil, runtimeInterface, nextTransactionLocation()) require.NoError(t, err) + // deploy the contract + + cacheHits = nil + signerAddresses = []Address{{accountCounter}} - err = runtime.ExecuteTransaction(updateCodeScript, nil, runtimeInterface, nextTransactionLocation()) + err = runtime.ExecuteTransaction(deployTx, nil, runtimeInterface, nextTransactionLocation()) + require.NoError(t, err) + require.Empty(t, cacheHits) + + // call the initial hello function + + callScript := []byte(fmt.Sprintf(callHelloScriptTemplate, Address{accountCounter})) + + result1, err := runtime.ExecuteScript(callScript, nil, runtimeInterface, nextTransactionLocation()) + require.NoError(t, err) + require.Equal(t, cadence.NewString("1"), result1) + + // update the contract + + cacheHits = nil + + err = runtime.ExecuteTransaction(updateTx, nil, runtimeInterface, nextTransactionLocation()) require.NoError(t, err) + require.Empty(t, cacheHits) - callScript := []byte(fmt.Sprintf(callHelloTxTemplate, Address{accountCounter})) + // call the new hello function - err = runtime.ExecuteTransaction(callScript, nil, runtimeInterface, nextTransactionLocation()) + result2, err := runtime.ExecuteScript(callScript, nil, runtimeInterface, nextTransactionLocation()) require.NoError(t, err) + require.Equal(t, cadence.NewString("2"), result2) } func TestRuntimeNoCacheHitForToplevelPrograms(t *testing.T) { @@ -4536,7 +4674,7 @@ func TestRuntimeNoCacheHitForToplevelPrograms(t *testing.T) { } ` - createAccountScript := []byte(` + createAccountTx := []byte(` transaction { prepare(signer: AuthAccount) { AuthAccount(payer: signer) @@ -4544,7 +4682,7 @@ func TestRuntimeNoCacheHitForToplevelPrograms(t *testing.T) { } `) - updateCodeScript := utils.DeploymentTransaction("HelloWorld", []byte(helloWorldContract)) + deployTx := utils.DeploymentTransaction("HelloWorld", []byte(helloWorldContract)) runtime := NewInterpreterRuntime() @@ -4599,24 +4737,31 @@ func TestRuntimeNoCacheHitForToplevelPrograms(t *testing.T) { signerAddresses = []Address{{accountCounter}} - err := runtime.ExecuteTransaction(createAccountScript, nil, runtimeInterface, nextTransactionLocation()) + // create the account + + err := runtime.ExecuteTransaction(createAccountTx, nil, runtimeInterface, nextTransactionLocation()) require.NoError(t, err) signerAddresses = []Address{{accountCounter}} - err = runtime.ExecuteTransaction(updateCodeScript, nil, runtimeInterface, nextTransactionLocation()) + err = runtime.ExecuteTransaction(deployTx, nil, runtimeInterface, nextTransactionLocation()) require.NoError(t, err) - callScript := []byte(fmt.Sprintf(callHelloTxTemplate, Address{accountCounter})) + // call the function + + callTx := []byte(fmt.Sprintf(callHelloTxTemplate, Address{accountCounter})) - err = runtime.ExecuteTransaction(callScript, nil, runtimeInterface, nextTransactionLocation()) + err = runtime.ExecuteTransaction(callTx, nil, runtimeInterface, nextTransactionLocation()) require.NoError(t, err) // We should only receive a cache hit for the imported program, not the transactions/scripts. + + // NOTE: if this test case fails with an additional cache hit, + // then the deployment is incorrectly using the cache! + require.Equal(t, []string{ "AC.0100000000000000.HelloWorld", - "AC.0100000000000000.HelloWorld", }, cacheHits, ) diff --git a/runtime/tests/utils/utils.go b/runtime/tests/utils/utils.go index 608dcbd347..c5d22bc943 100644 --- a/runtime/tests/utils/utils.go +++ b/runtime/tests/utils/utils.go @@ -94,3 +94,18 @@ func DeploymentTransaction(name string, contract []byte) []byte { hex.EncodeToString(contract), )) } + +func UpdateTransaction(name string, contract []byte) []byte { + return []byte(fmt.Sprintf( + ` + transaction { + + prepare(signer: AuthAccount) { + signer.contracts.update__experimental(name: "%s", code: "%s".decodeHex()) + } + } + `, + name, + hex.EncodeToString(contract), + )) +}