Skip to content

Commit

Permalink
Merge pull request #3043 from onflow/sainati/legacy-upgrade
Browse files Browse the repository at this point in the history
Use old parser for contract upgrades when legacy upgrade config option is set
  • Loading branch information
dsainati1 authored Jan 24, 2024
2 parents 2036ccc + f961da1 commit 3adb033
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 7 deletions.
2 changes: 2 additions & 0 deletions runtime/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,6 @@ type Config struct {
CoverageReport *CoverageReport
// AttachmentsEnabled specifies if attachments are enabled
AttachmentsEnabled bool
// LegacyContractUpgradeEnabled enabled specifies whether to use the old parser when parsing an old contract
LegacyContractUpgradeEnabled bool
}
115 changes: 115 additions & 0 deletions runtime/contract_update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -680,3 +680,118 @@ func TestRuntimeContractRedeploymentInSeparateTransactions(t *testing.T) {
)
require.NoError(t, err)
}

func TestRuntimeLegacyContractUpdate(t *testing.T) {
t.Parallel()

runtime := NewTestInterpreterRuntimeWithConfig(Config{
AtreeValidationEnabled: true,
LegacyContractUpgradeEnabled: true,
})

accountCodes := map[common.Location][]byte{}
signerAccount := common.MustBytesToAddress([]byte{0x1})
fooLocation := common.AddressLocation{
Address: signerAccount,
Name: "Foo",
}
var checkGetAndSetProgram, getProgramCalled bool

programs := map[Location]*interpreter.Program{}
clearPrograms := func() {
for l := range programs {
delete(programs, l)
}
}

runtimeInterface := &TestRuntimeInterface{
OnGetCode: func(location Location) (bytes []byte, err error) {
return accountCodes[location], nil
},
Storage: NewTestLedger(nil, nil),
OnGetSigningAccounts: func() ([]Address, error) {
return []Address{signerAccount}, nil
},
OnResolveLocation: NewSingleIdentifierLocationResolver(t),
OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) {
return accountCodes[location], nil
},
OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error {
accountCodes[location] = code
return nil
},
OnEmitEvent: func(event cadence.Event) error {
return nil
},
OnDecodeArgument: func(b []byte, t cadence.Type) (value cadence.Value, err error) {
return json.Decode(nil, b)
},
OnGetAndSetProgram: func(
location Location,
load func() (*interpreter.Program, error),
) (
program *interpreter.Program,
err error,
) {
_, isTransactionLocation := location.(common.TransactionLocation)
if checkGetAndSetProgram && !isTransactionLocation {
require.Equal(t, location, fooLocation)
require.False(t, getProgramCalled)
}

var ok bool
program, ok = programs[location]
if ok {
return
}

program, err = load()

// NOTE: important: still set empty program,
// even if error occurred

programs[location] = program

return
},
}

nextTransactionLocation := NewTransactionLocationGenerator()

const fooContractV1 = `
pub contract Foo {
init() {}
pub fun hello() {}
}
`

const fooContractV2 = `
access(all) contract Foo {
init() {}
access(all) fun hello() {}
}
`

// Mock the deploy of the old 'Foo' contract
accountCodes[fooLocation] = []byte(fooContractV1)

// Programs are only valid during the transaction
clearPrograms()

// Update 'Foo' contract to Cadence 1.0 version

signerAccount = common.MustBytesToAddress([]byte{0x1})
err := runtime.ExecuteTransaction(
Script{
Source: UpdateTransaction("Foo", []byte(fooContractV2)),
},
Context{
Interface: runtimeInterface,
Location: nextTransactionLocation(),
},
)
require.NoError(t, err)

// Programs are only valid during the transaction
clearPrograms()
}
1 change: 1 addition & 0 deletions runtime/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ func (e *interpreterEnvironment) newInterpreterConfig() *interpreter.Config {
OnInvokedFunctionReturn: e.newOnInvokedFunctionReturnHandler(),
CapabilityBorrowHandler: stdlib.BorrowCapabilityController,
CapabilityCheckHandler: stdlib.CheckCapabilityController,
LegacyContractUpgradeEnabled: e.config.LegacyContractUpgradeEnabled,
}
}

Expand Down
2 changes: 2 additions & 0 deletions runtime/interpreter/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,6 @@ type Config struct {
CapabilityCheckHandler CapabilityCheckHandlerFunc
// CapabilityBorrowHandler is used to borrow ID capabilities
CapabilityBorrowHandler CapabilityBorrowHandlerFunc
// LegacyContractUpgradeEnabled specifies whether to fall back to the old parser when attempting a contract upgrade
LegacyContractUpgradeEnabled bool
}
39 changes: 32 additions & 7 deletions runtime/stdlib/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/onflow/cadence/runtime/common"
"github.com/onflow/cadence/runtime/errors"
"github.com/onflow/cadence/runtime/interpreter"
"github.com/onflow/cadence/runtime/old_parser"
"github.com/onflow/cadence/runtime/parser"
"github.com/onflow/cadence/runtime/sema"
)
Expand Down Expand Up @@ -1585,24 +1586,48 @@ func changeAccountContracts(
oldCode, err := handler.GetAccountContractCode(location)
handleContractUpdateError(err)

memoryGauge := invocation.Interpreter.SharedState.Config.MemoryGauge
legacyUpgradeEnabled := invocation.Interpreter.SharedState.Config.LegacyContractUpgradeEnabled

oldProgram, err := parser.ParseProgram(
invocation.Interpreter.SharedState.Config.MemoryGauge,
memoryGauge,
oldCode,
parser.Config{
IgnoreLeadingIdentifierEnabled: true,
},
)

var legacyContractUpgrade bool
// if we are allowing legacy contract upgrades, fall back to the old parser when the new one fails
if !ignoreUpdatedProgramParserError(err) && legacyUpgradeEnabled {
legacyContractUpgrade = true
oldProgram, err = old_parser.ParseProgram(
memoryGauge,
oldCode,
old_parser.Config{},
)
}

if !ignoreUpdatedProgramParserError(err) {
handleContractUpdateError(err)
}

validator := NewContractUpdateValidator(
location,
contractName,
oldProgram,
program.Program,
)
var validator UpdateValidator
if legacyContractUpgrade {
validator = NewLegacyContractUpdateValidator(
location,
contractName,
oldProgram,
program.Program,
)
} else {
validator = NewContractUpdateValidator(
location,
contractName,
oldProgram,
program.Program,
)
}
err = validator.Validate()
handleContractUpdateError(err)
}
Expand Down
39 changes: 39 additions & 0 deletions runtime/stdlib/contract_update_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ import (
"github.com/onflow/cadence/runtime/errors"
)

type UpdateValidator interface {
Validate() error
}

type ContractUpdateValidator struct {
TypeComparator

Expand All @@ -40,6 +44,7 @@ type ContractUpdateValidator struct {

// ContractUpdateValidator should implement ast.TypeEqualityChecker
var _ ast.TypeEqualityChecker = &ContractUpdateValidator{}
var _ UpdateValidator = &ContractUpdateValidator{}

// NewContractUpdateValidator initializes and returns a validator, without performing any validation.
// Invoke the `Validate()` method of the validator returned, to start validating the contract.
Expand Down Expand Up @@ -608,3 +613,37 @@ func (e *MissingDeclarationError) Error() string {
e.Name,
)
}

type LegacyContractUpdateValidator struct {
TypeComparator

location common.Location
contractName string
oldProgram *ast.Program
newProgram *ast.Program
}

// NewContractUpdateValidator initializes and returns a validator, without performing any validation.
// Invoke the `Validate()` method of the validator returned, to start validating the contract.
func NewLegacyContractUpdateValidator(
location common.Location,
contractName string,
oldProgram *ast.Program,
newProgram *ast.Program,
) *LegacyContractUpdateValidator {

return &LegacyContractUpdateValidator{
location: location,
oldProgram: oldProgram,
newProgram: newProgram,
contractName: contractName,
}
}

var _ UpdateValidator = &LegacyContractUpdateValidator{}

// Validate validates the contract update, and returns an error if it is an invalid update.
// TODO: for now this is empty until we determine what validation is necessary for a Cadence 1.0 upgrade
func (validator *LegacyContractUpdateValidator) Validate() error {
return nil
}

0 comments on commit 3adb033

Please sign in to comment.