github.com/dim4egster/coreth@v0.10.2/core/vm/contracts_stateful_test.go (about)

     1  // (c) 2019-2020, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package vm
     5  
     6  import (
     7  	"math/big"
     8  	"testing"
     9  
    10  	"github.com/dim4egster/coreth/core/rawdb"
    11  	"github.com/dim4egster/coreth/core/state"
    12  	"github.com/dim4egster/coreth/params"
    13  	"github.com/dim4egster/coreth/vmerrs"
    14  	"github.com/ethereum/go-ethereum/common"
    15  	"github.com/ethereum/go-ethereum/log"
    16  	"github.com/stretchr/testify/assert"
    17  )
    18  
    19  func TestPrecompiledContractSpendsGas(t *testing.T) {
    20  	unwrapped := &sha256hash{}
    21  
    22  	input := []byte{'J', 'E', 'T', 'S'}
    23  	requiredGas := unwrapped.RequiredGas(input)
    24  	_, remainingGas, err := RunPrecompiledContract(unwrapped, input, requiredGas)
    25  	if err != nil {
    26  		t.Fatalf("Unexpectedly failed to run precompiled contract: %s", err)
    27  	}
    28  
    29  	if remainingGas != 0 {
    30  		t.Fatalf("Found more remaining gas than expected: %d", remainingGas)
    31  	}
    32  }
    33  
    34  // CanTransfer checks whether there are enough funds in the address' account to make a transfer.
    35  // This does not take the necessary gas in to account to make the transfer valid.
    36  func CanTransfer(db StateDB, addr common.Address, amount *big.Int) bool {
    37  	return db.GetBalance(addr).Cmp(amount) >= 0
    38  }
    39  
    40  func CanTransferMC(db StateDB, addr common.Address, to common.Address, coinID common.Hash, amount *big.Int) bool {
    41  	log.Info("CanTransferMC", "address", addr, "to", to, "coinID", coinID, "amount", amount)
    42  	return db.GetBalanceMultiCoin(addr, coinID).Cmp(amount) >= 0
    43  }
    44  
    45  // Transfer subtracts amount from sender and adds amount to recipient using the given Db
    46  func Transfer(db StateDB, sender, recipient common.Address, amount *big.Int) {
    47  	db.SubBalance(sender, amount)
    48  	db.AddBalance(recipient, amount)
    49  }
    50  
    51  // Transfer subtracts amount from sender and adds amount to recipient using the given Db
    52  func TransferMultiCoin(db StateDB, sender, recipient common.Address, coinID common.Hash, amount *big.Int) {
    53  	db.SubBalanceMultiCoin(sender, coinID, amount)
    54  	db.AddBalanceMultiCoin(recipient, coinID, amount)
    55  }
    56  
    57  func TestPackNativeAssetCallInput(t *testing.T) {
    58  	addr := common.BytesToAddress([]byte("hello"))
    59  	assetID := common.BytesToHash([]byte("ScoobyCoin"))
    60  	assetAmount := big.NewInt(50)
    61  	callData := []byte{1, 2, 3, 4, 5, 6, 7, 8}
    62  
    63  	input := PackNativeAssetCallInput(addr, assetID, assetAmount, callData)
    64  
    65  	unpackedAddr, unpackedAssetID, unpackedAssetAmount, unpackedCallData, err := UnpackNativeAssetCallInput(input)
    66  	assert.NoError(t, err)
    67  	assert.Equal(t, addr, unpackedAddr, "address")
    68  	assert.Equal(t, assetID, unpackedAssetID, "assetID")
    69  	assert.Equal(t, assetAmount, unpackedAssetAmount, "assetAmount")
    70  	assert.Equal(t, callData, unpackedCallData, "callData")
    71  }
    72  
    73  func TestStatefulPrecompile(t *testing.T) {
    74  	vmCtx := BlockContext{
    75  		BlockNumber:       big.NewInt(0),
    76  		Time:              big.NewInt(0),
    77  		CanTransfer:       CanTransfer,
    78  		CanTransferMC:     CanTransferMC,
    79  		Transfer:          Transfer,
    80  		TransferMultiCoin: TransferMultiCoin,
    81  	}
    82  
    83  	type statefulContractTest struct {
    84  		setupStateDB         func() StateDB
    85  		from                 common.Address
    86  		precompileAddr       common.Address
    87  		input                []byte
    88  		value                *big.Int
    89  		gasInput             uint64
    90  		expectedGasRemaining uint64
    91  		expectedErr          error
    92  		expectedResult       []byte
    93  		name                 string
    94  		stateDBCheck         func(*testing.T, StateDB)
    95  	}
    96  
    97  	userAddr1 := common.BytesToAddress([]byte("user1"))
    98  	userAddr2 := common.BytesToAddress([]byte("user2"))
    99  	assetID := common.BytesToHash([]byte("ScoobyCoin"))
   100  	zeroBytes := make([]byte, 32)
   101  	bigHundred := big.NewInt(100)
   102  	oneHundredBytes := make([]byte, 32)
   103  	big0.FillBytes(zeroBytes)
   104  	bigFifty := big.NewInt(50)
   105  	fiftyBytes := make([]byte, 32)
   106  	bigFifty.FillBytes(fiftyBytes)
   107  	bigHundred.FillBytes(oneHundredBytes)
   108  
   109  	tests := []statefulContractTest{
   110  		{
   111  			setupStateDB: func() StateDB {
   112  				statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
   113  				if err != nil {
   114  					t.Fatal(err)
   115  				}
   116  				// Create account
   117  				statedb.CreateAccount(userAddr1)
   118  				// Set balance to pay for gas fee
   119  				statedb.SetBalance(userAddr1, bigHundred)
   120  				// Set MultiCoin balance
   121  				statedb.Finalise(true)
   122  				return statedb
   123  			},
   124  			from:                 userAddr1,
   125  			precompileAddr:       NativeAssetBalanceAddr,
   126  			input:                PackNativeAssetBalanceInput(userAddr1, assetID),
   127  			value:                big0,
   128  			gasInput:             params.AssetBalanceApricot,
   129  			expectedGasRemaining: 0,
   130  			expectedErr:          nil,
   131  			expectedResult:       zeroBytes,
   132  			name:                 "native asset balance: uninitialized multicoin balance returns 0",
   133  		},
   134  		{
   135  			setupStateDB: func() StateDB {
   136  				statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
   137  				if err != nil {
   138  					t.Fatal(err)
   139  				}
   140  				// Create account
   141  				statedb.CreateAccount(userAddr1)
   142  				// Set balance to pay for gas fee
   143  				statedb.SetBalance(userAddr1, bigHundred)
   144  				// Initialize multicoin balance and set it back to 0
   145  				statedb.AddBalanceMultiCoin(userAddr1, assetID, bigHundred)
   146  				statedb.SubBalanceMultiCoin(userAddr1, assetID, bigHundred)
   147  				statedb.Finalise(true)
   148  				return statedb
   149  			},
   150  			from:                 userAddr1,
   151  			precompileAddr:       NativeAssetBalanceAddr,
   152  			input:                PackNativeAssetBalanceInput(userAddr1, assetID),
   153  			value:                big0,
   154  			gasInput:             params.AssetBalanceApricot,
   155  			expectedGasRemaining: 0,
   156  			expectedErr:          nil,
   157  			expectedResult:       zeroBytes,
   158  			name:                 "native asset balance: initialized multicoin balance returns 0",
   159  		},
   160  		{
   161  			setupStateDB: func() StateDB {
   162  				statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
   163  				if err != nil {
   164  					t.Fatal(err)
   165  				}
   166  				// Create account
   167  				statedb.CreateAccount(userAddr1)
   168  				// Set balance to pay for gas fee
   169  				statedb.SetBalance(userAddr1, bigHundred)
   170  				// Initialize multicoin balance to 100
   171  				statedb.AddBalanceMultiCoin(userAddr1, assetID, bigHundred)
   172  				statedb.Finalise(true)
   173  				return statedb
   174  			},
   175  			from:                 userAddr1,
   176  			precompileAddr:       NativeAssetBalanceAddr,
   177  			input:                PackNativeAssetBalanceInput(userAddr1, assetID),
   178  			value:                big0,
   179  			gasInput:             params.AssetBalanceApricot,
   180  			expectedGasRemaining: 0,
   181  			expectedErr:          nil,
   182  			expectedResult:       oneHundredBytes,
   183  			name:                 "native asset balance: returns correct non-zero multicoin balance",
   184  		},
   185  		{
   186  			setupStateDB: func() StateDB {
   187  				statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
   188  				if err != nil {
   189  					t.Fatal(err)
   190  				}
   191  				return statedb
   192  			},
   193  			from:                 userAddr1,
   194  			precompileAddr:       NativeAssetBalanceAddr,
   195  			input:                nil,
   196  			value:                big0,
   197  			gasInput:             params.AssetBalanceApricot,
   198  			expectedGasRemaining: 0,
   199  			expectedErr:          vmerrs.ErrExecutionReverted,
   200  			expectedResult:       nil,
   201  			name:                 "native asset balance: invalid input data reverts",
   202  		},
   203  		{
   204  			setupStateDB: func() StateDB {
   205  				statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
   206  				if err != nil {
   207  					t.Fatal(err)
   208  				}
   209  				return statedb
   210  			},
   211  			from:                 userAddr1,
   212  			precompileAddr:       NativeAssetBalanceAddr,
   213  			input:                PackNativeAssetBalanceInput(userAddr1, assetID),
   214  			value:                big0,
   215  			gasInput:             params.AssetBalanceApricot - 1,
   216  			expectedGasRemaining: 0,
   217  			expectedErr:          vmerrs.ErrOutOfGas,
   218  			expectedResult:       nil,
   219  			name:                 "native asset balance: insufficient gas errors",
   220  		},
   221  		{
   222  			setupStateDB: func() StateDB {
   223  				statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
   224  				if err != nil {
   225  					t.Fatal(err)
   226  				}
   227  				return statedb
   228  			},
   229  			from:                 userAddr1,
   230  			precompileAddr:       NativeAssetBalanceAddr,
   231  			input:                PackNativeAssetBalanceInput(userAddr1, assetID),
   232  			value:                bigHundred,
   233  			gasInput:             params.AssetBalanceApricot,
   234  			expectedGasRemaining: params.AssetBalanceApricot,
   235  			expectedErr:          vmerrs.ErrInsufficientBalance,
   236  			expectedResult:       nil,
   237  			name:                 "native asset balance: non-zero value with insufficient funds reverts before running pre-compile",
   238  		},
   239  		{
   240  			setupStateDB: func() StateDB {
   241  				statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
   242  				if err != nil {
   243  					t.Fatal(err)
   244  				}
   245  				statedb.SetBalance(userAddr1, bigHundred)
   246  				statedb.SetBalanceMultiCoin(userAddr1, assetID, bigHundred)
   247  				statedb.Finalise(true)
   248  				return statedb
   249  			},
   250  			from:                 userAddr1,
   251  			precompileAddr:       NativeAssetCallAddr,
   252  			input:                PackNativeAssetCallInput(userAddr2, assetID, big.NewInt(50), nil),
   253  			value:                big0,
   254  			gasInput:             params.AssetCallApricot + params.CallNewAccountGas,
   255  			expectedGasRemaining: 0,
   256  			expectedErr:          nil,
   257  			expectedResult:       nil,
   258  			name:                 "native asset call: multicoin transfer",
   259  			stateDBCheck: func(t *testing.T, stateDB StateDB) {
   260  				user1Balance := stateDB.GetBalance(userAddr1)
   261  				user2Balance := stateDB.GetBalance(userAddr2)
   262  				user1AssetBalance := stateDB.GetBalanceMultiCoin(userAddr1, assetID)
   263  				user2AssetBalance := stateDB.GetBalanceMultiCoin(userAddr2, assetID)
   264  
   265  				expectedBalance := big.NewInt(50)
   266  				assert.Equal(t, bigHundred, user1Balance, "user 1 balance")
   267  				assert.Equal(t, big0, user2Balance, "user 2 balance")
   268  				assert.Equal(t, expectedBalance, user1AssetBalance, "user 1 asset balance")
   269  				assert.Equal(t, expectedBalance, user2AssetBalance, "user 2 asset balance")
   270  			},
   271  		},
   272  		{
   273  			setupStateDB: func() StateDB {
   274  				statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
   275  				if err != nil {
   276  					t.Fatal(err)
   277  				}
   278  				statedb.SetBalance(userAddr1, bigHundred)
   279  				statedb.SetBalanceMultiCoin(userAddr1, assetID, bigHundred)
   280  				statedb.Finalise(true)
   281  				return statedb
   282  			},
   283  			from:                 userAddr1,
   284  			precompileAddr:       NativeAssetCallAddr,
   285  			input:                PackNativeAssetCallInput(userAddr2, assetID, big.NewInt(50), nil),
   286  			value:                big.NewInt(49),
   287  			gasInput:             params.AssetCallApricot + params.CallNewAccountGas,
   288  			expectedGasRemaining: 0,
   289  			expectedErr:          nil,
   290  			expectedResult:       nil,
   291  			name:                 "native asset call: multicoin transfer with non-zero value",
   292  			stateDBCheck: func(t *testing.T, stateDB StateDB) {
   293  				user1Balance := stateDB.GetBalance(userAddr1)
   294  				user2Balance := stateDB.GetBalance(userAddr2)
   295  				nativeAssetCallAddrBalance := stateDB.GetBalance(NativeAssetCallAddr)
   296  				user1AssetBalance := stateDB.GetBalanceMultiCoin(userAddr1, assetID)
   297  				user2AssetBalance := stateDB.GetBalanceMultiCoin(userAddr2, assetID)
   298  				expectedBalance := big.NewInt(50)
   299  
   300  				assert.Equal(t, big.NewInt(51), user1Balance, "user 1 balance")
   301  				assert.Equal(t, big0, user2Balance, "user 2 balance")
   302  				assert.Equal(t, big.NewInt(49), nativeAssetCallAddrBalance, "native asset call addr balance")
   303  				assert.Equal(t, expectedBalance, user1AssetBalance, "user 1 asset balance")
   304  				assert.Equal(t, expectedBalance, user2AssetBalance, "user 2 asset balance")
   305  			},
   306  		},
   307  		{
   308  			setupStateDB: func() StateDB {
   309  				statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
   310  				if err != nil {
   311  					t.Fatal(err)
   312  				}
   313  				statedb.SetBalance(userAddr1, bigHundred)
   314  				statedb.SetBalanceMultiCoin(userAddr1, assetID, big.NewInt(50))
   315  				statedb.Finalise(true)
   316  				return statedb
   317  			},
   318  			from:                 userAddr1,
   319  			precompileAddr:       NativeAssetCallAddr,
   320  			input:                PackNativeAssetCallInput(userAddr2, assetID, big.NewInt(51), nil),
   321  			value:                big.NewInt(50),
   322  			gasInput:             params.AssetCallApricot,
   323  			expectedGasRemaining: 0,
   324  			expectedErr:          vmerrs.ErrInsufficientBalance,
   325  			expectedResult:       nil,
   326  			name:                 "native asset call: insufficient multicoin funds",
   327  			stateDBCheck: func(t *testing.T, stateDB StateDB) {
   328  				user1Balance := stateDB.GetBalance(userAddr1)
   329  				user2Balance := stateDB.GetBalance(userAddr2)
   330  				user1AssetBalance := stateDB.GetBalanceMultiCoin(userAddr1, assetID)
   331  				user2AssetBalance := stateDB.GetBalanceMultiCoin(userAddr2, assetID)
   332  
   333  				assert.Equal(t, bigHundred, user1Balance, "user 1 balance")
   334  				assert.Equal(t, big0, user2Balance, "user 2 balance")
   335  				assert.Equal(t, big.NewInt(51), user1AssetBalance, "user 1 asset balance")
   336  				assert.Equal(t, big0, user2AssetBalance, "user 2 asset balance")
   337  			},
   338  		},
   339  		{
   340  			setupStateDB: func() StateDB {
   341  				statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
   342  				if err != nil {
   343  					t.Fatal(err)
   344  				}
   345  				statedb.SetBalance(userAddr1, big.NewInt(50))
   346  				statedb.SetBalanceMultiCoin(userAddr1, assetID, big.NewInt(50))
   347  				statedb.Finalise(true)
   348  				return statedb
   349  			},
   350  			from:                 userAddr1,
   351  			precompileAddr:       NativeAssetCallAddr,
   352  			input:                PackNativeAssetCallInput(userAddr2, assetID, big.NewInt(50), nil),
   353  			value:                big.NewInt(51),
   354  			gasInput:             params.AssetCallApricot,
   355  			expectedGasRemaining: params.AssetCallApricot,
   356  			expectedErr:          vmerrs.ErrInsufficientBalance,
   357  			expectedResult:       nil,
   358  			name:                 "native asset call: insufficient funds",
   359  			stateDBCheck: func(t *testing.T, stateDB StateDB) {
   360  				user1Balance := stateDB.GetBalance(userAddr1)
   361  				user2Balance := stateDB.GetBalance(userAddr2)
   362  				user1AssetBalance := stateDB.GetBalanceMultiCoin(userAddr1, assetID)
   363  				user2AssetBalance := stateDB.GetBalanceMultiCoin(userAddr2, assetID)
   364  
   365  				assert.Equal(t, big.NewInt(50), user1Balance, "user 1 balance")
   366  				assert.Equal(t, big0, user2Balance, "user 2 balance")
   367  				assert.Equal(t, big.NewInt(50), user1AssetBalance, "user 1 asset balance")
   368  				assert.Equal(t, big0, user2AssetBalance, "user 2 asset balance")
   369  			},
   370  		},
   371  		{
   372  			setupStateDB: func() StateDB {
   373  				statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
   374  				if err != nil {
   375  					t.Fatal(err)
   376  				}
   377  				statedb.SetBalance(userAddr1, bigHundred)
   378  				statedb.SetBalanceMultiCoin(userAddr1, assetID, bigHundred)
   379  				statedb.Finalise(true)
   380  				return statedb
   381  			},
   382  			from:                 userAddr1,
   383  			precompileAddr:       NativeAssetCallAddr,
   384  			input:                PackNativeAssetCallInput(userAddr2, assetID, big.NewInt(50), nil),
   385  			value:                big.NewInt(50),
   386  			gasInput:             params.AssetCallApricot - 1,
   387  			expectedGasRemaining: 0,
   388  			expectedErr:          vmerrs.ErrOutOfGas,
   389  			expectedResult:       nil,
   390  			name:                 "native asset call: insufficient gas for native asset call",
   391  		},
   392  		{
   393  			setupStateDB: func() StateDB {
   394  				statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
   395  				if err != nil {
   396  					t.Fatal(err)
   397  				}
   398  				statedb.SetBalance(userAddr1, bigHundred)
   399  				statedb.SetBalanceMultiCoin(userAddr1, assetID, bigHundred)
   400  				statedb.Finalise(true)
   401  				return statedb
   402  			},
   403  			from:                 userAddr1,
   404  			precompileAddr:       NativeAssetCallAddr,
   405  			input:                PackNativeAssetCallInput(userAddr2, assetID, big.NewInt(50), nil),
   406  			value:                big.NewInt(50),
   407  			gasInput:             params.AssetCallApricot + params.CallNewAccountGas - 1,
   408  			expectedGasRemaining: 0,
   409  			expectedErr:          vmerrs.ErrOutOfGas,
   410  			expectedResult:       nil,
   411  			name:                 "native asset call: insufficient gas to create new account",
   412  			stateDBCheck: func(t *testing.T, stateDB StateDB) {
   413  				user1Balance := stateDB.GetBalance(userAddr1)
   414  				user2Balance := stateDB.GetBalance(userAddr2)
   415  				user1AssetBalance := stateDB.GetBalanceMultiCoin(userAddr1, assetID)
   416  				user2AssetBalance := stateDB.GetBalanceMultiCoin(userAddr2, assetID)
   417  
   418  				assert.Equal(t, bigHundred, user1Balance, "user 1 balance")
   419  				assert.Equal(t, big0, user2Balance, "user 2 balance")
   420  				assert.Equal(t, bigHundred, user1AssetBalance, "user 1 asset balance")
   421  				assert.Equal(t, big0, user2AssetBalance, "user 2 asset balance")
   422  			},
   423  		},
   424  		{
   425  			setupStateDB: func() StateDB {
   426  				statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
   427  				if err != nil {
   428  					t.Fatal(err)
   429  				}
   430  				statedb.SetBalance(userAddr1, bigHundred)
   431  				statedb.SetBalanceMultiCoin(userAddr1, assetID, bigHundred)
   432  				statedb.Finalise(true)
   433  				return statedb
   434  			},
   435  			from:                 userAddr1,
   436  			precompileAddr:       NativeAssetCallAddr,
   437  			input:                make([]byte, 24),
   438  			value:                big.NewInt(50),
   439  			gasInput:             params.AssetCallApricot + params.CallNewAccountGas,
   440  			expectedGasRemaining: params.CallNewAccountGas,
   441  			expectedErr:          vmerrs.ErrExecutionReverted,
   442  			expectedResult:       nil,
   443  			name:                 "native asset call: invalid input",
   444  		},
   445  		{
   446  			setupStateDB: func() StateDB {
   447  				statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
   448  				if err != nil {
   449  					t.Fatal(err)
   450  				}
   451  				statedb.SetBalance(userAddr1, bigHundred)
   452  				statedb.SetBalanceMultiCoin(userAddr1, assetID, bigHundred)
   453  				statedb.Finalise(true)
   454  				return statedb
   455  			},
   456  			from:                 userAddr1,
   457  			precompileAddr:       genesisContractAddr,
   458  			input:                PackNativeAssetCallInput(userAddr2, assetID, big.NewInt(50), nil),
   459  			value:                big0,
   460  			gasInput:             params.AssetCallApricot + params.CallNewAccountGas,
   461  			expectedGasRemaining: params.AssetCallApricot + params.CallNewAccountGas,
   462  			expectedErr:          vmerrs.ErrExecutionReverted,
   463  			expectedResult:       nil,
   464  			name:                 "deprecated contract",
   465  		},
   466  	}
   467  	for _, test := range tests {
   468  		t.Run(test.name, func(t *testing.T) {
   469  			stateDB := test.setupStateDB()
   470  			// Create EVM with BlockNumber and Time initialized to 0 to enable Apricot Rules.
   471  			evm := NewEVM(vmCtx, TxContext{}, stateDB, params.TestApricotPhase5Config, Config{}) // Use ApricotPhase5Config because these precompiles are deprecated in ApricotPhase6.
   472  			ret, gasRemaining, err := evm.Call(AccountRef(test.from), test.precompileAddr, test.input, test.gasInput, test.value)
   473  			// Place gas remaining check before error check, so that it is not skipped when there is an error
   474  			assert.Equal(t, test.expectedGasRemaining, gasRemaining, "unexpected gas remaining")
   475  
   476  			if test.expectedErr != nil {
   477  				assert.Equal(t, test.expectedErr, err, "expected error to match")
   478  				return
   479  			}
   480  			if assert.NoError(t, err, "EVM Call produced unexpected error") {
   481  				assert.Equal(t, test.expectedResult, ret, "unexpected return value")
   482  				if test.stateDBCheck != nil {
   483  					test.stateDBCheck(t, stateDB)
   484  				}
   485  			}
   486  		})
   487  	}
   488  }