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