Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: updated develop-4.0 with changes from develop #842

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
10323a1
chore: readme update
JeneaVranceanu May 2, 2023
d7f5a95
chore: readme update #817
JeneaVranceanu May 2, 2023
c1d01d2
feat: sign without hash
Jun 7, 2023
c9e0edd
add default value
JuyeonYu Jun 8, 2023
2af0c71
Addresses generator (#1)
6od9i Jun 29, 2023
12b781f
- Grammar fixed
6od9i Jun 29, 2023
f30bd0e
- linter Trailing Whitespace Violation fixed
6od9i Jun 29, 2023
9e9e48d
- closure style updated
6od9i Jun 29, 2023
755ed95
- not used code removed
6od9i Jun 29, 2023
7876390
- Trailing Whitespace fixed in tests
6od9i Jun 29, 2023
d76e6ee
fix: createNewAccount always non-hardened derivation
JeneaVranceanu Jun 30, 2023
a9ed5cd
Merge branch 'develop' into develop-6od9i
JeneaVranceanu Jun 30, 2023
b82e347
Merge pull request #2 from JeneaVranceanu/develop-6od9i
6od9i Jun 30, 2023
216f11b
- bug with original pathAppendix fixed
6od9i Jun 30, 2023
a64e380
chore: refactoring of createNewCustomChildAccount
JeneaVranceanu Jun 30, 2023
9c68d9b
- Test testAddressGeneration modified with hardcoded addresses
6od9i Jun 30, 2023
886897c
chore: merged with develop
JeneaVranceanu Jun 30, 2023
91975f6
fix: trimming / only if it's present as a prefix; using let for 'newP…
JeneaVranceanu Jun 30, 2023
ccae14f
Merge branch 'develop' of https://github.com/6od9i/web3swift into dev…
JeneaVranceanu Jun 30, 2023
97def7b
fix: used short notation for null checking
JeneaVranceanu Jun 30, 2023
e806518
Merge pull request #3 from JeneaVranceanu/develop-6od9i
6od9i Jun 30, 2023
752171b
chore: trailing closure style for compactMap
JeneaVranceanu Jul 1, 2023
dd32c16
- linter warning fixed
6od9i Jul 3, 2023
e587175
- isHardened check removed from createNewCustomChildAccount
6od9i Jul 5, 2023
4833b1c
Merge pull request #825 from 6od9i/develop
yaroslavyaroslav Jul 19, 2023
dab2667
Merge pull request #824 from JuyeonYu/noHashInSign
yaroslavyaroslav Jul 24, 2023
7719379
feat: enable encode Event and IETH.getLogs
zhangliugang Aug 24, 2023
909a1b0
remove a force unwrap
zhangliugang Aug 24, 2023
df87030
Trim Trailing Whitespace
zhangliugang Aug 24, 2023
89a9863
add more test for encodeTopic
zhangliugang Aug 28, 2023
d744289
encodeTopic bytes should accept hex string
zhangliugang Sep 2, 2023
2d4bffd
Merge pull request #830 from zhangliugang/query-fileter
JeneaVranceanu Sep 20, 2023
bab86ca
refactor: random bytes generation; data and string extensions update …
pharms-eth Nov 7, 2023
6177c46
Merge branch 'develop' into develop-4.0
JeneaVranceanu Jan 16, 2024
ed830e6
Merge branch 'develop' into develop-4.0
JeneaVranceanu Jan 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
- [x] ✅**Literally following the standards** (BIP, EIP, etc):
- [x] **[BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) (HD Wallets), [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) (Seed phrases), [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) (Key generation prefixes)**
- [x] **[EIP-20](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md)** (Standard interface for tokens - ERC-20), **[EIP-67](https://github.com/ethereum/EIPs/issues/67)** (Standard URI scheme), **[EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md)** (Replay attacks protection), **[EIP-2718](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2718.md)** (Typed Transaction Envelope), **[EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md)** (Gas Fee market change)
- [x] **And many others** *(For details about this EIP's look at [Documentation page](https://github.com/web3swift-team/web3swift/blob/master/Documentation/))*: EIP-681, EIP-721, EIP-165, EIP-777, EIP-820, EIP-888, EIP-1400, EIP-1410, EIP-1594, EIP-1643, EIP-1644, EIP-1633, EIP-721, EIP-1155, EIP-1376, ST-20
- [x] **And many others** *(For details about this EIP's look at [Documentation page](https://github.com/web3swift-team/web3swift/blob/master/Documentation/))*: EIP-165, EIP-681, EIP-721, EIP-777, EIP-820, EIP-888, EIP-1155, EIP-1376, EIP-1400, EIP-1410, EIP-1594, EIP-1633, EIP-1643, EIP-1644, EIP-4361 ([SIWE](https://eips.ethereum.org/EIPS/eip-4361)), ST-20
- [x] **RLP encoding**
- [x] Base58 encoding scheme
- [x] Formatting to and from Ethereum Units
Expand Down
76 changes: 69 additions & 7 deletions Sources/Web3Core/Contract/ContractProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,12 @@ public protocol ContractProtocol {
/// - name with arguments:`myFunction(uint256)`.
/// - method signature (with or without `0x` prefix, case insensitive): `0xFFffFFff`;
/// - data: non empty bytes to decode;
/// - Returns: dictionary with decoded values. `nil` if decoding failed.
func decodeReturnData(_ method: String, data: Data) -> [String: Any]?
/// - Returns: dictionary with decoded values.
/// - Throws:
/// - `Web3Error.revert(String, String?)` when function call aborted by `revert(string)` and `require(expression, string)`.
/// - `Web3Error.revertCustom(String, Dictionary)` when function call aborted by `revert CustomError()`.
@discardableResult
func decodeReturnData(_ method: String, data: Data) throws -> [String: Any]

/// Decode input arguments of a function.
/// - Parameters:
Expand Down Expand Up @@ -280,6 +284,13 @@ extension DefaultContractProtocol {
return encodedData
}

public func event(_ event: String, parameters: [Any]) -> [EventFilterParameters.Topic?] {
guard let event = events[event] else {
return []
}
return event.encodeParameters(parameters)
}

public func parseEvent(_ eventLog: EventLog) -> (eventName: String?, eventData: [String: Any]?) {
for (eName, ev) in self.events {
if !ev.anonymous {
Expand Down Expand Up @@ -313,13 +324,40 @@ extension DefaultContractProtocol {
return bloom.test(topic: event.topic)
}

public func decodeReturnData(_ method: String, data: Data) -> [String: Any]? {
@discardableResult
public func decodeReturnData(_ method: String, data: Data) throws -> [String: Any] {
if method == "fallback" {
return [String: Any]()
return [:]
}

guard let function = methods[method]?.first else {
throw Web3Error.inputError(desc: "Make sure ABI you use contains '\(method)' method.")
}

switch data.count % 32 {
case 0:
return try function.decodeReturnData(data)
case 4:
let selector = data[0..<4]
if selector.toHexString() == "08c379a0", let reason = ABI.Element.EthError.decodeStringError(data[4...]) {
throw Web3Error.revert("revert(string)` or `require(expression, string)` was executed. reason: \(reason)", reason: reason)
}
else if selector.toHexString() == "4e487b71", let reason = ABI.Element.EthError.decodePanicError(data[4...]) {
let panicCode = String(format: "%02X", Int(reason)).addHexPrefix()
throw Web3Error.revert("Error: call revert exception; VM Exception while processing transaction: reverted with panic code \(panicCode)", reason: panicCode)
}
else if let customError = errors[selector.toHexString().addHexPrefix().lowercased()] {
if let errorArgs = customError.decodeEthError(data[4...]) {
throw Web3Error.revertCustom(customError.signature, errorArgs)
} else {
throw Web3Error.inputError(desc: "Signature matches \(customError.errorDeclaration) but failed to be decoded.")
}
} else {
throw Web3Error.inputError(desc: "Make sure ABI you use contains error that can match signature: 0x\(selector.toHexString())")
}
default:
throw Web3Error.inputError(desc: "Given data has invalid bytes count.")
}
return methods[method]?.compactMap({ function in
return function.decodeReturnData(data)
}).first
}

public func decodeInputData(_ method: String, data: Data) -> [String: Any]? {
Expand All @@ -339,8 +377,32 @@ extension DefaultContractProtocol {
return function.decodeInputData(Data(data[data.startIndex + 4 ..< data.startIndex + data.count]))
}

public func decodeEthError(_ data: Data) -> [String: Any]? {
guard data.count >= 4,
let err = errors.first(where: { $0.value.methodEncoding == data[0..<4] })?.value else {
return nil
}
return err.decodeEthError(data[4...])
}

public func getFunctionCalled(_ data: Data) -> ABI.Element.Function? {
guard data.count >= 4 else { return nil }
return methods[data[data.startIndex ..< data.startIndex + 4].toHexString().addHexPrefix()]?.first
}
}

extension DefaultContractProtocol {
@discardableResult
public func callStatic(_ method: String, parameters: [Any], provider: Web3Provider) async throws -> [String: Any] {
guard let address = address else {
throw Web3Error.inputError(desc: "RPC failed: contract is missing an address.")
}
guard let data = self.method(method, parameters: parameters, extraData: nil) else {
throw Web3Error.dataError
}
let transaction = CodableTransaction(to: address, data: data)

let result: Data = try await APIRequest.sendRequest(with: provider, for: .call(transaction, .latest)).result
return try decodeReturnData(method, data: result)
}
}
175 changes: 125 additions & 50 deletions Sources/Web3Core/EthereumABI/ABIElements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ extension ABI.Element.Constructor {
extension ABI.Element.Function {

/// Encode parameters of a given contract method
/// - Parameter parameters: Parameters to pass to Ethereum contract
/// - Parameters: Parameters to pass to Ethereum contract
/// - Returns: Encoded data
public func encodeParameters(_ parameters: [Any]) -> Data? {
guard parameters.count == inputs.count,
Expand All @@ -211,13 +211,123 @@ extension ABI.Element.Function {
}
}

// MARK: - Event logs decoding
// MARK: - Event logs decoding & encoding

extension ABI.Element.Event {
public func decodeReturnedLogs(eventLogTopics: [Data], eventLogData: Data) -> [String: Any]? {
guard let eventContent = ABIDecoder.decodeLog(event: self, eventLogTopics: eventLogTopics, eventLogData: eventLogData) else { return nil }
return eventContent
}

public static func encodeTopic(input: ABI.Element.Event.Input, value: Any) -> EventFilterParameters.Topic? {
switch input.type {
case .string:
guard let string = value as? String else {
return nil
}
return .string(string.sha3(.keccak256).addHexPrefix())
case .dynamicBytes:
guard let data = ABIEncoder.convertToData(value) else {
return nil
}
return .string(data.sha3(.keccak256).toHexString().addHexPrefix())
case .bytes(length: _):
guard let data = ABIEncoder.convertToData(value), let data = data.setLengthLeft(32) else {
return nil
}
return .string(data.toHexString().addHexPrefix())
case .address, .uint(bits: _), .int(bits: _), .bool:
guard let encoded = ABIEncoder.encodeSingleType(type: input.type, value: value) else {
return nil
}
return .string(encoded.toHexString().addHexPrefix())
default:
guard let data = try? ABIEncoder.abiEncode(value).setLengthLeft(32) else {
return nil
}
return .string(data.toHexString().addHexPrefix())
}
}

public func encodeParameters(_ parameters: [Any?]) -> [EventFilterParameters.Topic?] {
guard parameters.count <= inputs.count else {
// too many arguments for fragment
return []
}
var topics: [EventFilterParameters.Topic?] = []

if !anonymous {
topics.append(.string(topic.toHexString().addHexPrefix()))
}

for (i, p) in parameters.enumerated() {
let input = inputs[i]
if !input.indexed {
// cannot filter non-indexed parameters; must be null
return []
}
if p == nil {
topics.append(nil)
} else if input.type.isArray || input.type.isTuple {
// filtering with tuples or arrays not supported
return []
} else if let p = p as? Array<Any> {
topics.append(.strings(p.map { Self.encodeTopic(input: input, value: $0) }))
} else {
topics.append(Self.encodeTopic(input: input, value: p!))
}
}

// Trim off trailing nulls
while let last = topics.last {
if last == nil {
topics.removeLast()
} else if case .string(let string) = last, string == nil {
topics.removeLast()
} else {
break
}
}
return topics
}
}

// MARK: - Decode custom error

extension ABI.Element.EthError {
/// Decodes `revert CustomError(_)` calls.
/// - Parameters:
/// - data: bytes returned by a function call that stripped error signature hash.
/// - Returns: a dictionary containing decoded data mappend to indices and names of returned values or nil if decoding failed.
public func decodeEthError(_ data: Data) -> [String: Any]? {
guard inputs.count * 32 <= data.count,
let decoded = ABIDecoder.decode(types: inputs, data: data) else {
return nil
}

var result = [String: Any]()
for (index, out) in inputs.enumerated() {
result["\(index)"] = decoded[index]
if !out.name.isEmpty {
result[out.name] = decoded[index]
}
}
return result
}

/// Decodes `revert(string)` or `require(expression, string)` calls.
/// These calls are decomposed as `Error(string)` error.
public static func decodeStringError(_ data: Data) -> String? {
let decoded = ABIDecoder.decode(types: [.init(name: "", type: .string)], data: data)
return decoded?.first as? String
}

/// Decodes `Panic(uint256)` errors.
/// See more about panic code explain at: https://docs.soliditylang.org/en/v0.8.21/control-structures.html#panic-via-assert-and-error-via-require
public static func decodePanicError(_ data: Data) -> BigUInt? {
let decoded = ABIDecoder.decode(types: [.init(name: "", type: .uint(bits: 256))], data: data)
return decoded?.first as? BigUInt
}
}

// MARK: - Function input/output decoding
Expand All @@ -232,7 +342,7 @@ extension ABI.Element {
case .fallback:
return nil
case .function(let function):
return function.decodeReturnData(data)
return try? function.decodeReturnData(data)
case .receive:
return nil
case .error:
Expand Down Expand Up @@ -265,74 +375,38 @@ extension ABI.Element.Function {
return ABIDecoder.decodeInputData(rawData, methodEncoding: methodEncoding, inputs: inputs)
}

/// Decodes data returned by a function call. Able to decode `revert(string)`, `revert CustomError(...)` and `require(expression, string)` calls.
/// Decodes data returned by a function call.
/// - Parameters:
/// - data: bytes returned by a function call;
/// - errors: optional dictionary of known errors that could be returned by the function you called. Used to decode the error information.
/// - Returns: a dictionary containing decoded data mappend to indices and names of returned values if these are not `nil`.
/// If `data` is an error response returns dictionary containing all available information about that specific error. Read more for details.
/// - Throws:
/// - `Web3Error.processingError(desc: String)` when decode process failed.
///
/// Return cases:
/// - when no `outputs` declared and `data` is not an error response:
/// - when no `outputs` declared:
/// ```swift
/// ["_success": true]
/// [:]
/// ```
/// - when `outputs` declared and decoding completed successfully:
/// ```swift
/// ["_success": true, "0": value_1, "1": value_2, ...]
/// ["0": value_1, "1": value_2, ...]
/// ```
/// Additionally this dictionary will have mappings to output names if these names are specified in the ABI;
/// - function call was aborted using `revert(message)` or `require(expression, message)`:
/// ```swift
/// ["_success": false, "_abortedByRevertOrRequire": true, "_errorMessage": message]`
/// ```
/// - function call was aborted using `revert CustomMessage()` and `errors` argument contains the ABI of that custom error type:
/// ```swift
/// ["_success": false,
/// "_abortedByRevertOrRequire": true,
/// "_error": error_name_and_types, // e.g. `MyCustomError(uint256, address senderAddress)`
/// "0": error_arg1,
/// "1": error_arg2,
/// ...,
/// "error_arg1_name": error_arg1, // Only named arguments will be mapped to their names, e.g. `"senderAddress": EthereumAddress`
/// "error_arg2_name": error_arg2, // Otherwise, you can query them by position index.
/// ...]
/// ```
/// - in case of any error:
/// ```swift
/// ["_success": false, "_failureReason": String]
/// ```
/// Error reasons include:
/// - `outputs` declared but at least one value failed to be decoded;
/// - `data.count` is less than `outputs.count * 32`;
/// - `outputs` defined and `data` is empty;
/// - `data` represent reverted transaction
///
/// How `revert(string)` and `require(expression, string)` return value is decomposed:
/// - `08C379A0` function selector for `Error(string)`;
/// - next 32 bytes are the data offset;
/// - next 32 bytes are the error message length;
/// - the next N bytes, where N >= 32, are the message bytes
/// - the rest are 0 bytes padding.
public func decodeReturnData(_ data: Data, errors: [String: ABI.Element.EthError]? = nil) -> [String: Any] {
if let decodedError = decodeErrorResponse(data, errors: errors) {
return decodedError
}

public func decodeReturnData(_ data: Data) throws -> [String: Any] {
guard !outputs.isEmpty else {
NSLog("Function doesn't have any output types to decode given data.")
return ["_success": true]
return [:]
}

guard outputs.count * 32 <= data.count else {
return ["_success": false, "_failureReason": "Bytes count must be at least \(outputs.count * 32). Given \(data.count). Decoding will fail."]
throw Web3Error.processingError(desc: "Bytes count must be at least \(outputs.count * 32). Given \(data.count). Decoding will fail.")
}

// TODO: need improvement - we should be able to tell which value failed to be decoded
guard let values = ABIDecoder.decode(types: outputs, data: data) else {
return ["_success": false, "_failureReason": "Failed to decode at least one value."]
throw Web3Error.processingError(desc: "Failed to decode at least one value.")
}
var returnArray: [String: Any] = ["_success": true]
var returnArray: [String: Any] = [:]
for i in outputs.indices {
returnArray["\(i)"] = values[i]
if !outputs[i].name.isEmpty {
Expand Down Expand Up @@ -381,6 +455,7 @@ extension ABI.Element.Function {
/// // "_parsingError" is optional and is present only if decoding of custom error arguments failed
/// "_parsingError": "Data matches MyCustomError(uint256, address senderAddress) but failed to be decoded."]
/// ```
@available(*, deprecated, message: "Use decode function from `ABI.Element.EthError` instead")
public func decodeErrorResponse(_ data: Data, errors: [String: ABI.Element.EthError]? = nil) -> [String: Any]? {
/// If data is empty and outputs are expected it is treated as a `require(expression)` or `revert()` call with no message.
/// In solidity `require(false)` and `revert()` calls return empty error response.
Expand Down
Loading