hermez-node/test/til/txs.go
test/til/txs.goRaw
1package til
2
3import (
4 "crypto/ecdsa"
5 "fmt"
6 "math/big"
7 "strconv"
8 "strings"
9 "time"
10
11 ethCommon "github.com/ethereum/go-ethereum/common"
12 ethCrypto "github.com/ethereum/go-ethereum/crypto"
13 "github.com/hermeznetwork/hermez-node/common"
14 "github.com/hermeznetwork/hermez-node/log"
15 "github.com/iden3/go-iden3-crypto/babyjub"
16)
17
18// Context contains the data of the test
19type Context struct {
20 Instructions []instruction
21 userNames []string
22 Users map[string]*User
23 lastRegisteredTokenID common.TokenID
24 l1CreatedAccounts map[string]*Account
25
26 // rollupConstMaxL1UserTx Maximum L1-user transactions allowed to be queued in a batch
27 rollupConstMaxL1UserTx int
28
29 idx int
30 currBlock common.BlockData
31 currBatch common.BatchData
32 currBatchNum int
33 queues [][]L1Tx
34 toForgeNum int
35 openToForge int
36 currBatchTest struct {
37 l1CoordinatorTxs []L1Tx
38 l2Txs []L2Tx
39 }
40}
41
42// NewContext returns a new Context
43func NewContext(rollupConstMaxL1UserTx int) *Context {
44 currBatchNum := 1 // The protocol defines the first batchNum to be 1
45 return &Context{
46 Users: make(map[string]*User),
47 l1CreatedAccounts: make(map[string]*Account),
48 lastRegisteredTokenID: 0,
49
50 rollupConstMaxL1UserTx: rollupConstMaxL1UserTx,
51 idx: common.UserThreshold,
52 // We use some placeholder values for StateRoot and ExitTree
53 // because these values will never be nil
54 currBatch: common.BatchData{Batch: common.Batch{
55 BatchNum: common.BatchNum(currBatchNum),
56 StateRoot: big.NewInt(0), ExitRoot: big.NewInt(0)}},
57 currBatchNum: currBatchNum,
58 // start with 2 queues, one for toForge, and the other for openToForge
59 queues: make([][]L1Tx, 2),
60 toForgeNum: 0,
61 openToForge: 1,
62 }
63}
64
65// Account contains the data related to the account for a specific TokenID of a User
66type Account struct {
67 Idx common.Idx
68 Nonce common.Nonce
69}
70
71// User contains the data related to a testing user
72type User struct {
73 BJJ *babyjub.PrivateKey
74 Addr ethCommon.Address
75 Accounts map[common.TokenID]*Account
76}
77
78// L1Tx is the data structure used internally for transaction test generation,
79// which contains a common.L1Tx data plus some intermediate data for the
80// transaction generation.
81type L1Tx struct {
82 lineNum int
83 fromIdxName string
84 toIdxName string
85
86 L1Tx common.L1Tx
87}
88
89// L2Tx is the data structure used internally for transaction test generation,
90// which contains a common.L2Tx data plus some intermediate data for the
91// transaction generation.
92type L2Tx struct {
93 lineNum int
94 fromIdxName string
95 toIdxName string
96 tokenID common.TokenID
97 L2Tx common.L2Tx
98}
99
100// GenerateBlocks returns an array of BlockData for a given set. It uses the
101// users (keys & nonces) of the Context.
102func (tc *Context) GenerateBlocks(set string) ([]common.BlockData, error) {
103 parser := newParser(strings.NewReader(set))
104 parsedSet, err := parser.parse()
105 if err != nil {
106 return nil, err
107 }
108 if parsedSet.typ != setTypeBlockchain {
109 return nil, fmt.Errorf("Expected set type: %s, found: %s", setTypeBlockchain, parsedSet.typ)
110 }
111
112 tc.Instructions = parsedSet.instructions
113 tc.userNames = parsedSet.users
114
115 tc.generateKeys(tc.userNames)
116
117 var blocks []common.BlockData
118 for _, inst := range parsedSet.instructions {
119 switch inst.typ {
120 case txTypeCreateAccountDepositCoordinator: // tx source: L1CoordinatorTx
121 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
122 log.Error(err)
123 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
124 }
125 tx := common.L1Tx{
126 FromEthAddr: tc.Users[inst.from].Addr,
127 FromBJJ: tc.Users[inst.from].BJJ.Public(),
128 TokenID: inst.tokenID,
129 Amount: big.NewInt(0),
130 LoadAmount: big.NewInt(int64(inst.loadAmount)),
131 Type: common.TxTypeCreateAccountDeposit, // as txTypeCreateAccountDepositCoordinator is not valid oustide Til package
132 }
133 testTx := L1Tx{
134 lineNum: inst.lineNum,
135 fromIdxName: inst.from,
136 L1Tx: tx,
137 }
138 tc.currBatchTest.l1CoordinatorTxs = append(tc.currBatchTest.l1CoordinatorTxs, testTx)
139 case common.TxTypeCreateAccountDeposit, common.TxTypeCreateAccountDepositTransfer: // tx source: L1UserTx
140 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
141 log.Error(err)
142 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
143 }
144 tx := common.L1Tx{
145 FromEthAddr: tc.Users[inst.from].Addr,
146 FromBJJ: tc.Users[inst.from].BJJ.Public(),
147 TokenID: inst.tokenID,
148 Amount: big.NewInt(0),
149 LoadAmount: big.NewInt(int64(inst.loadAmount)),
150 Type: inst.typ,
151 }
152 if inst.typ == common.TxTypeCreateAccountDepositTransfer {
153 tx.Amount = big.NewInt(int64(inst.amount))
154 }
155 testTx := L1Tx{
156 lineNum: inst.lineNum,
157 fromIdxName: inst.from,
158 toIdxName: inst.to,
159 L1Tx: tx,
160 }
161 if err := tc.addToL1Queue(testTx); err != nil {
162 return nil, err
163 }
164 case common.TxTypeDeposit, common.TxTypeDepositTransfer: // tx source: L1UserTx
165 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
166 log.Error(err)
167 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
168 }
169 if err := tc.checkIfAccountExists(inst.from, inst); err != nil {
170 log.Error(err)
171 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
172 }
173 tx := common.L1Tx{
174 TokenID: inst.tokenID,
175 Amount: big.NewInt(0),
176 LoadAmount: big.NewInt(int64(inst.loadAmount)),
177 Type: inst.typ,
178 }
179 if inst.typ == common.TxTypeDepositTransfer {
180 tx.Amount = big.NewInt(int64(inst.amount))
181 }
182 testTx := L1Tx{
183 lineNum: inst.lineNum,
184 fromIdxName: inst.from,
185 toIdxName: inst.to,
186 L1Tx: tx,
187 }
188 if err := tc.addToL1Queue(testTx); err != nil {
189 return nil, err
190 }
191 case common.TxTypeTransfer: // L2Tx
192 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
193 log.Error(err)
194 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
195 }
196 tx := common.L2Tx{
197 Amount: big.NewInt(int64(inst.amount)),
198 Fee: common.FeeSelector(inst.fee),
199 Type: common.TxTypeTransfer,
200 }
201 tx.BatchNum = common.BatchNum(tc.currBatchNum) // when converted to PoolL2Tx BatchNum parameter is lost
202 testTx := L2Tx{
203 lineNum: inst.lineNum,
204 fromIdxName: inst.from,
205 toIdxName: inst.to,
206 tokenID: inst.tokenID,
207 L2Tx: tx,
208 }
209 tc.currBatchTest.l2Txs = append(tc.currBatchTest.l2Txs, testTx)
210 case common.TxTypeExit: // tx source: L2Tx
211 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
212 log.Error(err)
213 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
214 }
215 tx := common.L2Tx{
216 ToIdx: common.Idx(1), // as is an Exit
217 Amount: big.NewInt(int64(inst.amount)),
218 Type: common.TxTypeExit,
219 }
220 tx.BatchNum = common.BatchNum(tc.currBatchNum) // when converted to PoolL2Tx BatchNum parameter is lost
221 testTx := L2Tx{
222 lineNum: inst.lineNum,
223 fromIdxName: inst.from,
224 toIdxName: inst.to,
225 tokenID: inst.tokenID,
226 L2Tx: tx,
227 }
228 tc.currBatchTest.l2Txs = append(tc.currBatchTest.l2Txs, testTx)
229 case common.TxTypeForceExit: // tx source: L1UserTx
230 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
231 log.Error(err)
232 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
233 }
234 tx := common.L1Tx{
235 ToIdx: common.Idx(1), // as is an Exit
236 TokenID: inst.tokenID,
237 Amount: big.NewInt(int64(inst.amount)),
238 LoadAmount: big.NewInt(0),
239 Type: common.TxTypeExit,
240 }
241 testTx := L1Tx{
242 lineNum: inst.lineNum,
243 fromIdxName: inst.from,
244 toIdxName: inst.to,
245 L1Tx: tx,
246 }
247 if err := tc.addToL1Queue(testTx); err != nil {
248 return nil, err
249 }
250 case typeNewBatch:
251 if err = tc.calculateIdxForL1Txs(true, tc.currBatchTest.l1CoordinatorTxs); err != nil {
252 return nil, err
253 }
254 if err = tc.setIdxs(); err != nil {
255 log.Error(err)
256 return nil, err
257 }
258 case typeNewBatchL1:
259 // for each L1UserTx of the queues[ToForgeNum], calculate the Idx
260 if err = tc.calculateIdxForL1Txs(false, tc.queues[tc.toForgeNum]); err != nil {
261 return nil, err
262 }
263 if err = tc.calculateIdxForL1Txs(true, tc.currBatchTest.l1CoordinatorTxs); err != nil {
264 return nil, err
265 }
266 if err = tc.setIdxs(); err != nil {
267 log.Error(err)
268 return nil, err
269 }
270 // advance batch
271 tc.toForgeNum++
272 if tc.toForgeNum == tc.openToForge {
273 tc.openToForge++
274 newQueue := []L1Tx{}
275 tc.queues = append(tc.queues, newQueue)
276 }
277 case typeNewBlock:
278 blocks = append(blocks, tc.currBlock)
279 tc.currBlock = common.BlockData{}
280 case typeAddToken:
281 newToken := common.Token{
282 TokenID: inst.tokenID,
283 EthBlockNum: int64(len(blocks)),
284 }
285 if inst.tokenID != tc.lastRegisteredTokenID+1 {
286 return nil, fmt.Errorf("Line %d: AddToken TokenID should be sequential, expected TokenID: %d, defined TokenID: %d", inst.lineNum, tc.lastRegisteredTokenID+1, inst.tokenID)
287 }
288 tc.lastRegisteredTokenID++
289 tc.currBlock.AddedTokens = append(tc.currBlock.AddedTokens, newToken)
290 default:
291 return nil, fmt.Errorf("Line %d: Unexpected type: %s", inst.lineNum, inst.typ)
292 }
293 }
294
295 return blocks, nil
296}
297
298// calculateIdxsForL1Txs calculates new Idx for new created accounts. If
299// 'isCoordinatorTxs==true', adds the tx to tc.currBatch.L1CoordinatorTxs.
300func (tc *Context) calculateIdxForL1Txs(isCoordinatorTxs bool, txs []L1Tx) error {
301 // for each batch.L1CoordinatorTxs of the queues[ToForgeNum], calculate the Idx
302 for i := 0; i < len(txs); i++ {
303 tx := txs[i]
304 if tx.L1Tx.Type == common.TxTypeCreateAccountDeposit || tx.L1Tx.Type == common.TxTypeCreateAccountDepositTransfer {
305 if tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID] != nil { // if account already exists, return error
306 return fmt.Errorf("Can not create same account twice (same User & same TokenID) (this is a design property of Til)")
307 }
308 tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID] = &Account{
309 Idx: common.Idx(tc.idx),
310 Nonce: common.Nonce(0),
311 }
312 tc.l1CreatedAccounts[idxTokenIDToString(tx.fromIdxName, tx.L1Tx.TokenID)] = tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID]
313 tc.idx++
314 }
315 if isCoordinatorTxs {
316 tc.currBatch.L1CoordinatorTxs = append(tc.currBatch.L1CoordinatorTxs, tx.L1Tx)
317 }
318 }
319 return nil
320}
321
322// setIdxs sets the Idxs to the transactions of the tc.currBatch
323func (tc *Context) setIdxs() error {
324 // once Idxs are calculated, update transactions to use the new Idxs
325 for i := 0; i < len(tc.currBatchTest.l2Txs); i++ {
326 testTx := &tc.currBatchTest.l2Txs[i]
327
328 if tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID] == nil {
329 return fmt.Errorf("Line %d: %s from User %s for TokenID %d while account not created yet", testTx.lineNum, testTx.L2Tx.Type, testTx.fromIdxName, testTx.tokenID)
330 }
331 if testTx.L2Tx.Type == common.TxTypeTransfer {
332 if _, ok := tc.l1CreatedAccounts[idxTokenIDToString(testTx.toIdxName, testTx.tokenID)]; !ok {
333 return fmt.Errorf("Line %d: Can not create Transfer for a non existing account. Batch %d, ToIdx name: %s, TokenID: %d", testTx.lineNum, tc.currBatchNum, testTx.toIdxName, testTx.tokenID)
334 }
335 }
336 tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID].Nonce++
337 testTx.L2Tx.Nonce = tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID].Nonce
338
339 // set real Idx
340 testTx.L2Tx.FromIdx = tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID].Idx
341 if testTx.L2Tx.Type == common.TxTypeTransfer {
342 testTx.L2Tx.ToIdx = tc.Users[testTx.toIdxName].Accounts[testTx.tokenID].Idx
343 }
344 // in case Type==Exit, ToIdx=1, already set at the
345 // GenerateBlocks main switch inside TxTypeExit case
346
347 nTx, err := common.NewL2Tx(&testTx.L2Tx)
348 if err != nil {
349 return fmt.Errorf("Line %d: %s", testTx.lineNum, err.Error())
350 }
351 testTx.L2Tx = *nTx
352
353 tc.currBatch.L2Txs = append(tc.currBatch.L2Txs, testTx.L2Tx)
354 }
355
356 tc.currBlock.Batches = append(tc.currBlock.Batches, tc.currBatch)
357 tc.currBatchNum++
358 tc.currBatch = common.BatchData{Batch: tc.currBatch.Batch}
359 tc.currBatch.Batch.BatchNum = common.BatchNum(tc.currBatchNum)
360 tc.currBatchTest.l1CoordinatorTxs = nil
361 tc.currBatchTest.l2Txs = nil
362 return nil
363}
364
365// addToL1Queue adds the L1Tx into the queue that is open and has space
366func (tc *Context) addToL1Queue(tx L1Tx) error {
367 if len(tc.queues[tc.openToForge]) >= tc.rollupConstMaxL1UserTx {
368 // if current OpenToForge queue reached its Max, move into a
369 // new queue
370 tc.openToForge++
371 newQueue := []L1Tx{}
372 tc.queues = append(tc.queues, newQueue)
373 }
374 // Fill L1UserTx specific parameters
375 tx.L1Tx.UserOrigin = true
376 toForgeL1TxsNum := int64(tc.openToForge)
377 tx.L1Tx.ToForgeL1TxsNum = &toForgeL1TxsNum
378
379 // When an L1UserTx is generated, all idxs must be available (except when idx == 0 or idx == 1)
380 if tx.L1Tx.Type != common.TxTypeCreateAccountDeposit && tx.L1Tx.Type != common.TxTypeCreateAccountDepositTransfer {
381 tx.L1Tx.FromIdx = tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID].Idx
382 }
383 tx.L1Tx.FromEthAddr = tc.Users[tx.fromIdxName].Addr
384 tx.L1Tx.FromBJJ = tc.Users[tx.fromIdxName].BJJ.Public()
385 if tx.toIdxName == "" {
386 tx.L1Tx.ToIdx = common.Idx(0)
387 } else {
388 account, ok := tc.Users[tx.toIdxName].Accounts[tx.L1Tx.TokenID]
389 if !ok {
390 return fmt.Errorf("Line %d: Transfer to User: %s, for TokenID: %d, "+
391 "while account not created yet", tx.lineNum, tx.toIdxName, tx.L1Tx.TokenID)
392 }
393 tx.L1Tx.ToIdx = account.Idx
394 }
395 if tx.L1Tx.Type == common.TxTypeExit {
396 tx.L1Tx.ToIdx = common.Idx(1)
397 }
398 nTx, err := common.NewL1Tx(&tx.L1Tx)
399 if err != nil {
400 return fmt.Errorf("Line %d: %s", tx.lineNum, err.Error())
401 }
402 tx.L1Tx = *nTx
403
404 tc.queues[tc.openToForge] = append(tc.queues[tc.openToForge], tx)
405 tc.currBlock.L1UserTxs = append(tc.currBlock.L1UserTxs, tx.L1Tx)
406
407 return nil
408}
409
410func (tc *Context) checkIfAccountExists(tf string, inst instruction) error {
411 if tc.Users[tf].Accounts[inst.tokenID] == nil {
412 return fmt.Errorf("%s at User: %s, for TokenID: %d, while account not created yet", inst.typ, tf, inst.tokenID)
413 }
414 return nil
415}
416func (tc *Context) checkIfTokenIsRegistered(inst instruction) error {
417 if inst.tokenID > tc.lastRegisteredTokenID {
418 return fmt.Errorf("Can not process %s: TokenID %d not registered, last registered TokenID: %d", inst.typ, inst.tokenID, tc.lastRegisteredTokenID)
419 }
420 return nil
421}
422
423// GeneratePoolL2Txs returns an array of common.PoolL2Tx from a given set. It
424// uses the users (keys) of the Context.
425func (tc *Context) GeneratePoolL2Txs(set string) ([]common.PoolL2Tx, error) {
426 parser := newParser(strings.NewReader(set))
427 parsedSet, err := parser.parse()
428 if err != nil {
429 return nil, err
430 }
431 if parsedSet.typ != setTypePoolL2 {
432 return nil, fmt.Errorf("Expected set type: %s, found: %s", setTypePoolL2, parsedSet.typ)
433 }
434
435 tc.Instructions = parsedSet.instructions
436 tc.userNames = parsedSet.users
437
438 tc.generateKeys(tc.userNames)
439
440 txs := []common.PoolL2Tx{}
441 for _, inst := range tc.Instructions {
442 switch inst.typ {
443 case common.TxTypeTransfer, common.TxTypeTransferToEthAddr, common.TxTypeTransferToBJJ:
444 if err := tc.checkIfAccountExists(inst.from, inst); err != nil {
445 log.Error(err)
446 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
447 }
448 if inst.typ == common.TxTypeTransfer {
449 // if TxTypeTransfer, need to exist the ToIdx account
450 if err := tc.checkIfAccountExists(inst.to, inst); err != nil {
451 log.Error(err)
452 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
453 }
454 }
455 tc.Users[inst.from].Accounts[inst.tokenID].Nonce++
456 // if account of receiver does not exist, don't use
457 // ToIdx, and use only ToEthAddr & ToBJJ
458 tx := common.PoolL2Tx{
459 FromIdx: tc.Users[inst.from].Accounts[inst.tokenID].Idx,
460 TokenID: inst.tokenID,
461 Amount: big.NewInt(int64(inst.amount)),
462 Fee: common.FeeSelector(inst.fee),
463 Nonce: tc.Users[inst.from].Accounts[inst.tokenID].Nonce,
464 State: common.PoolL2TxStatePending,
465 Timestamp: time.Now(),
466 RqToEthAddr: common.EmptyAddr,
467 RqToBJJ: nil,
468 Type: inst.typ,
469 }
470 if tx.Type == common.TxTypeTransfer {
471 tx.ToIdx = tc.Users[inst.to].Accounts[inst.tokenID].Idx
472 tx.ToEthAddr = tc.Users[inst.to].Addr
473 tx.ToBJJ = tc.Users[inst.to].BJJ.Public()
474 } else if tx.Type == common.TxTypeTransferToEthAddr {
475 tx.ToIdx = common.Idx(0)
476 tx.ToEthAddr = tc.Users[inst.to].Addr
477 } else if tx.Type == common.TxTypeTransferToBJJ {
478 tx.ToIdx = common.Idx(0)
479 tx.ToEthAddr = common.FFAddr
480 tx.ToBJJ = tc.Users[inst.to].BJJ.Public()
481 }
482 nTx, err := common.NewPoolL2Tx(&tx)
483 if err != nil {
484 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
485 }
486 tx = *nTx
487 // perform signature and set it to tx.Signature
488 toSign, err := tx.HashToSign()
489 if err != nil {
490 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
491 }
492 sig := tc.Users[inst.from].BJJ.SignPoseidon(toSign)
493 tx.Signature = sig.Compress()
494
495 txs = append(txs, tx)
496 case common.TxTypeExit:
497 tc.Users[inst.from].Accounts[inst.tokenID].Nonce++
498 tx := common.PoolL2Tx{
499 FromIdx: tc.Users[inst.from].Accounts[inst.tokenID].Idx,
500 ToIdx: common.Idx(1), // as is an Exit
501 TokenID: inst.tokenID,
502 Amount: big.NewInt(int64(inst.amount)),
503 Nonce: tc.Users[inst.from].Accounts[inst.tokenID].Nonce,
504 Type: common.TxTypeExit,
505 }
506 nTx, err := common.NewPoolL2Tx(&tx)
507 if err != nil {
508 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
509 }
510 tx = *nTx
511 // perform signature and set it to tx.Signature
512 toSign, err := tx.HashToSign()
513 if err != nil {
514 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
515 }
516 sig := tc.Users[inst.from].BJJ.SignPoseidon(toSign)
517 tx.Signature = sig.Compress()
518 txs = append(txs, tx)
519 default:
520 return nil, fmt.Errorf("Line %d: instruction type unrecognized: %s", inst.lineNum, inst.typ)
521 }
522 }
523
524 return txs, nil
525}
526
527// generateKeys generates BabyJubJub & Address keys for the given list of user
528// names in a deterministic way. This means, that for the same given
529// 'userNames' in a certain order, the keys will be always the same.
530func (tc *Context) generateKeys(userNames []string) {
531 for i := 1; i < len(userNames)+1; i++ {
532 if _, ok := tc.Users[userNames[i-1]]; ok {
533 // account already created
534 continue
535 }
536 // babyjubjub key
537 var sk babyjub.PrivateKey
538 copy(sk[:], []byte(strconv.Itoa(i))) // only for testing
539
540 // eth address
541 var key ecdsa.PrivateKey
542 key.D = big.NewInt(int64(i)) // only for testing
543 key.PublicKey.X, key.PublicKey.Y = ethCrypto.S256().ScalarBaseMult(key.D.Bytes())
544 key.Curve = ethCrypto.S256()
545 addr := ethCrypto.PubkeyToAddress(key.PublicKey)
546
547 u := User{
548 BJJ: &sk,
549 Addr: addr,
550 Accounts: make(map[common.TokenID]*Account),
551 }
552 tc.Users[userNames[i-1]] = &u
553 }
554}