From e87ddb2215b513455c844463a25323bb9c01ff36 Mon Sep 17 00:00:00 2001 From: Clara van Staden Date: Sat, 19 Oct 2024 19:00:03 +0200 Subject: [PATCH] Relayers OFAC (#1313) * ofac * ofac * adds log * imports * space * decode * fix test * refactor * tests work * ofac * ethereum side * decode message * parachain side * finish off relayers ofac * revert testing change * fixes * return err * return early * add log --- relayer/chain/parachain/address.go | 33 ++++ relayer/chain/parachain/address_test.go | 14 ++ relayer/chain/parachain/message.go | 112 +++++++++++ relayer/chain/parachain/message_test.go | 33 ++++ relayer/config/config.go | 12 ++ relayer/ofac/ofac.go | 89 +++++++++ relayer/relays/execution/config.go | 16 +- relayer/relays/execution/main.go | 76 +++++++- relayer/relays/parachain/beefy-listener.go | 5 + relayer/relays/parachain/config.go | 11 +- relayer/relays/parachain/main.go | 4 + relayer/relays/parachain/scanner.go | 175 ++++++++++++++++++ relayer/relays/parachain/scanner_test.go | 27 +++ web/packages/test/config/execution-relay.json | 7 +- web/packages/test/config/parachain-relay.json | 4 + 15 files changed, 608 insertions(+), 10 deletions(-) create mode 100644 relayer/chain/parachain/address.go create mode 100644 relayer/chain/parachain/address_test.go create mode 100644 relayer/chain/parachain/message_test.go create mode 100644 relayer/ofac/ofac.go create mode 100644 relayer/relays/parachain/scanner_test.go diff --git a/relayer/chain/parachain/address.go b/relayer/chain/parachain/address.go new file mode 100644 index 0000000000..fef1d256be --- /dev/null +++ b/relayer/chain/parachain/address.go @@ -0,0 +1,33 @@ +package parachain + +import ( + "encoding/hex" + "fmt" + "strings" + + "github.com/decred/base58" + "golang.org/x/crypto/blake2b" +) + +func SS58Encode(pubKeyHex string, ss58Prefix uint8) (string, error) { + if strings.HasPrefix(pubKeyHex, "0x") { + pubKeyHex = pubKeyHex[2:] + } + + pubKey, err := hex.DecodeString(pubKeyHex) + if err != nil { + return "", fmt.Errorf("failed to decode hex: %w", err) + } + + address := append([]byte{ss58Prefix}, pubKey...) + + hashInput := append([]byte("SS58PRE"), address...) + + hash := blake2b.Sum512(hashInput) + checksum := hash[:2] + + fullAddress := append(address, checksum...) + + ss58Addr := base58.Encode(fullAddress) + return ss58Addr, nil +} diff --git a/relayer/chain/parachain/address_test.go b/relayer/chain/parachain/address_test.go new file mode 100644 index 0000000000..d969a35299 --- /dev/null +++ b/relayer/chain/parachain/address_test.go @@ -0,0 +1,14 @@ +package parachain + +import ( + assert "github.com/stretchr/testify/require" + "testing" +) + +func TestSS58Prefix(t *testing.T) { + address := "0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48" + + ss58Address, err := SS58Encode(address, 1) + assert.NoError(t, err) + assert.Equal(t, "A1k3praCLftTgBTb6aVavh3UNKwXN599Fqov17MkEy6bwCU", ss58Address) +} diff --git a/relayer/chain/parachain/message.go b/relayer/chain/parachain/message.go index 5f3a29c4ce..6613897c5b 100644 --- a/relayer/chain/parachain/message.go +++ b/relayer/chain/parachain/message.go @@ -4,6 +4,7 @@ package parachain import ( + "errors" "fmt" "strings" @@ -125,3 +126,114 @@ func removeLeadingZeroHashForSlice(s []string) []string { func removeLeadingZeroHash(s string) string { return strings.Replace(s, "0x", "", 1) } + +type Destination struct { + Variant types.U8 + DestinationBytes types.Data +} + +type ForeignAccountId32 struct { + ParaID uint32 + ID types.H256 + Fee types.U128 +} + +type ForeignAccountId20 struct { + ParaID uint32 + ID types.H160 + Fee types.U128 +} + +type RegisterToken struct { + Token types.H160 + Fee types.U128 +} + +type SendToken struct { + Token types.H160 + Destination Destination +} + +type SendNativeToken struct { + TokenID types.H256 + Destination Destination +} + +type InboundMessage struct { + Version types.U8 + ChainID types.U64 + Command types.U8 + CommandBytes types.Data +} + +func GetDestination(input []byte) (string, error) { + var inboundMessage = &InboundMessage{} + err := types.DecodeFromBytes(input, inboundMessage) + if err != nil { + return "", fmt.Errorf("failed to decode message: %v", err) + } + + address := "" + switch inboundMessage.Command { + case 0: + // Register token does not have a destination + break + case 1: + // Send token has a destination + var command = &SendToken{} + err = types.DecodeFromBytes(inboundMessage.CommandBytes, command) + if err != nil { + return "", fmt.Errorf("failed to decode send token command: %v", err) + } + + address, err = decodeDestination(command.Destination.Variant, command.Destination.DestinationBytes) + if err != nil { + return "", fmt.Errorf("decode destination: %v", err) + } + case 2: + // Send native token has a destination + var command = &SendNativeToken{} + err = types.DecodeFromBytes(inboundMessage.CommandBytes, command) + if err != nil { + return "", fmt.Errorf("failed to decode send native token command: %v", err) + } + + address, err = decodeDestination(command.Destination.Variant, command.Destination.DestinationBytes) + if err != nil { + return "", fmt.Errorf("decode destination: %v", err) + } + } + + return address, nil +} + +func decodeDestination(variant types.U8, destinationBytes []byte) (string, error) { + switch variant { + case 0: + // Account32 + account32 := &types.H256{} + err := types.DecodeFromBytes(destinationBytes, account32) + if err != nil { + return "", fmt.Errorf("failed to decode destination: %v", err) + } + return account32.Hex(), nil + case 1: + // Account32 on destination parachain + var account = &ForeignAccountId32{} + err := types.DecodeFromBytes(destinationBytes, account) + if err != nil { + return "", fmt.Errorf("failed to decode foreign account: %v", err) + } + return account.ID.Hex(), nil + case 2: + // Account20 + var account = &ForeignAccountId20{} + err := types.DecodeFromBytes(destinationBytes, account) + if err != nil { + return "", fmt.Errorf("failed to decode foreign account: %v", err) + } + return account.ID.Hex(), nil + } + + return "", errors.New("destination variant could not be matched") +} diff --git a/relayer/chain/parachain/message_test.go b/relayer/chain/parachain/message_test.go new file mode 100644 index 0000000000..869f1afccb --- /dev/null +++ b/relayer/chain/parachain/message_test.go @@ -0,0 +1,33 @@ +package parachain + +import ( + "testing" + + gethCommon "github.com/ethereum/go-ethereum/common" + assert "github.com/stretchr/testify/require" +) + +func TestGetDestination(t *testing.T) { + registerTokenPayload := "00a736aa000000000000774667629726ec1fabebcec0d9139bd1c8f72a2300e87648170000000000000000000000" + decodePayloadAndCompareDestinationAddress(t, registerTokenPayload, "") // register token does not have a destination + + sendTokenPayload := "00a736aa000000000001774667629726ec1fabebcec0d9139bd1c8f72a23008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4800c16ff2862300000000000000000000e87648170000000000000000000000" + bobAddress := "0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48" + decodePayloadAndCompareDestinationAddress(t, sendTokenPayload, bobAddress) + + sendTokenToPayload := "00a736aa000000000001774667629726ec1fabebcec0d9139bd1c8f72a2301d00700001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c00286bee000000000000000000000000000064a7b3b6e00d000000000000000000e87648170000000000000000000000" + ferdieAddress := "0x1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c" + decodePayloadAndCompareDestinationAddress(t, sendTokenToPayload, ferdieAddress) + + sendNativeTokenPayload := "00a736aa0000000000022121cfe35065c0c33465fbada265f08e9613428a4b9eb4bb717cd7db2abf622e008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48065cd1d00000000000000000000000000e87648170000000000000000000000" + decodePayloadAndCompareDestinationAddress(t, sendNativeTokenPayload, bobAddress) +} + +func decodePayloadAndCompareDestinationAddress(t *testing.T, payload, expectedAddress string) { + data := gethCommon.Hex2Bytes(payload) + + destination, err := GetDestination(data) + assert.NoError(t, err) + + assert.Equal(t, expectedAddress, destination) +} diff --git a/relayer/config/config.go b/relayer/config/config.go index 71a19a1caa..ea1de25fdc 100644 --- a/relayer/config/config.go +++ b/relayer/config/config.go @@ -18,6 +18,11 @@ type EthereumConfig struct { GasLimit uint64 `mapstructure:"gas-limit"` } +type OFACConfig struct { + Enabled bool `mapstructure:"enabled"` + ApiKey string `mapstructure:"apiKey"` +} + func (p ParachainConfig) Validate() error { if p.Endpoint == "" { return errors.New("[endpoint] is not set") @@ -41,3 +46,10 @@ func (p PolkadotConfig) Validate() error { } return nil } + +func (o OFACConfig) Validate() error { + if o.Enabled && o.ApiKey == "" { + return errors.New("OFAC is enabled but no [apiKey] set") + } + return nil +} diff --git a/relayer/ofac/ofac.go b/relayer/ofac/ofac.go new file mode 100644 index 0000000000..257fec6299 --- /dev/null +++ b/relayer/ofac/ofac.go @@ -0,0 +1,89 @@ +package ofac + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + + log "github.com/sirupsen/logrus" +) + +type OFAC struct { + enabled bool + apiKey string +} + +type Response struct { + Identifications []struct { + Category string `json:"category"` + Name string `json:"name"` + Description string `json:"description"` + URL string `json:"url"` + } `json:"identifications"` +} + +func New(enabled bool, apiKey string) *OFAC { + return &OFAC{enabled, apiKey} +} + +func (o OFAC) IsBanned(source, destination string) (bool, error) { + if !o.enabled { + return false, nil + } + + if source != "" { + isSourcedBanned, err := o.isOFACListed(source) + if err != nil { + return true, err + } + if isSourcedBanned { + log.WithField("source", source).Warn("found ofac banned source address") + return true, nil + } + } + + if destination != "" { + isDestinationBanned, err := o.isOFACListed(destination) + if err != nil { + return true, err + } + if isDestinationBanned { + log.WithField("destination", destination).Warn("found ofac banned destination address") + return true, nil + } + } + + return false, nil +} + +func (o OFAC) isOFACListed(address string) (bool, error) { + client := &http.Client{} + + req, err := http.NewRequest("GET", fmt.Sprintf("https://public.chainalysis.com/api/v1/address/%s", address), nil) + if err != nil { + return true, err + } + + req.Header.Add("Accept", "application/json") + req.Header.Add("X-API-Key", o.apiKey) + + resp, err := client.Do(req) + if err != nil { + return true, err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return true, err + } + + var response Response + err = json.Unmarshal(body, &response) + if err != nil { + return true, err + } + + return len(response.Identifications) > 0, nil +} diff --git a/relayer/relays/execution/config.go b/relayer/relays/execution/config.go index 70dd1d5cae..55fcedf9f9 100644 --- a/relayer/relays/execution/config.go +++ b/relayer/relays/execution/config.go @@ -9,10 +9,11 @@ import ( ) type Config struct { - Source SourceConfig `mapstructure:"source"` - Sink SinkConfig `mapstructure:"sink"` - InstantVerification bool `mapstructure:"instantVerification"` - Schedule ScheduleConfig `mapstructure:"schedule"` + Source SourceConfig `mapstructure:"source"` + Sink SinkConfig `mapstructure:"sink"` + InstantVerification bool `mapstructure:"instantVerification"` + Schedule ScheduleConfig `mapstructure:"schedule"` + OFAC config.OFACConfig `mapstructure:"ofac"` } type ScheduleConfig struct { @@ -46,7 +47,8 @@ type ContractsConfig struct { } type SinkConfig struct { - Parachain beaconconf.ParachainConfig `mapstructure:"parachain"` + Parachain beaconconf.ParachainConfig `mapstructure:"parachain"` + SS58Prefix uint8 `mapstructure:"ss58Prefix"` } type ChannelID [32]byte @@ -70,5 +72,9 @@ func (c Config) Validate() error { if err != nil { return fmt.Errorf("schedule config: %w", err) } + err = c.OFAC.Validate() + if err != nil { + return fmt.Errorf("ofac config: %w", err) + } return nil } diff --git a/relayer/relays/execution/main.go b/relayer/relays/execution/main.go index 099d263938..7f3aa1f873 100644 --- a/relayer/relays/execution/main.go +++ b/relayer/relays/execution/main.go @@ -4,12 +4,14 @@ import ( "context" "errors" "fmt" + "github.com/snowfork/snowbridge/relayer/ofac" "math/big" "sort" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus" "github.com/snowfork/go-substrate-rpc-client/v4/types" @@ -34,6 +36,8 @@ type Relay struct { beaconHeader *header.Header writer *parachain.ParachainWriter headerCache *ethereum.HeaderCache + ofac *ofac.OFAC + chainID *big.Int } func NewRelay( @@ -89,6 +93,8 @@ func (r *Relay) Start(ctx context.Context, eg *errgroup.Group) error { p := protocol.New(r.config.Source.Beacon.Spec, r.config.Sink.Parachain.HeaderRedundancy) + r.ofac = ofac.New(r.config.OFAC.Enabled, r.config.OFAC.ApiKey) + store := store.New(r.config.Source.Beacon.DataStore.Location, r.config.Source.Beacon.DataStore.MaxEntries, *p) store.Connect() @@ -103,11 +109,17 @@ func (r *Relay) Start(ctx context.Context, eg *errgroup.Group) error { ) r.beaconHeader = &beaconHeader + r.chainID, err = r.ethconn.Client().NetworkID(ctx) + if err != nil { + return err + } + log.WithFields(log.Fields{ "relayerId": r.config.Schedule.ID, "relayerCount": r.config.Schedule.TotalRelayerCount, "sleepInterval": r.config.Schedule.SleepInterval, - }).Info("decentralization config") + "chainId": r.chainID, + }).Info("relayer config") for { select { @@ -412,6 +424,27 @@ func (r *Relay) doSubmit(ctx context.Context, ev *contracts.GatewayOutboundMessa "channelID": types.H256(ev.ChannelID).Hex(), }) + source, err := r.getTransactionSender(ctx, ev) + if err != nil { + return err + } + + destination, err := r.getTransactionDestination(ev) + if err != nil { + return err + } + + banned, err := r.ofac.IsBanned(source, destination) + if err != nil { + return err + } + if banned { + log.Fatal("banned address found") + return errors.New("banned address found") + } else { + log.Info("address is not banned, continuing") + } + nextBlockNumber := new(big.Int).SetUint64(ev.Raw.BlockNumber + 1) blockHeader, err := r.ethconn.Client().HeaderByNumber(ctx, nextBlockNumber) @@ -481,3 +514,44 @@ func (r *Relay) isInFinalizedBlock(ctx context.Context, event *contracts.Gateway return r.beaconHeader.CheckHeaderFinalized(*blockHeader.ParentBeaconRoot, r.config.InstantVerification) } + +func (r *Relay) getTransactionSender(ctx context.Context, ev *contracts.GatewayOutboundMessageAccepted) (string, error) { + tx, _, err := r.ethconn.Client().TransactionByHash(ctx, ev.Raw.TxHash) + if err != nil { + return "", err + } + + sender, err := ethtypes.Sender(ethtypes.LatestSignerForChainID(r.chainID), tx) + if err != nil { + return "", fmt.Errorf("retrieve message sender: %w", err) + } + + log.WithFields(log.Fields{ + "sender": sender, + }).Debug("extracted sender from transaction") + + return sender.Hex(), nil +} + +func (r *Relay) getTransactionDestination(ev *contracts.GatewayOutboundMessageAccepted) (string, error) { + destination, err := parachain.GetDestination(ev.Payload) + if err != nil { + return "", fmt.Errorf("fetch execution header proof: %w", err) + } + + if destination == "" { + return "", nil + } + + destinationSS58, err := parachain.SS58Encode(destination, r.config.Sink.SS58Prefix) + if err != nil { + return "", fmt.Errorf("ss58 encode: %w", err) + } + + log.WithFields(log.Fields{ + "destinationSS58": destinationSS58, + "destination": destination, + }).Debug("extracted destination from message") + + return destinationSS58, nil +} diff --git a/relayer/relays/parachain/beefy-listener.go b/relayer/relays/parachain/beefy-listener.go index 41d0e5e292..04f687cb4e 100644 --- a/relayer/relays/parachain/beefy-listener.go +++ b/relayer/relays/parachain/beefy-listener.go @@ -17,6 +17,7 @@ import ( "github.com/snowfork/snowbridge/relayer/chain/relaychain" "github.com/snowfork/snowbridge/relayer/contracts" "github.com/snowfork/snowbridge/relayer/crypto/merkle" + "github.com/snowfork/snowbridge/relayer/ofac" log "github.com/sirupsen/logrus" ) @@ -28,6 +29,7 @@ type BeefyListener struct { beefyClientContract *contracts.BeefyClient relaychainConn *relaychain.Connection parachainConnection *parachain.Connection + ofac *ofac.OFAC paraID uint32 tasks chan<- *Task scanner *Scanner @@ -39,6 +41,7 @@ func NewBeefyListener( ethereumConn *ethereum.Connection, relaychainConn *relaychain.Connection, parachainConnection *parachain.Connection, + ofac *ofac.OFAC, tasks chan<- *Task, ) *BeefyListener { return &BeefyListener{ @@ -47,6 +50,7 @@ func NewBeefyListener( ethereumConn: ethereumConn, relaychainConn: relaychainConn, parachainConnection: parachainConnection, + ofac: ofac, tasks: tasks, } } @@ -81,6 +85,7 @@ func (li *BeefyListener) Start(ctx context.Context, eg *errgroup.Group) error { relayConn: li.relaychainConn, paraConn: li.parachainConnection, paraID: paraID, + ofac: li.ofac, } eg.Go(func() error { diff --git a/relayer/relays/parachain/config.go b/relayer/relays/parachain/config.go index 97a9ebd3dd..b35e1b4bcd 100644 --- a/relayer/relays/parachain/config.go +++ b/relayer/relays/parachain/config.go @@ -8,9 +8,10 @@ import ( ) type Config struct { - Source SourceConfig `mapstructure:"source"` - Sink SinkConfig `mapstructure:"sink"` - Schedule ScheduleConfig `mapstructure:"schedule"` + Source SourceConfig `mapstructure:"source"` + Sink SinkConfig `mapstructure:"sink"` + Schedule ScheduleConfig `mapstructure:"schedule"` + OFAC config.OFACConfig `mapstructure:"ofac"` } type SourceConfig struct { @@ -94,6 +95,10 @@ func (c Config) Validate() error { if err != nil { return fmt.Errorf("relay config: %w", err) } + err = c.OFAC.Validate() + if err != nil { + return fmt.Errorf("ofac config: %w", err) + } return nil } diff --git a/relayer/relays/parachain/main.go b/relayer/relays/parachain/main.go index d66da938fd..44f6d710e3 100644 --- a/relayer/relays/parachain/main.go +++ b/relayer/relays/parachain/main.go @@ -11,6 +11,7 @@ import ( "github.com/snowfork/snowbridge/relayer/chain/parachain" "github.com/snowfork/snowbridge/relayer/chain/relaychain" "github.com/snowfork/snowbridge/relayer/crypto/secp256k1" + "github.com/snowfork/snowbridge/relayer/ofac" log "github.com/sirupsen/logrus" ) @@ -34,6 +35,8 @@ func NewRelay(config *Config, keypair *secp256k1.Keypair) (*Relay, error) { ethereumConnWriter := ethereum.NewConnection(&config.Sink.Ethereum, keypair) ethereumConnBeefy := ethereum.NewConnection(&config.Source.Ethereum, keypair) + ofacClient := ofac.New(config.OFAC.Enabled, config.OFAC.ApiKey) + // channel for messages from beefy listener to ethereum writer var tasks = make(chan *Task, 1) @@ -52,6 +55,7 @@ func NewRelay(config *Config, keypair *secp256k1.Keypair) (*Relay, error) { ethereumConnBeefy, relaychainConn, parachainConn, + ofacClient, tasks, ) diff --git a/relayer/relays/parachain/scanner.go b/relayer/relays/parachain/scanner.go index 287ca93118..58a8dabad9 100644 --- a/relayer/relays/parachain/scanner.go +++ b/relayer/relays/parachain/scanner.go @@ -3,7 +3,11 @@ package parachain import ( "bytes" "context" + "errors" "fmt" + "github.com/ethereum/go-ethereum/accounts/abi" + "reflect" + "strings" "github.com/snowfork/go-substrate-rpc-client/v4/scale" @@ -16,6 +20,7 @@ import ( "github.com/snowfork/snowbridge/relayer/chain/parachain" "github.com/snowfork/snowbridge/relayer/chain/relaychain" "github.com/snowfork/snowbridge/relayer/contracts" + "github.com/snowfork/snowbridge/relayer/ofac" ) type Scanner struct { @@ -24,6 +29,7 @@ type Scanner struct { relayConn *relaychain.Connection paraConn *parachain.Connection paraID uint32 + ofac *ofac.OFAC tasks chan<- *Task } @@ -186,6 +192,15 @@ func (s *Scanner) findTasksImpl( if err != nil { return nil, fmt.Errorf("decode message error: %w", err) } + isBanned, err := s.IsBanned(m) + if err != nil { + log.WithError(err).Fatal("error checking banned address found") + return nil, fmt.Errorf("banned check: %w", err) + } + if isBanned { + log.Fatal("banned address found") + return nil, errors.New("banned address found") + } messages = append(messages, m) } @@ -464,3 +479,163 @@ func (s *Scanner) findLatestNonce(ctx context.Context) (uint64, error) { } return ethInboundNonce, err } + +func (s *Scanner) IsBanned(m OutboundQueueMessage) (bool, error) { + destination, err := GetDestination(m) + if err != nil { + return true, err + } + + return s.ofac.IsBanned("", destination) // TODO the source will be fetched from Subscan in a follow-up PR +} + +func GetDestination(message OutboundQueueMessage) (string, error) { + log.WithFields(log.Fields{ + "command": message.Command, + "params": common.Bytes2Hex(message.Params), + }).Debug("Checking message for OFAC") + + address := "" + + bytes32Ty, err := abi.NewType("bytes32", "", nil) + if err != nil { + return "", err + } + addressTy, err := abi.NewType("address", "", nil) + if err != nil { + return "", err + } + uint256Ty, err := abi.NewType("uint256", "", nil) + + switch message.Command { + case 0: + log.Debug("Found AgentExecute message") + + uintTy, err := abi.NewType("uint256", "", nil) + if err != nil { + return "", err + } + bytesTy, err := abi.NewType("bytes", "", nil) + if err != nil { + return "", err + } + tupleTy, err := abi.NewType("tuple", "", []abi.ArgumentMarshaling{ + {Name: "AgentId", Type: "bytes32"}, + {Name: "Command", Type: "bytes"}, + }) + if err != nil { + return "", err + } + + tupleArgument := abi.Arguments{ + {Type: tupleTy}, + } + commandArgument := abi.Arguments{ + {Type: uintTy}, + {Type: bytesTy}, + } + transferTokenArgument := abi.Arguments{ + {Type: addressTy}, + {Type: addressTy}, + {Type: uintTy}, + } + + // Decode the ABI-encoded byte payload + decodedTuple, err := tupleArgument.Unpack(message.Params) + if err != nil { + return "", fmt.Errorf("unpack tuple: %w", err) + } + if len(decodedTuple) < 1 { + return "", fmt.Errorf("decoded tuple not found") + } + + tuple := reflect.ValueOf(decodedTuple[0]) + commandBytes := tuple.FieldByName("Command").Bytes() + + decodedCommand, err := commandArgument.Unpack(commandBytes) + if err != nil { + return "", fmt.Errorf("unpack command: %w", err) + } + if len(decodedCommand) < 2 { + return "", errors.New("decoded command not found") + } + + decodedTransferToken, err := transferTokenArgument.Unpack(decodedCommand[1].([]byte)) + if err != nil { + return "", err + } + if len(decodedTransferToken) < 3 { + return "", errors.New("decode transfer token command") + } + + addressValue := decodedTransferToken[1].(common.Address) + address = addressValue.String() + case 6: + log.Debug("Found TransferNativeFromAgent message") + + if err != nil { + return "", err + } + arguments := abi.Arguments{ + {Type: bytes32Ty}, + {Type: addressTy}, + {Type: uint256Ty}, + } + + decodedMessage, err := arguments.Unpack(message.Params) + if err != nil { + return "", fmt.Errorf("unpack tuple: %w", err) + } + if len(decodedMessage) < 3 { + return "", fmt.Errorf("decoded message not found") + } + + addressValue := decodedMessage[1].(common.Address) + address = addressValue.String() + case 9: + log.Debug("Found TransferNativeToken message") + + arguments := abi.Arguments{ + {Type: bytes32Ty}, + {Type: addressTy}, + {Type: addressTy}, + {Type: uint256Ty}, + } + + decodedMessage, err := arguments.Unpack(message.Params) + if err != nil { + return "", fmt.Errorf("unpack tuple: %w", err) + } + if len(decodedMessage) < 4 { + return "", fmt.Errorf("decoded message not found") + } + + addressValue := decodedMessage[2].(common.Address) + address = addressValue.String() + case 11: + log.Debug("Found MintForeignToken message") + + arguments := abi.Arguments{ + {Type: bytes32Ty}, + {Type: addressTy}, + {Type: uint256Ty}, + } + + decodedMessage, err := arguments.Unpack(message.Params) + if err != nil { + return "", fmt.Errorf("unpack tuple: %w", err) + } + if len(decodedMessage) < 3 { + return "", fmt.Errorf("decoded message not found") + } + + addressValue := decodedMessage[1].(common.Address) + address = addressValue.String() + } + + destination := strings.ToLower(address) + + log.WithField("destination", destination).Debug("extracted destination from message") + + return destination, nil +} diff --git a/relayer/relays/parachain/scanner_test.go b/relayer/relays/parachain/scanner_test.go new file mode 100644 index 0000000000..c6738a53b1 --- /dev/null +++ b/relayer/relays/parachain/scanner_test.go @@ -0,0 +1,27 @@ +package parachain + +import ( + gethCommon "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestGetDestination(t *testing.T) { + agentExecutePayload := "000000000000000000000000000000000000000000000000000000000000002081c5ab2571199e3188135178f3c2c8e2d268be1313d029b30f534fa579b69b79000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000774667629726ec1fabebcec0d9139bd1c8f72a2300000000000000000000000044a57ee2f2fccb85fda2b0b18ebd0d8d2333700e000000000000000000000000000000000000000000000000000000003b9aca00" + decodePayloadAndCompareDestinationAddress(t, 0, agentExecutePayload, "0x44a57ee2f2fccb85fda2b0b18ebd0d8d2333700e") + + mintForeignTokenPayload := "2121cfe35065c0c33465fbada265f08e9613428a4b9eb4bb717cd7db2abf622e00000000000000000000000090a987b944cb1dcce5564e5fdecd7a54d3de27fe000000000000000000000000000000000000000000000000000000003b9aca00" + decodePayloadAndCompareDestinationAddress(t, 11, mintForeignTokenPayload, "0x90a987b944cb1dcce5564e5fdecd7a54d3de27fe") +} + +func decodePayloadAndCompareDestinationAddress(t *testing.T, command uint8, payload, expectedAddress string) { + data := gethCommon.Hex2Bytes(payload) + + destination, err := GetDestination(OutboundQueueMessage{ + Command: command, + Params: data, + }) + assert.NoError(t, err) + + assert.Equal(t, expectedAddress, destination) +} diff --git a/web/packages/test/config/execution-relay.json b/web/packages/test/config/execution-relay.json index f94caa75c3..ca40fdeaab 100644 --- a/web/packages/test/config/execution-relay.json +++ b/web/packages/test/config/execution-relay.json @@ -27,12 +27,17 @@ "endpoint": "ws://127.0.0.1:11144", "maxWatchedExtrinsics": 8, "headerRedundancy": 20 - } + }, + "ss58Prefix": 1 }, "instantVerification": false, "schedule": { "id": null, "totalRelayerCount": 3, "sleepInterval": 20 + }, + "ofac": { + "enabled": false, + "apiKey": "" } } diff --git a/web/packages/test/config/parachain-relay.json b/web/packages/test/config/parachain-relay.json index 8fd1c73cb3..31c1073fd7 100644 --- a/web/packages/test/config/parachain-relay.json +++ b/web/packages/test/config/parachain-relay.json @@ -29,5 +29,9 @@ "id": null, "totalRelayerCount": 3, "sleepInterval": 45 + }, + "ofac": { + "enabled": false, + "apiKey": "" } }