-
Notifications
You must be signed in to change notification settings - Fork 819
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Gov/Distribution precompiles (#1323)
- Loading branch information
Showing
12 changed files
with
655 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
address constant DISTR_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000001007; | ||
|
||
IDistr constant DISTR_CONTRACT = IDistr( | ||
DISTR_PRECOMPILE_ADDRESS | ||
); | ||
|
||
interface IDistr { | ||
// Transactions | ||
function setWithdrawAddress(address withdrawAddr) external returns (bool success); | ||
|
||
function withdrawDelegationRewards(string memory validator) external returns (bool success); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
[{"inputs":[{"internalType":"address","name":"withdrawAddr","type":"address"}],"name":"setWithdrawAddress","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"validator","type":"string"}],"name":"withdrawDelegationRewards","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package distribution | ||
|
||
import ( | ||
"bytes" | ||
"embed" | ||
"errors" | ||
|
||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
"github.com/ethereum/go-ethereum/accounts/abi" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/core/vm" | ||
pcommon "github.com/sei-protocol/sei-chain/precompiles/common" | ||
) | ||
|
||
const ( | ||
SetWithdrawAddressMethod = "setWithdrawAddress" | ||
WithdrawDelegationRewardsMethod = "withdrawDelegationRewards" | ||
) | ||
|
||
const ( | ||
DistrAddress = "0x0000000000000000000000000000000000001007" | ||
) | ||
|
||
var _ vm.PrecompiledContract = &Precompile{} | ||
|
||
// Embed abi json file to the executable binary. Needed when importing as dependency. | ||
// | ||
//go:embed abi.json | ||
var f embed.FS | ||
|
||
func GetABI() abi.ABI { | ||
abiBz, err := f.ReadFile("abi.json") | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
newAbi, err := abi.JSON(bytes.NewReader(abiBz)) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return newAbi | ||
} | ||
|
||
type Precompile struct { | ||
pcommon.Precompile | ||
distrKeeper pcommon.DistributionKeeper | ||
evmKeeper pcommon.EVMKeeper | ||
address common.Address | ||
|
||
SetWithdrawAddrID []byte | ||
WithdrawDelegationRewardsID []byte | ||
} | ||
|
||
func NewPrecompile(distrKeeper pcommon.DistributionKeeper, evmKeeper pcommon.EVMKeeper) (*Precompile, error) { | ||
newAbi := GetABI() | ||
|
||
p := &Precompile{ | ||
Precompile: pcommon.Precompile{ABI: newAbi}, | ||
distrKeeper: distrKeeper, | ||
evmKeeper: evmKeeper, | ||
address: common.HexToAddress(DistrAddress), | ||
} | ||
|
||
for name, m := range newAbi.Methods { | ||
switch name { | ||
case SetWithdrawAddressMethod: | ||
p.SetWithdrawAddrID = m.ID | ||
case WithdrawDelegationRewardsMethod: | ||
p.WithdrawDelegationRewardsID = m.ID | ||
} | ||
} | ||
|
||
return p, nil | ||
} | ||
|
||
// RequiredGas returns the required bare minimum gas to execute the precompile. | ||
func (p Precompile) RequiredGas(input []byte) uint64 { | ||
methodID := input[:4] | ||
|
||
if bytes.Equal(methodID, p.SetWithdrawAddrID) { | ||
return 30000 | ||
} else if bytes.Equal(methodID, p.WithdrawDelegationRewardsID) { | ||
return 50000 | ||
} | ||
panic("unknown method") | ||
} | ||
|
||
func (p Precompile) Address() common.Address { | ||
return p.address | ||
} | ||
|
||
func (p Precompile) Run(evm *vm.EVM, caller common.Address, input []byte) (bz []byte, err error) { | ||
ctx, method, args, err := p.Prepare(evm, input) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
switch method.Name { | ||
case SetWithdrawAddressMethod: | ||
return p.setWithdrawAddress(ctx, method, caller, args) | ||
case WithdrawDelegationRewardsMethod: | ||
return p.withdrawDelegationRewards(ctx, method, caller, args) | ||
} | ||
return | ||
} | ||
|
||
func (p Precompile) setWithdrawAddress(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}) ([]byte, error) { | ||
pcommon.AssertArgsLength(args, 1) | ||
delegator := p.evmKeeper.GetSeiAddressOrDefault(ctx, caller) | ||
withdrawAddr, err := p.accAddressFromArg(ctx, args[0]) | ||
if err != nil { | ||
return nil, err | ||
} | ||
err = p.distrKeeper.SetWithdrawAddr(ctx, delegator, withdrawAddr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return method.Outputs.Pack(true) | ||
} | ||
|
||
func (p Precompile) withdrawDelegationRewards(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}) ([]byte, error) { | ||
pcommon.AssertArgsLength(args, 1) | ||
delegator := p.evmKeeper.GetSeiAddressOrDefault(ctx, caller) | ||
validator, err := sdk.ValAddressFromBech32(args[0].(string)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
_, err = p.distrKeeper.WithdrawDelegationRewards(ctx, delegator, validator) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return method.Outputs.Pack(true) | ||
} | ||
|
||
func (p Precompile) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { | ||
addr := arg.(common.Address) | ||
if addr == (common.Address{}) { | ||
return nil, errors.New("invalid addr") | ||
} | ||
seiAddr, associated := p.evmKeeper.GetSeiAddress(ctx, addr) | ||
if !associated { | ||
return nil, errors.New("cannot use an unassociated address as withdraw address") | ||
} | ||
return seiAddr, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
package distribution_test | ||
|
||
import ( | ||
"encoding/hex" | ||
"math/big" | ||
"testing" | ||
"time" | ||
|
||
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" | ||
crptotypes "github.com/cosmos/cosmos-sdk/crypto/types" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" | ||
"github.com/cosmos/cosmos-sdk/x/staking/teststaking" | ||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" | ||
"github.com/ethereum/go-ethereum/common" | ||
ethtypes "github.com/ethereum/go-ethereum/core/types" | ||
"github.com/ethereum/go-ethereum/crypto" | ||
"github.com/sei-protocol/sei-chain/app" | ||
"github.com/sei-protocol/sei-chain/precompiles/distribution" | ||
"github.com/sei-protocol/sei-chain/precompiles/staking" | ||
testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" | ||
"github.com/sei-protocol/sei-chain/x/evm/ante" | ||
"github.com/sei-protocol/sei-chain/x/evm/keeper" | ||
"github.com/sei-protocol/sei-chain/x/evm/types" | ||
evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" | ||
"github.com/sei-protocol/sei-chain/x/evm/types/ethtx" | ||
minttypes "github.com/sei-protocol/sei-chain/x/mint/types" | ||
"github.com/stretchr/testify/require" | ||
tmtypes "github.com/tendermint/tendermint/proto/tendermint/types" | ||
) | ||
|
||
func TestWithdraw(t *testing.T) { | ||
testApp := testkeeper.EVMTestApp | ||
ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) | ||
distrParams := testApp.DistrKeeper.GetParams(ctx) | ||
distrParams.WithdrawAddrEnabled = true | ||
testApp.DistrKeeper.SetParams(ctx, distrParams) | ||
k := &testApp.EvmKeeper | ||
valPub1 := secp256k1.GenPrivKey().PubKey() | ||
val := setupValidator(t, ctx, testApp, stakingtypes.Unbonded, valPub1) | ||
|
||
// delegate | ||
abi := staking.GetABI() | ||
args, err := abi.Pack("delegate", val.String(), big.NewInt(100)) | ||
|
||
privKey := testkeeper.MockPrivateKey() | ||
testPrivHex := hex.EncodeToString(privKey.Bytes()) | ||
key, _ := crypto.HexToECDSA(testPrivHex) | ||
addr := common.HexToAddress(staking.StakingAddress) | ||
txData := ethtypes.LegacyTx{ | ||
GasPrice: big.NewInt(1000000000000), | ||
Gas: 20000000, | ||
To: &addr, | ||
Value: big.NewInt(0), | ||
Data: args, | ||
Nonce: 0, | ||
} | ||
chainID := k.ChainID(ctx) | ||
evmParams := k.GetParams(ctx) | ||
chainCfg := evmParams.GetChainConfig() | ||
ethCfg := chainCfg.EthereumConfig(chainID) | ||
blockNum := big.NewInt(ctx.BlockHeight()) | ||
signer := ethtypes.MakeSigner(ethCfg, blockNum, uint64(ctx.BlockTime().Unix())) | ||
tx, err := ethtypes.SignTx(ethtypes.NewTx(&txData), signer, key) | ||
require.Nil(t, err) | ||
txwrapper, err := ethtx.NewLegacyTx(tx) | ||
require.Nil(t, err) | ||
req, err := types.NewMsgEVMTransaction(txwrapper) | ||
require.Nil(t, err) | ||
|
||
_, evmAddr := testkeeper.PrivateKeyToAddresses(privKey) | ||
amt := sdk.NewCoins(sdk.NewCoin(k.GetBaseDenom(ctx), sdk.NewInt(200000000))) | ||
require.Nil(t, k.BankKeeper().MintCoins(ctx, evmtypes.ModuleName, sdk.NewCoins(sdk.NewCoin(k.GetBaseDenom(ctx), sdk.NewInt(200000000))))) | ||
require.Nil(t, k.BankKeeper().SendCoinsFromModuleToAccount(ctx, evmtypes.ModuleName, evmAddr[:], amt)) | ||
|
||
msgServer := keeper.NewMsgServerImpl(k) | ||
|
||
ante.Preprocess(ctx, req, k.GetParams(ctx)) | ||
res, err := msgServer.EVMTransaction(sdk.WrapSDKContext(ctx), req) | ||
require.Nil(t, err) | ||
require.Empty(t, res.VmError) | ||
|
||
d, found := testApp.StakingKeeper.GetDelegation(ctx, evmAddr[:], val) | ||
require.True(t, found) | ||
require.Equal(t, int64(100), d.Shares.RoundInt().Int64()) | ||
|
||
// set withdraw addr | ||
withdrawSeiAddr, withdrawAddr := testkeeper.MockAddressPair() | ||
k.SetAddressMapping(ctx, withdrawSeiAddr, withdrawAddr) | ||
abi = distribution.GetABI() | ||
args, err = abi.Pack("setWithdrawAddress", withdrawAddr) | ||
require.Nil(t, err) | ||
addr = common.HexToAddress(distribution.DistrAddress) | ||
txData = ethtypes.LegacyTx{ | ||
GasPrice: big.NewInt(1000000000000), | ||
Gas: 20000000, | ||
To: &addr, | ||
Value: big.NewInt(0), | ||
Data: args, | ||
Nonce: 1, | ||
} | ||
tx, err = ethtypes.SignTx(ethtypes.NewTx(&txData), signer, key) | ||
require.Nil(t, err) | ||
txwrapper, err = ethtx.NewLegacyTx(tx) | ||
require.Nil(t, err) | ||
req, err = types.NewMsgEVMTransaction(txwrapper) | ||
require.Nil(t, err) | ||
|
||
ante.Preprocess(ctx, req, k.GetParams(ctx)) | ||
res, err = msgServer.EVMTransaction(sdk.WrapSDKContext(ctx), req) | ||
require.Nil(t, err) | ||
require.Empty(t, res.VmError) | ||
require.Equal(t, withdrawSeiAddr.String(), testApp.DistrKeeper.GetDelegatorWithdrawAddr(ctx, evmAddr[:]).String()) | ||
|
||
// withdraw | ||
args, err = abi.Pack("withdrawDelegationRewards", val.String()) | ||
require.Nil(t, err) | ||
txData = ethtypes.LegacyTx{ | ||
GasPrice: big.NewInt(1000000000000), | ||
Gas: 20000000, | ||
To: &addr, | ||
Value: big.NewInt(0), | ||
Data: args, | ||
Nonce: 2, | ||
} | ||
tx, err = ethtypes.SignTx(ethtypes.NewTx(&txData), signer, key) | ||
require.Nil(t, err) | ||
txwrapper, err = ethtx.NewLegacyTx(tx) | ||
require.Nil(t, err) | ||
req, err = types.NewMsgEVMTransaction(txwrapper) | ||
require.Nil(t, err) | ||
|
||
ante.Preprocess(ctx, req, k.GetParams(ctx)) | ||
res, err = msgServer.EVMTransaction(sdk.WrapSDKContext(ctx), req) | ||
require.Nil(t, err) | ||
require.Empty(t, res.VmError) | ||
|
||
// reinitialized | ||
d, found = testApp.StakingKeeper.GetDelegation(ctx, evmAddr[:], val) | ||
require.True(t, found) | ||
} | ||
|
||
func setupValidator(t *testing.T, ctx sdk.Context, a *app.App, bondStatus stakingtypes.BondStatus, valPub crptotypes.PubKey) sdk.ValAddress { | ||
valAddr := sdk.ValAddress(valPub.Address()) | ||
bondDenom := a.StakingKeeper.GetParams(ctx).BondDenom | ||
selfBond := sdk.NewCoins(sdk.Coin{Amount: sdk.NewInt(100), Denom: bondDenom}) | ||
|
||
err := a.BankKeeper.MintCoins(ctx, minttypes.ModuleName, selfBond) | ||
require.NoError(t, err) | ||
|
||
err = a.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, sdk.AccAddress(valAddr), selfBond) | ||
require.NoError(t, err) | ||
|
||
sh := teststaking.NewHelper(t, ctx, a.StakingKeeper) | ||
msg := sh.CreateValidatorMsg(valAddr, valPub, selfBond[0].Amount) | ||
sh.Handle(msg, true) | ||
|
||
val, found := a.StakingKeeper.GetValidator(ctx, valAddr) | ||
require.True(t, found) | ||
|
||
val = val.UpdateStatus(bondStatus) | ||
a.StakingKeeper.SetValidator(ctx, val) | ||
|
||
consAddr, err := val.GetConsAddr() | ||
require.NoError(t, err) | ||
|
||
signingInfo := slashingtypes.NewValidatorSigningInfo( | ||
consAddr, | ||
ctx.BlockHeight(), | ||
0, | ||
time.Unix(0, 0), | ||
false, | ||
0, | ||
) | ||
a.SlashingKeeper.SetValidatorSigningInfo(ctx, consAddr, signingInfo) | ||
|
||
return valAddr | ||
} |
Oops, something went wrong.