Skip to content

Commit

Permalink
Merge pull request #179 from argentlabs/issue/decoding-negative-int
Browse files Browse the repository at this point in the history
[FIX] Decoding negative integers
  • Loading branch information
DarthMike authored Feb 14, 2022
2 parents c5bd9c1 + 7afc5cf commit 769762f
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 33 deletions.
14 changes: 14 additions & 0 deletions .swiftpm/xcode/xcshareddata/xcschemes/web3.swift.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,20 @@
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Internal_CryptoSwift_PBDKF2"
BuildableName = "Internal_CryptoSwift_PBDKF2"
BlueprintName = "Internal_CryptoSwift_PBDKF2"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
Expand Down
32 changes: 26 additions & 6 deletions web3sTests/Contract/ABIDecoderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import BigInt
class ABIDecoderTests: XCTestCase {
func testDecodeUint32() {
do {
let decoded = try ABIDecoder.decodeData("0x000000000000000000000000000000000000000000000000000000000000002a", types: [BigInt.self])
let decoded = try ABIDecoder.decodeData("0x000000000000000000000000000000000000000000000000000000000000002a", types: [BigUInt.self])
XCTAssertEqual(try decoded[0].decoded(), BigInt(42))
} catch let error {
print(error.localizedDescription)
Expand All @@ -23,7 +23,7 @@ class ABIDecoderTests: XCTestCase {

func testDecodeUint256Array() {
do {
let decoded = try ABIDecoder.decodeData("0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", types: [ABIArray<BigInt>.self])
let decoded = try ABIDecoder.decodeData("0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", types: [ABIArray<BigUInt>.self])
XCTAssertEqual(try decoded[0].decodedArray(), [BigInt(1), BigInt(2), BigInt(3)])
} catch let error {
print(error.localizedDescription)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -136,9 +156,9 @@ class ABIDecoderTests: XCTestCase {
do {
let decoded = try ABIDecoder.decodeData(
"0x0000000000000000000000000000000000000000000000000000000000a9f60c0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001753796e746865746978204e6574776f726b20546f6b656e000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003534e5800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012"
, types: [BigInt.self, ABIArray<String>.self])
, types: [BigUInt.self, ABIArray<String>.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))
Expand All @@ -153,9 +173,9 @@ class ABIDecoderTests: XCTestCase {
do {
let decoded = try ABIDecoder.decodeData(
"0x0000000000000000000000000000000000000000000000000000000000a9f60c0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001753796e746865746978204e6574776f726b20546f6b656e000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003534e580000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020aface4ed2e287b2f681c32da24383f2fb691f0b6962be5ae7950a5dd793e61ad"
, types: [BigInt.self, ABIArray<String>.self])
, types: [BigUInt.self, ABIArray<String>.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))
Expand Down
37 changes: 23 additions & 14 deletions web3sTests/Extensions/ByteExtensionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 2 additions & 3 deletions web3swift/src/Contract/ABIDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 [""]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
32 changes: 23 additions & 9 deletions web3swift/src/Extensions/ByteExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}

Expand All @@ -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()
}


Expand Down

0 comments on commit 769762f

Please sign in to comment.