Skip to content

Commit

Permalink
Use per-tx escrow for Wei balance (#1314)
Browse files Browse the repository at this point in the history
* Use per-tx escrow for Wei balance

* rebase
  • Loading branch information
codchen authored and udpatil committed Apr 19, 2024
1 parent ca71fe8 commit 990ecd2
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 18 deletions.
4 changes: 2 additions & 2 deletions aclmapping/evm/mappings.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ func TransactionDependencyGenerator(_ aclkeeper.Keeper, evmKeeper evmkeeper.Keep
return []sdkacltypes.AccessOperation{}, err
}
ops := []sdkacltypes.AccessOperation{}
ops = appendRWBalanceOps(ops, state.GetMiddleManAddress(ctx))
ops = appendRWBalanceOps(ops, state.GetCoinbaseAddress(ctx))
ops = appendRWBalanceOps(ops, state.GetMiddleManAddress(ctx.TxIndex()))
ops = appendRWBalanceOps(ops, state.GetCoinbaseAddress(ctx.TxIndex()))
sender := evmMsg.Derived.SenderSeiAddr
ops = appendRWBalanceOps(ops, sender)
ops = append(ops,
Expand Down
6 changes: 5 additions & 1 deletion x/evm/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,11 @@ func (k *Keeper) GetHashFn(ctx sdk.Context) vm.GetHashFunc {
func (k *Keeper) GetEVMTxDeferredInfo(ctx sdk.Context) (res []EvmTxDeferredInfo) {
k.deferredInfo.Range(func(key, value any) bool {
txIdx := key.(int)
if txIdx >= 0 && txIdx < len(k.txResults) && k.txResults[txIdx].Code == 0 {
if txIdx < 0 || txIdx >= len(k.txResults) {
ctx.Logger().Error(fmt.Sprintf("getting invalid tx index in EVM deferred info: %d, num of txs: %d", txIdx, len(k.txResults)))
return true
}
if k.txResults[txIdx].Code == 0 {
res = append(res, *(value.(*EvmTxDeferredInfo)))
}
return true
Expand Down
6 changes: 3 additions & 3 deletions x/evm/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func TestEVMTransaction(t *testing.T) {
require.NotEmpty(t, res.ReturnData)
require.NotEmpty(t, res.Hash)
require.Equal(t, uint64(1000000)-res.GasUsed, k.BankKeeper().GetBalance(ctx, sdk.AccAddress(evmAddr[:]), "usei").Amount.Uint64())
require.Equal(t, res.GasUsed, k.BankKeeper().GetBalance(ctx, state.GetCoinbaseAddress(ctx), k.GetBaseDenom(ctx)).Amount.Uint64())
require.Equal(t, res.GasUsed, k.BankKeeper().GetBalance(ctx, state.GetCoinbaseAddress(ctx.TxIndex()), k.GetBaseDenom(ctx)).Amount.Uint64())
receipt, err := k.GetReceipt(ctx, common.HexToHash(res.Hash))
require.Nil(t, err)
require.NotNil(t, receipt)
Expand Down Expand Up @@ -301,7 +301,7 @@ func TestEVMPrecompiles(t *testing.T) {
require.NotEmpty(t, res.ReturnData)
require.NotEmpty(t, res.Hash)
require.Equal(t, uint64(1000000)-res.GasUsed, k.BankKeeper().GetBalance(ctx, sdk.AccAddress(evmAddr[:]), "usei").Amount.Uint64())
require.Equal(t, res.GasUsed, k.BankKeeper().GetBalance(ctx, state.GetCoinbaseAddress(ctx), k.GetBaseDenom(ctx)).Amount.Uint64())
require.Equal(t, res.GasUsed, k.BankKeeper().GetBalance(ctx, state.GetCoinbaseAddress(ctx.TxIndex()), k.GetBaseDenom(ctx)).Amount.Uint64())
receipt, err := k.GetReceipt(ctx, common.HexToHash(res.Hash))
require.Nil(t, err)
require.NotNil(t, receipt)
Expand Down Expand Up @@ -406,7 +406,7 @@ func TestEVMBlockEnv(t *testing.T) {
require.NotEmpty(t, res.ReturnData)
require.NotEmpty(t, res.Hash)
require.Equal(t, uint64(1000000)-res.GasUsed, k.BankKeeper().GetBalance(ctx, sdk.AccAddress(evmAddr[:]), "usei").Amount.Uint64())
require.Equal(t, res.GasUsed, k.BankKeeper().GetBalance(ctx, state.GetCoinbaseAddress(ctx), k.GetBaseDenom(ctx)).Amount.Uint64())
require.Equal(t, res.GasUsed, k.BankKeeper().GetBalance(ctx, state.GetCoinbaseAddress(ctx.TxIndex()), k.GetBaseDenom(ctx)).Amount.Uint64())
receipt, err := k.GetReceipt(ctx, common.HexToHash(res.Hash))
require.Nil(t, err)
require.NotNil(t, receipt)
Expand Down
48 changes: 48 additions & 0 deletions x/evm/keeper/wei.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package keeper

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/sei-protocol/sei-chain/x/evm/state"
)

func (k *Keeper) SettleWeiEscrowAccounts(ctx sdk.Context, evmTxDeferredInfoList []EvmTxDeferredInfo) {
denom := k.GetBaseDenom(ctx)
// settle surplus escrow first
for _, info := range evmTxDeferredInfoList {
escrow := state.GetTempWeiEscrowAddress(info.TxIndx)
seiBalance := k.BankKeeper().GetBalance(ctx, escrow, denom)
if !seiBalance.Amount.IsPositive() {
continue
}
if err := k.BankKeeper().SendCoinsFromAccountToModule(ctx, escrow, banktypes.WeiEscrowName, sdk.NewCoins(seiBalance)); err != nil {
ctx.Logger().Error(fmt.Sprintf("failed to send %s from escrow %d to global escrow", seiBalance.String(), info.TxIndx))
// This should not happen in any case. We want to halt the chain if it does
panic(err)
}
}
// settle deficit escrows
for _, info := range evmTxDeferredInfoList {
escrow := state.GetTempWeiEscrowAddress(info.TxIndx)
seiBalance := k.BankKeeper().GetBalance(ctx, escrow, denom)
if !seiBalance.Amount.IsNegative() {
continue
}
settleAmt := sdk.NewCoin(seiBalance.Denom, seiBalance.Amount.Neg())
if err := k.BankKeeper().SendCoinsFromModuleToAccount(ctx, banktypes.WeiEscrowName, escrow, sdk.NewCoins(settleAmt)); err != nil {
ctx.Logger().Error(fmt.Sprintf("failed to send %s from global escrow to escrow %d", settleAmt.String(), info.TxIndx))
// This should not happen in any case. We want to halt the chain if it does
panic(err)
}
}
// sanity check
for _, info := range evmTxDeferredInfoList {
escrow := state.GetTempWeiEscrowAddress(info.TxIndx)
seiBalance := k.BankKeeper().GetBalance(ctx, escrow, denom)
if !seiBalance.Amount.IsZero() {
panic(fmt.Sprintf("failed to settle escrow account %d which still has a balance of %s", info.TxIndx, seiBalance.String()))
}
}
}
109 changes: 109 additions & 0 deletions x/evm/keeper/wei_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package keeper_test

import (
"math/big"
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper"
"github.com/sei-protocol/sei-chain/x/evm/state"
"github.com/sei-protocol/sei-chain/x/evm/types"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
)

func TestSettleCommon(t *testing.T) {
k, ctx := testkeeper.MockEVMKeeper()

_, addr1 := testkeeper.MockAddressPair()
_, addr2 := testkeeper.MockAddressPair()
_, addr3 := testkeeper.MockAddressPair()
amt := sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(10)))
k.BankKeeper().MintCoins(ctx, types.ModuleName, amt)
k.BankKeeper().SendCoinsFromModuleToAccount(ctx, types.ModuleName, sdk.AccAddress(addr1[:]), amt)

// addr1 send one sei and one Wei to addr2
// escrow 1 would have a one-sei surplus
s := state.NewDBImpl(ctx.WithTxIndex(1), k, false)
s.SubBalance(addr1, big.NewInt(1_000_000_000_001))
s.AddBalance(addr2, big.NewInt(1_000_000_000_001))
require.Nil(t, s.Finalize())
k.AppendToEvmTxDeferredInfo(ctx.WithTxIndex(1), ethtypes.Bloom{}, common.Hash{})

// addr2 send two weis to addr3
// escrow 2 would have a one-sei surplus
s = state.NewDBImpl(ctx.WithTxIndex(2), k, false)
s.SubBalance(addr2, big.NewInt(2))
s.AddBalance(addr3, big.NewInt(2))
require.Nil(t, s.Finalize())
k.AppendToEvmTxDeferredInfo(ctx.WithTxIndex(2), ethtypes.Bloom{}, common.Hash{})

// addr1 send one wei to addr2
// escrow 3 would have a one-sei deficit
s = state.NewDBImpl(ctx.WithTxIndex(3), k, false)
s.SubBalance(addr1, big.NewInt(1))
s.AddBalance(addr2, big.NewInt(1))
require.Nil(t, s.Finalize())
k.AppendToEvmTxDeferredInfo(ctx.WithTxIndex(3), ethtypes.Bloom{}, common.Hash{})

globalEscrowBalance := k.BankKeeper().GetBalance(ctx, k.AccountKeeper().GetModuleAddress(banktypes.WeiEscrowName), "usei")
require.True(t, globalEscrowBalance.Amount.IsZero())

k.SetTxResults([]*abci.ExecTxResult{{Code: 0}, {Code: 0}, {Code: 0}, {Code: 0}})
deferredInfo := k.GetEVMTxDeferredInfo(ctx)
k.SettleWeiEscrowAccounts(ctx, deferredInfo)
globalEscrowBalance = k.BankKeeper().GetBalance(ctx, k.AccountKeeper().GetModuleAddress(banktypes.WeiEscrowName), "usei")
require.Equal(t, int64(1), globalEscrowBalance.Amount.Int64())
}

func TestSettleMultiRedeem(t *testing.T) {
k, ctx := testkeeper.MockEVMKeeper()

_, addr1 := testkeeper.MockAddressPair()
_, addr2 := testkeeper.MockAddressPair()
_, addr3 := testkeeper.MockAddressPair()
amt := sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1)))
k.BankKeeper().MintCoins(ctx, types.ModuleName, amt)
k.BankKeeper().SendCoinsFromModuleToAccount(ctx, types.ModuleName, sdk.AccAddress(addr1[:]), amt)
k.BankKeeper().MintCoins(ctx, types.ModuleName, amt)
k.BankKeeper().SendCoinsFromModuleToAccount(ctx, types.ModuleName, sdk.AccAddress(addr2[:]), amt)
k.BankKeeper().MintCoins(ctx, types.ModuleName, amt)
k.BankKeeper().SendCoinsFromModuleToAccount(ctx, types.ModuleName, sdk.AccAddress(addr3[:]), amt)

// addr1 send one Wei to addr3
// addr2 send one Wei to addr3
// escrow 1 would have a two-sei surplus
s := state.NewDBImpl(ctx.WithTxIndex(1), k, false)
s.SubBalance(addr1, big.NewInt(1))
s.AddBalance(addr3, big.NewInt(1))
s.SubBalance(addr2, big.NewInt(1))
s.AddBalance(addr3, big.NewInt(1))
require.Nil(t, s.Finalize())
k.AppendToEvmTxDeferredInfo(ctx.WithTxIndex(1), ethtypes.Bloom{}, common.Hash{})

// addr3 send one wei to addr1
// addr3 send one wei to addr2
// addr3 send one wei to addr1
// escrow 2 would have a one-sei deficit
s = state.NewDBImpl(ctx.WithTxIndex(2), k, false)
s.SubBalance(addr3, big.NewInt(1))
s.AddBalance(addr1, big.NewInt(1))
s.SubBalance(addr3, big.NewInt(1))
s.AddBalance(addr2, big.NewInt(1))
s.SubBalance(addr3, big.NewInt(1))
s.AddBalance(addr1, big.NewInt(1))
require.Nil(t, s.Finalize())
k.AppendToEvmTxDeferredInfo(ctx.WithTxIndex(2), ethtypes.Bloom{}, common.Hash{})

globalEscrowBalance := k.BankKeeper().GetBalance(ctx, k.AccountKeeper().GetModuleAddress(banktypes.WeiEscrowName), "usei")
require.True(t, globalEscrowBalance.Amount.IsZero())

k.SetTxResults([]*abci.ExecTxResult{{Code: 0}, {Code: 0}, {Code: 0}})
deferredInfo := k.GetEVMTxDeferredInfo(ctx)
k.SettleWeiEscrowAccounts(ctx, deferredInfo)
globalEscrowBalance = k.BankKeeper().GetBalance(ctx, k.AccountKeeper().GetModuleAddress(banktypes.WeiEscrowName), "usei")
require.Equal(t, int64(1), globalEscrowBalance.Amount.Int64())
}
5 changes: 3 additions & 2 deletions x/evm/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,18 +171,19 @@ func (am AppModule) BeginBlock(sdk.Context, abci.RequestBeginBlock) {
// returns no validator updates.
func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
evmTxDeferredInfoList := am.keeper.GetEVMTxDeferredInfo(ctx)
am.keeper.SettleWeiEscrowAccounts(ctx, evmTxDeferredInfoList)
denom := am.keeper.GetBaseDenom(ctx)
for _, deferredInfo := range evmTxDeferredInfoList {
idx := deferredInfo.TxIndx
middleManAddress := state.GetMiddleManAddress(sdk.Context{}.WithTxIndex(idx))
middleManAddress := state.GetMiddleManAddress(idx)
balance := am.keeper.BankKeeper().GetBalance(ctx, middleManAddress, denom)
weiBalance := am.keeper.BankKeeper().GetWeiBalance(ctx, middleManAddress)
if !balance.Amount.IsZero() || !weiBalance.IsZero() {
if err := am.keeper.BankKeeper().SendCoinsAndWei(ctx, middleManAddress, am.keeper.AccountKeeper().GetModuleAddress(types.ModuleName), nil, denom, balance.Amount, weiBalance); err != nil {
panic(err)
}
}
coinbaseAddress := state.GetCoinbaseAddress(sdk.Context{}.WithTxIndex(idx))
coinbaseAddress := state.GetCoinbaseAddress(idx)
balance = am.keeper.BankKeeper().GetBalance(ctx, coinbaseAddress, denom)
weiBalance = am.keeper.BankKeeper().GetWeiBalance(ctx, coinbaseAddress)
if !balance.Amount.IsZero() || !weiBalance.IsZero() {
Expand Down
2 changes: 1 addition & 1 deletion x/evm/state/balance.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,5 @@ func (s *DBImpl) getSeiAddress(evmAddr common.Address) sdk.AccAddress {

func (s *DBImpl) send(from sdk.AccAddress, to sdk.AccAddress, amt *big.Int) {
usei, wei := SplitUseiWeiAmount(amt)
s.err = s.k.BankKeeper().SendCoinsAndWei(s.ctx, from, to, nil, s.k.GetBaseDenom(s.ctx), sdk.NewIntFromBigInt(usei), sdk.NewIntFromBigInt(wei))
s.err = s.k.BankKeeper().SendCoinsAndWei(s.ctx, from, to, s.weiEscrowAddress, s.k.GetBaseDenom(s.ctx), sdk.NewIntFromBigInt(usei), sdk.NewIntFromBigInt(wei))
}
2 changes: 1 addition & 1 deletion x/evm/state/balance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func TestAddBalance(t *testing.T) {
k, ctx := testkeeper.MockEVMKeeper()
amt := sdk.NewCoins(sdk.NewCoin(k.GetBaseDenom(ctx), sdk.NewInt(15)))
k.BankKeeper().MintCoins(ctx, types.ModuleName, amt)
k.BankKeeper().SendCoinsFromModuleToAccount(ctx, types.ModuleName, state.GetMiddleManAddress(ctx), amt)
k.BankKeeper().SendCoinsFromModuleToAccount(ctx, types.ModuleName, state.GetMiddleManAddress(ctx.TxIndex()), amt)
db := state.NewDBImpl(ctx, k, false)
seiAddr, evmAddr := testkeeper.MockAddressPair()
require.Equal(t, big.NewInt(0), db.GetBalance(evmAddr))
Expand Down
2 changes: 1 addition & 1 deletion x/evm/state/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func TestEmpty(t *testing.T) {

// has balance
k.BankKeeper().MintCoins(statedb.Ctx(), types.ModuleName, sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1))))
k.BankKeeper().SendCoinsFromModuleToAccount(statedb.Ctx(), types.ModuleName, state.GetMiddleManAddress(ctx), sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1))))
k.BankKeeper().SendCoinsFromModuleToAccount(statedb.Ctx(), types.ModuleName, state.GetMiddleManAddress(ctx.TxIndex()), sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1))))
statedb.AddBalance(addr, big.NewInt(1000000000000))
require.False(t, statedb.Empty(addr))

Expand Down
2 changes: 1 addition & 1 deletion x/evm/state/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func TestCreate(t *testing.T) {
statedb.SetState(evmAddr, key, val)
statedb.SetTransientState(evmAddr, tkey, tval)
k.BankKeeper().MintCoins(ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(k.GetBaseDenom(ctx), sdk.NewInt(10))))
k.BankKeeper().SendCoinsFromModuleToAccount(ctx, types.ModuleName, state.GetMiddleManAddress(ctx), sdk.NewCoins(sdk.NewCoin(k.GetBaseDenom(ctx), sdk.NewInt(10))))
k.BankKeeper().SendCoinsFromModuleToAccount(ctx, types.ModuleName, state.GetMiddleManAddress(ctx.TxIndex()), sdk.NewCoins(sdk.NewCoin(k.GetBaseDenom(ctx), sdk.NewInt(10))))
statedb.AddBalance(evmAddr, big.NewInt(10000000000000))
// recreate an account should clear its state, but keep its balance and transient state
statedb.CreateAccount(evmAddr)
Expand Down
8 changes: 6 additions & 2 deletions x/evm/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ type DBImpl struct {
// no single bottleneck for fee collection. Its account state and balance will be deleted
// before the block commits
coinbaseAddress sdk.AccAddress
// a temporary address that will serve as the Wei escrow account for this specific transaction.
weiEscrowAddress sdk.AccAddress

k EVMKeeper
simulation bool
Expand All @@ -37,8 +39,9 @@ func NewDBImpl(ctx sdk.Context, k EVMKeeper, simulation bool) *DBImpl {
ctx: ctx,
k: k,
snapshottedCtxs: []sdk.Context{},
middleManAddress: GetMiddleManAddress(ctx),
coinbaseAddress: GetCoinbaseAddress(ctx),
middleManAddress: GetMiddleManAddress(ctx.TxIndex()),
coinbaseAddress: GetCoinbaseAddress(ctx.TxIndex()),
weiEscrowAddress: GetTempWeiEscrowAddress(ctx.TxIndex()),
simulation: simulation,
tempStateCurrent: NewTemporaryState(),
}
Expand Down Expand Up @@ -98,6 +101,7 @@ func (s *DBImpl) Copy() vm.StateDB {
k: s.k,
middleManAddress: s.middleManAddress,
coinbaseAddress: s.coinbaseAddress,
weiEscrowAddress: s.weiEscrowAddress,
simulation: s.simulation,
err: s.err,
}
Expand Down
15 changes: 11 additions & 4 deletions x/evm/state/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,26 @@ var UseiToSweiMultiplier = big.NewInt(1_000_000_000_000)

var MiddleManAddressPrefix = []byte("evm_middleman")
var CoinbaseAddressPrefix = []byte("evm_coinbase")
var WeiTmpEscrowPrefix = []byte("evm_weiescrow")

func GetMiddleManAddress(ctx sdk.Context) sdk.AccAddress {
func GetMiddleManAddress(txIdx int) sdk.AccAddress {
txIndexBz := make([]byte, 8)
binary.BigEndian.PutUint64(txIndexBz, uint64(ctx.TxIndex()))
binary.BigEndian.PutUint64(txIndexBz, uint64(txIdx))
return sdk.AccAddress(append(MiddleManAddressPrefix, txIndexBz...))
}

func GetCoinbaseAddress(ctx sdk.Context) sdk.AccAddress {
func GetCoinbaseAddress(txIdx int) sdk.AccAddress {
txIndexBz := make([]byte, 8)
binary.BigEndian.PutUint64(txIndexBz, uint64(ctx.TxIndex()))
binary.BigEndian.PutUint64(txIndexBz, uint64(txIdx))
return sdk.AccAddress(append(CoinbaseAddressPrefix, txIndexBz...))
}

func GetTempWeiEscrowAddress(txIdx int) sdk.AccAddress {
txIndexBz := make([]byte, 8)
binary.BigEndian.PutUint64(txIndexBz, uint64(txIdx))
return sdk.AccAddress(append(WeiTmpEscrowPrefix, txIndexBz...))
}

func SplitUseiWeiAmount(amt *big.Int) (usei *big.Int, wei *big.Int) {
wei = new(big.Int).Mod(amt, UseiToSweiMultiplier)
usei = new(big.Int).Quo(amt, UseiToSweiMultiplier)
Expand Down

0 comments on commit 990ecd2

Please sign in to comment.