diff --git a/runtime/interpreter/simplecompositevalue.go b/runtime/interpreter/simplecompositevalue.go index 42ab2e928e..789db2fe9e 100644 --- a/runtime/interpreter/simplecompositevalue.go +++ b/runtime/interpreter/simplecompositevalue.go @@ -22,7 +22,6 @@ import ( "github.com/onflow/atree" "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/format" "github.com/onflow/cadence/runtime/sema" ) @@ -147,9 +146,10 @@ func (v *SimpleCompositeValue) GetMember( return nil } -func (*SimpleCompositeValue) RemoveMember(_ *Interpreter, _ LocationRange, _ string) Value { - // Simple composite values have no removable members (fields / functions) - panic(errors.NewUnreachableError()) +func (v *SimpleCompositeValue) RemoveMember(_ *Interpreter, _ LocationRange, name string) Value { + value := v.Fields[name] + delete(v.Fields, name) + return value } func (v *SimpleCompositeValue) SetMember(_ *Interpreter, _ LocationRange, name string, value Value) bool { diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index b2cc106afd..ebb2ef8d02 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -9660,3 +9660,99 @@ func TestNestedResourceMoveInDestructor(t *testing.T) { RequireError(t, err) require.ErrorAs(t, err, &interpreter.UseBeforeInitializationError{}) } + +func TestNestedResourceMoveInTransaction(t *testing.T) { + + t.Parallel() + + runtime := newTestInterpreterRuntime() + + signerAccount := common.MustBytesToAddress([]byte{0x1}) + + signers := []Address{signerAccount} + + accountCodes := map[Location][]byte{} + + runtimeInterface := &testRuntimeInterface{ + getCode: func(location Location) (bytes []byte, err error) { + return accountCodes[location], nil + }, + storage: newTestLedger(nil, nil), + getSigningAccounts: func() ([]Address, error) { + return signers, nil + }, + resolveLocation: singleIdentifierLocationResolver(t), + getAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + return accountCodes[location], nil + }, + updateAccountContractCode: func(location common.AddressLocation, code []byte) (err error) { + accountCodes[location] = code + return nil + }, + emitEvent: func(event cadence.Event) error { + return nil + }, + log: func(s string) { + fmt.Println(s) + }, + } + + nextTransactionLocation := newTransactionLocationGenerator() + + foo := []byte(` + pub contract Foo { + pub resource Vault {} + + pub fun createVault(): @Foo.Vault { + return <- create Foo.Vault() + } + } + `) + + // Deploy Foo + + deployVault := DeploymentTransaction("Foo", foo) + err := runtime.ExecuteTransaction( + Script{ + Source: deployVault, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + require.NoError(t, err) + + // Transaction + + attackTransaction := []byte(fmt.Sprintf(` + import Foo from %[1]s + + transaction { + + let vault: @Foo.Vault? + + prepare(acc: AuthAccount) { + self.vault <- Foo.createVault() + } + + execute { + let vault2 <- self.vault + destroy <- vault2 + } + }`, + signerAccount.HexWithPrefix(), + )) + + err = runtime.ExecuteTransaction( + Script{ + Source: attackTransaction, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + + require.NoError(t, err) +}