From 9f476821e3e32e2905dce2cf2ee32ff81d07d666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20Mu=CC=88ller?= Date: Tue, 24 Nov 2020 16:02:50 -0800 Subject: [PATCH 1/5] add a test case for migrating contract value registers --- .../multiple_contract_migration_test.go | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/cmd/util/ledger/migrations/multiple_contract_migration_test.go b/cmd/util/ledger/migrations/multiple_contract_migration_test.go index 128c4e3bced..0c8a7b49b38 100644 --- a/cmd/util/ledger/migrations/multiple_contract_migration_test.go +++ b/cmd/util/ledger/migrations/multiple_contract_migration_test.go @@ -547,4 +547,55 @@ import ITest from %s require.NoError(t, err) require.False(t, called) }) + + t.Run("contract object", func(t *testing.T) { + + key := ledger.Key{ + KeyParts: []ledger.KeyPart{ + ledger.NewKeyPart(state.KeyPartOwner, flow.HexToAddress("01").Bytes()), + ledger.NewKeyPart(state.KeyPartController, []byte("")), + ledger.NewKeyPart(state.KeyPartKey, []byte("contract")), + }, + } + payload := ledger.Payload{ + Key: key, + Value: []byte{ + // tag + 0xd8, 0x84, + // map, 4 pairs of items follow + 0xa4, + // key 0 + 0x0, + // tag + 0xd8, 0xC0, + // byte sequence, length 1 + 0x41, + // positive integer 1 + 0x1, + // key 1 + 0x1, + // UTF-8 string, length 18 + 0x72, + // A.0x1.SimpleStruct + 0x41, + 0x2E, 0x30, 0x78, 0x31, + 0x2E, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, + // key 2 + 0x2, + // positive integer 1 + 0x1, + // key 3 + 0x3, + // map, 0 pairs of items follow + 0xa0, + }, + } + + migrated, err := migrations.MultipleContractMigration([]ledger.Payload{payload}) + require.NoError(t, err) + require.Len(t, migrated, 1) + require.Equal(t, string(migrated[0].Key.KeyParts[2].Value), "contract\x1fSimpleStruct") + require.Equal(t, migrated[0].Value, payload.Value) + }) + } From c05207233bd125d52be88f2925d0ce8dd54c7195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20Mu=CC=88ller?= Date: Tue, 24 Nov 2020 17:14:28 -0800 Subject: [PATCH 2/5] migrate deferred values of contract values --- .../migrations/multiple_contract_migration.go | 172 +++++++++++++++--- .../multiple_contract_migration_test.go | 126 +++++++++---- 2 files changed, 233 insertions(+), 65 deletions(-) diff --git a/cmd/util/ledger/migrations/multiple_contract_migration.go b/cmd/util/ledger/migrations/multiple_contract_migration.go index c25d0cade2f..86b27ba6fb1 100644 --- a/cmd/util/ledger/migrations/multiple_contract_migration.go +++ b/cmd/util/ledger/migrations/multiple_contract_migration.go @@ -39,12 +39,12 @@ var ( MultipleContractsSpecialMigrations = make(map[string]func(ledger.Payload) ([]ledger.Payload, error)) ) -func MultipleContractMigration(payload []ledger.Payload) ([]ledger.Payload, error) { - migratedPayloads := make([]ledger.Payload, 0) - errors := make([]error, 0) +func MultipleContractMigration(payloads []ledger.Payload) ([]ledger.Payload, error) { + + migratedPayloads, contractValueMappings, errors := migrateContractValues(payloads) - for _, p := range payload { - results, err := migrateRegister(p) + for _, p := range payloads { + results, err := migrateNonContractValue(p, contractValueMappings) // dont fail fast... try to collect errors so multiple errors can be addressed at once if err != nil { errors = append(errors, err) @@ -52,6 +52,7 @@ func MultipleContractMigration(payload []ledger.Payload) ([]ledger.Payload, erro } migratedPayloads = append(migratedPayloads, results...) } + if len(errors) != 0 { return nil, &MultipleContractMigrationError{ Errors: errors, @@ -68,7 +69,11 @@ func keyToRegisterId(key ledger.Key) (flow.RegisterID, error) { return flow.RegisterID{}, fmt.Errorf("key not in expected format %s", key.String()) } - return flow.NewRegisterID(string(key.KeyParts[0].Value), string(key.KeyParts[1].Value), string(key.KeyParts[2].Value)), nil + return flow.NewRegisterID( + string(key.KeyParts[0].Value), + string(key.KeyParts[1].Value), + string(key.KeyParts[2].Value), + ), nil } func createContractNamesKey(originalKey ledger.Key) ledger.Key { @@ -106,37 +111,130 @@ func contractsRegister(contractsKey ledger.Key, contractNames []string) (ledger. }, nil } -func migrateRegister(p ledger.Payload) ([]ledger.Payload, error) { - registerId, err := keyToRegisterId(p.Key) +const deferredValueOfContractValuePrefix = "contract\x1f" + +func migrateNonContractValue(p ledger.Payload, contractValueMappings map[string]string) ([]ledger.Payload, error) { + registerID, err := keyToRegisterId(p.Key) if err != nil { return nil, err } - if !isAddress(registerId) { + + // ignore contract value registers, they have already been migrated + if registerID.Key == "contract" { + return nil, nil + } + + if !isAddress(registerID) { return []ledger.Payload{p}, nil } - switch registerId.Key { - case "code": - if em, hasEM := MultipleContractsSpecialMigrations[registerId.Owner]; hasEM { + // migrate contract code register + if registerID.Key == "code" { + if em, hasEM := MultipleContractsSpecialMigrations[registerID.Owner]; hasEM { log.Info(). Err(err). - Str("address", flow.BytesToAddress([]byte(registerId.Owner)).HexWithPrefix()). + Str("address", flow.BytesToAddress([]byte(registerID.Owner)).HexWithPrefix()). Msg("Using exceptional migration for address") return em(p) } return migrateContractCode(p) - case "contract": - return migrateContract(p) - default: - return []ledger.Payload{p}, nil } + + // migrate deferred value of contract value + if strings.HasPrefix(registerID.Key, deferredValueOfContractValuePrefix) { + return migrateDeferredValueOfContractValue(p, registerID, contractValueMappings) + } + + return []ledger.Payload{p}, nil +} + +func migrateDeferredValueOfContractValue( + payload ledger.Payload, + registerID flow.RegisterID, + mappings map[string]string, +) ( + []ledger.Payload, + error, +) { + registerKeySuffix := registerID.Key[len(deferredValueOfContractValuePrefix):] + + address := string(payloadKeyAddress(payload)) + contractName, ok := mappings[address] + if !ok { + return nil, fmt.Errorf("missing contract name for address: %s", address) + } + + newRegisterKey := strings.Join([]string{ + deferredValueOfContractValuePrefix, + contractName, + "\x1f", + registerKeySuffix, + }, "") + + newPayload := ledger.Payload{ + Key: changeKey(payload.Key, newRegisterKey), + Value: payload.Value, + } + + return []ledger.Payload{newPayload}, nil +} + +func migrateContractValues(payloads []ledger.Payload) ([]ledger.Payload, map[string]string, []error) { + migratedPayloads := make([]ledger.Payload, 0, len(payloads)) + contractValueMappings := make(map[string]string) + errors := make([]error, 0) + + for _, p := range payloads { + registerID, err := keyToRegisterId(p.Key) + if err != nil { + // dont fail fast... try to collect errors so multiple errors can be addressed at once + errors = append(errors, err) + continue + } + + if !isAddress(registerID) { + continue + } + + if registerID.Key != "contract" { + continue + } + + results, mapping, err := migrateContractValue(p) + if err != nil { + // dont fail fast... try to collect errors so multiple errors can be addressed at once + errors = append(errors, err) + continue + } + migratedPayloads = append(migratedPayloads, results...) + + if mapping != nil { + if _, ok := contractValueMappings[string(mapping.address)]; ok { + err = fmt.Errorf( + "contract value mapping for address %v already exists", + mapping.address, + ) + errors = append(errors, err) + continue + } + contractValueMappings[string(mapping.address)] = mapping.contractName + } + } + + return migratedPayloads, contractValueMappings, errors } -func migrateContract(p ledger.Payload) ([]ledger.Payload, error) { - address := common.BytesToAddress(flow.BytesToAddress(p.Key.KeyParts[0].Value).Bytes()) +type contractValueMapping struct { + address []byte + contractName string +} + +func migrateContractValue(p ledger.Payload) ([]ledger.Payload, *contractValueMapping, error) { + rawAddress := payloadKeyAddress(p) + address := common.BytesToAddress(flow.BytesToAddress(rawAddress).Bytes()) storedData, version := interpreter.StripMagic(p.Value) if len(storedData) == 0 { - return []ledger.Payload{}, nil + return []ledger.Payload{}, nil, nil } storedValue, err := interpreter.DecodeValue(storedData, &address, []string{"contract"}, version) if err != nil { @@ -144,7 +242,7 @@ func migrateContract(p ledger.Payload) ([]ledger.Payload, error) { Err(err). Str("address", address.Hex()). Msg("Cannot decode contract at address") - return nil, err + return nil, nil, err } value := interpreter.NewSomeValueOwningNonCopying(storedValue).Value.(*interpreter.CompositeValue) @@ -154,21 +252,39 @@ func migrateContract(p ledger.Payload) ([]ledger.Payload, error) { Str("TypeId", string(value.TypeID)). Str("address", address.Hex()). Msg("contract TypeId not in correct format") - return nil, fmt.Errorf("contract TypeId not in correct format") + return nil, nil, fmt.Errorf("contract TypeId not in correct format") } - newKey := changeKey(p.Key, fmt.Sprintf("contract\x1F%s", pieces[2])) + + contractName := pieces[2] + + newKey := changeKey(p.Key, fmt.Sprintf("contract\x1F%s", contractName)) + logKeyChange(address.Hex(), p.Key, newKey) - return []ledger.Payload{{ - Key: newKey, - Value: p.Value, - }}, nil + + newPayloads := []ledger.Payload{ + { + Key: newKey, + Value: p.Value, + }, + } + + mapping := &contractValueMapping{ + address: rawAddress, + contractName: contractName, + } + + return newPayloads, mapping, nil +} + +func payloadKeyAddress(p ledger.Payload) []byte { + return p.Key.KeyParts[0].Value } func migrateContractCode(p ledger.Payload) ([]ledger.Payload, error) { // we don't need the the empty code register value := p.Value - address := flow.BytesToAddress(p.Key.KeyParts[0].Value) + address := flow.BytesToAddress(payloadKeyAddress(p)) if len(value) == 0 { return []ledger.Payload{}, nil } diff --git a/cmd/util/ledger/migrations/multiple_contract_migration_test.go b/cmd/util/ledger/migrations/multiple_contract_migration_test.go index 0c8a7b49b38..0415f77ff78 100644 --- a/cmd/util/ledger/migrations/multiple_contract_migration_test.go +++ b/cmd/util/ledger/migrations/multiple_contract_migration_test.go @@ -67,7 +67,7 @@ func TestMultipleContractMigration(t *testing.T) { migrated, err := migrations.MultipleContractMigration([]ledger.Payload{payload}) require.NoError(t, err) require.Len(t, migrated, 1) - require.Equal(t, migrated[0], payload) + require.Equal(t, payload, migrated[0]) }) t.Run("Non address registers are not migrated", func(t *testing.T) { @@ -86,7 +86,7 @@ func TestMultipleContractMigration(t *testing.T) { migrated, err := migrations.MultipleContractMigration([]ledger.Payload{payload}) require.NoError(t, err) require.Len(t, migrated, 1) - require.Equal(t, migrated[0], payload) + require.Equal(t, payload, migrated[0]) }) t.Run("Empty code registers are removed", func(t *testing.T) { @@ -173,8 +173,8 @@ func TestMultipleContractMigration(t *testing.T) { migrated, err := migrations.MultipleContractMigration([]ledger.Payload{payload}) require.NoError(t, err) require.Len(t, migrated, 2) - require.Equal(t, string(migrated[0].Key.KeyParts[2].Value), "code.Test") - require.Equal(t, string(migrated[0].Value), contract) + require.Equal(t, "code.Test", string(migrated[0].Key.KeyParts[2].Value)) + require.Equal(t, contract, string(migrated[0].Value)) }) // this case was not allowed before, so it shouldn't really happen @@ -195,8 +195,8 @@ func TestMultipleContractMigration(t *testing.T) { migrated, err := migrations.MultipleContractMigration([]ledger.Payload{payload}) require.NoError(t, err) require.Len(t, migrated, 2) - require.Equal(t, string(migrated[0].Key.KeyParts[2].Value), "code.Test") - require.Equal(t, string(migrated[0].Value), contract) + require.Equal(t, "code.Test", string(migrated[0].Key.KeyParts[2].Value)) + require.Equal(t, contract, string(migrated[0].Value)) }) // this case was not allowed before, so it shouldn't really happen @@ -290,10 +290,10 @@ func TestMultipleContractMigration(t *testing.T) { migrated, err := migrations.MultipleContractMigration([]ledger.Payload{payload}) require.NoError(t, err) require.Len(t, migrated, 3) - require.Equal(t, string(migrated[0].Key.KeyParts[2].Value), "code.ITest") - require.Equal(t, string(migrated[0].Value), expectedInterfaceCode) - require.Equal(t, string(migrated[1].Key.KeyParts[2].Value), "code.Test") - require.Equal(t, string(migrated[1].Value), expectedContractCode) + require.Equal(t, "code.ITest", string(migrated[0].Key.KeyParts[2].Value)) + require.Equal(t, expectedInterfaceCode, string(migrated[0].Value)) + require.Equal(t, "code.Test", string(migrated[1].Key.KeyParts[2].Value)) + require.Equal(t, expectedContractCode, string(migrated[1].Value)) }) t.Run("If code has two declarations, correctly split them (ugly formatting)", func(t *testing.T) { address := flow.HexToAddress("01") @@ -331,10 +331,12 @@ func TestMultipleContractMigration(t *testing.T) { migrated, err := migrations.MultipleContractMigration([]ledger.Payload{payload}) require.NoError(t, err) require.Len(t, migrated, 3) - require.Equal(t, string(migrated[0].Key.KeyParts[2].Value), "code.ITest") - require.Equal(t, string(migrated[0].Value), expectedInterfaceCode) - require.Equal(t, string(migrated[1].Key.KeyParts[2].Value), "code.Test") - require.Equal(t, string(migrated[1].Value), expectedContractCode) + + require.Equal(t, "code.ITest", string(migrated[0].Key.KeyParts[2].Value)) + require.Equal(t, expectedInterfaceCode, string(migrated[0].Value)) + + require.Equal(t, "code.Test", string(migrated[1].Key.KeyParts[2].Value)) + require.Equal(t, expectedContractCode, string(migrated[1].Value)) }) t.Run("If code has two declarations, correctly split them (interface last)", func(t *testing.T) { @@ -385,10 +387,11 @@ func TestMultipleContractMigration(t *testing.T) { migrated, err := migrations.MultipleContractMigration([]ledger.Payload{payload}) require.NoError(t, err) require.Len(t, migrated, 3) - require.Equal(t, string(migrated[0].Key.KeyParts[2].Value), "code.ITest") - require.Equal(t, string(migrated[0].Value), expectedInterfaceCode) - require.Equal(t, string(migrated[1].Key.KeyParts[2].Value), "code.Test") - require.Equal(t, string(migrated[1].Value), expectedContractCode) + require.Equal(t, "code.ITest", string(migrated[0].Key.KeyParts[2].Value)) + require.Equal(t, expectedInterfaceCode, string(migrated[0].Value)) + + require.Equal(t, "code.Test", string(migrated[1].Key.KeyParts[2].Value)) + require.Equal(t, expectedContractCode, string(migrated[1].Value)) }) t.Run("If code has two declarations, correctly split them (imports)", func(t *testing.T) { @@ -461,10 +464,12 @@ import ITest from %s migrated, err := migrations.MultipleContractMigration([]ledger.Payload{payload}) require.NoError(t, err) require.Len(t, migrated, 3) - require.Equal(t, string(migrated[1].Key.KeyParts[2].Value), "code.Test") - require.Equal(t, string(migrated[1].Value), expectedContractCode) - require.Equal(t, string(migrated[0].Key.KeyParts[2].Value), "code.ITest") - require.Equal(t, string(migrated[0].Value), expectedInterfaceCode) + + require.Equal(t, "code.Test", string(migrated[1].Key.KeyParts[2].Value)) + require.Equal(t, expectedContractCode, string(migrated[1].Value)) + + require.Equal(t, "code.ITest", string(migrated[0].Key.KeyParts[2].Value)) + require.Equal(t, expectedInterfaceCode, string(migrated[0].Value)) }) t.Run("If code has two declarations of the same type return error", func(t *testing.T) { @@ -550,15 +555,54 @@ import ITest from %s t.Run("contract object", func(t *testing.T) { - key := ledger.Key{ - KeyParts: []ledger.KeyPart{ - ledger.NewKeyPart(state.KeyPartOwner, flow.HexToAddress("01").Bytes()), - ledger.NewKeyPart(state.KeyPartController, []byte("")), - ledger.NewKeyPart(state.KeyPartKey, []byte("contract")), + payload1 := ledger.Payload{ + Key: ledger.Key{ + KeyParts: []ledger.KeyPart{ + ledger.NewKeyPart(state.KeyPartOwner, flow.HexToAddress("01").Bytes()), + ledger.NewKeyPart(state.KeyPartController, []byte("")), + ledger.NewKeyPart(state.KeyPartKey, []byte("contract")), + }, + }, + Value: []byte{ + // tag + 0xd8, 0x84, + // map, 4 pairs of items follow + 0xa4, + // key 0 + 0x0, + // tag + 0xd8, 0xC0, + // byte sequence, length 1 + 0x41, + // positive integer 1 + 0x1, + // key 1 + 0x1, + // UTF-8 string, length 18 + 0x67, + // A.0x1.C + 0x41, + 0x2E, 0x30, 0x78, 0x31, + 0x2E, 0x43, + // key 2 + 0x2, + // positive integer 1 + 0x3, + // key 3 + 0x3, + // map, 0 pairs of items follow + 0xa0, }, } - payload := ledger.Payload{ - Key: key, + + payload2 := ledger.Payload{ + Key: ledger.Key{ + KeyParts: []ledger.KeyPart{ + ledger.NewKeyPart(state.KeyPartOwner, flow.HexToAddress("01").Bytes()), + ledger.NewKeyPart(state.KeyPartController, []byte("")), + ledger.NewKeyPart(state.KeyPartKey, []byte("contract\x1Fnodes\x1Fv\x1F1")), + }, + }, Value: []byte{ // tag 0xd8, 0x84, @@ -575,11 +619,11 @@ import ITest from %s // key 1 0x1, // UTF-8 string, length 18 - 0x72, - // A.0x1.SimpleStruct + 0x67, + // A.0x1.S 0x41, 0x2E, 0x30, 0x78, 0x31, - 0x2E, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, + 0x2E, 0x53, // key 2 0x2, // positive integer 1 @@ -591,11 +635,19 @@ import ITest from %s }, } - migrated, err := migrations.MultipleContractMigration([]ledger.Payload{payload}) + migrated, err := migrations.MultipleContractMigration([]ledger.Payload{ + payload1, + payload2, + }) require.NoError(t, err) - require.Len(t, migrated, 1) - require.Equal(t, string(migrated[0].Key.KeyParts[2].Value), "contract\x1fSimpleStruct") - require.Equal(t, migrated[0].Value, payload.Value) - }) + require.Len(t, migrated, 2) + + migrated1 := migrated[0] + require.Equal(t, "contract\x1fC", string(migrated1.Key.KeyParts[2].Value)) + require.Equal(t, payload1.Value, migrated1.Value) + migrated2 := migrated[1] + require.Equal(t, "contract\x1fC\x1Fnodes\x1Fv\x1F1", string(migrated2.Key.KeyParts[2].Value)) + require.Equal(t, payload2.Value, migrated2.Value) + }) } From 720779f98788a32dc7c3c7457f837e85084046d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20Mu=CC=88ller?= Date: Tue, 24 Nov 2020 18:22:58 -0800 Subject: [PATCH 3/5] log migration of deferred value of contract value --- cmd/util/ledger/migrations/multiple_contract_migration.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/util/ledger/migrations/multiple_contract_migration.go b/cmd/util/ledger/migrations/multiple_contract_migration.go index 86b27ba6fb1..2ab09353da3 100644 --- a/cmd/util/ledger/migrations/multiple_contract_migration.go +++ b/cmd/util/ledger/migrations/multiple_contract_migration.go @@ -158,8 +158,8 @@ func migrateDeferredValueOfContractValue( ) { registerKeySuffix := registerID.Key[len(deferredValueOfContractValuePrefix):] - address := string(payloadKeyAddress(payload)) - contractName, ok := mappings[address] + rawAddress := string(payloadKeyAddress(payload)) + contractName, ok := mappings[rawAddress] if !ok { return nil, fmt.Errorf("missing contract name for address: %s", address) } @@ -176,6 +176,10 @@ func migrateDeferredValueOfContractValue( Value: payload.Value, } + address := common.BytesToAddress(flow.BytesToAddress([]byte(rawAddress)).Bytes()) + + logKeyChange(address.Hex(), payload.Key, newPayload.Key) + return []ledger.Payload{newPayload}, nil } From b6218fe6c8d4c12ad8283a49478e9b3a70ff9178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20Mu=CC=88ller?= Date: Tue, 24 Nov 2020 18:24:11 -0800 Subject: [PATCH 4/5] fix error --- cmd/util/ledger/migrations/multiple_contract_migration.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/util/ledger/migrations/multiple_contract_migration.go b/cmd/util/ledger/migrations/multiple_contract_migration.go index 2ab09353da3..6b9f6b8bc51 100644 --- a/cmd/util/ledger/migrations/multiple_contract_migration.go +++ b/cmd/util/ledger/migrations/multiple_contract_migration.go @@ -159,6 +159,8 @@ func migrateDeferredValueOfContractValue( registerKeySuffix := registerID.Key[len(deferredValueOfContractValuePrefix):] rawAddress := string(payloadKeyAddress(payload)) + address := common.BytesToAddress(flow.BytesToAddress([]byte(rawAddress)).Bytes()) + contractName, ok := mappings[rawAddress] if !ok { return nil, fmt.Errorf("missing contract name for address: %s", address) @@ -176,7 +178,6 @@ func migrateDeferredValueOfContractValue( Value: payload.Value, } - address := common.BytesToAddress(flow.BytesToAddress([]byte(rawAddress)).Bytes()) logKeyChange(address.Hex(), payload.Key, newPayload.Key) From ffd0f0b5e6be778912028aa126c1ca68e04f15a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20Mu=CC=88ller?= Date: Tue, 24 Nov 2020 18:41:47 -0800 Subject: [PATCH 5/5] lint --- cmd/util/ledger/migrations/multiple_contract_migration.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/util/ledger/migrations/multiple_contract_migration.go b/cmd/util/ledger/migrations/multiple_contract_migration.go index 6b9f6b8bc51..20ffdd2ea46 100644 --- a/cmd/util/ledger/migrations/multiple_contract_migration.go +++ b/cmd/util/ledger/migrations/multiple_contract_migration.go @@ -174,11 +174,10 @@ func migrateDeferredValueOfContractValue( }, "") newPayload := ledger.Payload{ - Key: changeKey(payload.Key, newRegisterKey), + Key: changeKey(payload.Key, newRegisterKey), Value: payload.Value, } - logKeyChange(address.Hex(), payload.Key, newPayload.Key) return []ledger.Payload{newPayload}, nil @@ -230,7 +229,7 @@ func migrateContractValues(payloads []ledger.Payload) ([]ledger.Payload, map[str } type contractValueMapping struct { - address []byte + address []byte contractName string }