Skip to content

Commit

Permalink
feat: precompile staking validator info
Browse files Browse the repository at this point in the history
  • Loading branch information
nulnut authored and zakir-code committed Aug 28, 2024
1 parent 8fa9a08 commit 80a2e34
Show file tree
Hide file tree
Showing 14 changed files with 715 additions and 7 deletions.
3 changes: 2 additions & 1 deletion app/keepers/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,8 @@ func NewAppKeeper(
return crosschainprecompile.NewPrecompiledContract(appKeepers.BankKeeper, appKeepers.Erc20Keeper, appKeepers.IBCTransferKeeper, appKeepers.AccountKeeper, appKeepers.GovKeeper, precompileRouter)
},
func(_ sdk.Context, rules ethparams.Rules) vm.PrecompiledContract {
return stakingprecompile.NewPrecompiledContract(appKeepers.BankKeeper, appKeepers.StakingKeeper, appKeepers.DistrKeeper, fxtypes.DefaultDenom, appKeepers.GovKeeper)
return stakingprecompile.NewPrecompiledContract(appKeepers.BankKeeper, appKeepers.StakingKeeper,
appKeepers.DistrKeeper, fxtypes.DefaultDenom, appKeepers.GovKeeper, appKeepers.SlashingKeeper)
},
},
allKeys,
Expand Down
78 changes: 77 additions & 1 deletion contract/IStaking.go

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions solidity/contracts/staking/IStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
pragma solidity ^0.8.0;

interface IStaking {
enum ValidatorSortBy {
Power,
Missed
}

// Deprecated: use delegateV2
function delegate(
string memory _val
Expand Down Expand Up @@ -78,6 +83,14 @@ interface IStaking {
address _spender
) external view returns (uint256 _shares);

function slashingInfo(
string memory _val
) external view returns (bool _jailed, uint256 _missed);

function validatorList(
ValidatorSortBy _sortBy
) external view returns (string[] memory);

event Delegate(
address indexed delegator,
string validator,
Expand Down
12 changes: 12 additions & 0 deletions solidity/contracts/test/StakingTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,16 @@ contract StakingTest is IStaking {
) public view override returns (uint256) {
return StakingCall.allowanceShares(_val, _owner, _spender);
}

function slashingInfo(
string memory _val
) external view override returns (bool _jailed, uint256 _missed) {
return IStaking(StakingCall.STAKING_ADDRESS).slashingInfo(_val);
}

function validatorList(
IStaking.ValidatorSortBy _sortBy
) external view override returns (string[] memory) {
return IStaking(StakingCall.STAKING_ADDRESS).validatorList(_sortBy);
}
}
80 changes: 78 additions & 2 deletions tests/contract/StakingTest.go

Large diffs are not rendered by default.

15 changes: 12 additions & 3 deletions x/staking/precompile/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func NewPrecompiledContract(
distrKeeper distrkeeper.Keeper,
stakingDenom string,
govKeeper GovKeeper,
slashingKeeper SlashingKeeper,
) *Contract {
keeper := &Keeper{
bankKeeper: bankKeeper,
Expand All @@ -36,11 +37,14 @@ func NewPrecompiledContract(
stakingKeeper: stakingKeeper,
stakingMsgServer: stakingkeeper.NewMsgServerImpl(stakingKeeper.Keeper),
stakingDenom: stakingDenom,
slashingKeeper: slashingKeeper,
}

delegateV2 := NewDelegateV2Method(keeper)
redelegateV2 := NewRedelegateV2Method(keeper)
undelegateV2 := NewUndelegateV2Method(keeper)
slashingInfo := NewSlashingInfoMethod(keeper)
validatorList := NewValidatorListMethod(keeper)
return &Contract{
methods: []contract.PrecompileMethod{
NewAllowanceSharesMethod(keeper),
Expand All @@ -58,11 +62,16 @@ func NewPrecompiledContract(
delegateV2,
redelegateV2,
undelegateV2,

slashingInfo,
validatorList,
},
v2Methods: map[string]bool{
string(delegateV2.GetMethodId()): true,
string(redelegateV2.GetMethodId()): true,
string(undelegateV2.GetMethodId()): true,
string(delegateV2.GetMethodId()): true,
string(redelegateV2.GetMethodId()): true,
string(undelegateV2.GetMethodId()): true,
string(slashingInfo.GetMethodId()): true,
string(validatorList.GetMethodId()): true,
},
govKeeper: govKeeper,
}
Expand Down
2 changes: 2 additions & 0 deletions x/staking/precompile/contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ const (
TestApproveSharesName = "approveShares"
TestTransferSharesName = "transferShares"
TestTransferFromSharesName = "transferFromShares"
TestSlashingInfoName = "slashingInfo"
TestValidatorListName = "validatorList"
)

type PrecompileTestSuite struct {
Expand Down
6 changes: 6 additions & 0 deletions x/staking/precompile/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/ethereum/go-ethereum/common"
evmtypes "github.com/evmos/ethermint/x/evm/types"
Expand Down Expand Up @@ -38,6 +39,7 @@ type StakingKeeper interface {
BeginRedelegation(
ctx sdk.Context, delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress, sharesAmount sdk.Dec,
) (completionTime time.Time, err error)
GetLastValidators(ctx sdk.Context) (validators []stakingtypes.Validator)
}

type DistrKeeper interface {
Expand All @@ -60,3 +62,7 @@ type EvmKeeper interface {
type GovKeeper interface {
CheckDisabledPrecompiles(ctx sdk.Context, contractAddress common.Address, methodId []byte) error
}

type SlashingKeeper interface {
GetValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress) (info slashingtypes.ValidatorSigningInfo, found bool)
}
1 change: 1 addition & 0 deletions x/staking/precompile/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type Keeper struct {
distrMsgServer distrtypes.MsgServer
stakingKeeper StakingKeeper
stakingMsgServer stakingtypes.MsgServer
slashingKeeper SlashingKeeper
stakingDenom string
}

Expand Down
88 changes: 88 additions & 0 deletions x/staking/precompile/slashing_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package precompile

import (
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/core/vm"

"github.com/functionx/fx-core/v7/x/evm/types"
fxstakingtypes "github.com/functionx/fx-core/v7/x/staking/types"
)

type SlashingInfoMethod struct {
*Keeper
abi.Method
}

func NewSlashingInfoMethod(keeper *Keeper) *SlashingInfoMethod {
return &SlashingInfoMethod{
Keeper: keeper,
Method: fxstakingtypes.GetABI().Methods["slashingInfo"],
}
}

func (m *SlashingInfoMethod) IsReadonly() bool {
return true
}

func (m *SlashingInfoMethod) GetMethodId() []byte {
return m.Method.ID
}

func (m *SlashingInfoMethod) RequiredGas() uint64 {
return 1_000
}

func (m *SlashingInfoMethod) Run(evm *vm.EVM, contract *vm.Contract) ([]byte, error) {
args, err := m.UnpackInput(contract.Input)
if err != nil {
return nil, err
}

stateDB := evm.StateDB.(types.ExtStateDB)
cacheCtx := stateDB.CacheContext()

validator, found := m.Keeper.stakingKeeper.GetValidator(cacheCtx, args.GetValidator())
if !found {
return nil, fmt.Errorf("validator %s not found", args.Validator)
}

consAddr, err := validator.GetConsAddr()
if err != nil {
return nil, err
}

signingInfo, found := m.Keeper.slashingKeeper.GetValidatorSigningInfo(cacheCtx, consAddr)
if !found {
return nil, fmt.Errorf("signing info %s not found", consAddr.String())
}
return m.PackOutput(validator.Jailed, signingInfo.MissedBlocksCounter)
}

func (m *SlashingInfoMethod) PackInput(args fxstakingtypes.SlashingInfoArgs) ([]byte, error) {
arguments, err := m.Method.Inputs.Pack(args.Validator)
if err != nil {
return nil, err
}
return append(m.GetMethodId(), arguments...), nil
}

func (m *SlashingInfoMethod) UnpackInput(data []byte) (*fxstakingtypes.SlashingInfoArgs, error) {
args := new(fxstakingtypes.SlashingInfoArgs)
err := types.ParseMethodArgs(m.Method, args, data[4:])
return args, err
}

func (m *SlashingInfoMethod) PackOutput(jailed bool, missed int64) ([]byte, error) {
return m.Method.Outputs.Pack(jailed, big.NewInt(missed))
}

func (m *SlashingInfoMethod) UnpackOutput(data []byte) (bool, *big.Int, error) {
unpack, err := m.Method.Outputs.Unpack(data)
if err != nil {
return false, nil, err
}
return unpack[0].(bool), unpack[1].(*big.Int), nil
}
115 changes: 115 additions & 0 deletions x/staking/precompile/slashing_info_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package precompile_test

import (
"fmt"
"math/big"
"strings"
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"

"github.com/functionx/fx-core/v7/contract"
testscontract "github.com/functionx/fx-core/v7/tests/contract"
"github.com/functionx/fx-core/v7/testutil/helpers"
"github.com/functionx/fx-core/v7/x/staking/precompile"
"github.com/functionx/fx-core/v7/x/staking/types"
)

func TestSlashingInfoABI(t *testing.T) {
slashingInfoMethod := precompile.NewSlashingInfoMethod(nil)

require.Equal(t, 1, len(slashingInfoMethod.Method.Inputs))
require.Equal(t, 2, len(slashingInfoMethod.Method.Outputs))
}

func (suite *PrecompileTestSuite) TestSlashingInfo() {
slashingInfoMethod := precompile.NewSlashingInfoMethod(nil)
testCases := []struct {
name string
malleate func(val sdk.ValAddress) (types.SlashingInfoArgs, error)
result bool
}{
{
name: "ok",
malleate: func(val sdk.ValAddress) (types.SlashingInfoArgs, error) {
return types.SlashingInfoArgs{
Validator: val.String(),
}, nil
},
result: true,
},
{
name: "failed - invalid validator address",
malleate: func(val sdk.ValAddress) (types.SlashingInfoArgs, error) {
valStr := val.String() + "1"
return types.SlashingInfoArgs{
Validator: valStr,
}, fmt.Errorf("invalid validator address: %s", valStr)
},
result: false,
},

{
name: "contract - ok",
malleate: func(val sdk.ValAddress) (types.SlashingInfoArgs, error) {
return types.SlashingInfoArgs{
Validator: val.String(),
}, nil
},
result: true,
},
{
name: "contract - failed - invalid validator address",
malleate: func(val sdk.ValAddress) (types.SlashingInfoArgs, error) {
valStr := val.String() + "1"
return types.SlashingInfoArgs{
Validator: valStr,
}, fmt.Errorf("invalid validator address: %s", valStr)
},
result: false,
},
}

for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.name), func() {
val := suite.GetFirstValidator()
owner := suite.RandSigner()
spender := suite.RandSigner()
allowanceAmt := helpers.NewRandAmount()

// set allowance
suite.App.StakingKeeper.SetAllowance(suite.Ctx, val.GetOperator(), owner.AccAddress(), spender.AccAddress(), allowanceAmt.BigInt())

args, errResult := tc.malleate(val.GetOperator())

packData, err := slashingInfoMethod.PackInput(args)
suite.Require().NoError(err)
stakingContract := precompile.GetAddress()

if strings.HasPrefix(tc.name, "contract") {
stakingContract = suite.staking
packData, err = contract.MustABIJson(testscontract.StakingTestMetaData.ABI).Pack(TestSlashingInfoName, args.Validator)
suite.Require().NoError(err)
}

res := suite.EthereumTx(owner, stakingContract, big.NewInt(0), packData)

if tc.result {
suite.Require().False(res.Failed(), res.VmError)
jailed, missed, err := slashingInfoMethod.UnpackOutput(res.Ret)
suite.Require().NoError(err)
validator, found := suite.App.StakingKeeper.GetValidator(suite.Ctx, val.GetOperator())
suite.True(found)
suite.Equal(validator.Jailed, jailed)
consAddr, err := validator.GetConsAddr()
suite.NoError(err)
signingInfo, found := suite.App.SlashingKeeper.GetValidatorSigningInfo(suite.Ctx, consAddr)
suite.True(found)
suite.Equal(signingInfo.MissedBlocksCounter, missed.Int64())
} else {
suite.Error(res, errResult)
}
})
}
}
Loading

0 comments on commit 80a2e34

Please sign in to comment.