-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #35 from MaxMustermann2/feat/all-client-chains-pre…
…compile feat(precompile): add `getClientChains` function
- Loading branch information
Showing
10 changed files
with
348 additions
and
1 deletion.
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
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 | ||
} | ||
] |
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,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 | ||
} |
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,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); | ||
} | ||
|
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,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: ðtypes.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", | ||
) | ||
}) | ||
} |
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,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 | ||
} |
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,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) | ||
} |
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
Oops, something went wrong.