github.com/koko1123/flow-go-1@v0.29.6/fvm/environment/contract_updater.go (about) 1 package environment 2 3 import ( 4 "bytes" 5 "fmt" 6 "sort" 7 8 "github.com/onflow/cadence" 9 "github.com/onflow/cadence/runtime" 10 "github.com/onflow/cadence/runtime/common" 11 12 "github.com/koko1123/flow-go-1/fvm/blueprints" 13 "github.com/koko1123/flow-go-1/fvm/errors" 14 "github.com/koko1123/flow-go-1/fvm/state" 15 "github.com/koko1123/flow-go-1/fvm/tracing" 16 "github.com/koko1123/flow-go-1/fvm/utils" 17 "github.com/koko1123/flow-go-1/model/flow" 18 "github.com/koko1123/flow-go-1/module/trace" 19 ) 20 21 type ContractUpdaterParams struct { 22 // Depricated: RestrictedDeploymentEnabled is deprecated use 23 // SetIsContractDeploymentRestrictedTransaction instead. 24 // Can be removed after all networks are migrated to 25 // SetIsContractDeploymentRestrictedTransaction 26 RestrictContractDeployment bool 27 RestrictContractRemoval bool 28 } 29 30 func DefaultContractUpdaterParams() ContractUpdaterParams { 31 return ContractUpdaterParams{ 32 RestrictContractDeployment: true, 33 RestrictContractRemoval: true, 34 } 35 } 36 37 type sortableContractUpdates struct { 38 keys []ContractUpdateKey 39 updates []ContractUpdate 40 } 41 42 func (lists *sortableContractUpdates) Len() int { 43 return len(lists.keys) 44 } 45 46 func (lists *sortableContractUpdates) Swap(i, j int) { 47 lists.keys[i], lists.keys[j] = lists.keys[j], lists.keys[i] 48 lists.updates[i], lists.updates[j] = lists.updates[j], lists.updates[i] 49 } 50 51 func (lists *sortableContractUpdates) Less(i, j int) bool { 52 switch bytes.Compare(lists.keys[i].Address[:], lists.keys[j].Address[:]) { 53 case -1: 54 return true 55 case 0: 56 return lists.keys[i].Name < lists.keys[j].Name 57 default: 58 return false 59 } 60 } 61 62 // ContractUpdater handles all smart contracts modification. It also captures 63 // all changes as deltas and only commit them when called so smart contract 64 // updates can be delayed until end of the tx execution. 65 // 66 // Note that scripts cannot modify smart contracts, but must expose the API in 67 // compliance with the runtime environment interface. 68 type ContractUpdater interface { 69 // Cadence's runtime API. Note that the script variant will return 70 // OperationNotSupportedError. 71 UpdateAccountContractCode( 72 address runtime.Address, 73 name string, 74 code []byte, 75 ) error 76 77 // Cadence's runtime API. Note that the script variant will return 78 // OperationNotSupportedError. 79 RemoveAccountContractCode(address runtime.Address, name string) error 80 81 Commit() ([]ContractUpdateKey, error) 82 83 Reset() 84 } 85 86 type ParseRestrictedContractUpdater struct { 87 txnState *state.TransactionState 88 impl ContractUpdater 89 } 90 91 func NewParseRestrictedContractUpdater( 92 txnState *state.TransactionState, 93 impl ContractUpdater, 94 ) ParseRestrictedContractUpdater { 95 return ParseRestrictedContractUpdater{ 96 txnState: txnState, 97 impl: impl, 98 } 99 } 100 101 func (updater ParseRestrictedContractUpdater) UpdateAccountContractCode( 102 address runtime.Address, 103 name string, 104 code []byte, 105 ) error { 106 return parseRestrict3Arg( 107 updater.txnState, 108 trace.FVMEnvUpdateAccountContractCode, 109 updater.impl.UpdateAccountContractCode, 110 address, 111 name, 112 code) 113 } 114 115 func (updater ParseRestrictedContractUpdater) RemoveAccountContractCode( 116 address runtime.Address, 117 name string, 118 ) error { 119 return parseRestrict2Arg( 120 updater.txnState, 121 trace.FVMEnvRemoveAccountContractCode, 122 updater.impl.RemoveAccountContractCode, 123 address, 124 name) 125 } 126 127 func (updater ParseRestrictedContractUpdater) Commit() ( 128 []ContractUpdateKey, 129 error, 130 ) { 131 return updater.impl.Commit() 132 } 133 134 func (updater ParseRestrictedContractUpdater) Reset() { 135 updater.impl.Reset() 136 } 137 138 type NoContractUpdater struct{} 139 140 func (NoContractUpdater) UpdateAccountContractCode( 141 address runtime.Address, 142 name string, 143 code []byte, 144 ) error { 145 return errors.NewOperationNotSupportedError("UpdateAccountContractCode") 146 } 147 148 func (NoContractUpdater) RemoveAccountContractCode( 149 address runtime.Address, 150 name string, 151 ) error { 152 return errors.NewOperationNotSupportedError("RemoveAccountContractCode") 153 } 154 155 func (NoContractUpdater) Commit() ([]ContractUpdateKey, error) { 156 return nil, nil 157 } 158 159 func (NoContractUpdater) Reset() { 160 } 161 162 // Expose stub interface for testing. 163 type ContractUpdaterStubs interface { 164 RestrictedDeploymentEnabled() bool 165 RestrictedRemovalEnabled() bool 166 167 GetAuthorizedAccounts(path cadence.Path) []common.Address 168 169 UseContractAuditVoucher(address runtime.Address, code []byte) (bool, error) 170 } 171 172 type contractUpdaterStubsImpl struct { 173 chain flow.Chain 174 175 ContractUpdaterParams 176 177 logger *ProgramLogger 178 systemContracts *SystemContracts 179 runtime *Runtime 180 } 181 182 func (impl *contractUpdaterStubsImpl) RestrictedDeploymentEnabled() bool { 183 enabled, defined := impl.getIsContractDeploymentRestricted() 184 if !defined { 185 // If the contract deployment bool is not set by the state 186 // fallback to the default value set by the configuration 187 // after the contract deployment bool is set by the state on all 188 // chains, this logic can be simplified 189 return impl.RestrictContractDeployment 190 } 191 return enabled 192 } 193 194 // GetIsContractDeploymentRestricted returns if contract deployment 195 // restriction is defined in the state and the value of it 196 func (impl *contractUpdaterStubsImpl) getIsContractDeploymentRestricted() ( 197 restricted bool, 198 defined bool, 199 ) { 200 service := runtime.Address(impl.chain.ServiceAddress()) 201 202 runtime := impl.runtime.BorrowCadenceRuntime() 203 defer impl.runtime.ReturnCadenceRuntime(runtime) 204 205 value, err := runtime.ReadStored( 206 service, 207 blueprints.IsContractDeploymentRestrictedPath) 208 if err != nil { 209 impl.logger.Logger(). 210 Debug(). 211 Msg("Failed to read IsContractDeploymentRestricted from the " + 212 "service account. Using value from context instead.") 213 return false, false 214 } 215 restrictedCadence, ok := value.(cadence.Bool) 216 if !ok { 217 impl.logger.Logger(). 218 Debug(). 219 Msg("Failed to parse IsContractDeploymentRestricted from the " + 220 "service account. Using value from context instead.") 221 return false, false 222 } 223 restricted = restrictedCadence.ToGoValue().(bool) 224 return restricted, true 225 } 226 227 func (impl *contractUpdaterStubsImpl) RestrictedRemovalEnabled() bool { 228 // TODO read this from the chain similar to the contract deployment 229 // but for now we would honor the fallback context flag 230 return impl.RestrictContractRemoval 231 } 232 233 // GetAuthorizedAccounts returns a list of addresses authorized by the service 234 // account. Used to determine which accounts are permitted to deploy, update, 235 // or remove contracts. 236 // 237 // It reads a storage path from service account and parse the addresses. If any 238 // issue occurs on the process (missing registers, stored value properly not 239 // set), it gracefully handles it and falls back to default behaviour (only 240 // service account be authorized). 241 func (impl *contractUpdaterStubsImpl) GetAuthorizedAccounts( 242 path cadence.Path, 243 ) []common.Address { 244 // set default to service account only 245 service := runtime.Address(impl.chain.ServiceAddress()) 246 defaultAccounts := []runtime.Address{service} 247 248 runtime := impl.runtime.BorrowCadenceRuntime() 249 defer impl.runtime.ReturnCadenceRuntime(runtime) 250 251 value, err := runtime.ReadStored(service, path) 252 253 const warningMsg = "failed to read contract authorized accounts from " + 254 "service account. using default behaviour instead." 255 256 if err != nil { 257 impl.logger.Logger().Warn().Msg(warningMsg) 258 return defaultAccounts 259 } 260 addresses, ok := utils.CadenceValueToAddressSlice(value) 261 if !ok { 262 impl.logger.Logger().Warn().Msg(warningMsg) 263 return defaultAccounts 264 } 265 return addresses 266 } 267 268 func (impl *contractUpdaterStubsImpl) UseContractAuditVoucher( 269 address runtime.Address, 270 code []byte, 271 ) ( 272 bool, 273 error, 274 ) { 275 return impl.systemContracts.UseContractAuditVoucher( 276 address, 277 string(code[:])) 278 } 279 280 type ContractUpdaterImpl struct { 281 tracer tracing.TracerSpan 282 meter Meter 283 accounts Accounts 284 transactionInfo TransactionInfo 285 286 draftUpdates map[ContractUpdateKey]ContractUpdate 287 288 ContractUpdaterStubs 289 } 290 291 var _ ContractUpdater = &ContractUpdaterImpl{} 292 293 func NewContractUpdaterForTesting( 294 accounts Accounts, 295 stubs ContractUpdaterStubs, 296 ) *ContractUpdaterImpl { 297 updater := NewContractUpdater( 298 tracing.NewTracerSpan(), 299 nil, 300 accounts, 301 nil, 302 nil, 303 DefaultContractUpdaterParams(), 304 nil, 305 nil, 306 nil) 307 updater.ContractUpdaterStubs = stubs 308 return updater 309 } 310 311 func NewContractUpdater( 312 tracer tracing.TracerSpan, 313 meter Meter, 314 accounts Accounts, 315 transactionInfo TransactionInfo, 316 chain flow.Chain, 317 params ContractUpdaterParams, 318 logger *ProgramLogger, 319 systemContracts *SystemContracts, 320 runtime *Runtime, 321 ) *ContractUpdaterImpl { 322 updater := &ContractUpdaterImpl{ 323 tracer: tracer, 324 meter: meter, 325 accounts: accounts, 326 transactionInfo: transactionInfo, 327 ContractUpdaterStubs: &contractUpdaterStubsImpl{ 328 logger: logger, 329 chain: chain, 330 ContractUpdaterParams: params, 331 systemContracts: systemContracts, 332 runtime: runtime, 333 }, 334 } 335 336 updater.Reset() 337 return updater 338 } 339 340 func (updater *ContractUpdaterImpl) UpdateAccountContractCode( 341 address runtime.Address, 342 name string, 343 code []byte, 344 ) error { 345 defer updater.tracer.StartChildSpan( 346 trace.FVMEnvUpdateAccountContractCode).End() 347 348 err := updater.meter.MeterComputation( 349 ComputationKindUpdateAccountContractCode, 350 1) 351 if err != nil { 352 return fmt.Errorf("update account contract code failed: %w", err) 353 } 354 355 err = updater.accounts.CheckAccountNotFrozen(flow.Address(address)) 356 if err != nil { 357 return fmt.Errorf("update account contract code failed: %w", err) 358 } 359 360 err = updater.SetContract( 361 address, 362 name, 363 code, 364 updater.transactionInfo.SigningAccounts()) 365 if err != nil { 366 return fmt.Errorf("updating account contract code failed: %w", err) 367 } 368 369 return nil 370 } 371 372 func (updater *ContractUpdaterImpl) RemoveAccountContractCode( 373 address runtime.Address, 374 name string, 375 ) error { 376 defer updater.tracer.StartChildSpan( 377 trace.FVMEnvRemoveAccountContractCode).End() 378 379 err := updater.meter.MeterComputation( 380 ComputationKindRemoveAccountContractCode, 381 1) 382 if err != nil { 383 return fmt.Errorf("remove account contract code failed: %w", err) 384 } 385 386 err = updater.accounts.CheckAccountNotFrozen(flow.Address(address)) 387 if err != nil { 388 return fmt.Errorf("remove account contract code failed: %w", err) 389 } 390 391 err = updater.RemoveContract( 392 address, 393 name, 394 updater.transactionInfo.SigningAccounts()) 395 if err != nil { 396 return fmt.Errorf("remove account contract code failed: %w", err) 397 } 398 399 return nil 400 } 401 402 func (updater *ContractUpdaterImpl) SetContract( 403 address runtime.Address, 404 name string, 405 code []byte, 406 signingAccounts []runtime.Address, 407 ) (err error) { 408 409 flowAddress := flow.Address(address) 410 411 // Initial contract deployments must be authorized by signing accounts, 412 // or there must be an audit voucher available. 413 // 414 // Contract updates are always allowed. 415 416 var exists bool 417 exists, err = updater.accounts.ContractExists(name, flowAddress) 418 if err != nil { 419 return err 420 } 421 422 if !exists && !updater.isAuthorizedForDeployment(signingAccounts) { 423 // check if there's an audit voucher for the contract 424 voucherAvailable, err := updater.UseContractAuditVoucher(address, code) 425 if err != nil { 426 errInner := errors.NewOperationAuthorizationErrorf( 427 "SetContract", 428 "failed to check audit vouchers", 429 ) 430 return fmt.Errorf("setting contract failed: %w - %s", errInner, err) 431 } 432 if !voucherAvailable { 433 return fmt.Errorf( 434 "deploying contract failed: %w", 435 errors.NewOperationAuthorizationErrorf( 436 "SetContract", 437 "deploying contracts requires authorization from specific "+ 438 "accounts")) 439 } 440 } 441 442 contractUpdateKey := ContractUpdateKey{ 443 Address: address, 444 Name: name, 445 } 446 447 updater.draftUpdates[contractUpdateKey] = ContractUpdate{ 448 ContractUpdateKey: contractUpdateKey, 449 Code: code, 450 } 451 452 return nil 453 } 454 455 func (updater *ContractUpdaterImpl) RemoveContract( 456 address runtime.Address, 457 name string, 458 signingAccounts []runtime.Address, 459 ) (err error) { 460 // check if authorized 461 if !updater.isAuthorizedForRemoval(signingAccounts) { 462 return fmt.Errorf("removing contract failed: %w", 463 errors.NewOperationAuthorizationErrorf( 464 "RemoveContract", 465 "removing contracts requires authorization from specific "+ 466 "accounts")) 467 } 468 469 uk := ContractUpdateKey{Address: address, Name: name} 470 u := ContractUpdate{ContractUpdateKey: uk} 471 updater.draftUpdates[uk] = u 472 473 return nil 474 } 475 476 func (updater *ContractUpdaterImpl) Commit() ([]ContractUpdateKey, error) { 477 updatedKeys, updateList := updater.updates() 478 updater.Reset() 479 480 var err error 481 for _, v := range updateList { 482 if len(v.Code) > 0 { 483 err = updater.accounts.SetContract(v.Name, flow.BytesToAddress(v.Address.Bytes()), v.Code) 484 if err != nil { 485 return nil, err 486 } 487 } else { 488 err = updater.accounts.DeleteContract(v.Name, flow.BytesToAddress(v.Address.Bytes())) 489 if err != nil { 490 return nil, err 491 } 492 } 493 } 494 495 return updatedKeys, nil 496 } 497 498 func (updater *ContractUpdaterImpl) Reset() { 499 updater.draftUpdates = make(map[ContractUpdateKey]ContractUpdate) 500 } 501 502 func (updater *ContractUpdaterImpl) HasUpdates() bool { 503 return len(updater.draftUpdates) > 0 504 } 505 506 func (updater *ContractUpdaterImpl) updates() ( 507 []ContractUpdateKey, 508 []ContractUpdate, 509 ) { 510 if len(updater.draftUpdates) == 0 { 511 return nil, nil 512 } 513 keys := make([]ContractUpdateKey, 0, len(updater.draftUpdates)) 514 updates := make([]ContractUpdate, 0, len(updater.draftUpdates)) 515 for key, update := range updater.draftUpdates { 516 keys = append(keys, key) 517 updates = append(updates, update) 518 } 519 520 sort.Sort(&sortableContractUpdates{keys: keys, updates: updates}) 521 return keys, updates 522 } 523 524 func (updater *ContractUpdaterImpl) isAuthorizedForDeployment( 525 signingAccounts []runtime.Address, 526 ) bool { 527 if updater.RestrictedDeploymentEnabled() { 528 return updater.isAuthorized( 529 signingAccounts, 530 blueprints.ContractDeploymentAuthorizedAddressesPath) 531 } 532 return true 533 } 534 535 func (updater *ContractUpdaterImpl) isAuthorizedForRemoval( 536 signingAccounts []runtime.Address, 537 ) bool { 538 if updater.RestrictedRemovalEnabled() { 539 return updater.isAuthorized( 540 signingAccounts, 541 blueprints.ContractRemovalAuthorizedAddressesPath) 542 } 543 return true 544 } 545 546 func (updater *ContractUpdaterImpl) isAuthorized( 547 signingAccounts []runtime.Address, 548 path cadence.Path, 549 ) bool { 550 accts := updater.GetAuthorizedAccounts(path) 551 for _, authorized := range accts { 552 for _, signer := range signingAccounts { 553 if signer == authorized { 554 // a single authorized singer is enough 555 return true 556 } 557 } 558 } 559 return false 560 }