mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
338 lines
13 KiB
Go
338 lines
13 KiB
Go
// (c) 2023, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package warp
|
|
|
|
import (
|
|
_ "embed"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/luxfi/evm/iface"
|
|
"github.com/luxfi/evm/accounts/abi"
|
|
"github.com/luxfi/evm/precompile/contract"
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/luxfi/evm/core/types"
|
|
"github.com/luxfi/evm/vmerrs"
|
|
)
|
|
|
|
const (
|
|
GetVerifiedWarpMessageBaseCost uint64 = 2 // Base cost of entering getVerifiedWarpMessage
|
|
GetBlockchainIDGasCost uint64 = 2 // Based on GasQuickStep used in existing EVM instructions
|
|
AddWarpMessageGasCost uint64 = 20_000 // Cost of producing and serving a BLS Signature
|
|
// Sum of base log gas cost, cost of producing 4 topics, and producing + serving a BLS Signature (sign + trie write)
|
|
// Note: using trie write for the gas cost results in a conservative overestimate since the message is stored in a
|
|
// flat database that can be cleaned up after a period of time instead of the EVM trie.
|
|
|
|
SendWarpMessageGasCost uint64 = contract.LogGas + 3*contract.LogTopicGas + AddWarpMessageGasCost + contract.WriteGasCostPerSlot
|
|
// SendWarpMessageGasCostPerByte cost accounts for producing a signed message of a given size
|
|
SendWarpMessageGasCostPerByte uint64 = contract.LogDataGas
|
|
|
|
GasCostPerWarpSigner uint64 = 500
|
|
GasCostPerWarpMessageBytes uint64 = 100
|
|
GasCostPerSignatureVerification uint64 = 200_000
|
|
)
|
|
|
|
var (
|
|
errInvalidSendInput = errors.New("invalid sendWarpMessage input")
|
|
errInvalidIndexInput = errors.New("invalid index to specify warp message")
|
|
)
|
|
|
|
// Singleton StatefulPrecompiledContract and signatures.
|
|
var (
|
|
// WarpRawABI contains the raw ABI of Warp contract.
|
|
//go:embed contract.abi
|
|
WarpRawABI string
|
|
|
|
WarpABI = contract.ParseABI(WarpRawABI)
|
|
|
|
WarpPrecompile = createWarpPrecompile()
|
|
)
|
|
|
|
// WarpBlockHash is an auto generated low-level Go binding around an user-defined struct.
|
|
type WarpBlockHash struct {
|
|
SourceChainID common.Hash
|
|
BlockHash common.Hash
|
|
}
|
|
|
|
type GetVerifiedWarpBlockHashOutput struct {
|
|
WarpBlockHash WarpBlockHash
|
|
Valid bool
|
|
}
|
|
|
|
// WarpMessage is an auto generated low-level Go binding around an user-defined struct.
|
|
type WarpMessage struct {
|
|
SourceChainID common.Hash
|
|
OriginSenderAddress common.Address
|
|
Payload []byte
|
|
}
|
|
|
|
type GetVerifiedWarpMessageOutput struct {
|
|
Message WarpMessage
|
|
Valid bool
|
|
}
|
|
|
|
type SendWarpMessageEventData struct {
|
|
Message []byte
|
|
}
|
|
|
|
// PackGetBlockchainID packs the include selector (first 4 func signature bytes).
|
|
// This function is mostly used for tests.
|
|
func PackGetBlockchainID() ([]byte, error) {
|
|
return WarpABI.Pack("getBlockchainID")
|
|
}
|
|
|
|
// PackGetBlockchainIDOutput attempts to pack given blockchainID of type common.Hash
|
|
// to conform the ABI outputs.
|
|
func PackGetBlockchainIDOutput(blockchainID common.Hash) ([]byte, error) {
|
|
return WarpABI.PackOutput("getBlockchainID", blockchainID)
|
|
}
|
|
|
|
// getBlockchainID returns the consensus Chain Context ChainID of this blockchain.
|
|
func getBlockchainID(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {
|
|
if remainingGas, err = contract.DeductGas(suppliedGas, GetBlockchainIDGasCost); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
packedOutput, err := PackGetBlockchainIDOutput(common.Hash(accessibleState.GetConsensusContext().ChainID))
|
|
if err != nil {
|
|
return nil, remainingGas, err
|
|
}
|
|
|
|
// Return the packed output and the remaining gas
|
|
return packedOutput, remainingGas, nil
|
|
}
|
|
|
|
// UnpackGetVerifiedWarpBlockHashInput attempts to unpack [input] into the uint32 type argument
|
|
// assumes that [input] does not include selector (omits first 4 func signature bytes)
|
|
func UnpackGetVerifiedWarpBlockHashInput(input []byte) (uint32, error) {
|
|
// We don't use strict mode here because it was disabled with Durango.
|
|
// Since Warp will be deployed after Durango, we don't need to use strict mode
|
|
res, err := WarpABI.UnpackInput("getVerifiedWarpBlockHash", input, false)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
unpacked := *abi.ConvertType(res[0], new(uint32)).(*uint32)
|
|
return unpacked, nil
|
|
}
|
|
|
|
// PackGetVerifiedWarpBlockHash packs [index] of type uint32 into the appropriate arguments for getVerifiedWarpBlockHash.
|
|
// the packed bytes include selector (first 4 func signature bytes).
|
|
// This function is mostly used for tests.
|
|
func PackGetVerifiedWarpBlockHash(index uint32) ([]byte, error) {
|
|
return WarpABI.Pack("getVerifiedWarpBlockHash", index)
|
|
}
|
|
|
|
// PackGetVerifiedWarpBlockHashOutput attempts to pack given [outputStruct] of type GetVerifiedWarpBlockHashOutput
|
|
// to conform the ABI outputs.
|
|
func PackGetVerifiedWarpBlockHashOutput(outputStruct GetVerifiedWarpBlockHashOutput) ([]byte, error) {
|
|
return WarpABI.PackOutput("getVerifiedWarpBlockHash",
|
|
outputStruct.WarpBlockHash,
|
|
outputStruct.Valid,
|
|
)
|
|
}
|
|
|
|
// UnpackGetVerifiedWarpBlockHashOutput attempts to unpack [output] as GetVerifiedWarpBlockHashOutput
|
|
// assumes that [output] does not include selector (omits first 4 func signature bytes)
|
|
func UnpackGetVerifiedWarpBlockHashOutput(output []byte) (GetVerifiedWarpBlockHashOutput, error) {
|
|
outputStruct := GetVerifiedWarpBlockHashOutput{}
|
|
err := WarpABI.UnpackIntoInterface(&outputStruct, "getVerifiedWarpBlockHash", output)
|
|
|
|
return outputStruct, err
|
|
}
|
|
|
|
func getVerifiedWarpBlockHash(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {
|
|
return handleWarpMessage(accessibleState, input, suppliedGas, blockHashHandler{})
|
|
}
|
|
|
|
// UnpackGetVerifiedWarpMessageInput attempts to unpack [input] into the uint32 type argument
|
|
// assumes that [input] does not include selector (omits first 4 func signature bytes)
|
|
func UnpackGetVerifiedWarpMessageInput(input []byte) (uint32, error) {
|
|
// We don't use strict mode here because it was disabled with Durango.
|
|
// Since Warp will be deployed after Durango, we don't need to use strict mode.
|
|
res, err := WarpABI.UnpackInput("getVerifiedWarpMessage", input, false)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
unpacked := *abi.ConvertType(res[0], new(uint32)).(*uint32)
|
|
return unpacked, nil
|
|
}
|
|
|
|
// PackGetVerifiedWarpMessage packs [index] of type uint32 into the appropriate arguments for getVerifiedWarpMessage.
|
|
// the packed bytes include selector (first 4 func signature bytes).
|
|
// This function is mostly used for tests.
|
|
func PackGetVerifiedWarpMessage(index uint32) ([]byte, error) {
|
|
return WarpABI.Pack("getVerifiedWarpMessage", index)
|
|
}
|
|
|
|
// PackGetVerifiedWarpMessageOutput attempts to pack given [outputStruct] of type GetVerifiedWarpMessageOutput
|
|
// to conform the ABI outputs.
|
|
func PackGetVerifiedWarpMessageOutput(outputStruct GetVerifiedWarpMessageOutput) ([]byte, error) {
|
|
return WarpABI.PackOutput("getVerifiedWarpMessage",
|
|
outputStruct.Message,
|
|
outputStruct.Valid,
|
|
)
|
|
}
|
|
|
|
// UnpackGetVerifiedWarpMessageOutput attempts to unpack [output] as GetVerifiedWarpMessageOutput
|
|
// assumes that [output] does not include selector (omits first 4 func signature bytes)
|
|
func UnpackGetVerifiedWarpMessageOutput(output []byte) (GetVerifiedWarpMessageOutput, error) {
|
|
outputStruct := GetVerifiedWarpMessageOutput{}
|
|
err := WarpABI.UnpackIntoInterface(&outputStruct, "getVerifiedWarpMessage", output)
|
|
|
|
return outputStruct, err
|
|
}
|
|
|
|
// getVerifiedWarpMessage retrieves the pre-verified warp message from the predicate storage slots and returns
|
|
// the expected ABI encoding of the message to the caller.
|
|
func getVerifiedWarpMessage(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {
|
|
return handleWarpMessage(accessibleState, input, suppliedGas, addressedPayloadHandler{})
|
|
}
|
|
|
|
// UnpackSendWarpMessageInput attempts to unpack [input] as []byte
|
|
// assumes that [input] does not include selector (omits first 4 func signature bytes)
|
|
func UnpackSendWarpMessageInput(input []byte) ([]byte, error) {
|
|
// We don't use strict mode here because it was disabled with Durango.
|
|
// Since Warp will be deployed after Durango, we don't need to use strict mode.
|
|
res, err := WarpABI.UnpackInput("sendWarpMessage", input, false)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
unpacked := *abi.ConvertType(res[0], new([]byte)).(*[]byte)
|
|
return unpacked, nil
|
|
}
|
|
|
|
// PackSendWarpMessage packs [inputStruct] of type []byte into the appropriate arguments for sendWarpMessage.
|
|
func PackSendWarpMessage(payloadData []byte) ([]byte, error) {
|
|
return WarpABI.Pack("sendWarpMessage", payloadData)
|
|
}
|
|
|
|
// PackSendWarpMessageOutput attempts to pack given messageID of type common.Hash
|
|
// to conform the ABI outputs.
|
|
func PackSendWarpMessageOutput(messageID common.Hash) ([]byte, error) {
|
|
return WarpABI.PackOutput("sendWarpMessage", messageID)
|
|
}
|
|
|
|
// UnpackSendWarpMessageOutput attempts to unpack given [output] into the common.Hash type output
|
|
// assumes that [output] does not include selector (omits first 4 func signature bytes)
|
|
func UnpackSendWarpMessageOutput(output []byte) (common.Hash, error) {
|
|
res, err := WarpABI.Unpack("sendWarpMessage", output)
|
|
if err != nil {
|
|
return common.Hash{}, err
|
|
}
|
|
unpacked := *abi.ConvertType(res[0], new(common.Hash)).(*common.Hash)
|
|
return unpacked, nil
|
|
}
|
|
|
|
// sendWarpMessage constructs a Lux Warp Message containing an AddressedPayload and emits a log to signal validators that they should
|
|
// be willing to sign this message.
|
|
func sendWarpMessage(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {
|
|
if remainingGas, err = contract.DeductGas(suppliedGas, SendWarpMessageGasCost); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
// This gas cost includes buffer room because it is based off of the total size of the input instead of the produced iface.
|
|
// This ensures that we charge gas before we unpack the variable sized input.
|
|
payloadGas, overflow := iface.SafeMul(SendWarpMessageGasCostPerByte, uint64(len(input)))
|
|
if overflow {
|
|
return nil, 0, vmerrs.ErrOutOfGas
|
|
}
|
|
if remainingGas, err = contract.DeductGas(remainingGas, payloadGas); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if readOnly {
|
|
return nil, remainingGas, vmerrs.ErrWriteProtection
|
|
}
|
|
// unpack the arguments
|
|
payloadData, err := UnpackSendWarpMessageInput(input)
|
|
if err != nil {
|
|
return nil, remainingGas, fmt.Errorf("%w: %s", errInvalidSendInput, err)
|
|
}
|
|
|
|
var (
|
|
consensusCtx = accessibleState.GetConsensusContext()
|
|
sourceChainID = [32]byte(consensusCtx.ChainID)
|
|
sourceAddress = caller
|
|
)
|
|
|
|
// Create addressed payload with source address and payload data
|
|
addressedPayload := &iface.AddressedCall{
|
|
SourceAddress: sourceAddress.Bytes(),
|
|
Payload: payloadData,
|
|
}
|
|
// Validate addressed payload
|
|
if err := addressedPayload.Verify(); err != nil {
|
|
return nil, remainingGas, err
|
|
}
|
|
unsignedWarpMessage, err := iface.NewUnsignedMessage(
|
|
consensusCtx.NetworkID,
|
|
sourceChainID,
|
|
addressedPayload.Bytes(),
|
|
)
|
|
if err != nil {
|
|
return nil, remainingGas, err
|
|
}
|
|
|
|
// Add a log to be handled if this action is finalized.
|
|
topics, data, err := PackSendWarpMessageEvent(
|
|
sourceAddress,
|
|
common.Hash(unsignedWarpMessage.ID()),
|
|
unsignedWarpMessage.Bytes(),
|
|
)
|
|
if err != nil {
|
|
return nil, remainingGas, err
|
|
}
|
|
accessibleState.GetStateDB().AddLog(&types.Log{
|
|
Address: ContractAddress,
|
|
Topics: topics,
|
|
Data: data,
|
|
BlockNumber: accessibleState.GetBlockContext().Number().Uint64(),
|
|
})
|
|
|
|
packed, err := PackSendWarpMessageOutput(common.Hash(unsignedWarpMessage.ID()))
|
|
if err != nil {
|
|
return nil, remainingGas, err
|
|
}
|
|
|
|
// Return the packed message ID and the remaining gas
|
|
return packed, remainingGas, nil
|
|
}
|
|
|
|
// PackSendWarpMessageEvent packs the given arguments into SendWarpMessage events including topics and data.
|
|
func PackSendWarpMessageEvent(sourceAddress common.Address, unsignedMessageID common.Hash, unsignedMessageBytes []byte) ([]common.Hash, []byte, error) {
|
|
return WarpABI.PackEvent("SendWarpMessage", sourceAddress, unsignedMessageID, unsignedMessageBytes)
|
|
}
|
|
|
|
// UnpackSendWarpEventDataToMessage attempts to unpack event [data] as iface.UnsignedMessage.
|
|
func UnpackSendWarpEventDataToMessage(data []byte) (*iface.UnsignedMessage, error) {
|
|
event := SendWarpMessageEventData{}
|
|
err := WarpABI.UnpackIntoInterface(&event, "SendWarpMessage", data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return iface.ParseUnsignedMessage(event.Message)
|
|
}
|
|
|
|
// createWarpPrecompile returns a StatefulPrecompiledContract with getters and setters for the precompile.
|
|
func createWarpPrecompile() contract.StatefulPrecompiledContract {
|
|
var functions []*contract.StatefulPrecompileFunction
|
|
|
|
abiFunctionMap := map[string]contract.RunStatefulPrecompileFunc{
|
|
"getBlockchainID": getBlockchainID,
|
|
"getVerifiedWarpBlockHash": getVerifiedWarpBlockHash,
|
|
"getVerifiedWarpMessage": getVerifiedWarpMessage,
|
|
"sendWarpMessage": sendWarpMessage,
|
|
}
|
|
|
|
for name, function := range abiFunctionMap {
|
|
method, ok := WarpABI.Methods[name]
|
|
if !ok {
|
|
panic(fmt.Errorf("given method (%s) does not exist in the ABI", name))
|
|
}
|
|
functions = append(functions, contract.NewStatefulPrecompileFunction(method.ID, function))
|
|
}
|
|
// Construct the contract with no fallback function.
|
|
statefulContract, err := contract.NewStatefulPrecompileContract(nil, functions)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return statefulContract
|
|
}
|