Skip to content

Commit

Permalink
test(evm): add wasm precompile staking test
Browse files Browse the repository at this point in the history
  • Loading branch information
k-yang committed Jan 26, 2025
1 parent 0988d4b commit 3f5be38
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 7 deletions.
11 changes: 7 additions & 4 deletions x/common/testutil/testapp/test_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"

nibiruapp "github.com/NibiruChain/nibiru/v2/app"
"github.com/NibiruChain/nibiru/v2/app/appconst"
)

// GenesisStateWithSingleValidator initializes GenesisState with a single validator and genesis accounts
Expand All @@ -41,7 +42,7 @@ func GenesisStateWithSingleValidator(codec codec.Codec, genesisState nibiruapp.G

balances = append(balances, banktypes.Balance{
Address: acc.GetAddress().String(),
Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(100000000000000))),
Coins: sdk.NewCoins(sdk.NewCoin(appconst.BondDenom, math.NewInt(100000000000000))),
})

genesisState, err = genesisStateWithValSet(codec, genesisState, valSet, []authtypes.GenesisAccount{acc}, balances...)
Expand Down Expand Up @@ -93,7 +94,9 @@ func genesisStateWithValSet(
delegations = append(delegations, stakingtypes.NewDelegation(genAccs[0].GetAddress(), val.Address.Bytes(), math.LegacyOneDec()))
}
// set validators and delegations
stakingGenesis := stakingtypes.NewGenesisState(stakingtypes.DefaultParams(), validators, delegations)
stakingParams := stakingtypes.DefaultParams()
stakingParams.BondDenom = appconst.BondDenom
stakingGenesis := stakingtypes.NewGenesisState(stakingParams, validators, delegations)
genesisState[stakingtypes.ModuleName] = cdc.MustMarshalJSON(stakingGenesis)

totalSupply := sdk.NewCoins()
Expand All @@ -104,13 +107,13 @@ func genesisStateWithValSet(

for range delegations {
// add delegated tokens to total supply
totalSupply = totalSupply.Add(sdk.NewCoin(sdk.DefaultBondDenom, bondAmt))
totalSupply = totalSupply.Add(sdk.NewCoin(appconst.BondDenom, bondAmt))
}

// add bonded amount to bonded pool module account
balances = append(balances, banktypes.Balance{
Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(),
Coins: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, bondAmt)},
Coins: sdk.Coins{sdk.NewCoin(appconst.BondDenom, bondAmt)},
})

// update total supply
Expand Down
2 changes: 2 additions & 0 deletions x/common/testutil/testapp/testapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (

"github.com/NibiruChain/nibiru/v2/app"
"github.com/NibiruChain/nibiru/v2/app/appconst"
cryptocodec "github.com/NibiruChain/nibiru/v2/eth/crypto/codec"
"github.com/NibiruChain/nibiru/v2/x/common/asset"
"github.com/NibiruChain/nibiru/v2/x/common/denoms"
"github.com/NibiruChain/nibiru/v2/x/common/testutil"
Expand Down Expand Up @@ -126,6 +127,7 @@ func NewNibiruTestApp(gen app.GenesisState, baseAppOptions ...func(*baseapp.Base
logger := log.NewNopLogger()

encoding := app.MakeEncodingConfig()
cryptocodec.RegisterInterfaces(encoding.InterfaceRegistry)
SetDefaultSudoGenesis(gen)

app := app.NewNibiruApp(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"_format": "hh-sol-artifact-1",
"contractName": "TestDirtyStateAttack5",
"sourceName": "contracts/TestDirtyStateAttack5.sol",
"abi": [
{
"inputs": [],
"stateMutability": "payable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "string",
"name": "wasmAddr",
"type": "string"
},
{
"internalType": "bytes",
"name": "msgArgs",
"type": "bytes"
}
],
"name": "attack",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
],
"bytecode": "0x6080604052610760806100136000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c806333c8899714610030575b600080fd5b61004a6004803603810190610045919061028d565b61004c565b005b6000600167ffffffffffffffff8111156100695761006861030e565b5b6040519080825280602002602001820160405280156100a257816020015b61008f6101a4565b8152602001906001900390816100875790505b50905060405180604001604052806040518060400160405280600581526020017f756e6962690000000000000000000000000000000000000000000000000000008152508152602001624c4b40815250816000815181106101065761010561033d565b5b602002602001018190525061080273ffffffffffffffffffffffffffffffffffffffff166361ffaee486868686866040518663ffffffff1660e01b815260040161015495949392919061059f565b6000604051808303816000875af1158015610173573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f8201168201806040525081019061019c91906106e1565b505050505050565b604051806040016040528060608152602001600081525090565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b60008083601f8401126101f7576101f66101d2565b5b8235905067ffffffffffffffff811115610214576102136101d7565b5b6020830191508360018202830111156102305761022f6101dc565b5b9250929050565b60008083601f84011261024d5761024c6101d2565b5b8235905067ffffffffffffffff81111561026a576102696101d7565b5b602083019150836001820283011115610286576102856101dc565b5b9250929050565b600080600080604085870312156102a7576102a66101c8565b5b600085013567ffffffffffffffff8111156102c5576102c46101cd565b5b6102d1878288016101e1565b9450945050602085013567ffffffffffffffff8111156102f4576102f36101cd565b5b61030087828801610237565b925092505092959194509250565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082825260208201905092915050565b82818337600083830152505050565b6000601f19601f8301169050919050565b60006103a9838561036c565b93506103b683858461037d565b6103bf8361038c565b840190509392505050565b600082825260208201905092915050565b60006103e783856103ca565b93506103f483858461037d565b6103fd8361038c565b840190509392505050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561046e578082015181840152602081019050610453565b60008484015250505050565b600061048582610434565b61048f818561043f565b935061049f818560208601610450565b6104a88161038c565b840191505092915050565b6000819050919050565b6104c6816104b3565b82525050565b600060408301600083015184820360008601526104e9828261047a565b91505060208301516104fe60208601826104bd565b508091505092915050565b600061051583836104cc565b905092915050565b6000602082019050919050565b600061053582610408565b61053f8185610413565b93508360208202850161055185610424565b8060005b8581101561058d578484038952815161056e8582610509565b94506105798361051d565b925060208a01995050600181019050610555565b50829750879550505050505092915050565b600060608201905081810360008301526105ba81878961039d565b905081810360208301526105cf8185876103db565b905081810360408301526105e3818461052a565b90509695505050505050565b600080fd5b6105fd8261038c565b810181811067ffffffffffffffff8211171561061c5761061b61030e565b5b80604052505050565b600061062f6101be565b905061063b82826105f4565b919050565b600067ffffffffffffffff82111561065b5761065a61030e565b5b6106648261038c565b9050602081019050919050565b600061068461067f84610640565b610625565b9050828152602081018484840111156106a05761069f6105ef565b5b6106ab848285610450565b509392505050565b600082601f8301126106c8576106c76101d2565b5b81516106d8848260208601610671565b91505092915050565b6000602082840312156106f7576106f66101c8565b5b600082015167ffffffffffffffff811115610715576107146101cd565b5b610721848285016106b3565b9150509291505056fea2646970667358221220035e628d5c38012c486ab00ce495fa418401de59d5a1cbfe1e48245374f7d1be64736f6c63430008180033",
"deployedBytecode": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c806333c8899714610030575b600080fd5b61004a6004803603810190610045919061028d565b61004c565b005b6000600167ffffffffffffffff8111156100695761006861030e565b5b6040519080825280602002602001820160405280156100a257816020015b61008f6101a4565b8152602001906001900390816100875790505b50905060405180604001604052806040518060400160405280600581526020017f756e6962690000000000000000000000000000000000000000000000000000008152508152602001624c4b40815250816000815181106101065761010561033d565b5b602002602001018190525061080273ffffffffffffffffffffffffffffffffffffffff166361ffaee486868686866040518663ffffffff1660e01b815260040161015495949392919061059f565b6000604051808303816000875af1158015610173573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f8201168201806040525081019061019c91906106e1565b505050505050565b604051806040016040528060608152602001600081525090565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b60008083601f8401126101f7576101f66101d2565b5b8235905067ffffffffffffffff811115610214576102136101d7565b5b6020830191508360018202830111156102305761022f6101dc565b5b9250929050565b60008083601f84011261024d5761024c6101d2565b5b8235905067ffffffffffffffff81111561026a576102696101d7565b5b602083019150836001820283011115610286576102856101dc565b5b9250929050565b600080600080604085870312156102a7576102a66101c8565b5b600085013567ffffffffffffffff8111156102c5576102c46101cd565b5b6102d1878288016101e1565b9450945050602085013567ffffffffffffffff8111156102f4576102f36101cd565b5b61030087828801610237565b925092505092959194509250565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082825260208201905092915050565b82818337600083830152505050565b6000601f19601f8301169050919050565b60006103a9838561036c565b93506103b683858461037d565b6103bf8361038c565b840190509392505050565b600082825260208201905092915050565b60006103e783856103ca565b93506103f483858461037d565b6103fd8361038c565b840190509392505050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561046e578082015181840152602081019050610453565b60008484015250505050565b600061048582610434565b61048f818561043f565b935061049f818560208601610450565b6104a88161038c565b840191505092915050565b6000819050919050565b6104c6816104b3565b82525050565b600060408301600083015184820360008601526104e9828261047a565b91505060208301516104fe60208601826104bd565b508091505092915050565b600061051583836104cc565b905092915050565b6000602082019050919050565b600061053582610408565b61053f8185610413565b93508360208202850161055185610424565b8060005b8581101561058d578484038952815161056e8582610509565b94506105798361051d565b925060208a01995050600181019050610555565b50829750879550505050505092915050565b600060608201905081810360008301526105ba81878961039d565b905081810360208301526105cf8185876103db565b905081810360408301526105e3818461052a565b90509695505050505050565b600080fd5b6105fd8261038c565b810181811067ffffffffffffffff8211171561061c5761061b61030e565b5b80604052505050565b600061062f6101be565b905061063b82826105f4565b919050565b600067ffffffffffffffff82111561065b5761065a61030e565b5b6106648261038c565b9050602081019050919050565b600061068461067f84610640565b610625565b9050828152602081018484840111156106a05761069f6105ef565b5b6106ab848285610450565b509392505050565b600082601f8301126106c8576106c76101d2565b5b81516106d8848260208601610671565b91505092915050565b6000602082840312156106f7576106f66101c8565b5b600082015167ffffffffffffffff811115610715576107146101cd565b5b610721848285016106b3565b9150509291505056fea2646970667358221220035e628d5c38012c486ab00ce495fa418401de59d5a1cbfe1e48245374f7d1be64736f6c63430008180033",
"linkReferences": {},
"deployedLinkReferences": {}
}
20 changes: 20 additions & 0 deletions x/evm/embeds/contracts/TestDirtyStateAttack5.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

// Uncomment this line to use console.log
// import "hardhat/console.sol";
import "./Wasm.sol";
import "./NibiruEvmUtils.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract TestDirtyStateAttack5 {
constructor() payable {}

function attack(string calldata wasmAddr, bytes calldata msgArgs) external {
INibiruEvm.BankCoin[] memory funds = new INibiruEvm.BankCoin[](1);
funds[0] = INibiruEvm.BankCoin({denom: "unibi", amount: 5e6}); // 5 NIBI

WASM_PRECOMPILE.execute(wasmAddr, msgArgs, funds);
}
}
8 changes: 8 additions & 0 deletions x/evm/embeds/embeds.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ var (
testDirtyStateAttack3 []byte
//go:embed artifacts/contracts/TestDirtyStateAttack4.sol/TestDirtyStateAttack4.json
testDirtyStateAttack4 []byte
//go:embed artifacts/contracts/TestDirtyStateAttack5.sol/TestDirtyStateAttack5.json
testDirtyStateAttack5 []byte
)

var (
Expand Down Expand Up @@ -182,6 +184,11 @@ var (
Name: "TestDirtyStateAttack4.sol",
EmbedJSON: testDirtyStateAttack4,
}
// SmartContract_TestDirtyStateAttack5 is a test contract that calls a wasm contract with 5 NIBI
SmartContract_TestDirtyStateAttack5 = CompiledEvmContract{
Name: "TestDirtyStateAttack5.sol",
EmbedJSON: testDirtyStateAttack5,
}
)

func init() {
Expand All @@ -205,6 +212,7 @@ func init() {
SmartContract_TestDirtyStateAttack2.MustLoad()
SmartContract_TestDirtyStateAttack3.MustLoad()
SmartContract_TestDirtyStateAttack4.MustLoad()
SmartContract_TestDirtyStateAttack5.MustLoad()
}

type CompiledEvmContract struct {
Expand Down
32 changes: 32 additions & 0 deletions x/evm/keeper/bank_extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,38 @@ func (bk NibiruBankKeeper) UndelegateCoins(
)
}

func (bk NibiruBankKeeper) DelegateCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error {
return bk.ForceGasInvariant(
ctx,
func(ctx sdk.Context) error {
return bk.BaseKeeper.DelegateCoinsFromAccountToModule(ctx, senderAddr, recipientModule, amt)
},
func(ctx sdk.Context) {
if findEtherBalanceChangeFromCoins(amt) {
bk.SyncStateDBWithAccount(ctx, senderAddr)
moduleBech32Addr := auth.NewModuleAddress(recipientModule)
bk.SyncStateDBWithAccount(ctx, moduleBech32Addr)
}
},
)
}

func (bk NibiruBankKeeper) UndelegateCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error {
return bk.ForceGasInvariant(
ctx,
func(ctx sdk.Context) error {
return bk.BaseKeeper.UndelegateCoinsFromModuleToAccount(ctx, senderModule, recipientAddr, amt)
},
func(ctx sdk.Context) {
if findEtherBalanceChangeFromCoins(amt) {
moduleBech32Addr := auth.NewModuleAddress(senderModule)
bk.SyncStateDBWithAccount(ctx, moduleBech32Addr)
bk.SyncStateDBWithAccount(ctx, recipientAddr)
}
},
)
}

func (bk NibiruBankKeeper) MintCoins(
ctx sdk.Context,
moduleName string,
Expand Down
8 changes: 8 additions & 0 deletions x/evm/precompile/test/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ func SetupWasmContracts(deps *evmtest.TestDeps, s *suite.Suite) (
InstantiateMsg: []byte(`{}`),
Label: "https://github.com/k-yang/nibiru-wasm-plus/tree/main/contracts/bank-transfer/",
},
{
InstantiateMsg: []byte(`{}`),
Label: "https://github.com/k-yang/nibiru-wasm-plus/tree/main/contracts/staking/",
},
}

for i, wasmCode := range wasmCodes {
Expand Down Expand Up @@ -102,6 +106,10 @@ func deployWasmBytecode(
// https://github.com/k-yang/nibiru-wasm-plus/tree/main/contracts/bank-transfer/
"x/evm/precompile/test/bank_transfer.wasm",

// staking.wasm is a compiled version of:
// https://github.com/k-yang/nibiru-wasm-plus/tree/main/contracts/staking/
"x/evm/precompile/test/staking.wasm",

// Add other wasm bytecode here if needed...
} {
binPath := path.Join(rootPath, wasmFile)
Expand Down
Binary file modified x/evm/precompile/test/staking.wasm
Binary file not shown.
108 changes: 105 additions & 3 deletions x/evm/precompile/wasm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import (
"math/big"
"testing"

"cosmossdk.io/math"
wasm "github.com/CosmWasm/wasmd/x/wasm/types"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/stretchr/testify/suite"

"github.com/NibiruChain/nibiru/v2/eth"
"github.com/NibiruChain/nibiru/v2/x/common/testutil"
Expand All @@ -17,9 +22,6 @@ import (
"github.com/NibiruChain/nibiru/v2/x/evm/precompile"
"github.com/NibiruChain/nibiru/v2/x/evm/precompile/test"
tokenfactory "github.com/NibiruChain/nibiru/v2/x/tokenfactory/types"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/suite"
)

// rough gas limits for wasm execution - used in tests only
Expand Down Expand Up @@ -668,3 +670,103 @@ func (s *WasmSuite) TestWasmPrecompileDirtyStateAttack4() {
s.Require().Equal(balanceTestContract.Amount.BigInt(), big.NewInt(9e6))
})
}

// TestDirtyStateAttack5
// 1. Deploy a simple wasm contract that stakes NIBI
// 2. Calls the test contract
// a. call the wasm precompile which calls the wasm contract that stakes 5 NIBI
//
// INITIAL STATE:
// - Test contract funds: 10 NIBI
// CONTRACT CALL:
// - Sends 5 NIBI to the wasm contract
// - The wasm contract stakes 5 NIBI
// EXPECTED:
// - Test contract funds: 5 NIBI
// - Staked NIBI from wasm contract: 5 NIBI
// - Wasm contract: 0 NIBI
func (s *WasmSuite) TestWasmPrecompileDirtyStateAttack5() {
deps := evmtest.NewTestDeps()

wasmContracts := test.SetupWasmContracts(&deps, &s.Suite)
wasmContract := wasmContracts[3] // staking.wasm

s.T().Log("Deploy Test Contract")
deployResp, err := evmtest.DeployContract(
&deps,
embeds.SmartContract_TestDirtyStateAttack5,
)
s.Require().NoError(err)
testContractAddr := deployResp.ContractAddr

s.Run("Mint 10 NIBI to test contract", func() {
s.Require().NoError(testapp.FundAccount(
deps.App.BankKeeper,
deps.Ctx,
eth.EthAddrToNibiruAddr(testContractAddr),
sdk.NewCoins(sdk.NewCoin(evm.EVMBankDenom, sdk.NewInt(10e6))),
))
})

validator := evmtest.NewEthPrivAcc()
s.Run("create validator", func() {
s.Require().NoError(testapp.FundAccount(
deps.App.BankKeeper,
deps.Ctx,
validator.NibiruAddr,
sdk.NewCoins(sdk.NewCoin(evm.EVMBankDenom, sdk.NewInt(10e6))),
))

createValMsg, err := stakingtypes.NewMsgCreateValidator(
sdk.ValAddress(validator.NibiruAddr),
validator.PrivKey.PubKey(),
sdk.NewCoin(evm.EVMBankDenom, sdk.NewInt(10e6)),
stakingtypes.NewDescription("validator0", "", "", "", ""),
stakingtypes.NewCommissionRates(sdk.NewDec(1), sdk.NewDec(1), sdk.NewDec(1)),
math.OneInt(),
)
s.Require().NoError(err)

stakingMsgServer := stakingkeeper.NewMsgServerImpl(deps.App.StakingKeeper)
resp, err := stakingMsgServer.CreateValidator(deps.Ctx, createValMsg)
s.Require().NoError(err)
s.Require().NotNil(resp)
})

s.Run("call test contract", func() {
msgArgsBz := []byte(fmt.Sprintf(`{"run": {"validator": "%s"}}`, sdk.ValAddress(validator.NibiruAddr).String()))
contractInput, err := embeds.SmartContract_TestDirtyStateAttack5.ABI.Pack(
"attack",
wasmContract.String(),
msgArgsBz,
)
s.Require().NoError(err)

evmObj, _ := deps.NewEVM()
_, err = deps.EvmKeeper.CallContractWithInput(
deps.Ctx,
evmObj,
deps.Sender.EthAddr,
&testContractAddr,
true,
contractInput,
evmtest.FunTokenGasLimitSendToEvm,
)
s.Require().NoError(err)

testContractBalance := deps.App.BankKeeper.GetBalance(deps.Ctx, eth.EthAddrToNibiruAddr(testContractAddr), evm.EVMBankDenom)
s.Require().Equal(testContractBalance.Amount.BigInt(), big.NewInt(5e6))

wasmContractBalance := deps.App.BankKeeper.GetBalance(deps.Ctx, wasmContract, evm.EVMBankDenom)
s.Require().Equal(wasmContractBalance.Amount.BigInt(), big.NewInt(0))

delegations := deps.App.StakingKeeper.GetAllDelegatorDelegations(deps.Ctx, wasmContract)
s.Require().Equal(len(delegations), 1)
s.Require().Equal(sdk.ValAddress(validator.NibiruAddr).String(), delegations[0].ValidatorAddress)

// after converting the wasm contract address to an eth address, check the balances
wasmContractEthAddr := eth.NibiruAddrToEthAddr(wasmContract)
balance := deps.App.BankKeeper.GetBalance(deps.Ctx, eth.EthAddrToNibiruAddr(wasmContractEthAddr), evm.EVMBankDenom)
s.Require().Equal(big.NewInt(0), balance.Amount.BigInt())
})
}

0 comments on commit 3f5be38

Please sign in to comment.