Skip to content

Commit

Permalink
Merge branch 'feature/stable-cadence' of github.com:onflow/cadence in…
Browse files Browse the repository at this point in the history
…to sainati/entitlement-mapping-syntax
  • Loading branch information
dsainati1 committed Oct 19, 2023
2 parents 59cc7f2 + 0bfd2c0 commit dec3432
Show file tree
Hide file tree
Showing 36 changed files with 2,978 additions and 1,201 deletions.
5 changes: 5 additions & 0 deletions runtime/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,11 @@ func (*StandardLibraryHandler) RecordContractUpdate(_ common.AddressLocation, _
// NO-OP
}

func (h *StandardLibraryHandler) ContractUpdateRecorded(_ common.AddressLocation) bool {
// NO-OP
return false
}

func (*StandardLibraryHandler) InterpretContract(
_ common.AddressLocation,
_ *interpreter.Program,
Expand Down
24 changes: 14 additions & 10 deletions runtime/contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,8 @@ func TestRuntimeContract(t *testing.T) {
)
RequireError(t, err)

require.ErrorContains(t, err, "cannot overwrite existing contract")

// the deployed code should not have been updated,
// and no events should have been emitted,
// as the deployment should fail
Expand Down Expand Up @@ -450,6 +452,8 @@ func TestRuntimeContract(t *testing.T) {
} else {
RequireError(t, err)

require.ErrorContains(t, err, "cannot overwrite existing contract")

require.Empty(t, deployedCode)
require.Empty(t, events)
require.Empty(t, loggedMessages)
Expand All @@ -475,9 +479,11 @@ func TestRuntimeContract(t *testing.T) {
Location: nextTransactionLocation(),
},
)
require.NoError(t, err)
RequireError(t, err)

require.Equal(t, []byte(tc.code2), deployedCode)
require.ErrorContains(t, err, "cannot overwrite existing contract")

require.Empty(t, deployedCode)

require.Equal(t,
[]string{
Expand All @@ -486,20 +492,18 @@ func TestRuntimeContract(t *testing.T) {
`"Test"`,
codeArrayString,
`nil`,
`"Test"`,
code2ArrayString,
`"Test"`,
code2ArrayString,
},
loggedMessages,
)

require.Len(t, events, 2)
assert.EqualValues(t, stdlib.AccountContractRemovedEventType.ID(), events[0].Type().ID())
assert.EqualValues(t, stdlib.AccountContractAddedEventType.ID(), events[1].Type().ID())
require.Len(t, events, 1)
assert.EqualValues(t,
stdlib.AccountContractRemovedEventType.ID(),
events[0].Type().ID(),
)

contractValueExists := getContractValueExists()

// contract still exists (from previous transaction), if not interface
if tc.isInterface {
require.False(t, contractValueExists)
} else {
Expand Down
116 changes: 110 additions & 6 deletions runtime/contract_update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (
"github.com/onflow/cadence/runtime/common"
"github.com/onflow/cadence/runtime/interpreter"
. "github.com/onflow/cadence/runtime/tests/runtime_utils"
"github.com/onflow/cadence/runtime/tests/utils"
. "github.com/onflow/cadence/runtime/tests/utils"
)

func TestRuntimeContractUpdateWithDependencies(t *testing.T) {
Expand Down Expand Up @@ -142,7 +142,7 @@ func TestRuntimeContractUpdateWithDependencies(t *testing.T) {

err := runtime.ExecuteTransaction(
Script{
Source: utils.DeploymentTransaction(
Source: DeploymentTransaction(
"Foo",
[]byte(fooContractV1),
),
Expand All @@ -163,7 +163,7 @@ func TestRuntimeContractUpdateWithDependencies(t *testing.T) {

err = runtime.ExecuteTransaction(
Script{
Source: utils.DeploymentTransaction(
Source: DeploymentTransaction(
"Bar",
[]byte(barContractV1),
),
Expand All @@ -183,7 +183,7 @@ func TestRuntimeContractUpdateWithDependencies(t *testing.T) {
signerAccount = common.MustBytesToAddress([]byte{0x1})
err = runtime.ExecuteTransaction(
Script{
Source: utils.UpdateTransaction("Foo", []byte(fooContractV2)),
Source: UpdateTransaction("Foo", []byte(fooContractV2)),
},
Context{
Interface: runtimeInterface,
Expand All @@ -204,7 +204,7 @@ func TestRuntimeContractUpdateWithDependencies(t *testing.T) {

err = runtime.ExecuteTransaction(
Script{
Source: utils.UpdateTransaction("Bar", []byte(barContractV2)),
Source: UpdateTransaction("Bar", []byte(barContractV2)),
},
Context{
Interface: runtimeInterface,
Expand Down Expand Up @@ -283,7 +283,7 @@ func TestRuntimeContractUpdateWithPrecedingIdentifiers(t *testing.T) {

err := runtime.ExecuteTransaction(
Script{
Source: utils.UpdateTransaction("Foo", []byte(fooContractV2)),
Source: UpdateTransaction("Foo", []byte(fooContractV2)),
},
Context{
Interface: runtimeInterface,
Expand All @@ -293,3 +293,107 @@ func TestRuntimeContractUpdateWithPrecedingIdentifiers(t *testing.T) {
require.NoError(t, err)

}

func TestRuntimeInvalidContractRedeploy(t *testing.T) {

t.Parallel()

foo1 := []byte(`
access(all)
contract Foo {
access(all)
resource R {
access(all)
var x: Int
init() {
self.x = 0
}
}
access(all)
fun createR(): @R {
return <-create R()
}
}
`)

foo2 := []byte(`
access(all)
contract Foo {
access(all)
struct R {
access(all)
var x: Int
init() {
self.x = 0
}
}
}
`)

tx := []byte(`
transaction(foo1: String, foo2: String) {
prepare(signer: auth(Contracts) &Account) {
signer.contracts.add(name: "Foo", code: foo1.utf8)
signer.contracts.add(name: "Foo", code: foo2.utf8)
}
}
`)

runtime := NewTestInterpreterRuntimeWithConfig(Config{
AtreeValidationEnabled: false,
})

address := common.MustBytesToAddress([]byte{0x1})

var events []cadence.Event

runtimeInterface := &TestRuntimeInterface{
Storage: NewTestLedger(nil, nil),
OnGetSigningAccounts: func() ([]Address, error) {
return []Address{address}, nil
},
OnGetAccountContractCode: func(location common.AddressLocation) ([]byte, error) {
return nil, nil
},
OnResolveLocation: NewSingleIdentifierLocationResolver(t),
OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error {
// "delay"
return nil
},
OnEmitEvent: func(event cadence.Event) error {
events = append(events, event)
return nil
},
OnDecodeArgument: func(b []byte, t cadence.Type) (value cadence.Value, err error) {
return json.Decode(nil, b)
},
}

nextTransactionLocation := NewTransactionLocationGenerator()

// Deploy

err := runtime.ExecuteTransaction(
Script{
Source: tx,
Arguments: encodeArgs([]cadence.Value{
cadence.String(foo1),
cadence.String(foo2),
}),
},
Context{
Interface: runtimeInterface,
Location: nextTransactionLocation(),
},
)

RequireError(t, err)

require.ErrorContains(t, err, "cannot overwrite existing contract")
}
4 changes: 4 additions & 0 deletions runtime/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,10 @@ func (e *interpreterEnvironment) RecordContractUpdate(
e.storage.recordContractUpdate(location, contractValue)
}

func (e *interpreterEnvironment) ContractUpdateRecorded(location common.AddressLocation) bool {
return e.storage.contractUpdateRecorded(location)
}

func (e *interpreterEnvironment) TemporarilyRecordCode(location common.AddressLocation, code []byte) {
e.codesAndPrograms.setCode(location, code)
}
Expand Down
17 changes: 17 additions & 0 deletions runtime/interpreter/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,23 @@ func (e ValueTransferTypeError) Error() string {
)
}

// UnexpectedMappedEntitlementError
type UnexpectedMappedEntitlementError struct {
Type sema.Type
LocationRange
}

var _ errors.InternalError = UnexpectedMappedEntitlementError{}

func (UnexpectedMappedEntitlementError) IsInternalError() {}

func (e UnexpectedMappedEntitlementError) Error() string {
return fmt.Sprintf(
"invalid transfer of value: found an unexpected runtime mapped entitlement `%s`",
e.Type.QualifiedString(),
)
}

// ResourceConstructionError
type ResourceConstructionError struct {
CompositeType *sema.CompositeType
Expand Down
8 changes: 8 additions & 0 deletions runtime/interpreter/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -2115,6 +2115,14 @@ func (interpreter *Interpreter) convert(value Value, valueType, targetType sema.
// transferring a reference at runtime does not change its entitlements; this is so that an upcast reference
// can later be downcast back to its original entitlement set

// check defensively that we never create a runtime mapped entitlement value
if _, isMappedAuth := unwrappedTargetType.Authorization.(*sema.EntitlementMapAccess); isMappedAuth {
panic(UnexpectedMappedEntitlementError{
Type: unwrappedTargetType,
LocationRange: locationRange,
})
}

switch ref := value.(type) {
case *EphemeralReferenceValue:
return NewEphemeralReferenceValue(
Expand Down
57 changes: 42 additions & 15 deletions runtime/interpreter/interpreter_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,26 +136,53 @@ func (interpreter *Interpreter) valueIndexExpressionGetterSetter(indexExpression
},
)

// Normally, moves of nested resources (e.g `let r <- rs[0]`) are statically rejected.
//
// However, there are cases in which we do allow moves of nested resources:
//
// - In a swap statement (e.g. `rs[0] <-> rs[1]`)
// - In a variable declaration with two values/assignments (e.g. `let r <- rs["foo"] <- nil`)
//
// In both cases we know that a move of the nested resource is immediately followed by a replacement.
// This notion of an expression that moves a nested resource is tracked in the elaboration.
//
// When indexing is a move of a nested resource, we need to remove the key/value from the container.
// However, for some containers, like arrays, the removal influences other values in the container.
// In case of an array, the removal of an element shifts all following elements.
//
// A removal alone would thus result in subsequent code being executed incorrectly.
// For example, in the case where a swap operation through indexing is performed on the same array,
// e.g. `rs[0] <-> rs[1]`, once the first removal was performed, the second operates on a modified container.
//
// Prevent this problem by temporarily writing a placeholder value after the removal.
// Only perform the replacement with a placeholder in the case of a nested resource move.
// We know that in that case the get operation will be followed by a set operation,
// which will replace the temporary placeholder.

isNestedResourceMove := elaboration.IsNestedResourceMoveExpression(indexExpression)

var get func(allowMissing bool) Value

if isNestedResourceMove {
get = func(_ bool) Value {
value := target.RemoveKey(interpreter, locationRange, transferredIndexingValue)
target.InsertKey(interpreter, locationRange, transferredIndexingValue, placeholder)
return value
}
} else {
get = func(_ bool) Value {
value := target.GetKey(interpreter, locationRange, transferredIndexingValue)

// If the indexing value is a reference, then return a reference for the resulting value.
return interpreter.maybeGetReference(indexExpression, value)
}
}

return getterSetter{
target: target,
get: func(_ bool) Value {
if isNestedResourceMove {
return target.RemoveKey(interpreter, locationRange, transferredIndexingValue)
} else {
value := target.GetKey(interpreter, locationRange, transferredIndexingValue)

// If the indexing value is a reference, then return a reference for the resulting value.
return interpreter.maybeGetReference(indexExpression, value)
}
},
get: get,
set: func(value Value) {
if isNestedResourceMove {
target.InsertKey(interpreter, locationRange, transferredIndexingValue, value)
} else {
target.SetKey(interpreter, locationRange, transferredIndexingValue, value)
}
target.SetKey(interpreter, locationRange, transferredIndexingValue, value)
},
}
}
Expand Down
Loading

0 comments on commit dec3432

Please sign in to comment.