SL-EVM
Built on top of the go-ethereum client, SL-Chain is EVM compatible. Both Verifiable Sequencing Rule and Verifiable Aggregation Rule can be written in solidity and deployed to SL-Chain. Additional stateful precompiles have been added to SL-EVM, which offers more expressive functionalities for the VAR and VSR contracts.
Stateful Precompiles
SL-Chain includes a set of stateful precompile contracts designed to streamline MTX processing and extend the functionality of VARs and VSRs. Compared to the standard, stateless precompiles in geth, SL-Chain adds support for stateful precompiles, which enable more expressive functionalities beyond traditional stateless operations.
All stateful precompiles implement the following interface. The Run method is called when the precompile contract is invoked. A portion of EVM states such as stateDB are exposed and can be accessed by the precompile contract.
// StatefulPrecompiledContract defines the interface for the precompile contracts with state.
//
// It replaces the old stateless `PrecompiledContract` interface.
// All precompile contracts now need to follow this interface.
type StatefulPrecompiledContract interface {
RequiredGas(input []byte) uint64 // RequiredPrice calculates the contract gas use
// Run runs the precompiled contract.
Run(accessibleState AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, err error)
}
A precompile contract contains different functions. Those functions implements the following interfaces. The run method is called when the precompile function is being executed and the requiredGas method returns the gas cost to execute the function.
// StatefulPrecompileFunction defines a function implemented by a stateful precompile
type StatefulPrecompileFunction struct {
// selector is the 4 byte function selector for this function
selector []byte
// run is performed when this function is selected
run RunStatefulPrecompileFunc
// requiredGas returns the gas cost for executing the function
requiredGas RequiredGasPrecompileFunc
}
// StatefulPrecompileFallbackfunction defines the fallback function implemented by a stateful precompile
type StatefulPrecompileFallbackfunction struct {
// run is performed when this function is selected
run RunStatefulPrecompileFunc
// requiredGas returns the gas cost for executing the function
requiredGas RequiredGasPrecompileFunc
}
-
Example:
ReadOwner.solHere is an example of the stateful precompile. The
ReadOwnerprecompile can read the owner address from another contract./* This file implements a ReadOwner precompile. It reads the `owner` from another Ownable contract at storage slot 0x00....0. interface ReadOwner { function readOwner(address ownableContract) external returns(address owner); } the example ownable contract we read from contract Ownable { address public owner = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; } */ package vm import ( "fmt" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/sl/artifacts" ) var ( // Ownable precompile contract address readOwnerContractAddress = common.HexToAddress("0x0300000000000000000000000000000000000002") ) // readOwner implements function readOwner(address ownableContract) external view returns(address owner); // // It reads the owner variable in a contract func readOwner(accessibleState AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, err error) { // stateDB := accessibleState.GetStateDB() ownableContractAddr, err := unpackReadOwnerInput(input) if err != nil { return nil, err } if ownableContractAddr == (common.Address{}) { return nil, fmt.Errorf("empty ownable contract") } // read from ownable contract owner, err := getOwnerFromOwnable(accessibleState, ownableContractAddr) if err != nil { return nil, err } // pack output packedOutput, err := packReadOwnerOutput(owner) if err != nil { return nil, err } return packedOutput, nil } // readOwnerRequiredGas implements RequiredGas. func readOwnerRequiredGas(input []byte) uint64 { return uint64(2000) } // unpackReadOwnerInput unpacks the input of readOwner function. func unpackReadOwnerInput(input []byte) (common.Address, error) { res, err := artifacts.ContractABI.Methods["readOwner"].Inputs.Unpack(input) if err != nil { return common.Address{}, err } unpacked := *abi.ConvertType(res[0], new(common.Address)).(*common.Address) return unpacked, nil } // packReadOwnerOutput packs the output of readOwner function. func packReadOwnerOutput(owner common.Address) ([]byte, error) { return artifacts.ContractABI.Methods["readOwner"].Outputs.Pack(owner) } type MyContractRef struct { address common.Address } func (c *MyContractRef) Address() common.Address { return c.address } // getOwnerFromOwnable reads the owner in ownableContract at storage slot 0x0. func getOwnerFromOwnable(accessibleState AccessibleState, ownableContractAddr common.Address) (common.Address, error) { // perform a staticcall calldata, err := artifacts.ContractABI.Pack("owner") if err != nil { return common.Address{}, nil } output, _, err := accessibleState.StaticCall(&MyContractRef{address: readOwnerContractAddress}, ownableContractAddr, calldata, 10000) if err != nil { return common.Address{}, err } var res common.Address err = artifacts.ContractABI.UnpackIntoInterface(&res, "owner", output) if err != nil { return common.Address{}, err } return res, nil } // createReadOwnerPrecompile creates the readOwner precompile contract. func createReadOwnerPrecompile() StatefulPrecompiledContract { var functions []*StatefulPrecompileFunction abiFunctionMap := map[string]PrecompileFuncs{ "readOwner": {RunFunc: readOwner, GasFunc: readOwnerRequiredGas}, } for name, precompileFunc := range abiFunctionMap { method := artifacts.ContractABI.Methods[name] functions = append(functions, NewStatefulPrecompileFunction(method.ID, precompileFunc.RunFunc, precompileFunc.GasFunc)) } statefulContract, err := NewStatefulPrecompileContract(StatefulPrecompileFallbackfunction{}, functions) if err != nil { panic(err) } return statefulContract } -
MTX Precompile
The MTX Precompile contains logic for handling computationally intensive operations during MTX processing, providing necessary performance and security optimizations.
MTXPrecompile contract address is defined as
0x0300000000000000000000000000000000000003.The following methods are currently implemented:
- MTX Selection: Based on VSR and VAR scores, along with solver reputation (if enabled), the MTX Precompile determines the most optimal MTX bundle for execution.
- MTX Encryption: Sensitive data, such as user operations, solver solutions, and strategies are encrypted to maintain confidentiality until execution. This ensures that critical information is protected against unauthorized access.
- MTX Decryption: Provides utility functions that developers can call to reveal encrypted data once the MTX has been fully settled.
- UserMTX Partial Decryption: Provides a utility function allowing users and solvers to partially reveal the content of the UserMTX while keeping signatures confidential, before the UserMTX is fully settled.
interface IMTXPrecompile { /** * @dev return the solver mtx with the highest vsr score * @param mtxHubAddr mtxHub contract address * @param usermtxHash usermtx hash * @return solvermtxHash selected solvermtx hash with the highest vsr score * @return score vsr score */ function selectSolverMTXByVSR(address mtxHubAddr, bytes32 usermtxHash) external view returns (bytes32 solvermtxHash, uint256 score); /** * @dev it decrypts a solvermtx. It will revert if the mtx is not settled yet. * @param mtxHubAddr mtxHub contract address * @param solvermtxHash solvermtx hash */ function decryptSolverMTX(address mtxHubAddr, bytes32 solvermtxHash) external view returns (SolverMTX memory solvermtx); /** * @dev it decrypts a usrmtx. It will revert if the mtx is not settled yet. * @param mtxHubAddr mtxHub contract address * @param usermtxHash usermtx hash */ function decryptUserMTX(address mtxHubAddr, bytes32 usermtxHash) external view returns (UserMTX memory solvermtx); /** * @notice helper functions that can be used to get the encrypted mtx. * @dev encryption includes randomness. Use this function in read-only function or off-chain only. * @param solvermtx solvermtx object to be encrypted */ function encryptSolverMTX(SolverMTX calldata solvermtx) external view returns (bytes memory encrypted); /** * @notice helper functions that can be used to get the encrypted mtx. * @dev encryption includes randomness. Use this function in read-only function or off-chain only. * @param usermtx usermtx object to be encrypted */ function encryptUserMTX(UserMTX calldata usermtx) external view returns (bytes memory encrypted); /** * @dev partially reveal a usermtx content without any sigantures. * @param mtxHubAddr mtxHub contract address * @param usermtxHash usermtx hash */ function partialDecryptUserMTX(address mtxHubAddr, bytes32 usermtxHash) external view returns (UserMTX memory solvermtx); } -
Builder Precompile
The Builder Precompile offers functions for submitting MTXs as a bundled transaction to the builder of the destination execution environment, such as an Ethereum builder. This bundle contains the transaction required to execute MTXs on the SL execution contract of the destination chain.
Builder Precompile contract address is defined as:
0x0b0000000000000000000000000000000000000b.- Create Bundle: Allows developers to create a bundle of MTXs for submission.
- Submit Bundle: Allows developers to submit the created bundle to the block builder or sequencer.
interface BuilderPrecompile { function createBundle(bytes memory data) external view returns(bytes memory bundle); function submitBundle(string memory rpcURL, bytes memory data) external returns(bytes memory msg, bool ok); }