2020-07-23 18:30:31 +00:00
|
|
|
package keeper_test
|
2021-06-02 08:06:12 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"math/big"
|
|
|
|
|
2021-06-29 10:54:29 +00:00
|
|
|
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
|
|
|
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
2021-06-02 08:06:12 +00:00
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
2021-06-29 10:54:29 +00:00
|
|
|
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
|
|
|
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
|
2021-06-02 08:06:12 +00:00
|
|
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
|
|
|
|
2022-10-10 10:38:33 +00:00
|
|
|
"github.com/cerc-io/laconicd/crypto/ethsecp256k1"
|
2022-09-07 06:36:11 +00:00
|
|
|
"github.com/cerc-io/laconicd/tests"
|
|
|
|
"github.com/cerc-io/laconicd/x/evm/statedb"
|
|
|
|
"github.com/cerc-io/laconicd/x/evm/types"
|
2021-06-02 08:06:12 +00:00
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
|
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
2022-01-05 07:28:27 +00:00
|
|
|
"github.com/ethereum/go-ethereum/core/vm"
|
2021-06-02 08:06:12 +00:00
|
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
|
|
)
|
|
|
|
|
|
|
|
func (suite *KeeperTestSuite) TestCreateAccount() {
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
addr common.Address
|
2022-01-05 07:28:27 +00:00
|
|
|
malleate func(vm.StateDB, common.Address)
|
|
|
|
callback func(vm.StateDB, common.Address)
|
2021-06-02 08:06:12 +00:00
|
|
|
}{
|
|
|
|
{
|
2021-07-26 08:15:59 +00:00
|
|
|
"reset account (keep balance)",
|
2021-06-02 08:06:12 +00:00
|
|
|
suite.address,
|
2022-01-05 07:28:27 +00:00
|
|
|
func(vmdb vm.StateDB, addr common.Address) {
|
|
|
|
vmdb.AddBalance(addr, big.NewInt(100))
|
|
|
|
suite.Require().NotZero(vmdb.GetBalance(addr).Int64())
|
2021-06-02 08:06:12 +00:00
|
|
|
},
|
2022-01-05 07:28:27 +00:00
|
|
|
func(vmdb vm.StateDB, addr common.Address) {
|
|
|
|
suite.Require().Equal(vmdb.GetBalance(addr).Int64(), int64(100))
|
2021-06-02 08:06:12 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"create account",
|
|
|
|
tests.GenerateAddress(),
|
2022-01-05 07:28:27 +00:00
|
|
|
func(vmdb vm.StateDB, addr common.Address) {
|
|
|
|
suite.Require().False(vmdb.Exist(addr))
|
2021-06-02 08:06:12 +00:00
|
|
|
},
|
2022-01-05 07:28:27 +00:00
|
|
|
func(vmdb vm.StateDB, addr common.Address) {
|
|
|
|
suite.Require().True(vmdb.Exist(addr))
|
2021-06-02 08:06:12 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
suite.Run(tc.name, func() {
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb := suite.StateDB()
|
|
|
|
tc.malleate(vmdb, tc.addr)
|
|
|
|
vmdb.CreateAccount(tc.addr)
|
|
|
|
tc.callback(vmdb, tc.addr)
|
2021-06-02 08:06:12 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *KeeperTestSuite) TestAddBalance() {
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
amount *big.Int
|
|
|
|
isNoOp bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"positive amount",
|
|
|
|
big.NewInt(100),
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"zero amount",
|
|
|
|
big.NewInt(0),
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"negative amount",
|
|
|
|
big.NewInt(-1),
|
2022-01-05 07:28:27 +00:00
|
|
|
false, // seems to be consistent with go-ethereum's implementation
|
2021-06-02 08:06:12 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
suite.Run(tc.name, func() {
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb := suite.StateDB()
|
|
|
|
prev := vmdb.GetBalance(suite.address)
|
|
|
|
vmdb.AddBalance(suite.address, tc.amount)
|
|
|
|
post := vmdb.GetBalance(suite.address)
|
2021-06-02 08:06:12 +00:00
|
|
|
|
|
|
|
if tc.isNoOp {
|
|
|
|
suite.Require().Equal(prev.Int64(), post.Int64())
|
|
|
|
} else {
|
|
|
|
suite.Require().Equal(new(big.Int).Add(prev, tc.amount).Int64(), post.Int64())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *KeeperTestSuite) TestSubBalance() {
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
amount *big.Int
|
2022-01-05 07:28:27 +00:00
|
|
|
malleate func(vm.StateDB)
|
2021-06-02 08:06:12 +00:00
|
|
|
isNoOp bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"positive amount, below zero",
|
|
|
|
big.NewInt(100),
|
2022-01-05 07:28:27 +00:00
|
|
|
func(vm.StateDB) {},
|
|
|
|
false,
|
2021-06-02 08:06:12 +00:00
|
|
|
},
|
|
|
|
{
|
2022-01-05 07:28:27 +00:00
|
|
|
"positive amount, above zero",
|
2021-06-02 08:06:12 +00:00
|
|
|
big.NewInt(50),
|
2022-01-05 07:28:27 +00:00
|
|
|
func(vmdb vm.StateDB) {
|
|
|
|
vmdb.AddBalance(suite.address, big.NewInt(100))
|
2021-06-02 08:06:12 +00:00
|
|
|
},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"zero amount",
|
|
|
|
big.NewInt(0),
|
2022-01-05 07:28:27 +00:00
|
|
|
func(vm.StateDB) {},
|
2021-06-02 08:06:12 +00:00
|
|
|
true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"negative amount",
|
|
|
|
big.NewInt(-1),
|
2022-01-05 07:28:27 +00:00
|
|
|
func(vm.StateDB) {},
|
|
|
|
false,
|
2021-06-02 08:06:12 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
suite.Run(tc.name, func() {
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb := suite.StateDB()
|
|
|
|
tc.malleate(vmdb)
|
2021-06-02 08:06:12 +00:00
|
|
|
|
2022-01-05 07:28:27 +00:00
|
|
|
prev := vmdb.GetBalance(suite.address)
|
|
|
|
vmdb.SubBalance(suite.address, tc.amount)
|
|
|
|
post := vmdb.GetBalance(suite.address)
|
2021-06-02 08:06:12 +00:00
|
|
|
|
|
|
|
if tc.isNoOp {
|
|
|
|
suite.Require().Equal(prev.Int64(), post.Int64())
|
|
|
|
} else {
|
|
|
|
suite.Require().Equal(new(big.Int).Sub(prev, tc.amount).Int64(), post.Int64())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *KeeperTestSuite) TestGetNonce() {
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
address common.Address
|
|
|
|
expectedNonce uint64
|
2022-01-05 07:28:27 +00:00
|
|
|
malleate func(vm.StateDB)
|
2021-06-02 08:06:12 +00:00
|
|
|
}{
|
|
|
|
{
|
|
|
|
"account not found",
|
|
|
|
tests.GenerateAddress(),
|
|
|
|
0,
|
2022-01-05 07:28:27 +00:00
|
|
|
func(vm.StateDB) {},
|
2021-06-02 08:06:12 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"existing account",
|
|
|
|
suite.address,
|
|
|
|
1,
|
2022-01-05 07:28:27 +00:00
|
|
|
func(vmdb vm.StateDB) {
|
|
|
|
vmdb.SetNonce(suite.address, 1)
|
2021-06-02 08:06:12 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
suite.Run(tc.name, func() {
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb := suite.StateDB()
|
|
|
|
tc.malleate(vmdb)
|
2021-06-02 08:06:12 +00:00
|
|
|
|
2022-01-05 07:28:27 +00:00
|
|
|
nonce := vmdb.GetNonce(tc.address)
|
2021-06-02 08:06:12 +00:00
|
|
|
suite.Require().Equal(tc.expectedNonce, nonce)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *KeeperTestSuite) TestSetNonce() {
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
address common.Address
|
|
|
|
nonce uint64
|
|
|
|
malleate func()
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"new account",
|
|
|
|
tests.GenerateAddress(),
|
|
|
|
10,
|
|
|
|
func() {},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"existing account",
|
|
|
|
suite.address,
|
|
|
|
99,
|
|
|
|
func() {},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
suite.Run(tc.name, func() {
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb := suite.StateDB()
|
|
|
|
vmdb.SetNonce(tc.address, tc.nonce)
|
|
|
|
nonce := vmdb.GetNonce(tc.address)
|
2021-06-02 08:06:12 +00:00
|
|
|
suite.Require().Equal(tc.nonce, nonce)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *KeeperTestSuite) TestGetCodeHash() {
|
|
|
|
addr := tests.GenerateAddress()
|
|
|
|
baseAcc := &authtypes.BaseAccount{Address: sdk.AccAddress(addr.Bytes()).String()}
|
|
|
|
suite.app.AccountKeeper.SetAccount(suite.ctx, baseAcc)
|
|
|
|
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
address common.Address
|
|
|
|
expHash common.Hash
|
2022-01-05 07:28:27 +00:00
|
|
|
malleate func(vm.StateDB)
|
2021-06-02 08:06:12 +00:00
|
|
|
}{
|
|
|
|
{
|
|
|
|
"account not found",
|
|
|
|
tests.GenerateAddress(),
|
2022-01-05 07:28:27 +00:00
|
|
|
common.Hash{},
|
|
|
|
func(vm.StateDB) {},
|
2021-06-02 08:06:12 +00:00
|
|
|
},
|
|
|
|
{
|
2022-01-05 18:18:02 +00:00
|
|
|
"account not EthAccount type, EmptyCodeHash",
|
2021-06-02 08:06:12 +00:00
|
|
|
addr,
|
2022-01-05 18:18:02 +00:00
|
|
|
common.BytesToHash(types.EmptyCodeHash),
|
2022-01-05 07:28:27 +00:00
|
|
|
func(vm.StateDB) {},
|
2021-06-02 08:06:12 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"existing account",
|
|
|
|
suite.address,
|
|
|
|
crypto.Keccak256Hash([]byte("codeHash")),
|
2022-01-05 07:28:27 +00:00
|
|
|
func(vmdb vm.StateDB) {
|
|
|
|
vmdb.SetCode(suite.address, []byte("codeHash"))
|
2021-06-02 08:06:12 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
suite.Run(tc.name, func() {
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb := suite.StateDB()
|
|
|
|
tc.malleate(vmdb)
|
2021-06-02 08:06:12 +00:00
|
|
|
|
2022-01-05 07:28:27 +00:00
|
|
|
hash := vmdb.GetCodeHash(tc.address)
|
2021-06-02 08:06:12 +00:00
|
|
|
suite.Require().Equal(tc.expHash, hash)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *KeeperTestSuite) TestSetCode() {
|
|
|
|
addr := tests.GenerateAddress()
|
|
|
|
baseAcc := &authtypes.BaseAccount{Address: sdk.AccAddress(addr.Bytes()).String()}
|
|
|
|
suite.app.AccountKeeper.SetAccount(suite.ctx, baseAcc)
|
|
|
|
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
address common.Address
|
|
|
|
code []byte
|
|
|
|
isNoOp bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"account not found",
|
|
|
|
tests.GenerateAddress(),
|
|
|
|
[]byte("code"),
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"account not EthAccount type",
|
|
|
|
addr,
|
|
|
|
nil,
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"existing account",
|
|
|
|
suite.address,
|
|
|
|
[]byte("code"),
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"existing account, code deleted from store",
|
|
|
|
suite.address,
|
|
|
|
nil,
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
suite.Run(tc.name, func() {
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb := suite.StateDB()
|
|
|
|
prev := vmdb.GetCode(tc.address)
|
|
|
|
vmdb.SetCode(tc.address, tc.code)
|
|
|
|
post := vmdb.GetCode(tc.address)
|
2021-06-02 08:06:12 +00:00
|
|
|
|
|
|
|
if tc.isNoOp {
|
|
|
|
suite.Require().Equal(prev, post)
|
|
|
|
} else {
|
|
|
|
suite.Require().Equal(tc.code, post)
|
|
|
|
}
|
|
|
|
|
2022-01-05 07:28:27 +00:00
|
|
|
suite.Require().Equal(len(post), vmdb.GetCodeSize(tc.address))
|
2021-06-02 08:06:12 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *KeeperTestSuite) TestRefund() {
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
2022-01-05 07:28:27 +00:00
|
|
|
malleate func(vm.StateDB)
|
2021-06-02 08:06:12 +00:00
|
|
|
expRefund uint64
|
|
|
|
expPanic bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"success - add and subtract refund",
|
2022-01-05 07:28:27 +00:00
|
|
|
func(vmdb vm.StateDB) {
|
|
|
|
vmdb.AddRefund(11)
|
2021-06-02 08:06:12 +00:00
|
|
|
},
|
|
|
|
1,
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"fail - subtract amount > current refund",
|
2022-01-05 07:28:27 +00:00
|
|
|
func(vm.StateDB) {
|
2021-06-02 08:06:12 +00:00
|
|
|
},
|
|
|
|
0,
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
suite.Run(tc.name, func() {
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb := suite.StateDB()
|
|
|
|
tc.malleate(vmdb)
|
2021-06-02 08:06:12 +00:00
|
|
|
|
|
|
|
if tc.expPanic {
|
2022-01-05 07:28:27 +00:00
|
|
|
suite.Require().Panics(func() { vmdb.SubRefund(10) })
|
2021-06-02 08:06:12 +00:00
|
|
|
} else {
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb.SubRefund(10)
|
|
|
|
suite.Require().Equal(tc.expRefund, vmdb.GetRefund())
|
2021-06-02 08:06:12 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *KeeperTestSuite) TestState() {
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
key, value common.Hash
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"set state - delete from store",
|
|
|
|
common.BytesToHash([]byte("key")),
|
|
|
|
common.Hash{},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"set state - update value",
|
|
|
|
common.BytesToHash([]byte("key")),
|
|
|
|
common.BytesToHash([]byte("value")),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
suite.Run(tc.name, func() {
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb := suite.StateDB()
|
|
|
|
vmdb.SetState(suite.address, tc.key, tc.value)
|
|
|
|
value := vmdb.GetState(suite.address, tc.key)
|
2021-06-02 08:06:12 +00:00
|
|
|
suite.Require().Equal(tc.value, value)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-04 07:49:02 +00:00
|
|
|
func (suite *KeeperTestSuite) TestCommittedState() {
|
|
|
|
suite.SetupTest()
|
|
|
|
|
2021-09-05 11:03:06 +00:00
|
|
|
key := common.BytesToHash([]byte("key"))
|
|
|
|
value1 := common.BytesToHash([]byte("value1"))
|
|
|
|
value2 := common.BytesToHash([]byte("value2"))
|
2021-08-04 07:49:02 +00:00
|
|
|
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb := suite.StateDB()
|
|
|
|
vmdb.SetState(suite.address, key, value1)
|
|
|
|
vmdb.Commit()
|
2021-08-04 07:49:02 +00:00
|
|
|
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb = suite.StateDB()
|
|
|
|
vmdb.SetState(suite.address, key, value2)
|
|
|
|
tmp := vmdb.GetState(suite.address, key)
|
2021-08-04 07:49:02 +00:00
|
|
|
suite.Require().Equal(value2, tmp)
|
2022-01-05 07:28:27 +00:00
|
|
|
tmp = vmdb.GetCommittedState(suite.address, key)
|
2021-08-04 07:49:02 +00:00
|
|
|
suite.Require().Equal(value1, tmp)
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb.Commit()
|
2021-08-04 07:49:02 +00:00
|
|
|
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb = suite.StateDB()
|
|
|
|
tmp = vmdb.GetCommittedState(suite.address, key)
|
2021-08-04 07:49:02 +00:00
|
|
|
suite.Require().Equal(value2, tmp)
|
|
|
|
}
|
|
|
|
|
2021-06-02 08:06:12 +00:00
|
|
|
func (suite *KeeperTestSuite) TestSuicide() {
|
2021-12-23 16:07:23 +00:00
|
|
|
code := []byte("code")
|
2022-01-05 07:28:27 +00:00
|
|
|
db := suite.StateDB()
|
2021-12-23 16:07:23 +00:00
|
|
|
// Add code to account
|
2022-01-05 07:28:27 +00:00
|
|
|
db.SetCode(suite.address, code)
|
|
|
|
suite.Require().Equal(code, db.GetCode(suite.address))
|
2021-12-23 16:07:23 +00:00
|
|
|
// Add state to account
|
|
|
|
for i := 0; i < 5; i++ {
|
2022-01-05 07:28:27 +00:00
|
|
|
db.SetState(suite.address, common.BytesToHash([]byte(fmt.Sprintf("key%d", i))), common.BytesToHash([]byte(fmt.Sprintf("value%d", i))))
|
2021-06-02 08:06:12 +00:00
|
|
|
}
|
|
|
|
|
2022-01-05 07:28:27 +00:00
|
|
|
suite.Require().NoError(db.Commit())
|
|
|
|
db = suite.StateDB()
|
|
|
|
|
2022-10-10 10:38:33 +00:00
|
|
|
// Generate 2nd address
|
|
|
|
privkey, _ := ethsecp256k1.GenerateKey()
|
|
|
|
key, err := privkey.ToECDSA()
|
|
|
|
suite.Require().NoError(err)
|
|
|
|
addr2 := crypto.PubkeyToAddress(key.PublicKey)
|
|
|
|
|
|
|
|
// Add code and state to account 2
|
|
|
|
db.SetCode(addr2, code)
|
|
|
|
suite.Require().Equal(code, db.GetCode(addr2))
|
|
|
|
for i := 0; i < 5; i++ {
|
|
|
|
db.SetState(addr2, common.BytesToHash([]byte(fmt.Sprintf("key%d", i))), common.BytesToHash([]byte(fmt.Sprintf("value%d", i))))
|
|
|
|
}
|
|
|
|
|
2021-12-23 16:07:23 +00:00
|
|
|
// Call Suicide
|
2022-01-05 07:28:27 +00:00
|
|
|
suite.Require().Equal(true, db.Suicide(suite.address))
|
2021-12-23 16:07:23 +00:00
|
|
|
|
|
|
|
// Check suicided is marked
|
2022-01-05 07:28:27 +00:00
|
|
|
suite.Require().Equal(true, db.HasSuicided(suite.address))
|
|
|
|
|
2022-10-10 10:38:33 +00:00
|
|
|
// Commit state
|
2022-01-05 07:28:27 +00:00
|
|
|
suite.Require().NoError(db.Commit())
|
|
|
|
db = suite.StateDB()
|
|
|
|
|
2021-12-23 16:07:23 +00:00
|
|
|
// Check code is deleted
|
2022-01-05 07:28:27 +00:00
|
|
|
suite.Require().Nil(db.GetCode(suite.address))
|
2021-12-23 16:07:23 +00:00
|
|
|
// Check state is deleted
|
|
|
|
var storage types.Storage
|
2022-01-05 07:28:27 +00:00
|
|
|
suite.app.EvmKeeper.ForEachStorage(suite.ctx, suite.address, func(key, value common.Hash) bool {
|
2021-12-23 16:07:23 +00:00
|
|
|
storage = append(storage, types.NewState(key, value))
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
suite.Require().Equal(0, len(storage))
|
|
|
|
|
2022-01-05 07:28:27 +00:00
|
|
|
// Check account is deleted
|
|
|
|
suite.Require().Equal(common.Hash{}, db.GetCodeHash(suite.address))
|
2022-10-10 10:38:33 +00:00
|
|
|
|
|
|
|
// Check code is still present in addr2 and suicided is false
|
|
|
|
suite.Require().NotNil(db.GetCode(addr2))
|
|
|
|
suite.Require().Equal(false, db.HasSuicided(addr2))
|
2021-06-02 08:06:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *KeeperTestSuite) TestExist() {
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
address common.Address
|
2022-01-05 07:28:27 +00:00
|
|
|
malleate func(vm.StateDB)
|
2021-06-02 08:06:12 +00:00
|
|
|
exists bool
|
|
|
|
}{
|
2022-01-05 07:28:27 +00:00
|
|
|
{"success, account exists", suite.address, func(vm.StateDB) {}, true},
|
|
|
|
{"success, has suicided", suite.address, func(vmdb vm.StateDB) {
|
|
|
|
vmdb.Suicide(suite.address)
|
2021-06-02 08:06:12 +00:00
|
|
|
}, true},
|
2022-01-05 07:28:27 +00:00
|
|
|
{"success, account doesn't exist", tests.GenerateAddress(), func(vm.StateDB) {}, false},
|
2021-06-02 08:06:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
suite.Run(tc.name, func() {
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb := suite.StateDB()
|
|
|
|
tc.malleate(vmdb)
|
2021-06-02 08:06:12 +00:00
|
|
|
|
2022-01-05 07:28:27 +00:00
|
|
|
suite.Require().Equal(tc.exists, vmdb.Exist(tc.address))
|
2021-06-02 08:06:12 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *KeeperTestSuite) TestEmpty() {
|
2022-01-05 07:28:27 +00:00
|
|
|
suite.SetupTest()
|
|
|
|
|
2021-06-02 08:06:12 +00:00
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
address common.Address
|
2022-01-05 07:28:27 +00:00
|
|
|
malleate func(vm.StateDB)
|
2021-06-02 08:06:12 +00:00
|
|
|
empty bool
|
|
|
|
}{
|
2022-01-05 18:18:02 +00:00
|
|
|
{"empty, account exists", suite.address, func(vm.StateDB) {}, true},
|
|
|
|
{
|
|
|
|
"not empty, positive balance",
|
|
|
|
suite.address,
|
|
|
|
func(vmdb vm.StateDB) { vmdb.AddBalance(suite.address, big.NewInt(100)) },
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{"empty, account doesn't exist", tests.GenerateAddress(), func(vm.StateDB) {}, true},
|
2021-06-02 08:06:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
suite.Run(tc.name, func() {
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb := suite.StateDB()
|
|
|
|
tc.malleate(vmdb)
|
2021-06-02 08:06:12 +00:00
|
|
|
|
2022-01-05 07:28:27 +00:00
|
|
|
suite.Require().Equal(tc.empty, vmdb.Empty(tc.address))
|
2021-06-02 08:06:12 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *KeeperTestSuite) TestSnapshot() {
|
2021-09-05 11:03:06 +00:00
|
|
|
key := common.BytesToHash([]byte("key"))
|
|
|
|
value1 := common.BytesToHash([]byte("value1"))
|
|
|
|
value2 := common.BytesToHash([]byte("value2"))
|
2021-08-10 07:22:46 +00:00
|
|
|
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
2022-01-05 07:28:27 +00:00
|
|
|
malleate func(vm.StateDB)
|
2021-08-10 07:22:46 +00:00
|
|
|
}{
|
2022-01-05 07:28:27 +00:00
|
|
|
{"simple revert", func(vmdb vm.StateDB) {
|
|
|
|
revision := vmdb.Snapshot()
|
2021-08-10 07:22:46 +00:00
|
|
|
suite.Require().Zero(revision)
|
|
|
|
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb.SetState(suite.address, key, value1)
|
|
|
|
suite.Require().Equal(value1, vmdb.GetState(suite.address, key))
|
2021-08-10 07:22:46 +00:00
|
|
|
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb.RevertToSnapshot(revision)
|
2021-08-10 07:22:46 +00:00
|
|
|
|
|
|
|
// reverted
|
2022-01-05 07:28:27 +00:00
|
|
|
suite.Require().Equal(common.Hash{}, vmdb.GetState(suite.address, key))
|
2021-08-10 07:22:46 +00:00
|
|
|
}},
|
2022-01-05 07:28:27 +00:00
|
|
|
{"nested snapshot/revert", func(vmdb vm.StateDB) {
|
|
|
|
revision1 := vmdb.Snapshot()
|
2021-08-10 07:22:46 +00:00
|
|
|
suite.Require().Zero(revision1)
|
|
|
|
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb.SetState(suite.address, key, value1)
|
2021-08-10 07:22:46 +00:00
|
|
|
|
2022-01-05 07:28:27 +00:00
|
|
|
revision2 := vmdb.Snapshot()
|
2021-08-10 07:22:46 +00:00
|
|
|
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb.SetState(suite.address, key, value2)
|
|
|
|
suite.Require().Equal(value2, vmdb.GetState(suite.address, key))
|
2021-08-10 07:22:46 +00:00
|
|
|
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb.RevertToSnapshot(revision2)
|
|
|
|
suite.Require().Equal(value1, vmdb.GetState(suite.address, key))
|
2021-08-10 07:22:46 +00:00
|
|
|
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb.RevertToSnapshot(revision1)
|
|
|
|
suite.Require().Equal(common.Hash{}, vmdb.GetState(suite.address, key))
|
2021-08-10 07:22:46 +00:00
|
|
|
}},
|
2022-01-05 07:28:27 +00:00
|
|
|
{"jump revert", func(vmdb vm.StateDB) {
|
|
|
|
revision1 := vmdb.Snapshot()
|
|
|
|
vmdb.SetState(suite.address, key, value1)
|
|
|
|
vmdb.Snapshot()
|
|
|
|
vmdb.SetState(suite.address, key, value2)
|
|
|
|
vmdb.RevertToSnapshot(revision1)
|
|
|
|
suite.Require().Equal(common.Hash{}, vmdb.GetState(suite.address, key))
|
2021-08-10 07:22:46 +00:00
|
|
|
}},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
suite.Run(tc.name, func() {
|
|
|
|
suite.SetupTest()
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb := suite.StateDB()
|
|
|
|
tc.malleate(vmdb)
|
2021-08-10 07:22:46 +00:00
|
|
|
})
|
|
|
|
}
|
2021-06-02 08:06:12 +00:00
|
|
|
}
|
|
|
|
|
2021-09-03 18:06:36 +00:00
|
|
|
func (suite *KeeperTestSuite) CreateTestTx(msg *types.MsgEthereumTx, priv cryptotypes.PrivKey) authsigning.Tx {
|
|
|
|
option, err := codectypes.NewAnyWithValue(&types.ExtensionOptionsEthereumTx{})
|
2021-06-29 10:54:29 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
|
|
|
|
txBuilder := suite.clientCtx.TxConfig.NewTxBuilder()
|
|
|
|
builder, ok := txBuilder.(authtx.ExtensionOptionsTxBuilder)
|
|
|
|
suite.Require().True(ok)
|
|
|
|
|
|
|
|
builder.SetExtensionOptions(option)
|
|
|
|
|
|
|
|
err = msg.Sign(suite.ethSigner, tests.NewSigner(priv))
|
|
|
|
suite.Require().NoError(err)
|
|
|
|
|
|
|
|
err = txBuilder.SetMsgs(msg)
|
|
|
|
suite.Require().NoError(err)
|
|
|
|
|
|
|
|
return txBuilder.GetTx()
|
|
|
|
}
|
|
|
|
|
2021-06-02 08:06:12 +00:00
|
|
|
func (suite *KeeperTestSuite) TestAddLog() {
|
2021-06-29 10:54:29 +00:00
|
|
|
addr, privKey := tests.NewAddrKey()
|
2021-10-05 15:38:20 +00:00
|
|
|
msg := types.NewTx(big.NewInt(1), 0, &suite.address, big.NewInt(1), 100000, big.NewInt(1), nil, nil, []byte("test"), nil)
|
2021-06-29 10:54:29 +00:00
|
|
|
msg.From = addr.Hex()
|
|
|
|
|
|
|
|
tx := suite.CreateTestTx(msg, privKey)
|
2021-09-03 18:06:36 +00:00
|
|
|
msg, _ = tx.GetMsgs()[0].(*types.MsgEthereumTx)
|
2021-06-30 09:35:11 +00:00
|
|
|
txHash := msg.AsTransaction().Hash()
|
2021-06-29 10:54:29 +00:00
|
|
|
|
2021-10-05 15:38:20 +00:00
|
|
|
msg2 := types.NewTx(big.NewInt(1), 1, &suite.address, big.NewInt(1), 100000, big.NewInt(1), nil, nil, []byte("test"), nil)
|
2021-07-26 08:40:59 +00:00
|
|
|
msg2.From = addr.Hex()
|
|
|
|
|
|
|
|
tx2 := suite.CreateTestTx(msg2, privKey)
|
2021-09-03 18:06:36 +00:00
|
|
|
msg2, _ = tx2.GetMsgs()[0].(*types.MsgEthereumTx)
|
2021-07-26 08:40:59 +00:00
|
|
|
|
2021-10-19 08:49:29 +00:00
|
|
|
msg3 := types.NewTx(big.NewInt(1), 0, &suite.address, big.NewInt(1), 100000, nil, big.NewInt(1), big.NewInt(1), []byte("test"), nil)
|
|
|
|
msg3.From = addr.Hex()
|
|
|
|
|
|
|
|
tx3 := suite.CreateTestTx(msg3, privKey)
|
|
|
|
msg3, _ = tx3.GetMsgs()[0].(*types.MsgEthereumTx)
|
|
|
|
txHash3 := msg3.AsTransaction().Hash()
|
|
|
|
|
|
|
|
msg4 := types.NewTx(big.NewInt(1), 1, &suite.address, big.NewInt(1), 100000, nil, big.NewInt(1), big.NewInt(1), []byte("test"), nil)
|
|
|
|
msg4.From = addr.Hex()
|
|
|
|
|
|
|
|
tx4 := suite.CreateTestTx(msg4, privKey)
|
|
|
|
msg4, _ = tx4.GetMsgs()[0].(*types.MsgEthereumTx)
|
|
|
|
|
2021-06-02 08:06:12 +00:00
|
|
|
testCases := []struct {
|
|
|
|
name string
|
2021-06-24 16:05:45 +00:00
|
|
|
hash common.Hash
|
2021-06-02 08:06:12 +00:00
|
|
|
log, expLog *ethtypes.Log // pre and post populating log fields
|
2022-01-05 07:28:27 +00:00
|
|
|
malleate func(vm.StateDB)
|
2021-06-02 08:06:12 +00:00
|
|
|
}{
|
|
|
|
{
|
|
|
|
"tx hash from message",
|
2021-06-30 09:35:11 +00:00
|
|
|
txHash,
|
2021-06-02 08:06:12 +00:00
|
|
|
ðtypes.Log{
|
|
|
|
Address: addr,
|
2022-01-05 07:28:27 +00:00
|
|
|
Topics: make([]common.Hash, 0),
|
2021-06-02 08:06:12 +00:00
|
|
|
},
|
|
|
|
ðtypes.Log{
|
|
|
|
Address: addr,
|
|
|
|
TxHash: txHash,
|
2021-12-14 14:52:22 +00:00
|
|
|
Topics: make([]common.Hash, 0),
|
2021-06-02 08:06:12 +00:00
|
|
|
},
|
2022-01-05 07:28:27 +00:00
|
|
|
func(vm.StateDB) {},
|
2021-07-26 08:40:59 +00:00
|
|
|
},
|
2021-10-19 08:49:29 +00:00
|
|
|
{
|
|
|
|
"dynamicfee tx hash from message",
|
|
|
|
txHash3,
|
|
|
|
ðtypes.Log{
|
|
|
|
Address: addr,
|
2021-12-14 14:52:22 +00:00
|
|
|
Topics: make([]common.Hash, 0),
|
2021-10-19 08:49:29 +00:00
|
|
|
},
|
|
|
|
ðtypes.Log{
|
|
|
|
Address: addr,
|
2022-01-05 07:28:27 +00:00
|
|
|
TxHash: txHash3,
|
2021-12-14 14:52:22 +00:00
|
|
|
Topics: make([]common.Hash, 0),
|
2021-10-19 08:49:29 +00:00
|
|
|
},
|
2022-01-05 07:28:27 +00:00
|
|
|
func(vm.StateDB) {},
|
2021-10-19 08:49:29 +00:00
|
|
|
},
|
2021-06-02 08:06:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
suite.Run(tc.name, func() {
|
2021-07-26 08:40:59 +00:00
|
|
|
suite.SetupTest()
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb := statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewTxConfig(
|
|
|
|
common.BytesToHash(suite.ctx.HeaderHash().Bytes()),
|
|
|
|
tc.hash,
|
|
|
|
0, 0,
|
|
|
|
))
|
|
|
|
tc.malleate(vmdb)
|
|
|
|
|
|
|
|
vmdb.AddLog(tc.log)
|
|
|
|
logs := vmdb.Logs()
|
2021-07-26 08:40:59 +00:00
|
|
|
suite.Require().Equal(1, len(logs))
|
|
|
|
suite.Require().Equal(tc.expLog, logs[0])
|
2021-06-02 08:06:12 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-07 10:05:49 +00:00
|
|
|
func (suite *KeeperTestSuite) TestPrepareAccessList() {
|
2021-06-02 08:06:12 +00:00
|
|
|
dest := tests.GenerateAddress()
|
|
|
|
precompiles := []common.Address{tests.GenerateAddress(), tests.GenerateAddress()}
|
|
|
|
accesses := ethtypes.AccessList{
|
|
|
|
{Address: tests.GenerateAddress(), StorageKeys: []common.Hash{common.BytesToHash([]byte("key"))}},
|
|
|
|
{Address: tests.GenerateAddress(), StorageKeys: []common.Hash{common.BytesToHash([]byte("key1"))}},
|
|
|
|
}
|
|
|
|
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb := suite.StateDB()
|
|
|
|
vmdb.PrepareAccessList(suite.address, &dest, precompiles, accesses)
|
2021-06-02 08:06:12 +00:00
|
|
|
|
2022-01-05 07:28:27 +00:00
|
|
|
suite.Require().True(vmdb.AddressInAccessList(suite.address))
|
|
|
|
suite.Require().True(vmdb.AddressInAccessList(dest))
|
2021-06-02 08:06:12 +00:00
|
|
|
|
|
|
|
for _, precompile := range precompiles {
|
2022-01-05 07:28:27 +00:00
|
|
|
suite.Require().True(vmdb.AddressInAccessList(precompile))
|
2021-06-02 08:06:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, access := range accesses {
|
|
|
|
for _, key := range access.StorageKeys {
|
2022-01-05 07:28:27 +00:00
|
|
|
addrOK, slotOK := vmdb.SlotInAccessList(access.Address, key)
|
2021-06-07 10:05:49 +00:00
|
|
|
suite.Require().True(addrOK, access.Address.Hex())
|
|
|
|
suite.Require().True(slotOK, key.Hex())
|
2021-06-02 08:06:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-07 10:05:49 +00:00
|
|
|
func (suite *KeeperTestSuite) TestAddAddressToAccessList() {
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
addr common.Address
|
|
|
|
}{
|
|
|
|
{"new address", suite.address},
|
|
|
|
{"existing address", suite.address},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
suite.Run(tc.name, func() {
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb := suite.StateDB()
|
|
|
|
vmdb.AddAddressToAccessList(tc.addr)
|
|
|
|
addrOk := vmdb.AddressInAccessList(tc.addr)
|
2021-06-07 10:05:49 +00:00
|
|
|
suite.Require().True(addrOk, tc.addr.Hex())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *KeeperTestSuite) AddSlotToAccessList() {
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
addr common.Address
|
|
|
|
slot common.Hash
|
|
|
|
}{
|
|
|
|
{"new address and slot (1)", tests.GenerateAddress(), common.BytesToHash([]byte("hash"))},
|
|
|
|
{"new address and slot (2)", suite.address, common.Hash{}},
|
|
|
|
{"existing address and slot", suite.address, common.Hash{}},
|
|
|
|
{"existing address, new slot", suite.address, common.BytesToHash([]byte("hash"))},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
suite.Run(tc.name, func() {
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb := suite.StateDB()
|
|
|
|
vmdb.AddSlotToAccessList(tc.addr, tc.slot)
|
|
|
|
addrOk, slotOk := vmdb.SlotInAccessList(tc.addr, tc.slot)
|
2021-06-07 10:05:49 +00:00
|
|
|
suite.Require().True(addrOk, tc.addr.Hex())
|
|
|
|
suite.Require().True(slotOk, tc.slot.Hex())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-05 07:28:27 +00:00
|
|
|
// FIXME skip for now
|
|
|
|
func (suite *KeeperTestSuite) _TestForEachStorage() {
|
2021-06-02 08:06:12 +00:00
|
|
|
var storage types.Storage
|
|
|
|
|
|
|
|
testCase := []struct {
|
|
|
|
name string
|
2022-01-05 07:28:27 +00:00
|
|
|
malleate func(vm.StateDB)
|
2021-06-02 08:06:12 +00:00
|
|
|
callback func(key, value common.Hash) (stop bool)
|
|
|
|
expValues []common.Hash
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"aggregate state",
|
2022-01-05 07:28:27 +00:00
|
|
|
func(vmdb vm.StateDB) {
|
2021-06-02 08:06:12 +00:00
|
|
|
for i := 0; i < 5; i++ {
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb.SetState(suite.address, common.BytesToHash([]byte(fmt.Sprintf("key%d", i))), common.BytesToHash([]byte(fmt.Sprintf("value%d", i))))
|
2021-06-02 08:06:12 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
func(key, value common.Hash) bool {
|
|
|
|
storage = append(storage, types.NewState(key, value))
|
2021-11-30 10:34:33 +00:00
|
|
|
return true
|
2021-06-02 08:06:12 +00:00
|
|
|
},
|
|
|
|
[]common.Hash{
|
|
|
|
common.BytesToHash([]byte("value0")),
|
|
|
|
common.BytesToHash([]byte("value1")),
|
|
|
|
common.BytesToHash([]byte("value2")),
|
|
|
|
common.BytesToHash([]byte("value3")),
|
|
|
|
common.BytesToHash([]byte("value4")),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"filter state",
|
2022-01-05 07:28:27 +00:00
|
|
|
func(vmdb vm.StateDB) {
|
|
|
|
vmdb.SetState(suite.address, common.BytesToHash([]byte("key")), common.BytesToHash([]byte("value")))
|
|
|
|
vmdb.SetState(suite.address, common.BytesToHash([]byte("filterkey")), common.BytesToHash([]byte("filtervalue")))
|
2021-06-02 08:06:12 +00:00
|
|
|
},
|
|
|
|
func(key, value common.Hash) bool {
|
|
|
|
if value == common.BytesToHash([]byte("filtervalue")) {
|
|
|
|
storage = append(storage, types.NewState(key, value))
|
2021-11-30 10:34:33 +00:00
|
|
|
return false
|
2021-06-02 08:06:12 +00:00
|
|
|
}
|
2021-11-30 10:34:33 +00:00
|
|
|
return true
|
2021-06-02 08:06:12 +00:00
|
|
|
},
|
|
|
|
[]common.Hash{
|
|
|
|
common.BytesToHash([]byte("filtervalue")),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCase {
|
|
|
|
suite.Run(tc.name, func() {
|
|
|
|
suite.SetupTest() // reset
|
2022-01-05 07:28:27 +00:00
|
|
|
vmdb := suite.StateDB()
|
|
|
|
tc.malleate(vmdb)
|
2021-06-02 08:06:12 +00:00
|
|
|
|
2022-01-05 07:28:27 +00:00
|
|
|
err := vmdb.ForEachStorage(suite.address, tc.callback)
|
2021-06-02 08:06:12 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(len(tc.expValues), len(storage), fmt.Sprintf("Expected values:\n%v\nStorage Values\n%v", tc.expValues, storage))
|
|
|
|
|
|
|
|
vals := make([]common.Hash, len(storage))
|
|
|
|
for i := range storage {
|
|
|
|
vals[i] = common.HexToHash(storage[i].Value)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: not sure why Equals fails
|
|
|
|
suite.Require().ElementsMatch(tc.expValues, vals)
|
|
|
|
})
|
|
|
|
storage = types.Storage{}
|
|
|
|
}
|
|
|
|
}
|