Skip to content

Commit 2564f4b

Browse files
feat: solidity sha3 implementation added
1 parent cc9b109 commit 2564f4b

File tree

4 files changed

+181
-0
lines changed

4 files changed

+181
-0
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//
2+
// SoliditySha3.swift
3+
// web3swift
4+
//
5+
// Created by JeneaVranceanu on 28/03/2022.
6+
// Copyright © 2022 web3swift. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import BigInt
11+
12+
/**
13+
A convenience implementation of web3js [soliditySha3](https://web3js.readthedocs.io/en/v1.2.11/web3-utils.html?highlight=soliditySha3#soliditysha3)
14+
that is based on web3swift [`ABIEncoder`](https://github.com/skywinder/web3swift/blob/develop/Sources/web3swift/EthereumABI/ABIEncoding.swift ).
15+
*/
16+
func soliditySha3(_ values: [Any]) -> Data {
17+
abiEncode(values).sha3(.keccak256)
18+
}
19+
20+
func soliditySha3(_ value: Any) -> Data {
21+
if let values = value as? [Any] {
22+
return abiEncode(values).sha3(.keccak256)
23+
} else {
24+
return try! abiEncode(value).sha3(.keccak256)
25+
}
26+
}
27+
28+
/// Using AnyObject any number can be represented as Bool and Bool can be represented as number.
29+
/// That will lead to invalid hash output. DO NOT USE THIS FUNCTION.
30+
/// This function will exist to intentionally throw an error that will raise awareness that the hash output can be potentially,
31+
/// and most likely will be, wrong.
32+
/// - Parameter values: to hash
33+
/// - Returns: solidity sha3 hash
34+
func soliditySha3(_ values: [AnyObject]) throws -> Data {
35+
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.`")
36+
}
37+
38+
/// See docs for ``soliditySha3(_ values: [AnyObject])``
39+
func soliditySha3(_ value: AnyObject) throws -> Data {
40+
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.`")
41+
}
42+
43+
func abiEncode(_ values: [Any]) -> Data {
44+
return values.map {
45+
try! abiEncode($0)
46+
}.reduce(into: Data()) { partialResult, nextElement in
47+
partialResult.append(nextElement)
48+
}
49+
}
50+
51+
func abiEncode(_ value: Any) throws -> Data {
52+
if let v = value as? Bool {
53+
return Data(v ? [0b1] : [0b0])
54+
} else if let v = value as? Int {
55+
return ABIEncoder.convertToData(BigInt(exactly: v)?.abiEncode(bits: 256)! as AnyObject)!
56+
} else if let v = value as? Int8 {
57+
return ABIEncoder.convertToData(BigInt(exactly: v)?.abiEncode(bits: 8) as AnyObject)!
58+
} else if let v = value as? Int16 {
59+
return ABIEncoder.convertToData(BigInt(exactly: v)?.abiEncode(bits: 16)! as AnyObject)!
60+
} else if let v = value as? Int32 {
61+
return ABIEncoder.convertToData(BigInt(exactly: v)?.abiEncode(bits: 32)! as AnyObject)!
62+
} else if let v = value as? Int64 {
63+
return ABIEncoder.convertToData(BigInt(exactly: v)?.abiEncode(bits: 64)! as AnyObject)!
64+
} else if let v = value as? UInt {
65+
return ABIEncoder.convertToData(BigUInt(exactly: v)?.abiEncode(bits: 256)! as AnyObject)!
66+
} else if let v = value as? UInt8 {
67+
return ABIEncoder.convertToData(BigUInt(exactly: v)?.abiEncode(bits: 8)! as AnyObject)!
68+
} else if let v = value as? UInt16 {
69+
return ABIEncoder.convertToData(BigUInt(exactly: v)?.abiEncode(bits: 16)! as AnyObject)!
70+
} else if let v = value as? UInt32 {
71+
return ABIEncoder.convertToData(BigUInt(exactly: v)?.abiEncode(bits: 32)! as AnyObject)!
72+
} else if let v = value as? UInt64 {
73+
return ABIEncoder.convertToData(BigUInt(exactly: v)?.abiEncode(bits: 64)! as AnyObject)!
74+
} else if let data = ABIEncoder.convertToData(value as AnyObject) {
75+
return data
76+
}
77+
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)).")
78+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
//
2+
// soliditySha3.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 SoliditySha3Test: XCTestCase {
14+
15+
func test_soliditySha3() throws {
16+
var hex = soliditySha3(true).toHexString().addHexPrefix()
17+
assert(hex == "0x5fe7f977e71dba2ea1a68e21057beebb9be2ac30c6410aa38d4f3fbe41dcffd2")
18+
hex = soliditySha3(-10).toHexString().addHexPrefix()
19+
assert(hex == "0xd6fb717f7e270a360f5093ce6a7a3752183e89c9a9afe5c0cb54b458a304d3d5")
20+
hex = soliditySha3(Data.fromHex("0xfff23243")!).toHexString().addHexPrefix()
21+
assert(hex == "0x0ee4597224d3499c72aa0c309b0d0cb80ff3c2439a548c53edb479abfd6927ba")
22+
hex = soliditySha3(UInt(234564535)).toHexString().addHexPrefix()
23+
assert(hex == "0xb2daf574dc6ceac97e984c8a3ffce3c1ec19e81cc6b18aeea67b3ac2666f4e97")
24+
25+
hex = soliditySha3([UInt(234564535), Data.fromHex("0xfff23243")!, true, -10]).toHexString().addHexPrefix()
26+
assert(hex == "0x3e27a893dc40ef8a7f0841d96639de2f58a132be5ae466d40087a2cfa83b7179")
27+
28+
hex = 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 = soliditySha3(0xea).toHexString().addHexPrefix()
35+
assert(hex == "0x61c831beab28d67d1bb40b5ae1a11e2757fa842f031a2d0bc94a7867bc5d26c2")
36+
37+
hex = soliditySha3(234).toHexString().addHexPrefix()
38+
assert(hex == "0x61c831beab28d67d1bb40b5ae1a11e2757fa842f031a2d0bc94a7867bc5d26c2")
39+
40+
hex = soliditySha3(UInt64(234)).toHexString().addHexPrefix()
41+
assert(hex == "0x6e48b7f8b342032bfa46a07cf85358feee0efe560d6caa87d342f24cdcd07b0c")
42+
43+
hex = soliditySha3(UInt(234)).toHexString().addHexPrefix()
44+
assert(hex == "0x61c831beab28d67d1bb40b5ae1a11e2757fa842f031a2d0bc94a7867bc5d26c2")
45+
46+
hex = soliditySha3("0x407D73d8a49eeb85D32Cf465507dd71d507100c1").toHexString().addHexPrefix()
47+
assert(hex == "0x4e8ebbefa452077428f93c9520d3edd60594ff452a29ac7d2ccc11d47f3ab95b")
48+
49+
hex = soliditySha3(Data.fromHex("0x407D73d8a49eeb85D32Cf465507dd71d507100c1")!).toHexString().addHexPrefix()
50+
assert(hex == "0x4e8ebbefa452077428f93c9520d3edd60594ff452a29ac7d2ccc11d47f3ab95b")
51+
52+
hex = soliditySha3(EthereumAddress("0x407D73d8a49eeb85D32Cf465507dd71d507100c1")!).toHexString().addHexPrefix()
53+
assert(hex == "0x4e8ebbefa452077428f93c9520d3edd60594ff452a29ac7d2ccc11d47f3ab95b")
54+
55+
56+
hex = soliditySha3("Hello!%").toHexString().addHexPrefix()
57+
assert(hex == "0x661136a4267dba9ccdf6bfddb7c00e714de936674c4bdb065a531cf1cb15c7fc")
58+
59+
hex = soliditySha3(Int8(-23)).toHexString().addHexPrefix()
60+
assert(hex == "0xdc046d75852af4aea44a770057190294068a953828daaaab83800e2d0a8f1f35")
61+
62+
hex = soliditySha3(EthereumAddress("0x85F43D8a49eeB85d32Cf465507DD71d507100C1d")!).toHexString().addHexPrefix()
63+
assert(hex == "0xe88edd4848fdce08c45ecfafd2fbfdefc020a7eafb8178e94c5feaeec7ac0bb4")
64+
65+
hex = soliditySha3(["Hello!%", Int8(-23), EthereumAddress("0x85F43D8a49eeB85d32Cf465507DD71d507100C1d")!]).toHexString().addHexPrefix()
66+
assert(hex == "0xa13b31627c1ed7aaded5aecec71baf02fe123797fffd45e662eac8e06fbe4955")
67+
}
68+
69+
/// `[AnyObject]` is not allowed to be used directly as input for `solidtySha3`.
70+
/// `AnyObject` erases type data making it impossible to encode some types correctly,
71+
/// e.g.: Bool can be treated as Int (8/16/32/64) and 0/1 numbers can be treated as Bool.
72+
func test_soliditySha3Fail_1() throws {
73+
var didFail = false
74+
do {
75+
let _ = try soliditySha3([""] as [AnyObject])
76+
} catch {
77+
didFail = true
78+
}
79+
XCTAssertTrue(didFail)
80+
}
81+
82+
/// `AnyObject` is not allowed to be used directly as input for `solidtySha3`.
83+
/// `AnyObject` erases type data making it impossible to encode some types correctly,
84+
/// e.g.: Bool can be treated as Int (8/16/32/64) and 0/1 numbers can be treated as Bool.
85+
func test_soliditySha3Fail_2() throws {
86+
var didFail = false
87+
do {
88+
let _ = try soliditySha3("" as AnyObject)
89+
} catch {
90+
didFail = true
91+
}
92+
XCTAssertTrue(didFail)
93+
}
94+
}

Tests/web3swiftTests/remoteTests/RemoteTests.xctestplan

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
{
1616
"skippedTests" : [
1717
"EIP712Tests",
18+
"SoliditySha3Test",
1819
"web3swiftAdvancedABIv2Tests",
1920
"web3swiftBasicLocalNodeTests",
2021
"web3swiftEIP67Tests",

web3swift.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@
190190
5CF7E8BB276B79380009900F /* web3swiftENSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF7E8B6276B79380009900F /* web3swiftENSTests.swift */; };
191191
5CF7E8BC276B79380009900F /* web3swiftWebsocketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF7E8B7276B79380009900F /* web3swiftWebsocketTests.swift */; };
192192
CB50A52827060BD600D7E39B /* EIP712Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB50A52727060BD600D7E39B /* EIP712Tests.swift */; };
193+
D6A3D9B427F1D750009E3BCF /* SoliditySha3.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3D9B327F1D750009E3BCF /* SoliditySha3.swift */; };
194+
D6A3D9B627F1D9D6009E3BCF /* SoliditySha3.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3D9B527F1D9D6009E3BCF /* SoliditySha3.swift */; };
193195
E22A911F241ED71A00EC1021 /* browser.min.js in Resources */ = {isa = PBXBuildFile; fileRef = E22A911E241ED71A00EC1021 /* browser.min.js */; };
194196
E2B76710241ED479007EBFE3 /* browser.js in Resources */ = {isa = PBXBuildFile; fileRef = E2B7670F241ED479007EBFE3 /* browser.js */; };
195197
E2EDC5EA241EDE3600410EA6 /* BrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EDC5E9241EDE3600410EA6 /* BrowserViewController.swift */; platformFilter = ios; };
@@ -407,6 +409,8 @@
407409
5CF7E8B7276B79380009900F /* web3swiftWebsocketTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = web3swiftWebsocketTests.swift; sourceTree = "<group>"; };
408410
CB50A52727060BD600D7E39B /* EIP712Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Tests.swift; sourceTree = "<group>"; };
409411
CB50A52927060C5300D7E39B /* EIP712.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712.swift; sourceTree = "<group>"; };
412+
D6A3D9B327F1D750009E3BCF /* SoliditySha3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoliditySha3.swift; sourceTree = "<group>"; };
413+
D6A3D9B527F1D9D6009E3BCF /* SoliditySha3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoliditySha3.swift; sourceTree = "<group>"; };
410414
E22A911E241ED71A00EC1021 /* browser.min.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = browser.min.js; sourceTree = "<group>"; };
411415
E2B7670F241ED479007EBFE3 /* browser.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = browser.js; sourceTree = "<group>"; };
412416
E2EDC5E9241EDE3600410EA6 /* BrowserViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserViewController.swift; sourceTree = "<group>"; };
@@ -707,6 +711,7 @@
707711
3AA815482276E44100F5DB52 /* ABI.swift */,
708712
3AA815492276E44100F5DB52 /* ABIEncoding.swift */,
709713
3AA8154A2276E44100F5DB52 /* ABIParsing.swift */,
714+
D6A3D9B327F1D750009E3BCF /* SoliditySha3.swift */,
710715
);
711716
path = EthereumABI;
712717
sourceTree = "<group>";
@@ -998,6 +1003,7 @@
9981003
5CF7E893276B79270009900F /* web3swiftTransactionsTests.swift */,
9991004
5CF7E89E276B79280009900F /* web3swiftUserCases.swift */,
10001005
CB50A52727060BD600D7E39B /* EIP712Tests.swift */,
1006+
D6A3D9B527F1D9D6009E3BCF /* SoliditySha3.swift */,
10011007
5CDEF973275A74670004A2F2 /* LocalTests.xctestplan */,
10021008
);
10031009
path = localTests;
@@ -1308,6 +1314,7 @@
13081314
3AA815CD2276E44100F5DB52 /* Web3+Personal.swift in Sources */,
13091315
3AA8151E2276E42F00F5DB52 /* EthereumFilterEncodingExtensions.swift in Sources */,
13101316
3AA8151D2276E42F00F5DB52 /* ComparisonExtensions.swift in Sources */,
1317+
D6A3D9B427F1D750009E3BCF /* SoliditySha3.swift in Sources */,
13111318
3AA815AF2276E44100F5DB52 /* NonceMiddleware.swift in Sources */,
13121319
3AA815E92276E44100F5DB52 /* EthereumAddress.swift in Sources */,
13131320
3AA816072276E44100F5DB52 /* Web3+ERC1643.swift in Sources */,
@@ -1382,6 +1389,7 @@
13821389
5CF7E8A9276B792A0009900F /* web3swiftERC20ClassTests.swift in Sources */,
13831390
5CF7E8A8276B792A0009900F /* web3swiftERC20Tests.swift in Sources */,
13841391
5CF7E8B2276B792A0009900F /* web3swiftEIP67Tests.swift in Sources */,
1392+
D6A3D9B627F1D9D6009E3BCF /* SoliditySha3.swift in Sources */,
13851393
5CF7E8AE276B792A0009900F /* web3swiftPromisesTests.swift in Sources */,
13861394
5CF7E8A2276B79290009900F /* web3swiftEIP681Tests.swift in Sources */,
13871395
5CF7E8B1276B792A0009900F /* web3swiftAdvancedABIv2Tests.swift in Sources */,

0 commit comments

Comments
 (0)