From 6882887bdd0da13663d7f55f731b822d41058544 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 23 Nov 2022 17:44:03 +0300 Subject: [PATCH] [#2091] neofs-adm: Do not query hashes via network Signed-off-by: Evgenii Stratonikov --- .../internal/modules/morph/balance.go | 28 +++++-------------- .../internal/modules/morph/deploy.go | 4 +-- .../internal/modules/morph/generate.go | 6 ++-- .../internal/modules/morph/initialize.go | 17 ++++------- .../modules/morph/initialize_deploy.go | 12 ++++---- .../modules/morph/initialize_register.go | 8 +++--- .../modules/morph/initialize_roles.go | 11 +++----- .../modules/morph/initialize_transfer.go | 20 ++++++------- .../internal/modules/morph/notary.go | 23 +++++---------- .../internal/modules/morph/policy.go | 6 ++-- 10 files changed, 46 insertions(+), 89 deletions(-) diff --git a/cmd/neofs-adm/internal/modules/morph/balance.go b/cmd/neofs-adm/internal/modules/morph/balance.go index 7ce05682a..5f928a004 100644 --- a/cmd/neofs-adm/internal/modules/morph/balance.go +++ b/cmd/neofs-adm/internal/modules/morph/balance.go @@ -6,14 +6,15 @@ import ( "fmt" "math/big" - "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/gas" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/rolemgmt" "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/util" @@ -59,21 +60,6 @@ func dumpBalances(cmd *cobra.Command, _ []string) error { inv := invoker.New(c, nil) - ns, err := getNativeHashes(c) - if err != nil { - return fmt.Errorf("can't fetch the list of native contracts: %w", err) - } - - gasHash, ok := ns[nativenames.Gas] - if !ok { - return fmt.Errorf("can't find the %s native contract hash", nativenames.Gas) - } - - desigHash, ok := ns[nativenames.Designation] - if !ok { - return fmt.Errorf("can't find the %s native contract hash", nativenames.Designation) - } - if !notaryEnabled || dumpStorage || dumpAlphabet || dumpProxy { nnsCs, err = c.GetContractStateByID(1) if err != nil { @@ -86,12 +72,12 @@ func dumpBalances(cmd *cobra.Command, _ []string) error { } } - irList, err := fetchIRNodes(c, nmHash, desigHash) + irList, err := fetchIRNodes(c, nmHash, rolemgmt.Hash) if err != nil { return err } - if err := fetchBalances(inv, gasHash, irList); err != nil { + if err := fetchBalances(inv, gas.Hash, irList); err != nil { return err } printBalances(cmd, "Inner ring nodes balances:", irList) @@ -123,7 +109,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error { snList[i].scriptHash = pub.GetScriptHash() } - if err := fetchBalances(inv, gasHash, snList); err != nil { + if err := fetchBalances(inv, gas.Hash, snList); err != nil { return err } printBalances(cmd, "\nStorage node balances:", snList) @@ -136,7 +122,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error { } proxyList := []accBalancePair{{scriptHash: h}} - if err := fetchBalances(inv, gasHash, proxyList); err != nil { + if err := fetchBalances(inv, gas.Hash, proxyList); err != nil { return err } printBalances(cmd, "\nProxy contract balance:", proxyList) @@ -168,7 +154,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error { alphaList[i].scriptHash = h } - if err := fetchBalances(inv, gasHash, alphaList); err != nil { + if err := fetchBalances(inv, gas.Hash, alphaList); err != nil { return err } printBalances(cmd, "\nAlphabet contracts balances:", alphaList) diff --git a/cmd/neofs-adm/internal/modules/morph/deploy.go b/cmd/neofs-adm/internal/modules/morph/deploy.go index fea258a14..1b5708aa5 100644 --- a/cmd/neofs-adm/internal/modules/morph/deploy.go +++ b/cmd/neofs-adm/internal/modules/morph/deploy.go @@ -7,10 +7,10 @@ import ( "strings" "github.com/nspcc-dev/neo-go/cli/cmdargs" - "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/management" "github.com/nspcc-dev/neo-go/pkg/services/rpcsrv/params" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/vm/emit" @@ -81,7 +81,7 @@ func deployContractCmd(cmd *cobra.Command, args []string) error { return fmt.Errorf("can't fetch NNS contract state: %w", err) } - callHash := c.nativeHash(nativenames.Management) + callHash := management.Hash method := deployMethodName zone, _ := cmd.Flags().GetString(customZoneFlag) domain := ctrName + "." + zone diff --git a/cmd/neofs-adm/internal/modules/morph/generate.go b/cmd/neofs-adm/internal/modules/morph/generate.go index be9b8db49..71224acdc 100644 --- a/cmd/neofs-adm/internal/modules/morph/generate.go +++ b/cmd/neofs-adm/internal/modules/morph/generate.go @@ -6,11 +6,11 @@ import ( "os" "path/filepath" - "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/gas" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/util" @@ -198,10 +198,8 @@ func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error return err } - gasHash := wCtx.nativeHash(nativenames.Gas) - bw := io.NewBufBinWriter() - emit.AppCall(bw.BinWriter, gasHash, "transfer", callflag.All, + emit.AppCall(bw.BinWriter, gas.Hash, "transfer", callflag.All, wCtx.CommitteeAcc.Contract.ScriptHash(), gasReceiver, int64(gasAmount), nil) emit.Opcodes(bw.BinWriter, opcode.ASSERT) if bw.Err != nil { diff --git a/cmd/neofs-adm/internal/modules/morph/initialize.go b/cmd/neofs-adm/internal/modules/morph/initialize.go index ce6d2bfe2..ab8fef1a4 100644 --- a/cmd/neofs-adm/internal/modules/morph/initialize.go +++ b/cmd/neofs-adm/internal/modules/morph/initialize.go @@ -43,7 +43,6 @@ type initializeContext struct { Contracts map[string]*contractState Command *cobra.Command ContractPath string - Natives map[string]util.Uint160 } func initializeSideChainCmd(cmd *cobra.Command, args []string) error { @@ -166,8 +165,7 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex } } - nativeHashes, err := getNativeHashes(c) - if err != nil { + if err := checkNotaryEnabled(c); err != nil { return nil, err } @@ -195,7 +193,6 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex Command: cmd, Contracts: make(map[string]*contractState), ContractPath: ctrPath, - Natives: nativeHashes, } if needContracts { @@ -208,10 +205,6 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex return initCtx, nil } -func (c *initializeContext) nativeHash(name string) util.Uint160 { - return c.Natives[name] -} - func openAlphabetWallets(v *viper.Viper, walletDir string) ([]*wallet.Wallet, error) { walletFiles, err := os.ReadDir(walletDir) if err != nil { @@ -450,10 +443,10 @@ func getWalletAccount(w *wallet.Wallet, typ string) (*wallet.Account, error) { return nil, fmt.Errorf("account for '%s' not found", typ) } -func getNativeHashes(c Client) (map[string]util.Uint160, error) { +func checkNotaryEnabled(c Client) error { ns, err := c.GetNativeContracts() if err != nil { - return nil, fmt.Errorf("can't get native contract hashes: %w", err) + return fmt.Errorf("can't get native contract hashes: %w", err) } notaryEnabled := false @@ -465,7 +458,7 @@ func getNativeHashes(c Client) (map[string]util.Uint160, error) { nativeHashes[ns[i].Manifest.Name] = ns[i].Hash } if !notaryEnabled { - return nil, errors.New("notary contract must be enabled") + return errors.New("notary contract must be enabled") } - return nativeHashes, nil + return nil } diff --git a/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go b/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go index 605d92deb..19e3dadeb 100644 --- a/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go +++ b/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go @@ -12,13 +12,13 @@ import ( "path/filepath" "strings" - "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/encoding/address" io2 "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/rpcclient" "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/management" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" @@ -128,7 +128,7 @@ func (c *initializeContext) deployNNS(method string) error { Scopes: transaction.CalledByEntry, } - invokeHash := c.nativeHash(nativenames.Management) + invokeHash := management.Hash if method == updateMethodName { invokeHash = nnsCs.Hash } @@ -157,7 +157,6 @@ func (c *initializeContext) deployNNS(method string) error { } func (c *initializeContext) updateContracts() error { - mgmtHash := c.nativeHash(nativenames.Management) alphaCs := c.getContract(alphabetContract) nnsCs, err := c.nnsContractState() @@ -243,7 +242,7 @@ func (c *initializeContext) updateContracts() error { return fmt.Errorf("can't sign manifest group: %v", err) } - invokeHash := mgmtHash + invokeHash := management.Hash if method == updateMethodName { invokeHash = ctrHash } @@ -296,7 +295,6 @@ func (c *initializeContext) updateContracts() error { } func (c *initializeContext) deployContracts() error { - mgmtHash := c.nativeHash(nativenames.Management) alphaCs := c.getContract(alphabetContract) var keysParam []interface{} @@ -325,7 +323,7 @@ func (c *initializeContext) deployContracts() error { return fmt.Errorf("could not create actor: %w", err) } - txHash, vub, err := act.SendCall(mgmtHash, deployMethodName, params...) + txHash, vub, err := act.SendCall(management.Hash, deployMethodName, params...) if err != nil { return fmt.Errorf("can't deploy alphabet #%d contract: %w", i, err) } @@ -348,7 +346,7 @@ func (c *initializeContext) deployContracts() error { } params := getContractDeployParameters(cs, c.getContractDeployData(ctrName, keysParam)) - res, err := c.CommitteeAct.MakeCall(mgmtHash, deployMethodName, params...) + res, err := c.CommitteeAct.MakeCall(management.Hash, deployMethodName, params...) if err != nil { return fmt.Errorf("can't deploy %s contract: %w", ctrName, err) } diff --git a/cmd/neofs-adm/internal/modules/morph/initialize_register.go b/cmd/neofs-adm/internal/modules/morph/initialize_register.go index 6b7e47d62..1bfda7b54 100644 --- a/cmd/neofs-adm/internal/modules/morph/initialize_register.go +++ b/cmd/neofs-adm/internal/modules/morph/initialize_register.go @@ -5,11 +5,11 @@ import ( "fmt" "github.com/nspcc-dev/neo-go/pkg/core/native" - "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/rpcclient" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/neo" "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/util" @@ -21,7 +21,7 @@ import ( const initialAlphabetNEOAmount = native.NEOTotalSupply func (c *initializeContext) registerCandidates() error { - neoHash := c.nativeHash(nativenames.Neo) + neoHash := neo.Hash cc, err := unwrap.Array(c.ReadOnlyInvoker.Call(neoHash, "getCandidates")) if err != nil { @@ -83,7 +83,7 @@ func (c *initializeContext) registerCandidates() error { } func (c *initializeContext) transferNEOToAlphabetContracts() error { - neoHash := c.nativeHash(nativenames.Neo) + neoHash := neo.Hash ok, err := c.transferNEOFinished(neoHash) if ok || err != nil { @@ -120,7 +120,7 @@ func (c *initializeContext) getCandidateRegisterPrice() (int64, error) { case *rpcclient.Client: return ct.GetCandidateRegisterPrice() default: - neoHash := c.nativeHash(nativenames.Neo) + neoHash := neo.Hash res, err := invokeFunction(c.Client, neoHash, "getRegisterPrice", nil, nil) if err != nil { return 0, err diff --git a/cmd/neofs-adm/internal/modules/morph/initialize_roles.go b/cmd/neofs-adm/internal/modules/morph/initialize_roles.go index 0ed924c65..88415012c 100644 --- a/cmd/neofs-adm/internal/modules/morph/initialize_roles.go +++ b/cmd/neofs-adm/internal/modules/morph/initialize_roles.go @@ -1,9 +1,9 @@ package morph import ( - "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/rolemgmt" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/vm/emit" ) @@ -16,17 +16,15 @@ func (c *initializeContext) setNotaryAndAlphabetNodes() error { return err } - designateHash := c.nativeHash(nativenames.Designation) - var pubs []interface{} for _, acc := range c.Accounts { pubs = append(pubs, acc.PrivateKey().PublicKey().Bytes()) } w := io.NewBufBinWriter() - emit.AppCall(w.BinWriter, designateHash, "designateAsRole", + emit.AppCall(w.BinWriter, rolemgmt.Hash, "designateAsRole", callflag.States|callflag.AllowNotify, int64(noderoles.P2PNotary), pubs) - emit.AppCall(w.BinWriter, designateHash, "designateAsRole", + emit.AppCall(w.BinWriter, rolemgmt.Hash, "designateAsRole", callflag.States|callflag.AllowNotify, int64(noderoles.NeoFSAlphabet), pubs) if err := c.sendCommitteeTx(w.Bytes(), false); err != nil { @@ -42,7 +40,6 @@ func (c *initializeContext) setRolesFinished() (bool, error) { return false, err } - h := c.nativeHash(nativenames.Designation) - pubs, err := getDesignatedByRole(c.ReadOnlyInvoker, h, noderoles.NeoFSAlphabet, height) + pubs, err := getDesignatedByRole(c.ReadOnlyInvoker, rolemgmt.Hash, noderoles.NeoFSAlphabet, height) return len(pubs) == len(c.Wallets), err } diff --git a/cmd/neofs-adm/internal/modules/morph/initialize_transfer.go b/cmd/neofs-adm/internal/modules/morph/initialize_transfer.go index 9a0c57b65..1f8e53416 100644 --- a/cmd/neofs-adm/internal/modules/morph/initialize_transfer.go +++ b/cmd/neofs-adm/internal/modules/morph/initialize_transfer.go @@ -4,10 +4,11 @@ import ( "fmt" "github.com/nspcc-dev/neo-go/pkg/core/native" - "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/rpcclient" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/gas" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/neo" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" scContext "github.com/nspcc-dev/neo-go/pkg/smartcontract/context" "github.com/nspcc-dev/neo-go/pkg/vm/emit" @@ -32,15 +33,12 @@ func (c *initializeContext) transferFunds() error { return err } - gasHash := c.nativeHash(nativenames.Gas) - neoHash := c.nativeHash(nativenames.Neo) - var transfers []rpcclient.TransferTarget for _, acc := range c.Accounts { to := acc.Contract.ScriptHash() transfers = append(transfers, rpcclient.TransferTarget{ - Token: gasHash, + Token: gas.Hash, Address: to, Amount: initialAlphabetGASAmount, }, @@ -50,12 +48,12 @@ func (c *initializeContext) transferFunds() error { // It is convenient to have all funds at the committee account. transfers = append(transfers, rpcclient.TransferTarget{ - Token: gasHash, + Token: gas.Hash, Address: c.CommitteeAcc.Contract.ScriptHash(), Amount: (gasInitialTotalSupply - initialAlphabetGASAmount*int64(len(c.Wallets))) / 2, }, rpcclient.TransferTarget{ - Token: neoHash, + Token: neo.Hash, Address: c.CommitteeAcc.Contract.ScriptHash(), Amount: native.NEOTotalSupply, }, @@ -80,10 +78,9 @@ func (c *initializeContext) transferFunds() error { } func (c *initializeContext) transferFundsFinished() (bool, error) { - gasHash := c.nativeHash(nativenames.Gas) acc := c.Accounts[0] - res, err := c.Client.NEP17BalanceOf(gasHash, acc.Contract.ScriptHash()) + res, err := c.Client.NEP17BalanceOf(gas.Hash, acc.Contract.ScriptHash()) return res > initialAlphabetGASAmount/2, err } @@ -147,16 +144,15 @@ func (c *initializeContext) multiSign(tx *transaction.Transaction, accType strin } func (c *initializeContext) transferGASToProxy() error { - gasHash := c.nativeHash(nativenames.Gas) proxyCs := c.getContract(proxyContract) - bal, err := c.Client.NEP17BalanceOf(gasHash, proxyCs.Hash) + bal, err := c.Client.NEP17BalanceOf(gas.Hash, proxyCs.Hash) if err != nil || bal > 0 { return err } tx, err := createNEP17MultiTransferTx(c.Client, c.CommitteeAcc, 0, []rpcclient.TransferTarget{{ - Token: gasHash, + Token: gas.Hash, Address: proxyCs.Hash, Amount: initialProxyGASAmount, }}, nil) diff --git a/cmd/neofs-adm/internal/modules/morph/notary.go b/cmd/neofs-adm/internal/modules/morph/notary.go index 6445b5ecb..5c8bc8c11 100644 --- a/cmd/neofs-adm/internal/modules/morph/notary.go +++ b/cmd/neofs-adm/internal/modules/morph/notary.go @@ -6,12 +6,13 @@ import ( "strconv" "github.com/nspcc-dev/neo-go/cli/input" - "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/gas" "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/notary" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -84,18 +85,8 @@ func depositNotary(cmd *cobra.Command, _ []string) error { return err } - nhs, err := getNativeHashes(c) - if err != nil { - return fmt.Errorf("can't get native contract hashes: %w", err) - } - - gasHash, ok := nhs[nativenames.Gas] - if !ok { - return fmt.Errorf("can't retrieve %s contract hash", nativenames.Gas) - } - notaryHash, ok := nhs[nativenames.Notary] - if !ok { - return fmt.Errorf("can't retrieve %s contract hash", nativenames.Notary) + if err := checkNotaryEnabled(c); err != nil { + return err } height, err := c.GetBlockCount() @@ -114,11 +105,11 @@ func depositNotary(cmd *cobra.Command, _ []string) error { return fmt.Errorf("could not create actor: %w", err) } - gas := nep17.New(act, gasHash) + gasActor := nep17.New(act, gas.Hash) - txHash, vub, err := gas.Transfer( + txHash, vub, err := gasActor.Transfer( accHash, - notaryHash, + notary.Hash, big.NewInt(int64(gasAmount)), []interface{}{nil, int64(height) + till}, ) diff --git a/cmd/neofs-adm/internal/modules/morph/policy.go b/cmd/neofs-adm/internal/modules/morph/policy.go index 86e095fc4..b00fa9c7d 100644 --- a/cmd/neofs-adm/internal/modules/morph/policy.go +++ b/cmd/neofs-adm/internal/modules/morph/policy.go @@ -5,8 +5,8 @@ import ( "strconv" "strings" - "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/policy" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/spf13/cobra" @@ -25,8 +25,6 @@ func setPolicyCmd(cmd *cobra.Command, args []string) error { return fmt.Errorf("can't to initialize context: %w", err) } - policyHash := wCtx.nativeHash(nativenames.Policy) - bw := io.NewBufBinWriter() for i := range args { kv := strings.SplitN(args[i], "=", 2) @@ -45,7 +43,7 @@ func setPolicyCmd(cmd *cobra.Command, args []string) error { return fmt.Errorf("can't parse parameter value '%s': %w", args[1], err) } - emit.AppCall(bw.BinWriter, policyHash, "set"+kv[0], callflag.All, int64(value)) + emit.AppCall(bw.BinWriter, policy.Hash, "set"+kv[0], callflag.All, int64(value)) } if err := wCtx.sendCommitteeTx(bw.Bytes(), false); err != nil {