Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions precompiles/distribution/Distribution.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @title Distribution Precompile Interface
/// @notice This interface provides access to Cosmos SDK distribution module functionality
/// @dev The distribution precompile is deployed at a fixed address and allows EVM contracts to interact with staking rewards
address constant DISTR_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000001007;

/// @notice Global instance of the distribution precompile contract
IDistr constant DISTR_CONTRACT = IDistr(
DISTR_PRECOMPILE_ADDRESS
);
Expand All @@ -15,6 +12,11 @@ IDistr constant DISTR_CONTRACT = IDistr(
/// @notice Interface for interacting with the Cosmos SDK distribution module
/// @dev This interface allows managing staking rewards, commission, and withdrawal addresses
interface IDistr {
event WithdrawAddressSet(address indexed delegator, address withdrawAddr);
event DelegationRewardsWithdrawn(address indexed delegator, string validator, uint256 amount);
event MultipleDelegationRewardsWithdrawn(address indexed delegator, string[] validators, uint256[] amounts);
event ValidatorCommissionWithdrawn(string indexed validator, uint256 amount);

// Transactions

/// @notice Sets the withdrawal address for the caller's staking rewards
Expand Down Expand Up @@ -46,7 +48,7 @@ interface IDistr {
/// @dev Returns rewards from all validators the address has delegated to
/// @param delegatorAddress The EVM address of the delegator
/// @return rewards Structured data containing all pending rewards
function rewards(address delegatorAddress) external view returns (Rewards rewards);
function rewards(address delegatorAddress) external view returns (Rewards memory rewards);

/// @notice Represents a coin/token with amount, decimals, and denomination
/// @dev Used to represent various tokens in the Cosmos ecosystem
Expand Down
2 changes: 1 addition & 1 deletion precompiles/distribution/abi.json
Original file line number Diff line number Diff line change
@@ -1 +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"},{"inputs":[{"internalType":"string[]","name":"validators","type":"string[]"}],"name":"withdrawMultipleDelegationRewards","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawValidatorCommission","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"delegatorAddress","type":"address"}],"name":"rewards","outputs":[{"components":[{"components":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"decimals","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct Coin[]","name":"coins","type":"tuple[]"},{"internalType":"string","name":"validator_address","type":"string"}],"internalType":"struct Reward[]","name":"rewards","type":"tuple[]"},{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"decimals","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct Coin[]","name":"total","type":"tuple[]"}],"internalType":"struct Rewards","name":"rewards","type":"tuple"}],"stateMutability":"view","type":"function"}]
[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"string","name":"validator","type":"string"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"DelegationRewardsWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"string[]","name":"validators","type":"string[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"MultipleDelegationRewardsWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"string","name":"validator","type":"string"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ValidatorCommissionWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"address","name":"withdrawAddr","type":"address"}],"name":"WithdrawAddressSet","type":"event"},{"inputs":[{"internalType":"address","name":"delegatorAddress","type":"address"}],"name":"rewards","outputs":[{"components":[{"components":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"decimals","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct IDistr.Coin[]","name":"coins","type":"tuple[]"},{"internalType":"string","name":"validator_address","type":"string"}],"internalType":"struct IDistr.Reward[]","name":"rewards","type":"tuple[]"},{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"decimals","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct IDistr.Coin[]","name":"total","type":"tuple[]"}],"internalType":"struct IDistr.Rewards","name":"rewards","type":"tuple"}],"stateMutability":"view","type":"function"},{"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"},{"inputs":[{"internalType":"string[]","name":"validators","type":"string[]"}],"name":"withdrawMultipleDelegationRewards","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawValidatorCommission","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]
92 changes: 81 additions & 11 deletions precompiles/distribution/distribution.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"

pcommon "github.com/sei-protocol/sei-chain/precompiles/common"
"github.com/sei-protocol/sei-chain/precompiles/utils"
Expand All @@ -25,12 +26,24 @@ const (
WithdrawMultipleDelegationRewardsMethod = "withdrawMultipleDelegationRewards"
WithdrawValidatorCommissionMethod = "withdrawValidatorCommission"
RewardsMethod = "rewards"

SetWithdrawAddressEvent = "WithdrawAddressSet"
DelegationRewardsEvent = "DelegationRewardsWithdrawn"
MultipleDelegationRewardsEvent = "MultipleDelegationRewardsWithdrawn"
ValidatorCommissionEvent = "ValidatorCommissionWithdrawn"
)

const (
DistrAddress = "0x0000000000000000000000000000000000001007"
)

var (
SetWithdrawAddressEventSig = crypto.Keccak256Hash([]byte("WithdrawAddressSet(address,address)"))
DelegationRewardsEventSig = crypto.Keccak256Hash([]byte("DelegationRewardsWithdrawn(address,string,uint256)"))
MultipleDelegationRewardsEventSig = crypto.Keccak256Hash([]byte("MultipleDelegationRewardsWithdrawn(address,string[],uint256[])"))
ValidatorCommissionEventSig = crypto.Keccak256Hash([]byte("ValidatorCommissionWithdrawn(string,uint256)"))
)

// Embed abi json file to the executable binary. Needed when importing as dependency.
//
//go:embed abi.json
Expand All @@ -46,6 +59,8 @@ type PrecompileExecutor struct {
WithdrawMultipleDelegationRewardsID []byte
WithdrawValidatorCommissionID []byte
RewardsID []byte

abi abi.ABI
}

func NewPrecompile(keepers utils.Keepers) (*pcommon.DynamicGasPrecompile, error) {
Expand All @@ -55,6 +70,7 @@ func NewPrecompile(keepers utils.Keepers) (*pcommon.DynamicGasPrecompile, error)
distrKeeper: keepers.DistributionK(),
evmKeeper: keepers.EVMK(),
address: common.HexToAddress(DistrAddress),
abi: newAbi,
}

for name, m := range newAbi.Methods {
Expand Down Expand Up @@ -84,25 +100,25 @@ func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller
if readOnly {
return nil, 0, errors.New("cannot call distr precompile from staticcall")
}
return p.setWithdrawAddress(ctx, method, caller, args, value)
return p.setWithdrawAddress(ctx, method, caller, args, value, evm)
case WithdrawDelegationRewardsMethod:
if readOnly {
return nil, 0, errors.New("cannot call distr precompile from staticcall")
}
return p.withdrawDelegationRewards(ctx, method, caller, args, value)
return p.withdrawDelegationRewards(ctx, method, caller, args, value, evm)
case WithdrawMultipleDelegationRewardsMethod:
if readOnly {
return nil, 0, errors.New("cannot call distr precompile from staticcall")
}
return p.withdrawMultipleDelegationRewards(ctx, method, caller, args, value)
return p.withdrawMultipleDelegationRewards(ctx, method, caller, args, value, evm)
case WithdrawValidatorCommissionMethod:
if err = pcommon.ValidateNonPayable(value); err != nil {
return nil, 0, err
}
if readOnly {
return nil, 0, errors.New("cannot call distr precompile from staticcall")
}
return p.withdrawValidatorCommission(ctx, method, caller)
return p.withdrawValidatorCommission(ctx, method, caller, evm)
case RewardsMethod:
return p.rewards(ctx, method, args)
}
Expand All @@ -113,7 +129,7 @@ func (p PrecompileExecutor) EVMKeeper() utils.EVMKeeper {
return p.evmKeeper
}

func (p PrecompileExecutor) setWithdrawAddress(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) {
func (p PrecompileExecutor) setWithdrawAddress(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM) (ret []byte, remainingGas uint64, rerr error) {
defer func() {
if err := recover(); err != nil {
ret = nil
Expand Down Expand Up @@ -148,10 +164,23 @@ func (p PrecompileExecutor) setWithdrawAddress(ctx sdk.Context, method *abi.Meth
}
ret, rerr = method.Outputs.Pack(true)
remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper)

logData, err := p.abi.Events[SetWithdrawAddressEvent].Inputs.NonIndexed().Pack(args[0].(common.Address))
if err != nil {
rerr = err
return
}
if err := pcommon.EmitEVMLog(evm, p.address, []common.Hash{
SetWithdrawAddressEventSig,
common.BytesToHash(caller.Bytes()),
}, logData); err != nil {
rerr = err
return
}
return
}

func (p PrecompileExecutor) withdrawDelegationRewards(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) {
func (p PrecompileExecutor) withdrawDelegationRewards(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM) (ret []byte, remainingGas uint64, rerr error) {
defer func() {
if err := recover(); err != nil {
ret = nil
Expand All @@ -171,13 +200,26 @@ func (p PrecompileExecutor) withdrawDelegationRewards(ctx sdk.Context, method *a
rerr = err
return
}
_, err = p.withdraw(ctx, delegator, args[0].(string))
amts, err := p.withdraw(ctx, delegator, args[0].(string))
if err != nil {
rerr = err
return
}
ret, rerr = method.Outputs.Pack(true)
remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper)

logData, err := p.abi.Events[DelegationRewardsEvent].Inputs.NonIndexed().Pack(args[0].(string), amts.AmountOf(sdk.DefaultBondDenom).BigInt())
if err != nil {
rerr = err
return
}
if err := pcommon.EmitEVMLog(evm, p.address, []common.Hash{
DelegationRewardsEventSig,
common.BytesToHash(caller.Bytes()),
}, logData); err != nil {
rerr = err
return
}
return
}

Expand Down Expand Up @@ -210,7 +252,7 @@ func (p PrecompileExecutor) getDelegator(ctx sdk.Context, caller common.Address)
return delegator, nil
}

func (p PrecompileExecutor) withdrawMultipleDelegationRewards(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) {
func (p PrecompileExecutor) withdrawMultipleDelegationRewards(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM) (ret []byte, remainingGas uint64, rerr error) {
defer func() {
if err := recover(); err != nil {
ret = nil
Expand All @@ -231,16 +273,31 @@ func (p PrecompileExecutor) withdrawMultipleDelegationRewards(ctx sdk.Context, m
return
}
validators := args[0].([]string)
amts := make([]*big.Int, 0, len(validators))
for _, valAddr := range validators {
_, err := p.withdraw(ctx, delegator, valAddr)
amt, err := p.withdraw(ctx, delegator, valAddr)
if err != nil {
rerr = err
return
}
amts = append(amts, amt.AmountOf(sdk.DefaultBondDenom).BigInt())
}

ret, rerr = method.Outputs.Pack(true)
remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper)

logData, err := p.abi.Events[MultipleDelegationRewardsEvent].Inputs.NonIndexed().Pack(validators, amts)
if err != nil {
rerr = err
return
}
if err := pcommon.EmitEVMLog(evm, p.address, []common.Hash{
MultipleDelegationRewardsEventSig,
common.BytesToHash(caller.Bytes()),
}, logData); err != nil {
rerr = err
return
}
return
}

Expand Down Expand Up @@ -342,7 +399,7 @@ func getResponseOutput(response *distrtypes.QueryDelegationTotalRewardsResponse)
}
}

func (p PrecompileExecutor) withdrawValidatorCommission(ctx sdk.Context, method *abi.Method, caller common.Address) (ret []byte, remainingGas uint64, rerr error) {
func (p PrecompileExecutor) withdrawValidatorCommission(ctx sdk.Context, method *abi.Method, caller common.Address, evm *vm.EVM) (ret []byte, remainingGas uint64, rerr error) {
defer func() {
if err := recover(); err != nil {
ret = nil
Expand All @@ -367,13 +424,26 @@ func (p PrecompileExecutor) withdrawValidatorCommission(ctx sdk.Context, method
}

// Call the distribution keeper to withdraw validator commission
_, err = p.distrKeeper.WithdrawValidatorCommission(ctx, validator)
amts, err := p.distrKeeper.WithdrawValidatorCommission(ctx, validator)
if err != nil {
rerr = err
return
}

ret, rerr = method.Outputs.Pack(true)
remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper)

logData, err := p.abi.Events[ValidatorCommissionEvent].Inputs.NonIndexed().Pack(amts.AmountOf(sdk.DefaultBondDenom).BigInt())
if err != nil {
rerr = err
return
}
if err := pcommon.EmitEVMLog(evm, p.address, []common.Hash{
ValidatorCommissionEventSig,
crypto.Keccak256Hash([]byte(validator.String())),
}, logData); err != nil {
rerr = err
return
}
return
}
22 changes: 21 additions & 1 deletion precompiles/distribution/distribution_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ func TestWithdraw(t *testing.T) {
require.Nil(t, err)
require.Empty(t, res.VmError)
require.Equal(t, withdrawSeiAddr.String(), testApp.DistrKeeper.GetDelegatorWithdrawAddr(ctx, seiAddr).String())
receipt, err := k.GetTransientReceipt(ctx, tx.Hash(), 0)
require.Nil(t, err)
require.Equal(t, 1, len(receipt.Logs))
require.Equal(t, distribution.SetWithdrawAddressEventSig, common.HexToHash(receipt.Logs[0].Topics[0]))
require.Equal(t, common.BytesToHash(evmAddr.Bytes()), common.HexToHash(receipt.Logs[0].Topics[1]))
require.NotEmpty(t, receipt.Logs[0].Data)

// withdraw
args, err = abi.Pack("withdrawDelegationRewards", val.String())
Expand Down Expand Up @@ -154,6 +160,13 @@ func TestWithdraw(t *testing.T) {
// reinitialized
d, found = testApp.StakingKeeper.GetDelegation(ctx, seiAddr, val)
require.True(t, found)

receipt, err = k.GetTransientReceipt(ctx, tx.Hash(), 0)
require.Nil(t, err)
require.Equal(t, 1, len(receipt.Logs))
require.Equal(t, distribution.DelegationRewardsEventSig, common.HexToHash(receipt.Logs[0].Topics[0]))
require.Equal(t, common.BytesToHash(evmAddr.Bytes()), common.HexToHash(receipt.Logs[0].Topics[1]))
require.NotEmpty(t, receipt.Logs[0].Data)
}

func TestWithdrawMultipleDelegationRewards(t *testing.T) {
Expand Down Expand Up @@ -273,7 +286,7 @@ func setWithdrawAddressAndWithdraw(
res, err := msgServer.EVMTransaction(sdk.WrapSDKContext(ctx), req)
require.Nil(t, err)
require.Empty(t, res.VmError)
seiAddr, _ := testkeeper.PrivateKeyToAddresses(privKey)
seiAddr, evmAddr := testkeeper.PrivateKeyToAddresses(privKey)
require.Equal(t, withdrawSeiAddr.String(), testApp.DistrKeeper.GetDelegatorWithdrawAddr(ctx, seiAddr).String())

var validators []string
Expand Down Expand Up @@ -304,6 +317,13 @@ func setWithdrawAddressAndWithdraw(
require.Empty(t, res.VmError)
require.Equal(t, uint64(148290), res.GasUsed)

receipt, err := k.GetTransientReceipt(ctx, tx.Hash(), 0)
require.Nil(t, err)
require.Equal(t, 1, len(receipt.Logs))
require.Equal(t, distribution.MultipleDelegationRewardsEventSig, common.HexToHash(receipt.Logs[0].Topics[0]))
require.Equal(t, common.BytesToHash(evmAddr.Bytes()), common.HexToHash(receipt.Logs[0].Topics[1]))
require.NotEmpty(t, receipt.Logs[0].Data)

// reinitialized
for _, val := range vals {
_, found := testApp.StakingKeeper.GetDelegation(ctx, seiAddr, val)
Expand Down
Loading
Loading