github.com/koko1123/flow-go-1@v0.29.6/fvm/environment/contract_updater_test.go (about) 1 package environment_test 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/onflow/cadence" 8 "github.com/onflow/cadence/runtime" 9 "github.com/onflow/cadence/runtime/common" 10 "github.com/stretchr/testify/mock" 11 "github.com/stretchr/testify/require" 12 13 "github.com/koko1123/flow-go-1/fvm/blueprints" 14 "github.com/koko1123/flow-go-1/fvm/environment" 15 envMock "github.com/koko1123/flow-go-1/fvm/environment/mock" 16 "github.com/koko1123/flow-go-1/fvm/state" 17 "github.com/koko1123/flow-go-1/fvm/utils" 18 "github.com/koko1123/flow-go-1/model/flow" 19 ) 20 21 type testContractUpdaterStubs struct { 22 deploymentEnabled bool 23 removalEnabled bool 24 deploymentAuthorized []common.Address 25 removalAuthorized []common.Address 26 27 auditFunc func(address runtime.Address, code []byte) (bool, error) 28 } 29 30 func (p testContractUpdaterStubs) RestrictedDeploymentEnabled() bool { 31 return p.deploymentEnabled 32 } 33 34 func (p testContractUpdaterStubs) RestrictedRemovalEnabled() bool { 35 return p.removalEnabled 36 } 37 38 func (p testContractUpdaterStubs) GetAuthorizedAccounts( 39 path cadence.Path, 40 ) []common.Address { 41 if path == blueprints.ContractDeploymentAuthorizedAddressesPath { 42 return p.deploymentAuthorized 43 } 44 return p.removalAuthorized 45 } 46 47 func (p testContractUpdaterStubs) UseContractAuditVoucher( 48 address runtime.Address, 49 code []byte, 50 ) ( 51 bool, 52 error, 53 ) { 54 return p.auditFunc(address, code) 55 } 56 57 func TestContract_ChildMergeFunctionality(t *testing.T) { 58 txnState := state.NewTransactionState( 59 utils.NewSimpleView(), 60 state.DefaultParameters()) 61 accounts := environment.NewAccounts(txnState) 62 address := flow.HexToAddress("01") 63 rAdd := runtime.Address(address) 64 err := accounts.Create(nil, address) 65 require.NoError(t, err) 66 67 contractUpdater := environment.NewContractUpdaterForTesting( 68 accounts, 69 testContractUpdaterStubs{}) 70 71 // no contract initially 72 names, err := accounts.GetContractNames(address) 73 require.NoError(t, err) 74 require.Equal(t, len(names), 0) 75 76 // set contract no need for signing accounts 77 err = contractUpdater.SetContract(rAdd, "testContract", []byte("ABC"), nil) 78 require.NoError(t, err) 79 require.True(t, contractUpdater.HasUpdates()) 80 81 // should not be readable from draft 82 cont, err := accounts.GetContract("testContract", address) 83 require.NoError(t, err) 84 require.Equal(t, len(cont), 0) 85 86 // commit 87 _, err = contractUpdater.Commit() 88 require.NoError(t, err) 89 cont, err = accounts.GetContract("testContract", address) 90 require.NoError(t, err) 91 require.Equal(t, cont, []byte("ABC")) 92 93 // rollback 94 err = contractUpdater.SetContract(rAdd, "testContract2", []byte("ABC"), nil) 95 require.NoError(t, err) 96 contractUpdater.Reset() 97 require.False(t, contractUpdater.HasUpdates()) 98 _, err = contractUpdater.Commit() 99 require.NoError(t, err) 100 101 // test contract shouldn't be there 102 cont, err = accounts.GetContract("testContract2", address) 103 require.NoError(t, err) 104 require.Equal(t, len(cont), 0) 105 106 // test contract should be there 107 cont, err = accounts.GetContract("testContract", address) 108 require.NoError(t, err) 109 require.Equal(t, cont, []byte("ABC")) 110 111 // remove 112 err = contractUpdater.RemoveContract(rAdd, "testContract", nil) 113 require.NoError(t, err) 114 115 // contract still there because no commit yet 116 cont, err = accounts.GetContract("testContract", address) 117 require.NoError(t, err) 118 require.Equal(t, cont, []byte("ABC")) 119 120 // commit removal 121 _, err = contractUpdater.Commit() 122 require.NoError(t, err) 123 124 // contract should no longer be there 125 cont, err = accounts.GetContract("testContract", address) 126 require.NoError(t, err) 127 require.Equal(t, []byte(nil), cont) 128 } 129 130 func TestContract_AuthorizationFunctionality(t *testing.T) { 131 txnState := state.NewTransactionState( 132 utils.NewSimpleView(), 133 state.DefaultParameters()) 134 accounts := environment.NewAccounts(txnState) 135 136 authAdd := flow.HexToAddress("01") 137 rAdd := runtime.Address(authAdd) 138 err := accounts.Create(nil, authAdd) 139 require.NoError(t, err) 140 141 authRemove := flow.HexToAddress("02") 142 rRemove := runtime.Address(authRemove) 143 err = accounts.Create(nil, authRemove) 144 require.NoError(t, err) 145 146 authBoth := flow.HexToAddress("03") 147 rBoth := runtime.Address(authBoth) 148 err = accounts.Create(nil, authBoth) 149 require.NoError(t, err) 150 151 unAuth := flow.HexToAddress("04") 152 unAuthR := runtime.Address(unAuth) 153 err = accounts.Create(nil, unAuth) 154 require.NoError(t, err) 155 156 makeUpdater := func() *environment.ContractUpdaterImpl { 157 return environment.NewContractUpdaterForTesting( 158 accounts, 159 testContractUpdaterStubs{ 160 deploymentEnabled: true, 161 removalEnabled: true, 162 deploymentAuthorized: []common.Address{rAdd, rBoth}, 163 removalAuthorized: []common.Address{rRemove, rBoth}, 164 auditFunc: func(address runtime.Address, code []byte) (bool, error) { return false, nil }, 165 }) 166 167 } 168 169 t.Run("try to set contract with unauthorized account", func(t *testing.T) { 170 contractUpdater := makeUpdater() 171 172 err = contractUpdater.SetContract(rAdd, "testContract1", []byte("ABC"), []common.Address{unAuthR}) 173 require.Error(t, err) 174 require.False(t, contractUpdater.HasUpdates()) 175 }) 176 177 t.Run("try to set contract with account only authorized for removal", func(t *testing.T) { 178 contractUpdater := makeUpdater() 179 180 err = contractUpdater.SetContract(rAdd, "testContract1", []byte("ABC"), []common.Address{rRemove}) 181 require.Error(t, err) 182 require.False(t, contractUpdater.HasUpdates()) 183 }) 184 185 t.Run("set contract with account authorized for adding", func(t *testing.T) { 186 contractUpdater := makeUpdater() 187 188 err = contractUpdater.SetContract(rAdd, "testContract2", []byte("ABC"), []common.Address{rAdd}) 189 require.NoError(t, err) 190 require.True(t, contractUpdater.HasUpdates()) 191 }) 192 193 t.Run("set contract with account authorized for adding and removing", func(t *testing.T) { 194 contractUpdater := makeUpdater() 195 196 err = contractUpdater.SetContract(rAdd, "testContract2", []byte("ABC"), []common.Address{rBoth}) 197 require.NoError(t, err) 198 require.True(t, contractUpdater.HasUpdates()) 199 }) 200 201 t.Run("try to remove contract with unauthorized account", func(t *testing.T) { 202 contractUpdater := makeUpdater() 203 204 err = contractUpdater.SetContract(rAdd, "testContract1", []byte("ABC"), []common.Address{rAdd}) 205 require.NoError(t, err) 206 _, err = contractUpdater.Commit() 207 require.NoError(t, err) 208 209 err = contractUpdater.RemoveContract(unAuthR, "testContract2", []common.Address{unAuthR}) 210 require.Error(t, err) 211 require.False(t, contractUpdater.HasUpdates()) 212 }) 213 214 t.Run("remove contract account authorized for removal", func(t *testing.T) { 215 contractUpdater := makeUpdater() 216 217 err = contractUpdater.SetContract(rAdd, "testContract1", []byte("ABC"), []common.Address{rAdd}) 218 require.NoError(t, err) 219 _, err = contractUpdater.Commit() 220 require.NoError(t, err) 221 222 err = contractUpdater.RemoveContract(rRemove, "testContract2", []common.Address{rRemove}) 223 require.NoError(t, err) 224 require.True(t, contractUpdater.HasUpdates()) 225 }) 226 227 t.Run("try to remove contract with account only authorized for adding", func(t *testing.T) { 228 contractUpdater := makeUpdater() 229 230 err = contractUpdater.SetContract(rAdd, "testContract1", []byte("ABC"), []common.Address{rAdd}) 231 require.NoError(t, err) 232 _, err = contractUpdater.Commit() 233 require.NoError(t, err) 234 235 err = contractUpdater.RemoveContract(rAdd, "testContract2", []common.Address{rAdd}) 236 require.Error(t, err) 237 require.False(t, contractUpdater.HasUpdates()) 238 }) 239 240 t.Run("remove contract with account authorized for adding and removing", func(t *testing.T) { 241 contractUpdater := makeUpdater() 242 243 err = contractUpdater.SetContract(rAdd, "testContract1", []byte("ABC"), []common.Address{rAdd}) 244 require.NoError(t, err) 245 _, err = contractUpdater.Commit() 246 require.NoError(t, err) 247 248 err = contractUpdater.RemoveContract(rBoth, "testContract2", []common.Address{rBoth}) 249 require.NoError(t, err) 250 require.True(t, contractUpdater.HasUpdates()) 251 }) 252 } 253 254 func TestContract_DeploymentVouchers(t *testing.T) { 255 256 txnState := state.NewTransactionState( 257 utils.NewSimpleView(), 258 state.DefaultParameters()) 259 accounts := environment.NewAccounts(txnState) 260 261 addressWithVoucher := flow.HexToAddress("01") 262 addressWithVoucherRuntime := runtime.Address(addressWithVoucher) 263 err := accounts.Create(nil, addressWithVoucher) 264 require.NoError(t, err) 265 266 addressNoVoucher := flow.HexToAddress("02") 267 addressNoVoucherRuntime := runtime.Address(addressNoVoucher) 268 err = accounts.Create(nil, addressNoVoucher) 269 require.NoError(t, err) 270 271 contractUpdater := environment.NewContractUpdaterForTesting( 272 accounts, 273 testContractUpdaterStubs{ 274 deploymentEnabled: true, 275 removalEnabled: true, 276 auditFunc: func(address runtime.Address, code []byte) (bool, error) { 277 if address.String() == addressWithVoucher.String() { 278 return true, nil 279 } 280 return false, nil 281 }, 282 }) 283 284 // set contract without voucher 285 err = contractUpdater.SetContract( 286 addressNoVoucherRuntime, 287 "TestContract1", 288 []byte("pub contract TestContract1 {}"), 289 []common.Address{ 290 addressNoVoucherRuntime, 291 }, 292 ) 293 require.Error(t, err) 294 require.False(t, contractUpdater.HasUpdates()) 295 296 // try to set contract with voucher 297 err = contractUpdater.SetContract( 298 addressWithVoucherRuntime, 299 "TestContract2", 300 []byte("pub contract TestContract2 {}"), 301 []common.Address{ 302 addressWithVoucherRuntime, 303 }, 304 ) 305 require.NoError(t, err) 306 require.True(t, contractUpdater.HasUpdates()) 307 } 308 309 func TestContract_ContractUpdate(t *testing.T) { 310 311 txnState := state.NewTransactionState( 312 utils.NewSimpleView(), 313 state.DefaultParameters()) 314 accounts := environment.NewAccounts(txnState) 315 316 flowAddress := flow.HexToAddress("01") 317 flowCommonAddress := common.MustBytesToAddress(flowAddress.Bytes()) 318 runtimeAddress := runtime.Address(flowAddress) 319 err := accounts.Create(nil, flowAddress) 320 require.NoError(t, err) 321 322 var authorizationChecked bool 323 324 contractUpdater := environment.NewContractUpdaterForTesting( 325 accounts, 326 testContractUpdaterStubs{ 327 deploymentEnabled: true, 328 removalEnabled: true, 329 auditFunc: func(address runtime.Address, code []byte) (bool, error) { 330 // Ensure the voucher check is only called once, 331 // for the initial contract deployment, 332 // and not for the subsequent update 333 require.False(t, authorizationChecked) 334 authorizationChecked = true 335 return true, nil 336 }, 337 }) 338 339 // deploy contract with voucher 340 err = contractUpdater.SetContract( 341 runtimeAddress, 342 "TestContract", 343 []byte("pub contract TestContract {}"), 344 []common.Address{ 345 runtimeAddress, 346 }, 347 ) 348 require.NoError(t, err) 349 require.True(t, contractUpdater.HasUpdates()) 350 351 contractUpdateKeys, err := contractUpdater.Commit() 352 require.NoError(t, err) 353 require.Equal( 354 t, 355 []environment.ContractUpdateKey{ 356 { 357 Address: flowCommonAddress, 358 Name: "TestContract", 359 }, 360 }, 361 contractUpdateKeys, 362 ) 363 364 // try to update contract without voucher 365 err = contractUpdater.SetContract( 366 runtimeAddress, 367 "TestContract", 368 []byte("pub contract TestContract {}"), 369 []common.Address{ 370 runtimeAddress, 371 }, 372 ) 373 require.NoError(t, err) 374 require.True(t, contractUpdater.HasUpdates()) 375 } 376 377 func TestContract_DeterministicErrorOnCommit(t *testing.T) { 378 mockAccounts := &envMock.Accounts{} 379 380 mockAccounts.On("ContractExists", mock.Anything, mock.Anything). 381 Return(false, nil) 382 383 mockAccounts.On("SetContract", mock.Anything, mock.Anything, mock.Anything). 384 Return(func(contractName string, address flow.Address, contract []byte) error { 385 return fmt.Errorf("%s %s", contractName, address.Hex()) 386 }) 387 388 contractUpdater := environment.NewContractUpdaterForTesting( 389 mockAccounts, 390 testContractUpdaterStubs{}) 391 392 address1 := runtime.Address(flow.HexToAddress("0000000000000001")) 393 address2 := runtime.Address(flow.HexToAddress("0000000000000002")) 394 395 err := contractUpdater.SetContract(address2, "A", []byte("ABC"), nil) 396 require.NoError(t, err) 397 398 err = contractUpdater.SetContract(address1, "B", []byte("ABC"), nil) 399 require.NoError(t, err) 400 401 err = contractUpdater.SetContract(address1, "A", []byte("ABC"), nil) 402 require.NoError(t, err) 403 404 _, err = contractUpdater.Commit() 405 require.EqualError(t, err, "A 0000000000000001") 406 } 407 408 func TestContract_ContractRemoval(t *testing.T) { 409 410 txnState := state.NewTransactionState( 411 utils.NewSimpleView(), 412 state.DefaultParameters()) 413 accounts := environment.NewAccounts(txnState) 414 415 flowAddress := flow.HexToAddress("01") 416 flowCommonAddress := common.MustBytesToAddress(flowAddress.Bytes()) 417 runtimeAddress := runtime.Address(flowAddress) 418 err := accounts.Create(nil, flowAddress) 419 require.NoError(t, err) 420 421 t.Run("contract removal with restriction", func(t *testing.T) { 422 var authorizationChecked bool 423 424 contractUpdater := environment.NewContractUpdaterForTesting( 425 accounts, 426 testContractUpdaterStubs{ 427 removalEnabled: true, 428 auditFunc: func(address runtime.Address, code []byte) (bool, error) { 429 // Ensure the voucher check is only called once, 430 // for the initial contract deployment, 431 // and not for the subsequent update 432 require.False(t, authorizationChecked) 433 authorizationChecked = true 434 return true, nil 435 }, 436 }) 437 438 // deploy contract with voucher 439 err = contractUpdater.SetContract( 440 runtimeAddress, 441 "TestContract", 442 []byte("pub contract TestContract {}"), 443 []common.Address{ 444 runtimeAddress, 445 }, 446 ) 447 require.NoError(t, err) 448 require.True(t, contractUpdater.HasUpdates()) 449 450 contractUpdateKeys, err := contractUpdater.Commit() 451 require.NoError(t, err) 452 require.Equal( 453 t, 454 []environment.ContractUpdateKey{ 455 { 456 Address: flowCommonAddress, 457 Name: "TestContract", 458 }, 459 }, 460 contractUpdateKeys, 461 ) 462 463 // update should work 464 err = contractUpdater.SetContract( 465 runtimeAddress, 466 "TestContract", 467 []byte("pub contract TestContract {}"), 468 []common.Address{ 469 runtimeAddress, 470 }, 471 ) 472 require.NoError(t, err) 473 require.True(t, contractUpdater.HasUpdates()) 474 475 // try remove contract should fail 476 err = contractUpdater.RemoveContract( 477 runtimeAddress, 478 "TestContract", 479 []common.Address{ 480 runtimeAddress, 481 }, 482 ) 483 require.Error(t, err) 484 }) 485 486 t.Run("contract removal without restriction", func(t *testing.T) { 487 var authorizationChecked bool 488 489 contractUpdater := environment.NewContractUpdaterForTesting( 490 accounts, 491 testContractUpdaterStubs{ 492 auditFunc: func(address runtime.Address, code []byte) (bool, error) { 493 // Ensure the voucher check is only called once, 494 // for the initial contract deployment, 495 // and not for the subsequent update 496 require.False(t, authorizationChecked) 497 authorizationChecked = true 498 return true, nil 499 }, 500 }) 501 502 // deploy contract with voucher 503 err = contractUpdater.SetContract( 504 runtimeAddress, 505 "TestContract", 506 []byte("pub contract TestContract {}"), 507 []common.Address{ 508 runtimeAddress, 509 }, 510 ) 511 require.NoError(t, err) 512 require.True(t, contractUpdater.HasUpdates()) 513 514 contractUpdateKeys, err := contractUpdater.Commit() 515 require.NoError(t, err) 516 require.Equal( 517 t, 518 []environment.ContractUpdateKey{ 519 { 520 Address: flowCommonAddress, 521 Name: "TestContract", 522 }, 523 }, 524 contractUpdateKeys, 525 ) 526 527 // update should work 528 err = contractUpdater.SetContract( 529 runtimeAddress, 530 "TestContract", 531 []byte("pub contract TestContract {}"), 532 []common.Address{ 533 runtimeAddress, 534 }, 535 ) 536 require.NoError(t, err) 537 require.True(t, contractUpdater.HasUpdates()) 538 539 // try remove contract should fail 540 err = contractUpdater.RemoveContract( 541 runtimeAddress, 542 "TestContract", 543 []common.Address{ 544 runtimeAddress, 545 }, 546 ) 547 require.NoError(t, err) 548 require.True(t, contractUpdater.HasUpdates()) 549 550 }) 551 }