txverifier

package
v0.0.0-...-54fc8aa Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 29, 2025 License: Apache-2.0 Imports: 23 Imported by: 0

README

Transfer Verifier - Package Documentation

Package Structure

├── README.md
├── transfer-verifier-evm-structs.go
├── transfer-verifier-evm-structs_test.go
├── transfer-verifier-evm.go
├── transfer-verifier-evm_test.go
├── transfer-verifier-sui-structs.go
├── transfer-verifier-sui.go
├── transfer-verifier-sui_test.go
├── transfer-verifier-utils.go
└── transfer-verifier-utils_test.go

The package is organized by runtime environment. Currently there are implementations for the Ethereum and Sui blockchains. Because the Ethereum implementation is (hopefully) generalizable to other EVM-chains, it is referred to as transfer-verifier-evm rather than transfer-verifier-ethereum.

For each implementation, the code is divided into separate files. The core logic is contained in the main file and the supporting structs and utility methods are defined in a separate file. The hope here is that this makes the overall algorithm easier to reason about: a developer new to the program can focus on the main file and high-level concepts rather and avoid low-level details.

Main file -- Core Algorithm

The main file contains the algorithm for Transfer Verification, handling tasks such as tracking deposits and transfers into the Token Bridge, cross-referencing these with messages emitted from the core bridge, and emitting errors when suspicious activity is detected.

Structs file -- Parsing and Encapsulation

The structs file defines the major conceptual building blocks used by the algorithm in the main file. It is also responsible for lower-level operations such as establishing a subscription or polling mechanisms to a supported chain. This file also handles parsing and conversions, transforming things like JSON blobs or byte slices into concepts like a Message Receipt or Deposit.

Utilities file

There is also a utilities file that contains functions used by more than one runtime implementation, such as performing de/normalization of decimals.

Documentation

Index

Constants

View Source
const (
	// LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel);
	EVENTHASH_WORMHOLE_LOG_MESSAGE_PUBLISHED = "0x6eb224fb001ed210e379b335e35efe88672a8ce935d981a6896b27ffdf52a3b2"
	// Transfer(address,address,uint256)
	EVENTHASH_ERC20_TRANSFER = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
	// Deposit(address,uint256)
	EVENTHASH_WETH_DEPOSIT = "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"
)

Event Signatures

View Source
const (
	// EVM uses 32 bytes for words. Note that vaa.Address is an alias for a slice of 32 bytes
	EVM_WORD_LENGTH = 32
	// The expected total number of indexed topics for an ERC20 Transfer event
	TOPICS_COUNT_TRANSFER = 3
	// The expected total number of indexed topics for a WETH Deposit event
	TOPICS_COUNT_DEPOSIT = 2
)

EVM chain constants

View Source
const (
	MAX_DECIMALS = 8
	KEY_FORMAT   = "%s-%d"
)

Constants

View Source
const (
	// Seconds to wait before trying to reconnect to the core contract event subscription.
	RECONNECT_DELAY = 5 * time.Second
)
View Source
const (
	RPC_TIMEOUT = 10 * time.Second
)
View Source
const SUI_CHAIN_ID = 21

Variables

View Source
var (
	// wrappedAsset(uint16 tokenChainId, bytes32 tokenAddress) => 0x1ff1e286
	TOKEN_BRIDGE_WRAPPED_ASSET_SIGNATURE = []byte("\x1f\xf1\xe2\x86")
	// isWrappedAsset(address token) => 0x1a2be4da
	TOKEN_BRIDGE_IS_WRAPPED_ASSET_SIGNATURE = []byte("\x1a\x2b\xe4\xda")
	// decimals() => 0x313ce567
	ERC20_DECIMALS_SIGNATURE = []byte("\x31\x3c\xe5\x67")
	// chainId() => 0x9a8a0592
	WRAPPED_ERC20_CHAIN_ID_SIGNATURE = []byte("\x9a\x8a\x05\x92")
)

Function signatures

Fixed addresses

Functions

func TryWormholeChainIdFromNative

func TryWormholeChainIdFromNative(evmChainId uint64) (wormholeChainID vaa.ChainID, err error)

Yields the registered Wormhole chain ID corresponding to an EVM chain ID.

func VAAAddrFrom

func VAAAddrFrom(gethAddr common.Address) (vaaAddr vaa.Address)

Gives the representation of a geth address in vaa.Address

Types

type Bytes

type Bytes interface {
	Bytes() []byte
}

Interface useful for comparing vaa.Address and common.Address

type ERC20Transfer

type ERC20Transfer struct {
	// The address of the token. Also equivalent to the Emitter of the event.
	TokenAddress common.Address
	// The native chain of the token (where it was minted)
	TokenChain vaa.ChainID
	From       common.Address
	To         common.Address
	Amount     *big.Int
}

Abstraction over an ERC20 Transfer event.

func ERC20TransferFromLog

func ERC20TransferFromLog(
	log *types.Log,

	chainId vaa.ChainID,
) (transfer *ERC20Transfer, err error)

ERC20TransferFromLog() creates an ERC20Transfer struct given a log and Wormhole chain ID.

func (*ERC20Transfer) Destination

func (t *ERC20Transfer) Destination() vaa.Address

func (*ERC20Transfer) Emitter

func (t *ERC20Transfer) Emitter() common.Address

func (*ERC20Transfer) OriginAddress

func (t *ERC20Transfer) OriginAddress() vaa.Address

func (*ERC20Transfer) OriginChain

func (t *ERC20Transfer) OriginChain() vaa.ChainID

func (*ERC20Transfer) Sender

func (t *ERC20Transfer) Sender() vaa.Address

func (*ERC20Transfer) String

func (t *ERC20Transfer) String() string

func (*ERC20Transfer) TransferAmount

func (t *ERC20Transfer) TransferAmount() *big.Int

type InvalidLogError

type InvalidLogError struct {
	Msg string
}

Custom error type indicating an issue in issue in a type that implements the TransferLog interface. Used to ensure that a TransferLog is well-formed. Typically indicates a bug in the code.

func (InvalidLogError) Error

func (i InvalidLogError) Error() string

type InvariantError

type InvariantError struct {
	Msg string
}

Custom error type used to signal that a core invariant of the token bridge has been violated.

func (InvariantError) Error

func (i InvariantError) Error() string

type LogMessagePublished

type LogMessagePublished struct {
	// Which contract emitted the event.
	EventEmitter common.Address
	// Which address sent the transaction that triggered the message publication.
	MsgSender common.Address
	// Abstraction over fields encoded in the event's Data field which in turn contains the transfer's payload.
	TransferDetails *TransferDetails
}

Abstraction over a LogMessagePublished event emitted by the core bridge. TODO add String() method

func (*LogMessagePublished) Destination

func (l *LogMessagePublished) Destination() (destination vaa.Address)

func (*LogMessagePublished) Emitter

func (l *LogMessagePublished) Emitter() common.Address

func (*LogMessagePublished) OriginAddress

func (l *LogMessagePublished) OriginAddress() (origin vaa.Address)

func (*LogMessagePublished) OriginChain

func (l *LogMessagePublished) OriginChain() (chainID vaa.ChainID)

func (*LogMessagePublished) Sender

func (l *LogMessagePublished) Sender() vaa.Address

func (*LogMessagePublished) String

func (l *LogMessagePublished) String() string

func (*LogMessagePublished) TransferAmount

func (l *LogMessagePublished) TransferAmount() (amount *big.Int)

type NativeDeposit

type NativeDeposit struct {
	// The address of the token.
	TokenAddress common.Address
	// The native chain of the token (where it was minted)
	TokenChain vaa.ChainID
	Receiver   common.Address
	Amount     *big.Int
}

Abstraction over a Deposit event for a wrapped native asset, e.g. WETH for Ethereum.

func DepositFromLog

func DepositFromLog(
	log *types.Log,

	chainId vaa.ChainID,
) (deposit *NativeDeposit, err error)

DepositFromLog() creates a NativeDeposit struct given a log and Wormhole chain ID.

func (*NativeDeposit) Destination

func (d *NativeDeposit) Destination() vaa.Address

func (*NativeDeposit) Emitter

func (d *NativeDeposit) Emitter() common.Address

func (*NativeDeposit) OriginAddress

func (d *NativeDeposit) OriginAddress() vaa.Address

func (*NativeDeposit) OriginChain

func (d *NativeDeposit) OriginChain() vaa.ChainID

func (*NativeDeposit) Sender

func (d *NativeDeposit) Sender() vaa.Address

Deposit does not actually have a sender but this is required to implement the interface

func (*NativeDeposit) String

func (d *NativeDeposit) String() string

func (*NativeDeposit) TransferAmount

func (d *NativeDeposit) TransferAmount() *big.Int

type ObjectChange

type ObjectChange struct {
	ObjectType      string `json:"objectType"`
	ObjectId        string `json:"objectId"`
	Version         string `json:"version"`
	PreviousVersion string `json:"previousVersion"`
}

func (ObjectChange) ValidateTypeInformation

func (o ObjectChange) ValidateTypeInformation(expectedPackageId string) (success bool)

Validate the type information of the object change. The following checks are performed:

  • pass the object through a regex that extracts the package ID, coin type, and asset type
  • ensure that the asset type is wrapped or native
  • ensure that the package IDs match the expected package ID
  • ensure that the coin types match

type ReceiptSummary

type ReceiptSummary struct {
	// contains filtered or unexported fields
}

Summary of a processed TransferReceipt. Contains information about relevant transfers requested in and out of the bridge.

func NewReceiptSummary

func NewReceiptSummary() *ReceiptSummary

func (*ReceiptSummary) String

func (s *ReceiptSummary) String() (outStr string)

type Subscription

type Subscription struct {
	// contains filtered or unexported fields
}

func NewSubscription

func NewSubscription(client *ethClient.Client, connector connectors.Connector) *Subscription

func (*Subscription) Close

func (s *Subscription) Close()

func (*Subscription) Errors

func (s *Subscription) Errors() <-chan error

func (*Subscription) Events

func (s *Subscription) Events() <-chan *ethabi.AbiLogMessagePublished

func (*Subscription) Subscribe

func (s *Subscription) Subscribe(ctx context.Context)

Subscribe creates a subscription to WatchLogMessagePublished events and will attempt to reconnect when errors occur, such as Websocket connection problems.

type SuiApiConnection

type SuiApiConnection struct {
	// contains filtered or unexported fields
}

func (*SuiApiConnection) GetTransactionBlock

func (s *SuiApiConnection) GetTransactionBlock(txDigest string) (SuiGetTransactionBlockResponse, error)

func (*SuiApiConnection) QueryEvents

func (s *SuiApiConnection) QueryEvents(filter string, cursor string, limit int, descending bool) (SuiQueryEventsResponse, error)

func (*SuiApiConnection) TryMultiGetPastObjects

func (s *SuiApiConnection) TryMultiGetPastObjects(objectId string, version string, previousVersion string) (SuiTryMultiGetPastObjectsResponse, error)

type SuiApiInterface

type SuiApiInterface interface {
	QueryEvents(filter string, cursor string, limit int, descending bool) (SuiQueryEventsResponse, error)
	GetTransactionBlock(txDigest string) (SuiGetTransactionBlockResponse, error)
	TryMultiGetPastObjects(objectId string, version string, previousVersion string) (SuiTryMultiGetPastObjectsResponse, error)
}

The SuiApi interface defines the functions that are required to interact with the Sui RPC.

func NewSuiApiConnection

func NewSuiApiConnection(rpc string) SuiApiInterface

type SuiApiResponse

type SuiApiResponse interface {
	GetError() error
}

type SuiApiStandardResponse

type SuiApiStandardResponse struct {
	Jsonrpc string `json:"jsonrpc"`
	ID      int    `json:"id"`
	// error_msg is typically populated when a non-api-related error occurs (like ratelimiting)
	ErrorMessage *string `json:"error_msg"`
	// error is typically populated when an api-related error occurs
	Error *struct {
		Code    int    `json:"code"`
		Message string `json:"message"`
	} `json:"error"`
}

This struct defines the standard properties that get returned from the RPC. It includes the ErrorMessage and Error fields as well, with a standard implementation of a `GetError()` function. `suiApiRequest` requires `GetError()` for standard API error handling.

func (SuiApiStandardResponse) GetError

func (e SuiApiStandardResponse) GetError() error

type SuiEvent

type SuiEvent struct {
	ID struct {
		TxDigest *string `json:"txDigest"`
		EventSeq *string `json:"eventSeq"`
	} `json:"id"`
	PackageID         *string `json:"packageId"`
	TransactionModule *string `json:"transactionModule"`
	Sender            *string `json:"sender"`
	Type              *string `json:"type"`
	// Bcs               *string          `json:"bcs"`
	Timestamp *string          `json:"timestampMs"`
	Message   *WormholeMessage `json:"parsedJson"`
}

type SuiGetTransactionBlockResponse

type SuiGetTransactionBlockResponse struct {
	SuiApiStandardResponse
	Result SuiGetTransactionBlockResult `json:"result"`
}

The response object for sui_GetTransactionBlock

type SuiGetTransactionBlockResult

type SuiGetTransactionBlockResult struct {
	ObjectChanges []ObjectChange `json:"objectChanges"`
	Events        []SuiEvent     `json:"events"`
}

type SuiQueryEventsResponse

type SuiQueryEventsResponse struct {
	SuiApiStandardResponse
	Result SuiQueryEventsResult `json:"result"`
}

The response object for suix_queryEvents

type SuiQueryEventsResult

type SuiQueryEventsResult struct {
	Data []SuiEvent `json:"data"`
}

type SuiTransferVerifier

type SuiTransferVerifier struct {
	// contains filtered or unexported fields
}

func NewSuiTransferVerifier

func NewSuiTransferVerifier(suiCoreContract, suiTokenBridgeEmitter, suiTokenBridgeContract string) *SuiTransferVerifier

func (*SuiTransferVerifier) GetEventFilter

func (s *SuiTransferVerifier) GetEventFilter() string

Filter to be used for querying events The `MoveEventType` filter doesn't seem to be available in the documentation. However, there is an example showing the inclusion of `type` in the `MoveModule` filter. Reference: https://docs.sui.io/guides/developer/sui-101/using-events#query-events-with-rpc

func (*SuiTransferVerifier) ProcessDigest

func (s *SuiTransferVerifier) ProcessDigest(digest string, suiApiConnection SuiApiInterface, logger *zap.Logger) (uint, error)

type SuiTryMultiGetPastObjectsResponse

type SuiTryMultiGetPastObjectsResponse struct {
	SuiApiStandardResponse
	Result []SuiTryMultiGetPastObjectsResult `json:"result"`
}

The response object for suix_tryMultiGetPastObjects

func (SuiTryMultiGetPastObjectsResponse) GetBalanceDiff

func (r SuiTryMultiGetPastObjectsResponse) GetBalanceDiff() (*big.Int, error)

Gets the balance difference of the two result objects.

func (SuiTryMultiGetPastObjectsResponse) GetDecimals

func (r SuiTryMultiGetPastObjectsResponse) GetDecimals() (uint8, error)

Gets the decimals

func (SuiTryMultiGetPastObjectsResponse) GetObjectId

func (r SuiTryMultiGetPastObjectsResponse) GetObjectId() (string, error)

func (SuiTryMultiGetPastObjectsResponse) GetObjectType

func (r SuiTryMultiGetPastObjectsResponse) GetObjectType() (string, error)

func (SuiTryMultiGetPastObjectsResponse) GetPreviousVersion

func (r SuiTryMultiGetPastObjectsResponse) GetPreviousVersion() (string, error)

func (SuiTryMultiGetPastObjectsResponse) GetTokenAddress

func (r SuiTryMultiGetPastObjectsResponse) GetTokenAddress() (string, error)

func (SuiTryMultiGetPastObjectsResponse) GetTokenChain

func (r SuiTryMultiGetPastObjectsResponse) GetTokenChain() (uint16, error)

func (SuiTryMultiGetPastObjectsResponse) GetVersion

type SuiTryMultiGetPastObjectsResult

type SuiTryMultiGetPastObjectsResult struct {
	Status  string           `json:"status"`
	Details *json.RawMessage `json:"details"`
}

The result object for suix_tryMultiGetPastObjects.

func (SuiTryMultiGetPastObjectsResult) GetDecimals

func (r SuiTryMultiGetPastObjectsResult) GetDecimals() (uint8, error)

Get the result object's decimals.

func (SuiTryMultiGetPastObjectsResult) GetObjectId

func (r SuiTryMultiGetPastObjectsResult) GetObjectId() (string, error)

func (SuiTryMultiGetPastObjectsResult) GetObjectType

func (r SuiTryMultiGetPastObjectsResult) GetObjectType() (string, error)

func (SuiTryMultiGetPastObjectsResult) GetTokenAddress

func (r SuiTryMultiGetPastObjectsResult) GetTokenAddress() (tokenAddress string, err error)

Get the result object's token address. This will be the address of the token on it's chain of origin.

func (SuiTryMultiGetPastObjectsResult) GetTokenChain

func (r SuiTryMultiGetPastObjectsResult) GetTokenChain() (uint16, error)

Get the token's chain ID. This will be the chain ID of the network the token originated from.

func (SuiTryMultiGetPastObjectsResult) GetVersion

func (r SuiTryMultiGetPastObjectsResult) GetVersion() (string, error)

func (SuiTryMultiGetPastObjectsResult) GetVersionBalance

func (r SuiTryMultiGetPastObjectsResult) GetVersionBalance(isWrapped bool) (*big.Int, error)

Get the balance of the result object.

func (SuiTryMultiGetPastObjectsResult) IsWrapped

func (r SuiTryMultiGetPastObjectsResult) IsWrapped() (bool, error)

Check if the result object is wrapped.

type TVAddresses

type TVAddresses struct {
	CoreBridgeAddr common.Address
	// Address of the Wormhole token bridge contract for this chain
	TokenBridgeAddr common.Address
	// Wrapped version of the native asset, e.g. WETH for Ethereum
	WrappedNativeAddr common.Address
}

Important addresses for Transfer Verification.

type TransferDetails

type TransferDetails struct {
	PayloadType VAAPayloadType
	// Denormalized amount, accounting for decimal differences between contracts and chains
	Amount *big.Int
	// Amount as sent in the raw payload
	AmountRaw *big.Int
	// Original wormhole chain ID where the token was minted.
	TokenChain vaa.ChainID
	// Original address of the token when minted natively. Corresponds to the "unwrapped" address in the token bridge.
	OriginAddress common.Address
	// Raw token address parsed from the payload. May be wrapped.
	OriginAddressRaw []byte
	// Not necessarily an EVM address, so vaa.Address is used instead
	TargetAddress vaa.Address
}

Abstraction of a Token Bridge transfer payload encoded in the Data field of a LogMessagePublished event. It is meant to correspond to the API for Token Transfer messages as described in the Token Bridge whitepaper: https://github.com/wormhole-foundation/wormhole/blob/main/whitepapers/0003_token_bridge.md#api--database-schema

func (*TransferDetails) String

func (td *TransferDetails) String() string

type TransferLog

type TransferLog interface {
	// Amount after (de)normalization
	TransferAmount() *big.Int
	// The Transferror: EOA or contract that initiated the transfer. Not to be confused with msg.sender.
	Sender() vaa.Address
	// The Transferee. Ultimate recipient of funds.
	Destination() vaa.Address
	// Event emitter
	Emitter() common.Address // Emitter will always be an Ethereum address
	// Chain where the token was minted
	OriginChain() vaa.ChainID
	// Address that minted the token
	OriginAddress() vaa.Address
}

Abstraction over the fields that are expected to be present for Transfer types encoded in receipt logs: Deposits, Transfers, and LogMessagePublished events.

type TransferReceipt

type TransferReceipt struct {
	Deposits  *[]*NativeDeposit
	Transfers *[]*ERC20Transfer
	// There must be at least one LogMessagePublished for a valid receipt.
	MessagePublicatons *[]*LogMessagePublished
}

TransferReceipt is an abstraction over an EVM transaction receipt for a Token Bridge transfer. It represents Deposit, Transfer, and LogMessagePublished events that can appear in a Receipt logs. Other event types are not represented by this program because they are not relevant for checking the invariants on transfers sent from the token bridge.

func (*TransferReceipt) String

func (r *TransferReceipt) String() string

type TransferVerifier

type TransferVerifier[E evmClient, C connector] struct {
	Addresses *TVAddresses
	// contains filtered or unexported fields
}

TransferVerifier contains configuration values for verifying transfers.

func NewTransferVerifier

func NewTransferVerifier(connector connectors.Connector, tvAddrs *TVAddresses, pruneHeightDelta uint64, logger *zap.Logger) (*TransferVerifier[*ethClient.Client, connectors.Connector], error)

func (*TransferVerifier[evmClient, connector]) ParseReceipt

func (tv *TransferVerifier[evmClient, connector]) ParseReceipt(
	receipt *geth.Receipt,
) (*TransferReceipt, error)

This function parses only events with topics needed for Transfer Verification. Any other events will be discarded. This function is not responsible for checking that the values for the various fields are relevant, only that they are well-formed.

func (*TransferVerifier[ethClient, Connector]) ProcessEvent

func (tv *TransferVerifier[ethClient, Connector]) ProcessEvent(
	ctx context.Context,
	vLog *ethabi.AbiLogMessagePublished,

	receipt *geth.Receipt,
) bool

ProcessEvent processes a LogMessagePublished event, and is either called from a watcher or from the transfer verifier standalone process. It fetches the full transaction receipt associated with the log, and parses all events emitted in the transaction, tracking LogMessagePublished events as outbound transfers and token deposits into the token bridge as inbound transfers. It then verifies that the sum of the inbound transfers is at least as much as the sum of the outbound transfers. If the return value is true, it implies that the event was processed successfully. If the return value is false, it implies that something serious has gone wrong.

func (*TransferVerifier[evmClient, connector]) ProcessReceipt

func (tv *TransferVerifier[evmClient, connector]) ProcessReceipt(
	receipt *TransferReceipt,
) (summary *ReceiptSummary, err error)

ProcessReceipt verifies that a receipt for a LogMessagedPublished event does not verify a fundamental invariant of Wormhole token transfers: when the core bridge reports a transfer has occurred, there must be a corresponding transfer in the token bridge. This is determined by iterating through the logs of the receipt and ensuring that the sum transferred into the token bridge does not exceed the sum emitted by the core bridge. If this function returns an error, that means there is some serious trouble. An error should be returned if a deposit or transfer in the receipt is missing crucial information, or else if the sum of the funds in are less than the funds out. When modifying this code, be cautious not to return errors unless something is really wrong.

func (*TransferVerifier[ethClient, Connector]) UpdateReceiptDetails

func (tv *TransferVerifier[ethClient, Connector]) UpdateReceiptDetails(
	receipt *TransferReceipt,
) (updateErr error)

Do additional processing on the raw data that has been parsed. This consists of checking whether assets are wrapped for both ERC20 Transfer logs and LogMessagePublished events. If so, unwrap the assets and fetch information about the native chain, native address, and token decimals. All of this information is required to determine whether the amounts deposited into the token bridge match the amount that was requested out. This is done separately from parsing step so that RPC calls are done independently of parsing code, which facilitates testing. Updates the receipt parameter directly.

type VAAPayloadType

type VAAPayloadType uint8

https://wormhole.com/docs/learn/infrastructure/vaas/#payload-types

const (
	TransferTokens            VAAPayloadType = 1
	TransferTokensWithPayload VAAPayloadType = 3
)

type WormholeMessage

type WormholeMessage struct {
	ConsistencyLevel *uint8  `json:"consistency_level"`
	Nonce            *uint64 `json:"nonce"`
	Payload          []byte  `json:"payload"`
	Sender           *string `json:"sender"`
	Sequence         *string `json:"sequence"`
	Timestamp        *string `json:"timestamp"`
}

Definition of the WormholeMessage event

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL