diff --git a/web3swift/Convenience/UInt256.swift b/web3swift/Convenience/UInt256.swift index 106a15a9..da62e8ee 100644 --- a/web3swift/Convenience/UInt256.swift +++ b/web3swift/Convenience/UInt256.swift @@ -30,6 +30,101 @@ extension BigUInt { } self = mainPart } + /// Formats a BigUInt object to String. The supplied number is first divided into integer and decimal part based on "toUnits", + /// then limit the decimal part to "decimals" symbols and uses a "decimalSeparator" as a separator. + /// + /// Returns nil of formatting is not possible to satisfy. + public func string(units: Web3Units = .eth, decimals: Int = 4, decimalSeparator: String = ".", fallbackToScientific: Bool = false) -> String { + return string(numberDecimals: units.decimals, formattingDecimals: decimals, decimalSeparator: decimalSeparator, fallbackToScientific: fallbackToScientific) + } + + /// Formats a BigUInt object to String. The supplied number is first divided into integer and decimal part based on "toUnits", + /// then limit the decimal part to "decimals" symbols and uses a "decimalSeparator" as a separator. + /// Fallbacks to scientific format if higher precision is required. + /// + /// Returns nil of formatting is not possible to satisfy. + public func string(numberDecimals: Int = 18, formattingDecimals: Int = 4, decimalSeparator: String = ".", fallbackToScientific: Bool = false) -> String { + guard self != 0 else { return "0" } + let unitDecimals = numberDecimals + var toDecimals = formattingDecimals + if unitDecimals < toDecimals { + toDecimals = unitDecimals + } + let divisor = BigUInt(10).power(unitDecimals) + let (quotient, remainder) = quotientAndRemainder(dividingBy: divisor) + var fullRemainder = String(remainder) + let fullPaddedRemainder = fullRemainder.leftPadding(toLength: unitDecimals, withPad: "0") + let remainderPadded = fullPaddedRemainder[0 ..< toDecimals] + if remainderPadded == String(repeating: "0", count: toDecimals) { + if quotient != 0 { + return String(quotient) + } else if fallbackToScientific { + var firstDigit = 0 + for char in fullPaddedRemainder { + if char == "0" { + firstDigit = firstDigit + 1 + } else { + let firstDecimalUnit = String(fullPaddedRemainder[firstDigit ..< firstDigit+1]) + var remainingDigits = "" + let numOfRemainingDecimals = fullPaddedRemainder.count - firstDigit - 1 + if numOfRemainingDecimals <= 0 { + remainingDigits = "" + } else if numOfRemainingDecimals > formattingDecimals { + let end = firstDigit+1+formattingDecimals > fullPaddedRemainder.count ? fullPaddedRemainder.count : firstDigit+1+formattingDecimals + remainingDigits = String(fullPaddedRemainder[firstDigit+1 ..< end]) + } else { + remainingDigits = String(fullPaddedRemainder[firstDigit+1 ..< fullPaddedRemainder.count]) + } + fullRemainder = firstDecimalUnit + if !remainingDigits.isEmpty { + fullRemainder += decimalSeparator + remainingDigits + } + firstDigit = firstDigit + 1 + break + } + } + return fullRemainder + "e-" + String(firstDigit) + } + } + if toDecimals == 0 { + return String(quotient) + } else { + return String(quotient) + decimalSeparator + remainderPadded + } + } +} + +extension BigInt { + /// Returns .description to not confuse + public func string() -> String { + return description + } + /// Formats a BigInt object to String. The supplied number is first divided into integer and decimal part based on "units", + /// then limit the decimal part to "decimals" symbols and uses a "decimalSeparator" as a separator. + /// Fallbacks to scientific format if higher precision is required. + public func string(numberDecimals: Int, formattingDecimals: Int = 4, decimalSeparator: String = ".", fallbackToScientific: Bool = false) -> String { + + let formatted = magnitude.string(numberDecimals: numberDecimals, formattingDecimals: formattingDecimals, decimalSeparator: decimalSeparator, fallbackToScientific: fallbackToScientific) + switch sign { + case .plus: + return formatted + case .minus: + return "-" + formatted + } + } + + /// Formats a BigInt object to String. The supplied number is first divided into integer and decimal part based on "units", + /// then limit the decimal part to "decimals" symbols and uses a "decimalSeparator" as a separator. + public func string(units: Web3Units, decimals: Int = 4, decimalSeparator: String = ".") -> String { + + let formatted = magnitude.string(numberDecimals: units.decimals, formattingDecimals: decimals, decimalSeparator: decimalSeparator) + switch sign { + case .plus: + return formatted + case .minus: + return "-" + formatted + } + } } public struct NaturalUnits { diff --git a/web3swift/Web3/Web3+Utils.swift b/web3swift/Web3/Web3+Utils.swift index d98567ff..0cfda24b 100644 --- a/web3swift/Web3/Web3+Utils.swift +++ b/web3swift/Web3/Web3+Utils.swift @@ -194,15 +194,9 @@ extension Web3.Utils { /// then limit the decimal part to "decimals" symbols and uses a "decimalSeparator" as a separator. /// /// Returns nil of formatting is not possible to satisfy. - public static func formatToEthereumUnits(_ bigNumber: BigInt, toUnits: Web3Units = .eth, decimals: Int = 4, decimalSeparator: String = ".") -> String? { - let magnitude = bigNumber.magnitude - guard let formatted = formatToEthereumUnits(magnitude, toUnits: toUnits, decimals: decimals, decimalSeparator: decimalSeparator) else { return nil } - switch bigNumber.sign { - case .plus: - return formatted - case .minus: - return "-" + formatted - } + @available(*,deprecated: 2.0,message: "Use number.string(units:formattingDecimals:decimalSeparator:fallbackToScientific") + public static func formatToEthereumUnits(_ bigNumber: BigInt, toUnits: Web3Units = .eth, decimals: Int = 4, decimalSeparator: String = ".") -> String { + return bigNumber.string(units: toUnits, decimals: decimals, decimalSeparator: decimalSeparator) } /// Formats a BigInt object to String. The supplied number is first divided into integer and decimal part based on "toUnits", @@ -210,23 +204,18 @@ extension Web3.Utils { /// Fallbacks to scientific format if higher precision is required. /// /// Returns nil of formatting is not possible to satisfy. - public static func formatToPrecision(_ bigNumber: BigInt, numberDecimals: Int = 18, formattingDecimals: Int = 4, decimalSeparator: String = ".", fallbackToScientific: Bool = false) -> String? { - let magnitude = bigNumber.magnitude - guard let formatted = formatToPrecision(magnitude, numberDecimals: numberDecimals, formattingDecimals: formattingDecimals, decimalSeparator: decimalSeparator, fallbackToScientific: fallbackToScientific) else { return nil } - switch bigNumber.sign { - case .plus: - return formatted - case .minus: - return "-" + formatted - } + @available(*,deprecated: 2.0,message: "Use number.string(numberDecimals:formattingDecimals:decimalSeparator:fallbackToScientific") + public static func formatToPrecision(_ bigNumber: BigInt, numberDecimals: Int = 18, formattingDecimals: Int = 4, decimalSeparator: String = ".", fallbackToScientific: Bool = false) -> String { + return bigNumber.string(numberDecimals: numberDecimals, formattingDecimals: formattingDecimals, decimalSeparator: decimalSeparator, fallbackToScientific: fallbackToScientific) } /// Formats a BigUInt object to String. The supplied number is first divided into integer and decimal part based on "toUnits", /// then limit the decimal part to "decimals" symbols and uses a "decimalSeparator" as a separator. /// /// Returns nil of formatting is not possible to satisfy. - public static func formatToEthereumUnits(_ bigNumber: BigUInt, toUnits: Web3Units = .eth, decimals: Int = 4, decimalSeparator: String = ".", fallbackToScientific: Bool = false) -> String? { - return formatToPrecision(bigNumber, numberDecimals: toUnits.decimals, formattingDecimals: decimals, decimalSeparator: decimalSeparator, fallbackToScientific: fallbackToScientific) + @available(*,deprecated: 2.0,message: "Use number.string(units:formattingDecimals:decimalSeparator:fallbackToScientific") + public static func formatToEthereumUnits(_ bigNumber: BigUInt, toUnits: Web3Units = .eth, decimals: Int = 4, decimalSeparator: String = ".", fallbackToScientific: Bool = false) -> String { + return bigNumber.string(units: toUnits, decimals: decimals, decimalSeparator: decimalSeparator, fallbackToScientific: fallbackToScientific) } /// Formats a BigUInt object to String. The supplied number is first divided into integer and decimal part based on "toUnits", @@ -234,40 +223,9 @@ extension Web3.Utils { /// Fallbacks to scientific format if higher precision is required. /// /// Returns nil of formatting is not possible to satisfy. - public static func formatToPrecision(_ bigNumber: BigUInt, numberDecimals: Int = 18, formattingDecimals: Int = 4, decimalSeparator: String = ".", fallbackToScientific: Bool = false) -> String? { - if bigNumber == 0 { - return "0" - } - let unitDecimals = numberDecimals - var toDecimals = formattingDecimals - if unitDecimals < toDecimals { - toDecimals = unitDecimals - } - let divisor = BigUInt(10).power(unitDecimals) - let (quotient, remainder) = bigNumber.quotientAndRemainder(dividingBy: divisor) - let fullRemainder = String(remainder) - let fullPaddedRemainder = fullRemainder.leftPadding(toLength: unitDecimals, withPad: "0") - let remainderPadded = fullPaddedRemainder[0 ..< toDecimals] - if remainderPadded == String(repeating: "0", count: toDecimals) { - if quotient != 0 { - return String(quotient) - } else if fallbackToScientific { - var firstDigit = 0 - for char in fullPaddedRemainder { - if char == "0" { - firstDigit = firstDigit + 1 - } else { - firstDigit = firstDigit + 1 - break - } - } - return fullRemainder + "e-" + String(firstDigit) - } - } - if toDecimals == 0 { - return String(quotient) - } - return String(quotient) + decimalSeparator + remainderPadded + @available(*,deprecated: 2.0,message: "Use number.string(numberDecimals:formattingDecimals:decimalSeparator:fallbackToScientific") + public static func formatToPrecision(_ bigNumber: BigUInt, numberDecimals: Int = 18, formattingDecimals: Int = 4, decimalSeparator: String = ".", fallbackToScientific: Bool = false) -> String { + return bigNumber.string(numberDecimals: numberDecimals, formattingDecimals: formattingDecimals, decimalSeparator: decimalSeparator, fallbackToScientific: fallbackToScientific) } /// Recover the Ethereum address from recoverable secp256k1 signature. Message is first hashed using the "personal hash" protocol. diff --git a/web3swiftTests/ERC20Tests.swift b/web3swiftTests/ERC20Tests.swift index 42bdcc4b..63ce9eaf 100644 --- a/web3swiftTests/ERC20Tests.swift +++ b/web3swiftTests/ERC20Tests.swift @@ -96,8 +96,8 @@ class ERC20Tests: XCTestCase { let contract = try web3.contract(Web3.Utils.erc20ABI, at: "0x45245bc59219eeaaf6cd3f382e078a461ff9de7b") var options = Web3Options() - options.from = EthereumAddress("0x6394b37Cf80A7358b38068f0CA4760ad49983a1B") - let addressOfUser = EthereumAddress("0x6394b37Cf80A7358b38068f0CA4760ad49983a1B") + options.from = "0x6394b37Cf80A7358b38068f0CA4760ad49983a1B" + let addressOfUser = "0x6394b37Cf80A7358b38068f0CA4760ad49983a1B" let tokenBalance = try contract.method("balanceOf", args: addressOfUser, options: options).call(options: nil).uint256() print(tokenBalance) } diff --git a/web3swiftTests/InfuraTests.swift b/web3swiftTests/InfuraTests.swift index 7d365a39..70c3ea5f 100644 --- a/web3swiftTests/InfuraTests.swift +++ b/web3swiftTests/InfuraTests.swift @@ -14,8 +14,8 @@ class InfuraTests: XCTestCase { let web3 = Web3(infura: .mainnet) let address = EthereumAddress("0x6394b37Cf80A7358b38068f0CA4760ad49983a1B") let balance = try web3.eth.getBalance(address: address) - let balString = Web3.Utils.formatToEthereumUnits(balance, toUnits: .eth, decimals: 3) - print(balString ?? "nil") + let balString = balance.string(units: .eth, decimals: 3) + print(balString) } func testGetBlockByHash() throws { diff --git a/web3swiftTests/MainTests.swift b/web3swiftTests/MainTests.swift index 9ad88bc8..b4da1470 100644 --- a/web3swiftTests/MainTests.swift +++ b/web3swiftTests/MainTests.swift @@ -83,7 +83,7 @@ class Tests: XCTestCase { } func testMakePrivateKey() { - let privateKey = SECP256K1.generatePrivateKey() + let privateKey = Data.random(length: 32) let publicKey = try? SECP256K1.privateToPublic(privateKey: privateKey) XCTAssert(publicKey != nil, "Failed to create new private key") } diff --git a/web3swiftTests/NumberFormattingUtilTests.swift b/web3swiftTests/NumberFormattingUtilTests.swift index 19f4cc97..314c68fe 100644 --- a/web3swiftTests/NumberFormattingUtilTests.swift +++ b/web3swiftTests/NumberFormattingUtilTests.swift @@ -16,43 +16,55 @@ import XCTest class NumberFormattingUtilTests: XCTestCase { func testNumberFormattingUtil() { let balance = BigInt("-1000000000000000000")! - let formatted = Web3.Utils.formatToPrecision(balance, numberDecimals: 18, formattingDecimals: 4, decimalSeparator: ",") - XCTAssert(formatted == "-1") + let formatted = balance.string(numberDecimals: 18, formattingDecimals: 4, decimalSeparator: ",") + XCTAssertEqual(formatted, "-1") } func testNumberFormattingUtil2() { let balance = BigInt("-1000000000000000")! - let formatted = Web3.Utils.formatToPrecision(balance, numberDecimals: 18, formattingDecimals: 4, decimalSeparator: ",") - XCTAssert(formatted == "-0,0010") + let formatted = balance.string(numberDecimals: 18, formattingDecimals: 4, decimalSeparator: ",") + XCTAssertEqual(formatted, "-0,0010") } func testNumberFormattingUtil3() { let balance = BigInt("-1000000000000")! - let formatted = Web3.Utils.formatToPrecision(balance, numberDecimals: 18, formattingDecimals: 4, decimalSeparator: ",") - XCTAssert(formatted == "-0,0000") + let formatted = balance.string(numberDecimals: 18, formattingDecimals: 4, decimalSeparator: ",") + XCTAssertEqual(formatted, "-0,0000") } func testNumberFormattingUtil4() { let balance = BigInt("-1000000000000")! - let formatted = Web3.Utils.formatToPrecision(balance, numberDecimals: 18, formattingDecimals: 9, decimalSeparator: ",") - XCTAssert(formatted == "-0,000001000") + let formatted = balance.string(numberDecimals: 18, formattingDecimals: 9, decimalSeparator: ",") + XCTAssertEqual(formatted, "-0,000001000") } func testNumberFormattingUtil5() { let balance = BigInt("-1")! - let formatted = Web3.Utils.formatToPrecision(balance, numberDecimals: 18, formattingDecimals: 9, decimalSeparator: ",", fallbackToScientific: true) - XCTAssert(formatted == "-1e-18") + let formatted = balance.string(numberDecimals: 18, formattingDecimals: 9, decimalSeparator: ",", fallbackToScientific: true) + XCTAssertEqual(formatted, "-1e-18") } func testNumberFormattingUtil6() { let balance = BigInt("0")! - let formatted = Web3.Utils.formatToPrecision(balance, numberDecimals: 18, formattingDecimals: 9, decimalSeparator: ",") - XCTAssert(formatted == "0") + let formatted = balance.string(numberDecimals: 18, formattingDecimals: 9, decimalSeparator: ",") + XCTAssertEqual(formatted, "0") } func testNumberFormattingUtil7() { let balance = BigInt("-1100000000000000000")! - let formatted = Web3.Utils.formatToPrecision(balance, numberDecimals: 18, formattingDecimals: 4, decimalSeparator: ",") - XCTAssert(formatted == "-1,1000") + let formatted = balance.string(numberDecimals: 18, formattingDecimals: 4, decimalSeparator: ",") + XCTAssertEqual(formatted, "-1,1000") + } + + func testNumberFormattingUtil8() { + let balance = BigInt("100")! + let formatted = balance.string(numberDecimals: 18, formattingDecimals: 4, decimalSeparator: ",", fallbackToScientific: true) + XCTAssertEqual(formatted, "1,00e-16") + } + + func testNumberFormattingUtil9() { + let balance = BigInt("1000000")! + let formatted = balance.string(numberDecimals: 18, formattingDecimals: 4, decimalSeparator: ",", fallbackToScientific: true) + XCTAssertEqual(formatted, "1,0000e-12") } }