github.com/koko1123/flow-go-1@v0.29.6/fvm/environment/programs_test.go (about) 1 package environment_test 2 3 import ( 4 "encoding/hex" 5 "fmt" 6 "testing" 7 8 "github.com/onflow/cadence/runtime/common" 9 "github.com/stretchr/testify/require" 10 11 "github.com/koko1123/flow-go-1/engine/execution/state/delta" 12 "github.com/koko1123/flow-go-1/fvm" 13 "github.com/koko1123/flow-go-1/fvm/derived" 14 "github.com/koko1123/flow-go-1/fvm/environment" 15 "github.com/koko1123/flow-go-1/fvm/state" 16 "github.com/koko1123/flow-go-1/model/flow" 17 ) 18 19 func Test_Programs(t *testing.T) { 20 21 addressA := flow.HexToAddress("0a") 22 addressB := flow.HexToAddress("0b") 23 addressC := flow.HexToAddress("0c") 24 25 contractALocation := common.AddressLocation{ 26 Address: common.Address(addressA), 27 Name: "A", 28 } 29 30 contractBLocation := common.AddressLocation{ 31 Address: common.Address(addressB), 32 Name: "B", 33 } 34 35 contractCLocation := common.AddressLocation{ 36 Address: common.Address(addressC), 37 Name: "C", 38 } 39 40 contractA0Code := ` 41 pub contract A { 42 pub fun hello(): String { 43 return "bad version" 44 } 45 } 46 ` 47 48 contractACode := ` 49 pub contract A { 50 pub fun hello(): String { 51 return "hello from A" 52 } 53 } 54 ` 55 56 contractBCode := ` 57 import A from 0xa 58 59 pub contract B { 60 pub fun hello(): String { 61 return "hello from B but also ".concat(A.hello()) 62 } 63 } 64 ` 65 66 contractCCode := ` 67 import B from 0xb 68 69 pub contract C { 70 pub fun hello(): String { 71 return "hello from C, ".concat(B.hello()) 72 } 73 } 74 ` 75 76 callTx := func(name string, address flow.Address) *flow.TransactionBody { 77 78 return flow.NewTransactionBody().SetScript([]byte(fmt.Sprintf(` 79 import %s from %s 80 transaction { 81 prepare() { 82 log(%s.hello()) 83 } 84 }`, name, address.HexWithPrefix(), name)), 85 ) 86 } 87 88 contractDeployTx := func(name, code string, address flow.Address) *flow.TransactionBody { 89 encoded := hex.EncodeToString([]byte(code)) 90 91 return flow.NewTransactionBody().SetScript([]byte(fmt.Sprintf(`transaction { 92 prepare(signer: AuthAccount) { 93 signer.contracts.add(name: "%s", code: "%s".decodeHex()) 94 } 95 }`, name, encoded)), 96 ).AddAuthorizer(address) 97 } 98 99 updateContractTx := func(name, code string, address flow.Address) *flow.TransactionBody { 100 encoded := hex.EncodeToString([]byte(code)) 101 102 return flow.NewTransactionBody().SetScript([]byte(fmt.Sprintf(`transaction { 103 prepare(signer: AuthAccount) { 104 signer.contracts.update__experimental(name: "%s", code: "%s".decodeHex()) 105 } 106 }`, name, encoded)), 107 ).AddAuthorizer(address) 108 } 109 110 mainView := delta.NewView(func(_, _ string) (flow.RegisterValue, error) { 111 return nil, nil 112 }) 113 114 txnState := state.NewTransactionState(mainView, state.DefaultParameters()) 115 116 vm := fvm.NewVirtualMachine() 117 derivedBlockData := derived.NewEmptyDerivedBlockData() 118 119 accounts := environment.NewAccounts(txnState) 120 121 err := accounts.Create(nil, addressA) 122 require.NoError(t, err) 123 124 err = accounts.Create(nil, addressB) 125 require.NoError(t, err) 126 127 err = accounts.Create(nil, addressC) 128 require.NoError(t, err) 129 130 // err = stm. 131 require.NoError(t, err) 132 133 fmt.Printf("Account created\n") 134 135 context := fvm.NewContext( 136 fvm.WithContractDeploymentRestricted(false), 137 fvm.WithAuthorizationChecksEnabled(false), 138 fvm.WithSequenceNumberCheckAndIncrementEnabled(false), 139 fvm.WithCadenceLogging(true), 140 fvm.WithDerivedBlockData(derivedBlockData)) 141 142 var contractAView *delta.View = nil 143 var contractBView *delta.View = nil 144 var txAView *delta.View = nil 145 146 t.Run("contracts can be updated", func(t *testing.T) { 147 retrievedContractA, err := accounts.GetContract("A", addressA) 148 require.NoError(t, err) 149 require.Empty(t, retrievedContractA) 150 151 // deploy contract A0 152 procContractA0 := fvm.Transaction( 153 contractDeployTx("A", contractA0Code, addressA), 154 derivedBlockData.NextTxIndexForTestingOnly()) 155 err = vm.Run(context, procContractA0, mainView) 156 require.NoError(t, err) 157 158 retrievedContractA, err = accounts.GetContract("A", addressA) 159 require.NoError(t, err) 160 161 require.Equal(t, contractA0Code, string(retrievedContractA)) 162 163 // deploy contract A 164 procContractA := fvm.Transaction( 165 updateContractTx("A", contractACode, addressA), 166 derivedBlockData.NextTxIndexForTestingOnly()) 167 err = vm.Run(context, procContractA, mainView) 168 require.NoError(t, err) 169 require.NoError(t, procContractA.Err) 170 171 retrievedContractA, err = accounts.GetContract("A", addressA) 172 require.NoError(t, err) 173 174 require.Equal(t, contractACode, string(retrievedContractA)) 175 176 }) 177 178 t.Run("register touches are captured for simple contract A", func(t *testing.T) { 179 180 // deploy contract A 181 procContractA := fvm.Transaction( 182 contractDeployTx("A", contractACode, addressA), 183 derivedBlockData.NextTxIndexForTestingOnly()) 184 err := vm.Run(context, procContractA, mainView) 185 require.NoError(t, err) 186 187 fmt.Println("---------- Real transaction here ------------") 188 189 // run a TX using contract A 190 procCallA := fvm.Transaction( 191 callTx("A", addressA), 192 derivedBlockData.NextTxIndexForTestingOnly()) 193 194 loadedCode := false 195 viewExecA := delta.NewView(func(owner, key string) (flow.RegisterValue, error) { 196 if key == environment.ContractKey("A") { 197 loadedCode = true 198 } 199 200 return mainView.Peek(owner, key) 201 }) 202 203 err = vm.Run(context, procCallA, viewExecA) 204 require.NoError(t, err) 205 206 // make sure tx was really run 207 require.Contains(t, procCallA.Logs, "\"hello from A\"") 208 209 // Make sure the code has been loaded from storage 210 require.True(t, loadedCode) 211 212 entry := derivedBlockData.GetProgramForTestingOnly(contractALocation) 213 require.NotNil(t, entry) 214 215 // assert dependencies are correct 216 require.Len(t, entry.Value.Dependencies, 1) 217 require.NotNil(t, entry.Value.Dependencies[common.MustBytesToAddress(addressA.Bytes())]) 218 219 // type assertion for further inspections 220 require.IsType(t, entry.State.View(), &delta.View{}) 221 222 // assert some reads were recorded (at least loading of code) 223 deltaView := entry.State.View().(*delta.View) 224 require.NotEmpty(t, deltaView.Interactions().Reads) 225 226 contractAView = deltaView 227 txAView = viewExecA 228 229 // merge it back 230 err = mainView.MergeView(viewExecA) 231 require.NoError(t, err) 232 233 // execute transaction again, this time make sure it doesn't load code 234 viewExecA2 := delta.NewView(func(owner, key string) (flow.RegisterValue, error) { 235 // this time we fail if a read of code occurs 236 require.NotEqual(t, key, environment.ContractKey("A")) 237 238 return mainView.Peek(owner, key) 239 }) 240 241 procCallA = fvm.Transaction( 242 callTx("A", addressA), 243 derivedBlockData.NextTxIndexForTestingOnly()) 244 245 err = vm.Run(context, procCallA, viewExecA2) 246 require.NoError(t, err) 247 248 require.Contains(t, procCallA.Logs, "\"hello from A\"") 249 250 // same transaction should produce the exact same views 251 // but only because we don't do any conditional update in a tx 252 compareViews(t, viewExecA, viewExecA2) 253 254 // merge it back 255 err = mainView.MergeView(viewExecA2) 256 require.NoError(t, err) 257 }) 258 259 t.Run("deploying another contract invalidates dependant programs", func(t *testing.T) { 260 261 // deploy contract B 262 procContractB := fvm.Transaction( 263 contractDeployTx("B", contractBCode, addressB), 264 derivedBlockData.NextTxIndexForTestingOnly()) 265 err := vm.Run(context, procContractB, mainView) 266 require.NoError(t, err) 267 268 // b and c are invalid 269 entryB := derivedBlockData.GetProgramForTestingOnly(contractBLocation) 270 entryC := derivedBlockData.GetProgramForTestingOnly(contractCLocation) 271 // a is still valid 272 entryA := derivedBlockData.GetProgramForTestingOnly(contractALocation) 273 274 require.Nil(t, entryB) 275 require.Nil(t, entryC) 276 require.NotNil(t, entryA) 277 }) 278 279 var viewExecB *delta.View 280 281 t.Run("contract B imports contract A", func(t *testing.T) { 282 283 // programs should have no entries for A and B, as per previous test 284 285 // run a TX using contract B 286 procCallB := fvm.Transaction( 287 callTx("B", addressB), 288 derivedBlockData.NextTxIndexForTestingOnly()) 289 290 viewExecB = delta.NewView(mainView.Peek) 291 292 err = vm.Run(context, procCallB, viewExecB) 293 require.NoError(t, err) 294 295 require.Contains(t, procCallB.Logs, "\"hello from B but also hello from A\"") 296 297 entry := derivedBlockData.GetProgramForTestingOnly(contractALocation) 298 require.NotNil(t, entry) 299 300 // state should be essentially the same as one which we got in tx with contract A 301 require.IsType(t, entry.State.View(), &delta.View{}) 302 deltaA := entry.State.View().(*delta.View) 303 304 compareViews(t, contractAView, deltaA) 305 306 entryB := derivedBlockData.GetProgramForTestingOnly(contractBLocation) 307 require.NotNil(t, entryB) 308 309 // assert dependencies are correct 310 require.Len(t, entryB.Value.Dependencies, 2) 311 require.NotNil(t, entryB.Value.Dependencies[common.MustBytesToAddress(addressA.Bytes())]) 312 require.NotNil(t, entryB.Value.Dependencies[common.MustBytesToAddress(addressB.Bytes())]) 313 314 // program B should contain all the registers used by program A, as it depends on it 315 require.IsType(t, entryB.State.View(), &delta.View{}) 316 deltaB := entryB.State.View().(*delta.View) 317 318 idsA, valuesA := deltaA.Delta().RegisterUpdates() 319 for i, id := range idsA { 320 v, has := deltaB.Delta().Get(id.Owner, id.Key) 321 require.True(t, has) 322 323 require.Equal(t, valuesA[i], v) 324 } 325 326 for id, registerA := range deltaA.Interactions().Reads { 327 328 registerB, has := deltaB.Interactions().Reads[id] 329 require.True(t, has) 330 331 require.Equal(t, registerA, registerB) 332 } 333 334 contractBView = deltaB 335 336 // merge it back 337 err = mainView.MergeView(viewExecB) 338 require.NoError(t, err) 339 340 // rerun transaction 341 342 // execute transaction again, this time make sure it doesn't load code 343 viewExecB2 := delta.NewView(func(owner, key string) (flow.RegisterValue, error) { 344 // this time we fail if a read of code occurs 345 require.NotEqual(t, key, environment.ContractKey("A")) 346 require.NotEqual(t, key, environment.ContractKey("B")) 347 348 return mainView.Peek(owner, key) 349 }) 350 351 procCallB = fvm.Transaction( 352 callTx("B", addressB), 353 derivedBlockData.NextTxIndexForTestingOnly()) 354 355 err = vm.Run(context, procCallB, viewExecB2) 356 require.NoError(t, err) 357 358 require.Contains(t, procCallB.Logs, "\"hello from B but also hello from A\"") 359 360 compareViews(t, viewExecB, viewExecB2) 361 362 // merge it back 363 err = mainView.MergeView(viewExecB2) 364 require.NoError(t, err) 365 }) 366 367 t.Run("contract A runs from cache after program B has been loaded", func(t *testing.T) { 368 369 // at this point programs cache should contain data for contract A 370 // only because contract B has been called 371 372 viewExecA := delta.NewView(func(owner, key string) (flow.RegisterValue, error) { 373 require.NotEqual(t, key, environment.ContractKey("A")) 374 return mainView.Peek(owner, key) 375 }) 376 377 // run a TX using contract A 378 procCallA := fvm.Transaction( 379 callTx("A", addressA), 380 derivedBlockData.NextTxIndexForTestingOnly()) 381 382 err = vm.Run(context, procCallA, viewExecA) 383 require.NoError(t, err) 384 385 require.Contains(t, procCallA.Logs, "\"hello from A\"") 386 387 compareViews(t, txAView, viewExecA) 388 389 // merge it back 390 err = mainView.MergeView(viewExecA) 391 require.NoError(t, err) 392 }) 393 394 t.Run("deploying contract C invalidates C", func(t *testing.T) { 395 require.NotNil(t, contractBView) 396 397 // deploy contract C 398 procContractC := fvm.Transaction( 399 contractDeployTx("C", contractCCode, addressC), 400 derivedBlockData.NextTxIndexForTestingOnly()) 401 err := vm.Run(context, procContractC, mainView) 402 require.NoError(t, err) 403 404 entryA := derivedBlockData.GetProgramForTestingOnly(contractALocation) 405 entryB := derivedBlockData.GetProgramForTestingOnly(contractBLocation) 406 entryC := derivedBlockData.GetProgramForTestingOnly(contractCLocation) 407 408 require.NotNil(t, entryA) 409 require.NotNil(t, entryB) 410 require.Nil(t, entryC) 411 412 }) 413 414 t.Run("importing C should chain-import B and A", func(t *testing.T) { 415 procCallC := fvm.Transaction( 416 callTx("C", addressC), 417 derivedBlockData.NextTxIndexForTestingOnly()) 418 419 viewExecC := delta.NewView(mainView.Peek) 420 421 err = vm.Run(context, procCallC, viewExecC) 422 require.NoError(t, err) 423 424 require.Contains(t, procCallC.Logs, "\"hello from C, hello from B but also hello from A\"") 425 426 // program A is the same 427 entryA := derivedBlockData.GetProgramForTestingOnly(contractALocation) 428 require.NotNil(t, entryA) 429 430 require.IsType(t, entryA.State.View(), &delta.View{}) 431 deltaA := entryA.State.View().(*delta.View) 432 compareViews(t, contractAView, deltaA) 433 434 // program B is the same 435 entryB := derivedBlockData.GetProgramForTestingOnly(contractBLocation) 436 require.NotNil(t, entryB) 437 438 require.IsType(t, entryB.State.View(), &delta.View{}) 439 deltaB := entryB.State.View().(*delta.View) 440 compareViews(t, contractBView, deltaB) 441 442 // program C assertions 443 entryC := derivedBlockData.GetProgramForTestingOnly(contractCLocation) 444 require.NotNil(t, entryC) 445 446 // assert dependencies are correct 447 require.Len(t, entryC.Value.Dependencies, 3) 448 require.NotNil(t, entryC.Value.Dependencies[common.MustBytesToAddress(addressA.Bytes())]) 449 require.NotNil(t, entryC.Value.Dependencies[common.MustBytesToAddress(addressB.Bytes())]) 450 require.NotNil(t, entryC.Value.Dependencies[common.MustBytesToAddress(addressC.Bytes())]) 451 }) 452 } 453 454 // compareViews compares views using only data that matters (ie. two different hasher instances 455 // trips the library comparison, even if actual SPoCKs are the same) 456 func compareViews(t *testing.T, a, b *delta.View) { 457 require.Equal(t, a.Delta(), b.Delta()) 458 require.Equal(t, a.Interactions(), b.Interactions()) 459 require.Equal(t, a.ReadsCount(), b.ReadsCount()) 460 require.Equal(t, a.SpockSecret(), b.SpockSecret()) 461 }