From 4b3838f98b6a6cd2e9b429fb2e5451a7476e56fd Mon Sep 17 00:00:00 2001 From: Miguel Angel Quinones Date: Thu, 10 Feb 2022 14:35:25 +0100 Subject: [PATCH] [FIX] Decoding negative integers --- .../xcschemes/web3.swift.xcscheme | 14 +++++++ web3sTests/Contract/ABIDecoderTests.swift | 32 +++++++++++++--- .../Extensions/ByteExtensionsTests.swift | 37 ++++++++++++------- web3swift/src/Contract/ABIDecoder.swift | 5 +-- .../Statically Typed/ABIDecoder+Static.swift | 2 +- web3swift/src/Extensions/ByteExtensions.swift | 32 +++++++++++----- 6 files changed, 89 insertions(+), 33 deletions(-) diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/web3.swift.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/web3.swift.xcscheme index ef141582..adac788c 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/web3.swift.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/web3.swift.xcscheme @@ -104,6 +104,20 @@ ReferencedContainer = "container:"> + + + + .self]) + let decoded = try ABIDecoder.decodeData("0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", types: [ABIArray.self]) XCTAssertEqual(try decoded[0].decodedArray(), [BigInt(1), BigInt(2), BigInt(3)]) } catch let error { print(error.localizedDescription) @@ -94,6 +94,26 @@ class ABIDecoderTests: XCTestCase { XCTFail() } } + + func test_GivenBigInt_WhenValueIsNegative_ThenDecodesCorrectly() { + do { + let decoded = try ABIDecoder.decodeData("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff38", types: [BigInt.self]) + XCTAssertEqual(try decoded[0].decoded(), BigInt(-200)) + } catch let error { + print(error.localizedDescription) + XCTFail() + } + } + + func test_GivenBigInt_WhenValueIsPositive_ThenDecodesCorrectly() { + do { + let decoded = try ABIDecoder.decodeData("0x00000000000000000000000000000000000000000000000000000000000000c8", types: [BigInt.self]) + XCTAssertEqual(try decoded[0].decoded(), BigInt(200)) + } catch let error { + print(error.localizedDescription) + XCTFail() + } + } func testDecodeEmptyDynamicData() { do { @@ -136,9 +156,9 @@ class ABIDecoderTests: XCTestCase { do { let decoded = try ABIDecoder.decodeData( "0x0000000000000000000000000000000000000000000000000000000000a9f60c0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001753796e746865746978204e6574776f726b20546f6b656e000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003534e5800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012" - , types: [BigInt.self, ABIArray.self]) + , types: [BigUInt.self, ABIArray.self]) - XCTAssertEqual(try decoded[0].decoded(), BigInt(integerLiteral: 11138572)) + XCTAssertEqual(try decoded[0].decoded(), BigUInt(integerLiteral: 11138572)) XCTAssertEqual(try ERC20Responses.nameResponse(data: decoded[1].entry[0])?.value, "Synthetix Network Token") XCTAssertEqual(try ERC20Responses.symbolResponse(data: decoded[1].entry[1])?.value, "SNX") XCTAssertEqual(try ERC20Responses.balanceResponse(data: decoded[1].entry[2])?.value, BigUInt(integerLiteral: 0)) @@ -153,9 +173,9 @@ class ABIDecoderTests: XCTestCase { do { let decoded = try ABIDecoder.decodeData( "0x0000000000000000000000000000000000000000000000000000000000a9f60c0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001753796e746865746978204e6574776f726b20546f6b656e000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003534e580000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020aface4ed2e287b2f681c32da24383f2fb691f0b6962be5ae7950a5dd793e61ad" - , types: [BigInt.self, ABIArray.self]) + , types: [BigUInt.self, ABIArray.self]) - XCTAssertEqual(try decoded[0].decoded(), BigInt(integerLiteral: 11138572)) + XCTAssertEqual(try decoded[0].decoded(), BigUInt(integerLiteral: 11138572)) XCTAssertEqual(try ERC20Responses.nameResponse(data: decoded[1].entry[0])?.value, "Synthetix Network Token") XCTAssertEqual(try ERC20Responses.symbolResponse(data: decoded[1].entry[1])?.value, "SNX") XCTAssertEqual(try ERC20Responses.balanceResponse(data: decoded[1].entry[2])?.value, BigUInt(integerLiteral: 0)) diff --git a/web3sTests/Extensions/ByteExtensionsTests.swift b/web3sTests/Extensions/ByteExtensionsTests.swift index 37a6cdd2..e45167f0 100644 --- a/web3sTests/Extensions/ByteExtensionsTests.swift +++ b/web3sTests/Extensions/ByteExtensionsTests.swift @@ -11,29 +11,38 @@ import BigInt @testable import web3 class ByteExtensionsTests: XCTestCase { - - override func setUp() { - super.setUp() - } - - override func tearDown() { - super.tearDown() - } - func testBytesFromBigInt() { XCTAssertEqual(BigInt(3251).web3.bytes, [12, 179]) XCTAssertEqual(BigInt(434350411044).web3.bytes, [101, 33, 77, 77, 36]) - XCTAssertEqual(BigInt(-404).web3.bytes, [254, 108]) + XCTAssertEqual(BigInt(-404).web3.bytes, [255, 254, 108]) } - func testBigIntFromTwosComplement() { - let bytes: [UInt8] = [3, 0, 24, 124, 109] + func testGivenBigInt_WhenPositive_ThenParsesCorrectly() { + let bytes: [UInt8] = [0x00, 0xc8] let data = Data(bytes) let bint = BigInt(twosComplement: data) - XCTAssertEqual(bint, 12886506605) + XCTAssertEqual(bint, 200) } - + + func testGivenBigInt_WhenNegative_ThenParsesCorrectly() { + let bytes: [UInt8] = [0xff, 0x38] + let data = Data(bytes) + let bint = BigInt(twosComplement: data) + + XCTAssertEqual(bint, -200) + } + + func testGivenBigInt_WhenPositive_ThenBytesArrayIsTwosComplement() { + let bint = BigInt(200) + XCTAssertEqual(bint.web3.bytes, [0xc8]) + } + + func testGivenBigInt_WhenNegative_ThenBytesArrayIsTwosComplement() { + let bint = BigInt(-200) + XCTAssertEqual(bint.web3.bytes, [0xff, 0x38]) + } + func testBytesFromData() { let bytes: [UInt8] = [255, 0, 123, 64] let data = Data(bytes) diff --git a/web3swift/src/Contract/ABIDecoder.swift b/web3swift/src/Contract/ABIDecoder.swift index 54181409..1a445b0a 100644 --- a/web3swift/src/Contract/ABIDecoder.swift +++ b/web3swift/src/Contract/ABIDecoder.swift @@ -75,9 +75,8 @@ public class ABIDecoder { let startIndex = offset + 32 - type.size let endIndex = offset + 31 guard data.count > endIndex else { throw ABIError.invalidValue } - let buf = Data( Array(data[startIndex...endIndex])) - let bint = BigInt(twosComplement: buf) - return [String(hexFromBytes: bint.web3.bytes)] + let buf = Array(data[startIndex...endIndex]) + return [String(hexFromBytes: buf)] case .FixedUInt(_): guard data.count > 0 else { return [""] diff --git a/web3swift/src/Contract/Statically Typed/ABIDecoder+Static.swift b/web3swift/src/Contract/Statically Typed/ABIDecoder+Static.swift index 4ea04d2d..567d8d29 100644 --- a/web3swift/src/Contract/Statically Typed/ABIDecoder+Static.swift +++ b/web3swift/src/Contract/Statically Typed/ABIDecoder+Static.swift @@ -95,7 +95,7 @@ extension ABIDecoder { } public static func decode(_ data: ParsedABIEntry, to: BigInt.Type) throws -> BigInt { - guard let value = BigInt(hex: data) else { throw ABIError.invalidValue } + guard let value = data.web3.hexData.map(BigInt.init(twosComplement:)) else { throw ABIError.invalidValue } return value } diff --git a/web3swift/src/Extensions/ByteExtensions.swift b/web3swift/src/Extensions/ByteExtensions.swift index 2e468bd1..bc32b779 100644 --- a/web3swift/src/Extensions/ByteExtensions.swift +++ b/web3swift/src/Extensions/ByteExtensions.swift @@ -26,11 +26,26 @@ public extension Web3Extensions where Base == BigUInt { extension BigInt { init(twosComplement data: Data) { - let unsigned = BigUInt(data) - self.init(BigInt(unsigned)) - if data[0] == 0xff { - self.negate() + guard data.count > 1 else { + self.init(0) + return } + + let isNegative = data[0] & 0x80 == 0x80 + guard isNegative else { + self = BigInt(BigUInt(data)) + return + } + + let bytesLength = data.count + let signBit = BigUInt(2).power(bytesLength * 8) / 2 + let signValue = isNegative ? signBit : 0 + let rest = data.enumerated().map { index, value in + index == 0 ? value & 0x7f : value + } + + self = BigInt(signValue - BigUInt(Data(rest))) + self.negate() } } @@ -40,11 +55,10 @@ public extension Web3Extensions where Base == BigInt { if base.sign == .plus { data = base.magnitude.serialize() } else { - // Twos Complement - let len = base.magnitude.serialize().count - let maximum = BigUInt(1) << (len * 8) - let twosComplement = maximum - base.magnitude - data = twosComplement.serialize() + let len = base.magnitude.serialize().count + 1 + let maximum = BigUInt(2).power(len * 8) + let (twosComplement, _) = maximum.subtractingReportingOverflow(base.magnitude) + data = (twosComplement).serialize() }