Skip to content

Commit

Permalink
Merge pull request #35 from MaxMustermann2/feat/all-client-chains-pre…
Browse files Browse the repository at this point in the history
…compile

feat(precompile): add `getClientChains` function
  • Loading branch information
MaxMustermann2 authored Apr 18, 2024
2 parents 11222fb + 3b40a73 commit 91af9db
Show file tree
Hide file tree
Showing 10 changed files with 348 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .github/workflows/consensuswarn.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
steps:
# This is used for warning when a PR touches any of the roots, or any function or method directly or indirectly called by a root
- uses: actions/checkout@v4
- uses: orijtech/consensuswarn@main
- uses: orijtech/consensuswarn@956f047a43f56021a28afdfb2a2291a20955f48d
with:
# example.com/pkg/path.Type.Method
roots: 'github.com/ExocoreNetwork/exocore/app.ExocoreApp.DeliverTx,github.com/ExocoreNetwork/exocore/app.ExocoreApp.BeginBlocker,github.com/ExocoreNetwork/exocore/app.ExocoreApp.EndBlocker'
21 changes: 21 additions & 0 deletions precompiles/clientchains/abi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[
{
"name": "getClientChains",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "success",
"type": "bool",
"internalType": "bool"
},
{
"name": "chainIds",
"type": "uint16[]",
"internalType": "uint16[]"
}
],
"stateMutability": "view",
"payable": false
}
]
119 changes: 119 additions & 0 deletions precompiles/clientchains/clientchains.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package clientchains

import (
"bytes"
"embed"
"fmt"

assetskeeper "github.com/ExocoreNetwork/exocore/x/assets/keeper"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper"
"github.com/ethereum/go-ethereum/accounts/abi"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
cmn "github.com/evmos/evmos/v14/precompiles/common"
)

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

// Precompile defines the precompiled contract for deposit.
type Precompile struct {
cmn.Precompile
assetsKeeper assetskeeper.Keeper
}

// NewPrecompile creates a new deposit Precompile instance as a
// PrecompiledContract interface.
func NewPrecompile(
assetsKeeper assetskeeper.Keeper,
authzKeeper authzkeeper.Keeper,
) (*Precompile, error) {
abiBz, err := f.ReadFile("abi.json")
if err != nil {
return nil, fmt.Errorf("error loading the client chains ABI %s", err)
}

newAbi, err := abi.JSON(bytes.NewReader(abiBz))
if err != nil {
return nil, fmt.Errorf(cmn.ErrInvalidABI, err)
}

return &Precompile{
Precompile: cmn.Precompile{
ABI: newAbi,
AuthzKeeper: authzKeeper,
KvGasConfig: storetypes.KVGasConfig(),
TransientKVGasConfig: storetypes.TransientGasConfig(),
// should be configurable in the future.
ApprovalExpiration: cmn.DefaultExpirationDuration,
},
assetsKeeper: assetsKeeper,
}, nil
}

// Address defines the address of the client chains precompile contract.
// address: 0x0000000000000000000000000000000000000801
func (p Precompile) Address() common.Address {
return common.HexToAddress("0x0000000000000000000000000000000000000801")
}

// RequiredGas calculates the precompiled contract's base gas rate.
func (p Precompile) RequiredGas(input []byte) uint64 {
methodID := input[:4]

method, err := p.MethodById(methodID)
if err != nil {
// This should never happen since this method is going to fail during Run
return 0
}
return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name))
}

// Run executes the precompiled contract client chain methods defined in the ABI.
func (p Precompile) Run(
evm *vm.EVM, contract *vm.Contract, readOnly bool,
) (bz []byte, err error) {
// if the user calls instead of staticcalls, it is their problem. we don't validate that.
ctx, _, method, initialGas, args, err := p.RunSetup(
evm, contract, readOnly, p.IsTransaction,
)
if err != nil {
return nil, err
}

// This handles any out of gas errors that may occur during the execution of a precompile tx
// or query. It avoids panics and returns the out of gas error so the EVM can continue
// gracefully.
defer cmn.HandleGasError(ctx, contract, initialGas, &err)()

bz, err = p.GetClientChains(ctx, method, args)
if err != nil {
ctx.Logger().Error(
"call client chains precompile error",
"module", "client chains precompile",
"err", err,
)
return nil, err
}

cost := ctx.GasMeter().GasConsumed() - initialGas

if !contract.UseGas(cost) {
return nil, vm.ErrOutOfGas
}

return bz, nil
}

// IsTransaction checks if the given methodID corresponds to a transaction (true)
// or query (false).
func (Precompile) IsTransaction(string) bool {
// there are no transaction methods in this precompile, only queries.
return false
}
19 changes: 19 additions & 0 deletions precompiles/clientchains/clientchains.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
pragma solidity >=0.8.17;

/// @dev The CLIENT_CHAINS contract's address.
address constant CLIENT_CHAINS_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000801;

/// @dev The CLIENT_CHAINS contract's instance.
IClientChains constant CLIENT_CHAINS_CONTRACT = IClientChains(
CLIENT_CHAINS_PRECOMPILE_ADDRESS
);

/// @author Exocore Team
/// @title Client Chains Precompile Contract
/// @dev The interface through which solidity contracts will interact with ClientChains
/// @custom:address 0x0000000000000000000000000000000000000801
interface IClientChains {
/// @dev Returns the chain indices of the client chains.
function getClientChains() external view returns (bool, uint16[] memory);
}

100 changes: 100 additions & 0 deletions precompiles/clientchains/clientchains_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package clientchains_test

import (
"math/big"

"github.com/ExocoreNetwork/exocore/app"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
evmtypes "github.com/evmos/evmos/v14/x/evm/types"
)

func (s *ClientChainsPrecompileSuite) TestIsTransaction() {
testCases := []struct {
name string
method string
isTx bool
}{
{
"non existant method",
"HelloFakeMethod",
false,
},
{
"actual method",
"getClientChains",
false,
},
}

for _, tc := range testCases {
s.Run(tc.name, func() {
s.Require().Equal(s.precompile.IsTransaction(tc.method), tc.isTx)
})
}
}

func paddingClientChainAddress(input []byte, outputLength int) []byte {
if len(input) < outputLength {
padding := make([]byte, outputLength-len(input))
return append(input, padding...)
}
return input
}

func (s *ClientChainsPrecompileSuite) TestGetClientChains() {
input, err := s.precompile.Pack("getClientChains")
s.Require().NoError(err, "failed to pack input")
output, err := s.precompile.Methods["getClientChains"].Outputs.Pack(true, []uint16{101})
s.Require().NoError(err, "failed to pack output")
s.Run("get client chains", func() {
s.SetupTest()
baseFee := s.App.FeeMarketKeeper.GetBaseFee(s.Ctx)
contract := vm.NewPrecompile(
vm.AccountRef(s.Address),
s.precompile,
big.NewInt(0),
uint64(1e6),
)
contract.Input = input
contractAddr := contract.Address()
txArgs := evmtypes.EvmTxArgs{
ChainID: s.App.EvmKeeper.ChainID(),
Nonce: 0,
To: &contractAddr,
Amount: nil,
GasLimit: 100000,
GasPrice: app.MainnetMinGasPrices.BigInt(),
GasFeeCap: baseFee,
GasTipCap: big.NewInt(1),
Accesses: &ethtypes.AccessList{},
}
msgEthereumTx := evmtypes.NewTx(&txArgs)
msgEthereumTx.From = s.Address.String()
err := msgEthereumTx.Sign(s.EthSigner, s.Signer)
s.Require().NoError(err, "failed to sign Ethereum message")
proposerAddress := s.Ctx.BlockHeader().ProposerAddress
cfg, err := s.App.EvmKeeper.EVMConfig(
s.Ctx, proposerAddress, s.App.EvmKeeper.ChainID(),
)
s.Require().NoError(err, "failed to instantiate EVM config")
msg, err := msgEthereumTx.AsMessage(s.EthSigner, baseFee)
s.Require().NoError(err, "failed to instantiate Ethereum message")
evm := s.App.EvmKeeper.NewEVM(
s.Ctx, msg, cfg, nil, s.StateDB,
)
params := s.App.EvmKeeper.GetParams(s.Ctx)
activePrecompiles := params.GetActivePrecompilesAddrs()
precompileMap := s.App.EvmKeeper.Precompiles(activePrecompiles...)
err = vm.ValidatePrecompiles(precompileMap, activePrecompiles)
s.Require().NoError(err, "invalid precompiles", activePrecompiles)
evm.WithPrecompiles(precompileMap, activePrecompiles)
bz, err := s.precompile.Run(evm, contract, true)
s.Require().NoError(
err, "expected no error when running the precompile",
)
s.Require().Equal(
output, bz, "the return doesn't match the expected result",
)
})
}
32 changes: 32 additions & 0 deletions precompiles/clientchains/setup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package clientchains_test

import (
"testing"

"github.com/ExocoreNetwork/exocore/precompiles/clientchains"
"github.com/ExocoreNetwork/exocore/testutil"

"github.com/stretchr/testify/suite"
)

var s *ClientChainsPrecompileSuite

type ClientChainsPrecompileSuite struct {
testutil.BaseTestSuite

precompile *clientchains.Precompile
}

func TestPrecompileTestSuite(t *testing.T) {
s = new(ClientChainsPrecompileSuite)
suite.Run(t, s)
}

func (s *ClientChainsPrecompileSuite) SetupTest() {
s.DoSetupTest()
precompile, err := clientchains.NewPrecompile(
s.App.AssetsKeeper, s.App.AuthzKeeper,
)
s.Require().NoError(err)
s.precompile = precompile
}
39 changes: 39 additions & 0 deletions precompiles/clientchains/tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package clientchains

import (
"math"

errorsmod "cosmossdk.io/errors"
assetstypes "github.com/ExocoreNetwork/exocore/x/assets/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/accounts/abi"
)

func (p Precompile) GetClientChains(
ctx sdk.Context,
method *abi.Method,
args []interface{},
) ([]byte, error) {
if len(args) > 0 {
return nil, errorsmod.Wrapf(assetstypes.ErrInvalidInput, "no input is required")
}
infos, err := p.assetsKeeper.GetAllClientChainInfo(ctx)
if err != nil {
return nil, err
}
ids := make([]uint16, 0, len(infos))
for id := range infos {
// technically LZ supports uint32, but unfortunately all the precompiles
// based it on uint16, so we have to stick with it.
// TODO: change it to uint32 here and in other precompiles.
if id > math.MaxUint16 {
return nil, errorsmod.Wrapf(
assetstypes.ErrInvalidInput, "client chain id is too large",
)
}
// #nosec G701 // already checked
convID := uint16(id)
ids = append(ids, convID)
}
return method.Outputs.Pack(true, ids)
}
6 changes: 6 additions & 0 deletions x/assets/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ var (
ModuleName, 0,
"there is no stored key for the input chain index",
)

ErrNoClientChainAssetKey = errorsmod.Register(
ModuleName, 1,
"there is no stored key for the input assetID",
Expand Down Expand Up @@ -80,4 +81,9 @@ var (
ModuleName, 14,
"the genesis data supplied is invalid",
)

ErrInvalidInput = errorsmod.Register(
ModuleName, 15,
"the input is invalid",
)
)
10 changes: 10 additions & 0 deletions x/evm/keeper/precompiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
avsManagerPrecompile "github.com/ExocoreNetwork/exocore/precompiles/avs"
taskPrecompile "github.com/ExocoreNetwork/exocore/precompiles/avsTask"
blsPrecompile "github.com/ExocoreNetwork/exocore/precompiles/bls"
clientchainsprecompile "github.com/ExocoreNetwork/exocore/precompiles/clientchains"
delegationprecompile "github.com/ExocoreNetwork/exocore/precompiles/delegation"
depositprecompile "github.com/ExocoreNetwork/exocore/precompiles/deposit"
rewardPrecompile "github.com/ExocoreNetwork/exocore/precompiles/reward"
Expand Down Expand Up @@ -68,6 +69,14 @@ func AvailablePrecompiles(
}

// add exoCore chain preCompiles
clientChainsPrecompile, err := clientchainsprecompile.NewPrecompile(
stakingStateKeeper,
authzKeeper,
)
if err != nil {
panic(fmt.Errorf("failed to load client chains precompile: %w", err))
}

depositPrecompile, err := depositprecompile.NewPrecompile(
stakingStateKeeper,
depositKeeper,
Expand Down Expand Up @@ -123,6 +132,7 @@ func AvailablePrecompiles(
precompiles[slashPrecompile.Address()] = slashPrecompile
precompiles[rewardPrecompile.Address()] = rewardPrecompile
precompiles[withdrawPrecompile.Address()] = withdrawPrecompile
precompiles[clientChainsPrecompile.Address()] = clientChainsPrecompile
precompiles[depositPrecompile.Address()] = depositPrecompile
precompiles[delegationPrecompile.Address()] = delegationPrecompile
precompiles[avsManagerPrecompile.Address()] = avsManagerPrecompile
Expand Down
Loading

0 comments on commit 91af9db

Please sign in to comment.