|
| 1 | +// |
| 2 | +// BIP44Tests.swift |
| 3 | +// Created by Alberto Penas Amor on 15/12/22. |
| 4 | +// |
| 5 | + |
| 6 | +import XCTest |
| 7 | +import Web3Core |
| 8 | +@testable import web3swift |
| 9 | + |
| 10 | +final class BIP44Tests: XCTestCase { |
| 11 | + private var accountZeroScannedAddresses: [String] { |
| 12 | + [ |
| 13 | + "0x31a4aD7593D06D049b3Cc07aB5430264Bf7e069f", |
| 14 | + "0x2b4fb04d485446ade5889e77b0cbC2c71075209c", |
| 15 | + "0x93DDC6583D4BF6e9b309cfBdC681A78F8B5f37Ff", |
| 16 | + "0xab2bBC1392f957F7A5DDCE89b64f30064D39C08b", |
| 17 | + "0x5Ae1794fFD14bebF34e0BA65815dF9DCB0FD11a8", |
| 18 | + "0x4894C017C7fEfB53A9dc3Cf707d098EBCFD8BdF1", |
| 19 | + "0x29cC28Cd30e21e73B51389792453818DaCe33f65", |
| 20 | + "0x6B3cB8CFBC89ab7A1D9Ccb53537020c53dD4f6E0", |
| 21 | + "0xD5FD55fcB93a47Ef176062ac8265E28A5f09887D", |
| 22 | + "0xa8A99549A522aF52a2050e081100ef3D42228B55", |
| 23 | + "0x2007f83D32cd82b013b9d0d33Ac9e5Ae725367C5", |
| 24 | + "0x80a9A6Dd42D67Dd2EEC5c3D6568Fd16e7c964948", |
| 25 | + "0xC7781cd86F6336CfE56Fc243f1a9544595dC984E", |
| 26 | + "0x7E3eDEB0201D5A5cAF2b50749a7C7843374c312F", |
| 27 | + "0x800853194B31Bf5D621Be0b402E8c2b3b402a2Ed", |
| 28 | + "0x73BE98d0a3702E8279ca087B2564b6977389C242", |
| 29 | + "0x3eFC4765C5BaB65947864fDf4669b7fb8073d89B", |
| 30 | + "0xd521A57ea2bAA6396AE916aD2bC4972a9b3635EB", |
| 31 | + "0x561192570145C499f0951dEc0a4Df80D0D0A96bb", |
| 32 | + "0x4DdBe17BB1b0056941A1425739978e44D462D7DD"] |
| 33 | + } |
| 34 | + private var accountZeroAndOneScannedAddresses: [String] { |
| 35 | + [ |
| 36 | + "0x31a4aD7593D06D049b3Cc07aB5430264Bf7e069f", |
| 37 | + "0x3C7b0FadC415d0be5EBa971DC7Dcc39DdDcd4AF7", |
| 38 | + "0x73C13e421eF367c4F55BBC02a8e2a2b12e82f717", |
| 39 | + "0xE9D8f89452CF0a0d501B9C798cE696C3a1BAE535", |
| 40 | + "0x662e78FD3C77A9B8e693f5DC75398C9c0E7233a6", |
| 41 | + "0xBEDF61A3466b40f2591702c91cF888843C81e576", |
| 42 | + "0xb406aD2666D36716a847c27BAA6d742ECdA85F23", |
| 43 | + "0x069c7bF73d17aeb7b8Ff490177A6eefB7aCcb4a8", |
| 44 | + "0xa9dbD111007cAfF0804b98195F7f9231bcBEdf86", |
| 45 | + "0x2DDDf0447Eb85ae4B16815B010a7007cd30f0A64", |
| 46 | + "0x35ff1f3dcb02B6F137A654a419bFb66FE74dFDFE", |
| 47 | + "0xd3A77dE492A58386129546469D0E3D3C67Dd520E", |
| 48 | + "0x1c011fEfb24210EB1415DD87C161591f5040d71A", |
| 49 | + "0x6C289DCE390863ed58bBd56948950f4D96c7Ab8f", |
| 50 | + "0xbB13176bf7571D15E1600077F4da6eD22075676b", |
| 51 | + "0x618c1ddD96a3Dc2Bd1E90F7053bCc48986A412f7", |
| 52 | + "0x5220836980697693fE2137b64e545f926856feAe", |
| 53 | + "0xC49D7d886CA02C438c413ceabE6C1f8138ED6ef8", |
| 54 | + "0x049e9466CD2417A615e98DD7233eeec4Fcf5632D", |
| 55 | + "0x111FbB56b0B5c97F2896Ee722A917b261bCC77fC", |
| 56 | + "0xF3F66e5C119620eBDbD7Fb48B4b5d365De5c9750"] |
| 57 | + } |
| 58 | + private var mockTransactionChecker: MockTransactionChecker = .init() |
| 59 | + |
| 60 | + func testDeriveWithoutThrowOnWarning() async throws { |
| 61 | + let rootNode = try rootNode() |
| 62 | + |
| 63 | + let childNode = try await rootNode.derive(path: "m/44'/60'/8096'/0/1", throwOnWarning: false, transactionChecker: mockTransactionChecker) |
| 64 | + |
| 65 | + XCTAssertEqual(try XCTUnwrap(childNode).publicKey.toHexString(), "035785d4918449c87892371c0f9ccf6e4eda40a7fb0f773f1254c064d3bba64026") |
| 66 | + XCTAssertEqual(mockTransactionChecker.addresses.count, 0) |
| 67 | + } |
| 68 | + |
| 69 | + func testDeriveInvalidPath() async throws { |
| 70 | + let rootNode = try rootNode() |
| 71 | + |
| 72 | + let childNode = try? await rootNode.derive(path: "", throwOnWarning: true, transactionChecker: mockTransactionChecker) |
| 73 | + |
| 74 | + XCTAssertNil(childNode) |
| 75 | + XCTAssertEqual(mockTransactionChecker.addresses.count, 0) |
| 76 | + } |
| 77 | + |
| 78 | + // MARK: - address |
| 79 | + |
| 80 | + func testZeroAccountNeverThrow() async throws { |
| 81 | + let rootNode = try rootNode() |
| 82 | + |
| 83 | + let childNode = try await rootNode.derive(path: "m/44'/60'/0'/0/255", throwOnWarning: true, transactionChecker: mockTransactionChecker) |
| 84 | + |
| 85 | + XCTAssertEqual(try XCTUnwrap(childNode).publicKey.toHexString(), "0262fba1af8f149258123265318114066decf50d16c1222a9d657b7de2296c2734") |
| 86 | + XCTAssertEqual(mockTransactionChecker.addresses.count, 0) |
| 87 | + } |
| 88 | + |
| 89 | + func testFirstAccountWithNoPreviousTransactionHistory() async throws { |
| 90 | + do { |
| 91 | + let rootNode = try rootNode() |
| 92 | + let path = "m/44'/60'/1'/0/0" |
| 93 | + var results = false.times(n: 20) |
| 94 | + results.append(true) |
| 95 | + mockTransactionChecker.results = results |
| 96 | + |
| 97 | + _ = try await rootNode.derive(path: path, throwOnWarning: true, transactionChecker: mockTransactionChecker) |
| 98 | + |
| 99 | + XCTFail("Child must not be created using throwOnWarning true for the path: \(path)") |
| 100 | + } catch BIP44Error.warning { |
| 101 | + XCTAssertEqual(mockTransactionChecker.addresses, accountZeroScannedAddresses) |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + func testFirstAccountWithPreviousTransactionHistory() async throws { |
| 106 | + do { |
| 107 | + let rootNode = try rootNode() |
| 108 | + let path = "m/44'/60'/1'/0/0" |
| 109 | + var results = false.times(n: 19) |
| 110 | + results.append(true) |
| 111 | + mockTransactionChecker.results = results |
| 112 | + |
| 113 | + let childNode = try await rootNode.derive(path: path, throwOnWarning: true, transactionChecker: mockTransactionChecker) |
| 114 | + |
| 115 | + XCTAssertEqual(try XCTUnwrap(childNode).publicKey.toHexString(), "036cd8f1bad46fa7caf7a80d48528b90db2a3b7a5c9a18d74d61b286e03850abf4") |
| 116 | + XCTAssertEqual(mockTransactionChecker.addresses, accountZeroScannedAddresses) |
| 117 | + } catch BIP44Error.warning { |
| 118 | + XCTFail("BIP44Error.warning must not be thrown") |
| 119 | + } |
| 120 | + } |
| 121 | + |
| 122 | + func testSecondAccountWithNoPreviousTransactionHistory() async throws { |
| 123 | + do { |
| 124 | + let rootNode = try rootNode() |
| 125 | + let path = "m/44'/60'/2'/0/0" |
| 126 | + var results: [Bool] = .init() |
| 127 | + results.append(true) |
| 128 | + results.append(contentsOf: false.times(n: 20)) |
| 129 | + mockTransactionChecker.results = results |
| 130 | + |
| 131 | + _ = try await rootNode.derive(path: path, throwOnWarning: true, transactionChecker: mockTransactionChecker) |
| 132 | + |
| 133 | + XCTFail("Child must not be created using throwOnWarning true for the path: \(path)") |
| 134 | + } catch BIP44Error.warning { |
| 135 | + XCTAssertEqual(mockTransactionChecker.addresses, accountZeroAndOneScannedAddresses) |
| 136 | + XCTAssertEqual(mockTransactionChecker.addresses.count, 21) |
| 137 | + } |
| 138 | + } |
| 139 | + |
| 140 | + // MARK: - change + addressIndex |
| 141 | + |
| 142 | + func testNotZeroChangeAndAddressIndexWithPreviousTransactionHistory() async throws { |
| 143 | + do { |
| 144 | + let rootNode = try rootNode() |
| 145 | + let path = "m/44'/60'/1'/1/128" |
| 146 | + var results = false.times(n: 19) |
| 147 | + results.append(true) |
| 148 | + mockTransactionChecker.results = results |
| 149 | + |
| 150 | + let childNode = try await rootNode.derive(path: path, throwOnWarning: true, transactionChecker: mockTransactionChecker) |
| 151 | + |
| 152 | + XCTAssertEqual(try XCTUnwrap(childNode).publicKey.toHexString(), "0282134e44d4c040a4b4c1a780d8302955096cf1d5e738b161c83f0ce1b863c14e") |
| 153 | + XCTAssertEqual(mockTransactionChecker.addresses, accountZeroScannedAddresses) |
| 154 | + } catch BIP44Error.warning { |
| 155 | + XCTFail("BIP44Error.warning must not be thrown") |
| 156 | + } |
| 157 | + } |
| 158 | + |
| 159 | + // MARK: - private |
| 160 | + |
| 161 | + private func rootNode() throws -> HDNode { |
| 162 | + let mnemonic = "fruit wave dwarf banana earth journey tattoo true farm silk olive fence" |
| 163 | + let seed = try XCTUnwrap(BIP39.seedFromMmemonics(mnemonic, password: "")) |
| 164 | + return try XCTUnwrap(HDNode(seed: seed)) |
| 165 | + } |
| 166 | +} |
| 167 | + |
| 168 | +// MARK: - BIP44ErrorTests |
| 169 | + |
| 170 | +final class BIP44ErrorTests: XCTestCase { |
| 171 | + func testLocalizedDescription() { |
| 172 | + let error = BIP44Error.warning |
| 173 | + XCTAssertEqual(error.localizedDescription, "Couldn't derive key as it doesn't have a previous account with at least one transaction") |
| 174 | + } |
| 175 | +} |
| 176 | + |
| 177 | +// MARK: - helper |
| 178 | + |
| 179 | +private extension Bool { |
| 180 | + func times(n: Int) -> [Bool] { |
| 181 | + var array: [Bool] = .init() |
| 182 | + (0..<n).forEach { _ in |
| 183 | + array.append(self) |
| 184 | + } |
| 185 | + return array |
| 186 | + } |
| 187 | +} |
| 188 | + |
| 189 | +// MARK: - test double |
| 190 | + |
| 191 | +private final class MockTransactionChecker: TransactionChecker { |
| 192 | + var addresses: [String] = .init() |
| 193 | + var results: [Bool] = .init() |
| 194 | + |
| 195 | + func hasTransactions(address: String) async throws -> Bool { |
| 196 | + addresses.append(address) |
| 197 | + return results.removeFirst() |
| 198 | + } |
| 199 | +} |
0 commit comments