Skip to content

Commit c45a71c

Browse files
Merge pull request #506 from JeneaVranceanu/feat/solidity-sha3
2 parents 2e5da95 + fdc880f commit c45a71c

File tree

4 files changed

+181
-0
lines changed

4 files changed

+181
-0
lines changed

Sources/web3swift/EthereumABI/ABIEncoding.swift

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,3 +383,75 @@ extension ABIEncoder {
383383
return nil
384384
}
385385
}
386+
387+
// MARK: - SoliditySHA3 implementation based on web3js
388+
389+
public extension ABIEncoder {
390+
/**
391+
A convenience implementation of web3js [soliditySha3](https://web3js.readthedocs.io/en/v1.2.11/web3-utils.html?highlight=soliditySha3#soliditysha3)
392+
that is based on web3swift [`ABIEncoder`](https://github.com/skywinder/web3swift/blob/develop/Sources/web3swift/EthereumABI/ABIEncoding.swift ).
393+
*/
394+
static func soliditySha3(_ values: [Any]) throws -> Data {
395+
try abiEncode(values).sha3(.keccak256)
396+
}
397+
398+
static func soliditySha3(_ value: Any) throws -> Data {
399+
if let values = value as? [Any] {
400+
return try abiEncode(values).sha3(.keccak256)
401+
} else {
402+
return try abiEncode(value).sha3(.keccak256)
403+
}
404+
}
405+
406+
/// Using AnyObject any number can be represented as Bool and Bool can be represented as number.
407+
/// That will lead to invalid hash output. DO NOT USE THIS FUNCTION.
408+
/// This function will exist to intentionally throw an error that will raise awareness that the hash output can be potentially,
409+
/// and most likely will be, wrong.
410+
/// - Parameter values: to hash
411+
/// - Returns: solidity sha3 hash
412+
static func soliditySha3(_ values: [AnyObject]) throws -> Data {
413+
throw Web3Error.inputError(desc: "AnyObject creates ambiguity and does not guarantee that the output will be correct. Please, use `soliditySha3(Any) or soliditySha3([Any]) instead.`")
414+
}
415+
416+
/// See docs for ``soliditySha3(_ values: [AnyObject])``
417+
static func soliditySha3(_ value: AnyObject) throws -> Data {
418+
throw Web3Error.inputError(desc: "AnyObject creates ambiguity and does not guarantee that the output will be correct. Please, use `soliditySha3(Any) or soliditySha3([Any]) instead.`")
419+
}
420+
421+
static func abiEncode(_ values: [Any]) throws -> Data {
422+
return try values.map {
423+
try abiEncode($0)
424+
}.reduce(into: Data()) { partialResult, nextElement in
425+
partialResult.append(nextElement)
426+
}
427+
}
428+
429+
static func abiEncode(_ value: Any) throws -> Data {
430+
if let v = value as? Bool {
431+
return Data(v ? [0b1] : [0b0])
432+
} else if let v = value as? Int {
433+
return ABIEncoder.convertToData(BigInt(exactly: v)?.abiEncode(bits: 256)! as AnyObject)!
434+
} else if let v = value as? Int8 {
435+
return ABIEncoder.convertToData(BigInt(exactly: v)?.abiEncode(bits: 8) as AnyObject)!
436+
} else if let v = value as? Int16 {
437+
return ABIEncoder.convertToData(BigInt(exactly: v)?.abiEncode(bits: 16)! as AnyObject)!
438+
} else if let v = value as? Int32 {
439+
return ABIEncoder.convertToData(BigInt(exactly: v)?.abiEncode(bits: 32)! as AnyObject)!
440+
} else if let v = value as? Int64 {
441+
return ABIEncoder.convertToData(BigInt(exactly: v)?.abiEncode(bits: 64)! as AnyObject)!
442+
} else if let v = value as? UInt {
443+
return ABIEncoder.convertToData(BigUInt(exactly: v)?.abiEncode(bits: 256)! as AnyObject)!
444+
} else if let v = value as? UInt8 {
445+
return ABIEncoder.convertToData(BigUInt(exactly: v)?.abiEncode(bits: 8)! as AnyObject)!
446+
} else if let v = value as? UInt16 {
447+
return ABIEncoder.convertToData(BigUInt(exactly: v)?.abiEncode(bits: 16)! as AnyObject)!
448+
} else if let v = value as? UInt32 {
449+
return ABIEncoder.convertToData(BigUInt(exactly: v)?.abiEncode(bits: 32)! as AnyObject)!
450+
} else if let v = value as? UInt64 {
451+
return ABIEncoder.convertToData(BigUInt(exactly: v)?.abiEncode(bits: 64)! as AnyObject)!
452+
} else if let data = ABIEncoder.convertToData(value as AnyObject) {
453+
return data
454+
}
455+
throw Web3Error.inputError(desc: "SoliditySha3: `abiEncode` accepts an Int/UInt (any of 8, 16, 32, 64 bits long), HEX string, Bool, Data, BigInt or BigUInt instance. Given value is of type \(type(of: value)).")
456+
}
457+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
//
2+
// ABIEncoderSoliditySha3Test.swift
3+
// Tests
4+
//
5+
// Created by JeneaVranceanu on 28/03/2022.
6+
// Copyright © 2022 web3swift. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import XCTest
11+
@testable import web3swift
12+
13+
class ABIEncoderSoliditySha3Test: XCTestCase {
14+
15+
func test_soliditySha3() throws {
16+
var hex = try ABIEncoder.soliditySha3(true).toHexString().addHexPrefix()
17+
assert(hex == "0x5fe7f977e71dba2ea1a68e21057beebb9be2ac30c6410aa38d4f3fbe41dcffd2")
18+
hex = try ABIEncoder.soliditySha3(-10).toHexString().addHexPrefix()
19+
assert(hex == "0xd6fb717f7e270a360f5093ce6a7a3752183e89c9a9afe5c0cb54b458a304d3d5")
20+
hex = try ABIEncoder.soliditySha3(Data.fromHex("0xfff23243")!).toHexString().addHexPrefix()
21+
assert(hex == "0x0ee4597224d3499c72aa0c309b0d0cb80ff3c2439a548c53edb479abfd6927ba")
22+
hex = try ABIEncoder.soliditySha3(UInt(234564535)).toHexString().addHexPrefix()
23+
assert(hex == "0xb2daf574dc6ceac97e984c8a3ffce3c1ec19e81cc6b18aeea67b3ac2666f4e97")
24+
25+
hex = try ABIEncoder.soliditySha3([UInt(234564535), Data.fromHex("0xfff23243")!, true, -10]).toHexString().addHexPrefix()
26+
assert(hex == "0x3e27a893dc40ef8a7f0841d96639de2f58a132be5ae466d40087a2cfa83b7179")
27+
28+
hex = try ABIEncoder.soliditySha3("Hello!%").toHexString().addHexPrefix()
29+
assert(hex == "0x661136a4267dba9ccdf6bfddb7c00e714de936674c4bdb065a531cf1cb15c7fc")
30+
31+
// This is not JS. '234' (with single or double qoutes) will be a String, not any kind of number.
32+
// From Web3JS docs:> web3.utils.soliditySha3('234'); // auto detects: uint256
33+
34+
hex = try ABIEncoder.soliditySha3(0xea).toHexString().addHexPrefix()
35+
assert(hex == "0x61c831beab28d67d1bb40b5ae1a11e2757fa842f031a2d0bc94a7867bc5d26c2")
36+
37+
hex = try ABIEncoder.soliditySha3(234).toHexString().addHexPrefix()
38+
assert(hex == "0x61c831beab28d67d1bb40b5ae1a11e2757fa842f031a2d0bc94a7867bc5d26c2")
39+
40+
hex = try ABIEncoder.soliditySha3(UInt64(234)).toHexString().addHexPrefix()
41+
assert(hex == "0x6e48b7f8b342032bfa46a07cf85358feee0efe560d6caa87d342f24cdcd07b0c")
42+
43+
hex = try ABIEncoder.soliditySha3(UInt(234)).toHexString().addHexPrefix()
44+
assert(hex == "0x61c831beab28d67d1bb40b5ae1a11e2757fa842f031a2d0bc94a7867bc5d26c2")
45+
46+
hex = try ABIEncoder.soliditySha3("0x407D73d8a49eeb85D32Cf465507dd71d507100c1").toHexString().addHexPrefix()
47+
assert(hex == "0x4e8ebbefa452077428f93c9520d3edd60594ff452a29ac7d2ccc11d47f3ab95b")
48+
49+
hex = try ABIEncoder.soliditySha3(Data.fromHex("0x407D73d8a49eeb85D32Cf465507dd71d507100c1")!).toHexString().addHexPrefix()
50+
assert(hex == "0x4e8ebbefa452077428f93c9520d3edd60594ff452a29ac7d2ccc11d47f3ab95b")
51+
52+
hex = try ABIEncoder.soliditySha3(EthereumAddress("0x407D73d8a49eeb85D32Cf465507dd71d507100c1")!).toHexString().addHexPrefix()
53+
assert(hex == "0x4e8ebbefa452077428f93c9520d3edd60594ff452a29ac7d2ccc11d47f3ab95b")
54+
55+
56+
hex = try ABIEncoder.soliditySha3("Hello!%").toHexString().addHexPrefix()
57+
assert(hex == "0x661136a4267dba9ccdf6bfddb7c00e714de936674c4bdb065a531cf1cb15c7fc")
58+
59+
hex = try ABIEncoder.soliditySha3(Int8(-23)).toHexString().addHexPrefix()
60+
assert(hex == "0xdc046d75852af4aea44a770057190294068a953828daaaab83800e2d0a8f1f35")
61+
62+
hex = try ABIEncoder.soliditySha3(EthereumAddress("0x85F43D8a49eeB85d32Cf465507DD71d507100C1d")!).toHexString().addHexPrefix()
63+
assert(hex == "0xe88edd4848fdce08c45ecfafd2fbfdefc020a7eafb8178e94c5feaeec7ac0bb4")
64+
65+
hex = try ABIEncoder.soliditySha3(["Hello!%", Int8(-23), EthereumAddress("0x85F43D8a49eeB85d32Cf465507DD71d507100C1d")!]).toHexString().addHexPrefix()
66+
assert(hex == "0xa13b31627c1ed7aaded5aecec71baf02fe123797fffd45e662eac8e06fbe4955")
67+
}
68+
69+
func test_soliditySha3Fail_FloatDouble() throws {
70+
assert((try? ABIEncoder.soliditySha3(Float(1))) == nil)
71+
assert((try? ABIEncoder.soliditySha3(Double(1))) == nil)
72+
assert((try? ABIEncoder.soliditySha3(CGFloat(1))) == nil)
73+
assert((try? ABIEncoder.soliditySha3([Float(1)])) == nil)
74+
assert((try? ABIEncoder.soliditySha3([Double(1)])) == nil)
75+
assert((try? ABIEncoder.soliditySha3([CGFloat(1)])) == nil)
76+
}
77+
78+
/// `[AnyObject]` is not allowed to be used directly as input for `solidtySha3`.
79+
/// `AnyObject` erases type data making it impossible to encode some types correctly,
80+
/// e.g.: Bool can be treated as Int (8/16/32/64) and 0/1 numbers can be treated as Bool.
81+
func test_soliditySha3Fail_1() throws {
82+
var didFail = false
83+
do {
84+
let _ = try ABIEncoder.soliditySha3([""] as [AnyObject])
85+
} catch {
86+
didFail = true
87+
}
88+
XCTAssertTrue(didFail)
89+
}
90+
91+
/// `AnyObject` is not allowed to be used directly as input for `solidtySha3`.
92+
/// `AnyObject` erases type data making it impossible to encode some types correctly,
93+
/// e.g.: Bool can be treated as Int (8/16/32/64) and 0/1 numbers can be treated as Bool.
94+
func test_soliditySha3Fail_2() throws {
95+
var didFail = false
96+
do {
97+
let _ = try ABIEncoder.soliditySha3("" as AnyObject)
98+
} catch {
99+
didFail = true
100+
}
101+
XCTAssertTrue(didFail)
102+
}
103+
}

Tests/web3swiftTests/remoteTests/RemoteTests.xctestplan

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
"testTargets" : [
1515
{
1616
"skippedTests" : [
17+
"ABIEncoderSoliditySha3Test",
1718
"DataConversionTests",
1819
"EIP712Tests",
20+
"SoliditySha3Test",
1921
"web3swiftAdvancedABIv2Tests",
2022
"web3swiftBasicLocalNodeTests",
2123
"web3swiftEIP67Tests",

web3swift.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@
191191
5CF7E8BC276B79380009900F /* web3swiftWebsocketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF7E8B7276B79380009900F /* web3swiftWebsocketTests.swift */; };
192192
604FA4FF27ECBDC80021108F /* DataConversionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 604FA4FE27ECBDC80021108F /* DataConversionTests.swift */; };
193193
CB50A52827060BD600D7E39B /* EIP712Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB50A52727060BD600D7E39B /* EIP712Tests.swift */; };
194+
D6A3D9B827F1E785009E3BCF /* ABIEncoderSoliditySha3Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3D9B727F1E785009E3BCF /* ABIEncoderSoliditySha3Test.swift */; };
194195
E22A911F241ED71A00EC1021 /* browser.min.js in Resources */ = {isa = PBXBuildFile; fileRef = E22A911E241ED71A00EC1021 /* browser.min.js */; };
195196
E2B76710241ED479007EBFE3 /* browser.js in Resources */ = {isa = PBXBuildFile; fileRef = E2B7670F241ED479007EBFE3 /* browser.js */; };
196197
E2EDC5EA241EDE3600410EA6 /* BrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EDC5E9241EDE3600410EA6 /* BrowserViewController.swift */; platformFilter = ios; };
@@ -409,6 +410,7 @@
409410
604FA4FE27ECBDC80021108F /* DataConversionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataConversionTests.swift; sourceTree = "<group>"; };
410411
CB50A52727060BD600D7E39B /* EIP712Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Tests.swift; sourceTree = "<group>"; };
411412
CB50A52927060C5300D7E39B /* EIP712.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712.swift; sourceTree = "<group>"; };
413+
D6A3D9B727F1E785009E3BCF /* ABIEncoderSoliditySha3Test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ABIEncoderSoliditySha3Test.swift; sourceTree = "<group>"; };
412414
E22A911E241ED71A00EC1021 /* browser.min.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = browser.min.js; sourceTree = "<group>"; };
413415
E2B7670F241ED479007EBFE3 /* browser.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = browser.js; sourceTree = "<group>"; };
414416
E2EDC5E9241EDE3600410EA6 /* BrowserViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserViewController.swift; sourceTree = "<group>"; };
@@ -1001,6 +1003,7 @@
10011003
5CF7E893276B79270009900F /* web3swiftTransactionsTests.swift */,
10021004
5CF7E89E276B79280009900F /* web3swiftUserCases.swift */,
10031005
CB50A52727060BD600D7E39B /* EIP712Tests.swift */,
1006+
D6A3D9B727F1E785009E3BCF /* ABIEncoderSoliditySha3Test.swift */,
10041007
5CDEF973275A74670004A2F2 /* LocalTests.xctestplan */,
10051008
);
10061009
path = localTests;
@@ -1396,6 +1399,7 @@
13961399
5CF7E8AC276B792A0009900F /* web3swiftObjCTests.swift in Sources */,
13971400
5CF7E8A3276B792A0009900F /* web3swiftPersonalSignatureTests.swift in Sources */,
13981401
5CF7E8BB276B79380009900F /* web3swiftENSTests.swift in Sources */,
1402+
D6A3D9B827F1E785009E3BCF /* ABIEncoderSoliditySha3Test.swift in Sources */,
13991403
);
14001404
runOnlyForDeploymentPostprocessing = 0;
14011405
};

0 commit comments

Comments
 (0)