Skip to content

Commit

Permalink
add contract check on tvf
Browse files Browse the repository at this point in the history
  • Loading branch information
llbartekll committed Feb 3, 2025
1 parent ba8517c commit 1475cfc
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 32 deletions.
2 changes: 1 addition & 1 deletion Example/Shared/Signer/ETHSigner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ struct ETHSigner {
// let signedTx = try transaction.sign(with: self.privateKey, chainId: 4)
// let (r, s, v) = (signedTx.r, signedTx.s, signedTx.v)
// let result = r.hex() + s.hex().dropFirst(2) + String(v.quantity, radix: 16)
return AnyCodable("0x")
return AnyCodable("0xabcd12340000000000000000000000111111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000f0")
}

private func dataToHash(_ data: Data) -> Bytes {
Expand Down
41 changes: 31 additions & 10 deletions Sources/WalletConnectUtils/TVFCollector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,11 @@ public struct TVFCollector {
// MARK: - Computed Properties

private var evm: [String] {
[Self.ETH_SEND_TRANSACTION,
Self.ETH_SEND_RAW_TRANSACTION]
[Self.ETH_SEND_TRANSACTION, Self.ETH_SEND_RAW_TRANSACTION]
}

private var solana: [String] {
[Self.SOLANA_SIGN_TRANSACTION,
Self.SOLANA_SIGN_AND_SEND_TRANSACTION,
Self.SOLANA_SIGN_ALL_TRANSACTION]
[Self.SOLANA_SIGN_TRANSACTION, Self.SOLANA_SIGN_AND_SEND_TRANSACTION, Self.SOLANA_SIGN_ALL_TRANSACTION]
}

private var wallet: [String] {
Expand All @@ -81,7 +78,7 @@ public struct TVFCollector {
/// - rpcParams: An `AnyCodable` containing arbitrary JSON or primitive content.
/// - chainID: A `Blockchain` instance (e.g., `Blockchain("eip155:1")`).
/// - rpcResult: An optional `RPCResult` representing `.response(AnyCodable)` or `.error(...)`.
/// - tag: Integer that should map to `.sessionRequest (1008)` or `.sessionResponse (1009)`.
/// - tag: Integer that should map to `.sessionRequest (1108)` or `.sessionResponse (1109)`.
///
/// - Returns: `TVFData` if successful, otherwise `nil`.
public func collect(
Expand All @@ -108,7 +105,7 @@ public struct TVFCollector {
rpcParams: rpcParams
)

// 3. If this is a sessionResponse (1009), gather transaction hashes from rpcResult
// 3. If this is a sessionResponse (1109), gather transaction hashes from rpcResult
let txHashes: [String]? = {
switch theTag {
case .sessionRequest:
Expand Down Expand Up @@ -138,7 +135,12 @@ public struct TVFCollector {
// Attempt to decode the array of EthSendTransaction from AnyCodable
let transactions = try rpcParams.get([EthSendTransaction].self)
if let firstTo = transactions.first?.to {
return [firstTo]
// Use our contract data check: if it is valid contract call data, then return it.
if TVFCollector.isValidContractData(firstTo) {
return [firstTo]
} else {
return []
}
}
} catch {
print("Failed to parse EthSendTransaction: \(error)")
Expand All @@ -154,7 +156,6 @@ public struct TVFCollector {
}
switch rpcResult {
case .error(_):
// We do not parse tx hashes in the event of an error
return nil
case .response(let anycodable):
return parseTxHashes(forMethod: rpcMethod, from: anycodable)
Expand All @@ -167,7 +168,6 @@ public struct TVFCollector {
switch method {
// EVM or wallet methods return the raw string as the transaction hash
case _ where evm.contains(method) || wallet.contains(method):
// Try to decode as a single string
if let rawHash = try? anycodable.get(String.self) {
return [rawHash]
}
Expand All @@ -194,3 +194,24 @@ public struct TVFCollector {
}
}
}

// MARK: - Contract Data Check

extension TVFCollector {
/// Checks whether a given hex string (possibly prefixed with "0x") is valid contract call data.
public static func isValidContractData(_ data: String) -> Bool {
var hex = data
if hex.hasPrefix("0x") {
hex = String(hex.dropFirst(2))
}
// Ensure there are at least 136 hex characters (8 for method, 64 for recipient, 64 for amount)
guard !hex.isEmpty, hex.count >= 73 else { return false }
let methodId = hex.prefix(8)
guard !methodId.isEmpty else { return false }
let recipient = hex.dropFirst(8).prefix(64).drop(while: { $0 == "0" })
guard !recipient.isEmpty else { return false }
let amount = hex.dropFirst(72).drop(while: { $0 == "0" })
guard !amount.isEmpty else { return false }
return true
}
}
60 changes: 39 additions & 21 deletions Tests/WalletConnectUtilsTests/TVFCollectorTests.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import XCTest

@testable import WalletConnectUtils

final class TVFCollectorTests: XCTestCase {
Expand All @@ -17,7 +16,7 @@ final class TVFCollectorTests: XCTestCase {
return .error(JSONRPCError(code: code, message: message))
}

// MARK: - Session Request (tag=1008)
// MARK: - Session Request (tag = 1108)

func testSessionRequest_UnknownMethod_ReturnsNil() {
let data = tvf.collect(
Expand All @@ -31,7 +30,9 @@ final class TVFCollectorTests: XCTestCase {
}

func testSessionRequest_EthSendTransaction_ParsesContractAddress() {
// "eth_sendTransaction" => parse the "to" field from rpcParams
// "eth_sendTransaction" — parse the "to" field from rpcParams.
// Here we supply a normal address string "0x1234567890abcdef" which is not long enough to be valid contract data.
// Therefore, the updated collector should return an empty array rather than returning the address.
let rpcParams = AnyCodable([
[
"from": "0x9876543210fedcba",
Expand All @@ -48,13 +49,13 @@ final class TVFCollectorTests: XCTestCase {
XCTAssertNotNil(data)
XCTAssertEqual(data?.rpcMethods, ["eth_sendTransaction"])
XCTAssertEqual(data?.chainId?.absoluteString, "eip155:1")
XCTAssertEqual(data?.contractAddresses, ["0x1234567890abcdef"])
// For sessionRequest => txHashes should be nil
// Expecting an empty array because "0x1234567890abcdef" is invalid contract call data.
XCTAssertEqual(data?.contractAddresses, [])
XCTAssertNil(data?.txHashes)
}

func testSessionRequest_EthSendTransaction_Malformed() {
// If decoding fails => no contractAddresses
// If decoding fails no contractAddresses
let rpcParams = AnyCodable("malformed_data")
let data = tvf.collect(
rpcMethod: "eth_sendTransaction",
Expand All @@ -68,12 +69,36 @@ final class TVFCollectorTests: XCTestCase {
XCTAssertNil(data?.txHashes)
}

// MARK: - Session Response (tag=1009)
func testSessionRequest_EthSendTransaction_WithValidContractData_ParsesContractAddress() {
// Construct a valid contract call data string:
// - 8 hex chars for method ID: "abcd1234"
// - 64 hex chars for recipient: "0000000000000000000000001111111111111111111111111111111111111111"
// - 64 hex chars for amount: "00000000000000000000000000000000000000000000000000000000000000f0"
let validContractData = "0xabcd12340000000000000000000000111111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000f0"
let rpcParams = AnyCodable([
[
"from": "0x9876543210fedcba",
"to": validContractData
]
])
let data = tvf.collect(
rpcMethod: "eth_sendTransaction",
rpcParams: rpcParams,
chainID: chain,
rpcResult: nil,
tag: 1108
)
XCTAssertNotNil(data)
// When contract data is valid the collector returns the value.
XCTAssertEqual(data?.contractAddresses, [validContractData])
}

// MARK: - Session Response (tag = 1109)

func testSessionResponse_EthSendTransaction_ReturnsTxHash() {
// EVM => parse single string from .response(AnyCodable)
// EVM parse single string from .response(AnyCodable)
let rpcParams = AnyCodable([String]())
let rpcResult = makeResponse("0x123abc") // returning as string
let rpcResult = makeResponse("0x123abc")
let data = tvf.collect(
rpcMethod: "eth_sendTransaction",
rpcParams: rpcParams,
Expand All @@ -86,7 +111,7 @@ final class TVFCollectorTests: XCTestCase {
}

func testSessionResponse_EthSendTransaction_ErrorCase() {
// If .error, no txHashes
// If .error, then no txHashes
let rpcParams = AnyCodable([String]())
let rpcResult = makeError(code: -32000, message: "some error")
let data = tvf.collect(
Expand All @@ -101,13 +126,10 @@ final class TVFCollectorTests: XCTestCase {
}

func testSessionResponse_SolanaSignTransaction_ReturnsSignature() {
// "solana_signTransaction" => parse "signature" from .response(AnyCodable)
// "solana_signTransaction" parse "signature" from .response(AnyCodable)
let rpcParams = AnyCodable([String]())
let responseData = [
"signature": "0xsolanaSignature"
]
let responseData = ["signature": "0xsolanaSignature"]
let rpcResult = makeResponse(responseData)

let data = tvf.collect(
rpcMethod: "solana_signTransaction",
rpcParams: rpcParams,
Expand All @@ -120,10 +142,9 @@ final class TVFCollectorTests: XCTestCase {
}

func testSessionResponse_SolanaSignTransaction_Malformed() {
// If decoding fails => txHashes is nil
// If decoding fails txHashes is nil
let rpcParams = AnyCodable([String]())
let rpcResult = makeResponse("malformedData")

let data = tvf.collect(
rpcMethod: "solana_signTransaction",
rpcParams: rpcParams,
Expand All @@ -137,11 +158,8 @@ final class TVFCollectorTests: XCTestCase {

func testSessionResponse_SolanaSignAllTransactions_ReturnsTransactions() {
let rpcParams = AnyCodable([String]())
let responseData = [
"transactions": ["tx1", "tx2"]
]
let responseData = ["transactions": ["tx1", "tx2"]]
let rpcResult = makeResponse(responseData)

let data = tvf.collect(
rpcMethod: "solana_signAllTransactions",
rpcParams: rpcParams,
Expand Down

0 comments on commit 1475cfc

Please sign in to comment.