diff --git a/README.md b/README.md index 1273727f3..bfbddba55 100755 --- a/README.md +++ b/README.md @@ -250,7 +250,7 @@ If you believe you have identified a security vulnerability with web3swift, you ## Donations -[The Matters](https://github.com/orgs/matterinc/people) are charged with open-sorсe and do not require money for using their web3swift lib. +[The Matters](https://github.com/orgs/matterinc/people) are charged with open-sourсe and do not require money for using their web3swift lib. We want to continue to do everything we can to move the needle forward. If you use any of our libraries for work, see if your employers would be interested in donating. Any amount you can donate today to help us reach our goal would be greatly appreciated. diff --git a/web3swift.podspec b/web3swift.podspec index 1a7df8ca5..0c0865fde 100755 --- a/web3swift.podspec +++ b/web3swift.podspec @@ -1,33 +1,35 @@ Pod::Spec.new do |s| s.name = "web3swift" -s.version = "2.0.1" +s.version = "2.1.1" s.summary = "Web3 implementation in vanilla Swift for iOS ans macOS" s.description = <<-DESC Web3 implementation in vanilla Swift, intended for mobile developers of wallets, Dapps and Web3.0 DESC -s.homepage = "https://github.com/matterinc/web3swift" +s.homepage = "https://github.com/matter-labs/web3swift" s.license = 'Apache License 2.0' s.author = { "Alex Vlasov" => "alex.m.vlasov@gmail.com" } -s.source = { :git => 'https://github.com/matterinc/web3swift.git', :tag => s.version.to_s } +s.source = { :git => 'https://github.com/matter-labs/web3swift.git', :tag => s.version.to_s } s.social_media_url = 'https://twitter.com/shamatar' s.swift_version = '4.1' s.module_name = 'Web3swift' s.ios.deployment_target = "9.0" s.osx.deployment_target = "10.11" -s.source_files = "web3swift/{PrecompiledContracts,Promises,Web3,Contract,KeystoreManager,Transaction,Convenience}/Classes/*.{h,swift}", "web3swift/Utils/Classes/{EIP67Code, EIP681}.swift", "web3swift/HookedFunctions/Classes/*.swift", "web3swift/web3swift.h" +s.source_files = "web3swift/{Promises,HookedFunctions,Web3,Contract,KeystoreManager,Transaction,Convenience}/Classes/*.{h,swift}", "web3swift/Utils/Classes/{EIP67Code, EIP681, ENS, ENSResolver, NameHash}.swift", "web3swift/PrecompiledContracts/**/*.swift", "web3swift/web3swift.h" s.public_header_files = "web3swift/web3swift.h" s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.frameworks = 'CoreImage' +s.dependency 'scrypt', '~> 2.0' +s.dependency 'PromiseKit', '~> 6.4.1' s.dependency 'BigInt', '~> 3.1' s.dependency 'CryptoSwift', '~> 0.13' -s.dependency 'PromiseKit', '~> 6.3' -s.dependency 'scrypt', '~> 2.0' +s.dependency 'Result', '~> 4.0' s.dependency 'secp256k1_swift', '~> 1.0.3' s.dependency 'SwiftRLP', '~> 1.1' s.dependency 'EthereumAddress', '~> 1.0.0' s.dependency 'EthereumABI', '~> 1.1.1' + end diff --git a/web3swift.xcodeproj/project.pbxproj b/web3swift.xcodeproj/project.pbxproj index 2ebdfddd4..5d4b3b77b 100755 --- a/web3swift.xcodeproj/project.pbxproj +++ b/web3swift.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 00E5FE8220EA3FF40030E0D6 /* web3swift_infura_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00E5FE8120EA3FF40030E0D6 /* web3swift_infura_Tests.swift */; }; 13AE5971A972F5B55FA6FB69 /* libPods-web3swift-iOS_Tests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8349531F1984454E50389370 /* libPods-web3swift-iOS_Tests.a */; }; 1CD91B341FD769A6007BFB45 /* web3swift_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CD91AFC1FD76910007BFB45 /* web3swift_iOS.framework */; }; + 3A0CD5E9223B865000D0A4FE /* web3swift_ST20AndSecurityToken_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0CD5E8223B865000D0A4FE /* web3swift_ST20AndSecurityToken_Tests.swift */; }; 3AC1E7CB222D6A8C004F43D8 /* Web3+BrowserFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81C0FCF8204456E600D82FAF /* Web3+BrowserFunctions.swift */; }; 3AC1E7CC222D6A99004F43D8 /* Web3+ERC1376.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F8083521CB142000B6BF15 /* Web3+ERC1376.swift */; }; 3AC1E7CD222D6A99004F43D8 /* Web3+ERC1155.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F8083221CB0EF300B6BF15 /* Web3+ERC1155.swift */; }; @@ -25,6 +26,10 @@ 3AC1E7D7222D6A99004F43D8 /* Web3+ERC777.swift in Sources */ = {isa = PBXBuildFile; fileRef = E279C9F421C47B4A0081695F /* Web3+ERC777.swift */; }; 3AC1E7D8222D6A99004F43D8 /* Web3+ERC165.swift in Sources */ = {isa = PBXBuildFile; fileRef = E279C9EE21C46A140081695F /* Web3+ERC165.swift */; }; 3AC1E7D9222D6AA0004F43D8 /* Web3+BrowserFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81C0FCF8204456E600D82FAF /* Web3+BrowserFunctions.swift */; }; + 3AC3BD7D222EA70500656EC7 /* Web3+ST20.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC3BD7C222EA70500656EC7 /* Web3+ST20.swift */; }; + 3AC3BD7E222EA70500656EC7 /* Web3+ST20.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC3BD7C222EA70500656EC7 /* Web3+ST20.swift */; }; + 3AC3BD80222EC6C900656EC7 /* Web3+SecurityToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC3BD7F222EC6C900656EC7 /* Web3+SecurityToken.swift */; }; + 3AC3BD81222EC6C900656EC7 /* Web3+SecurityToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC3BD7F222EC6C900656EC7 /* Web3+SecurityToken.swift */; }; 4194811B203630530065A83B /* Web3+HttpProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 817EBB142004FE4200E02EAA /* Web3+HttpProvider.swift */; }; 4194811E203630530065A83B /* Web3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81DDECCE1FDF004E0063684A /* Web3.swift */; }; 4194811F203630530065A83B /* Web3+Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 818ABD5A1FE95F8F002657BB /* Web3+Instance.swift */; }; @@ -178,8 +183,6 @@ 81DFB400210775320011DC85 /* Web3+Infura.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81DFB3FE210775320011DC85 /* Web3+Infura.swift */; }; 81EB1E4B208173D7003BD47F /* Web3+Personal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81EB1E4A208173D7003BD47F /* Web3+Personal.swift */; }; 81EB1E4C208173D7003BD47F /* Web3+Personal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81EB1E4A208173D7003BD47F /* Web3+Personal.swift */; }; - 81ED4EA82190D922003E932E /* NonceMiddleware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81ED4EA72190D922003E932E /* NonceMiddleware.swift */; }; - 81ED4EA92190D922003E932E /* NonceMiddleware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81ED4EA72190D922003E932E /* NonceMiddleware.swift */; }; 81FA43F62044097100EE14D5 /* web3swift_macOS_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81FA43F52044097100EE14D5 /* web3swift_macOS_Tests.swift */; }; 81FA43F82044097100EE14D5 /* web3swift_macOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 417715D420362916005C3E16 /* web3swift_macOS.framework */; }; 81FB21FE207BB297007F9A83 /* EIP67Code.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81FB21F8207BA78B007F9A83 /* EIP67Code.swift */; }; @@ -252,6 +255,9 @@ 2B8FEFF3962166E1BEADC886 /* Pods_web3swift_ios.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_web3swift_ios.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 342700493511FEB189700D13 /* Pods-web3swift-iOS_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-web3swift-iOS_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-web3swift-iOS_Tests/Pods-web3swift-iOS_Tests.debug.xcconfig"; sourceTree = ""; }; 391A0D2EF42488E5C8AB2F71 /* Pods_web3swift_osx_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_web3swift_osx_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3A0CD5E8223B865000D0A4FE /* web3swift_ST20AndSecurityToken_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = web3swift_ST20AndSecurityToken_Tests.swift; sourceTree = ""; }; + 3AC3BD7C222EA70500656EC7 /* Web3+ST20.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Web3+ST20.swift"; sourceTree = ""; }; + 3AC3BD7F222EC6C900656EC7 /* Web3+SecurityToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Web3+SecurityToken.swift"; sourceTree = ""; }; 417715D420362916005C3E16 /* web3swift_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = web3swift_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 419481432036338A0065A83B /* Pods_web3swift_osx.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Pods_web3swift_osx.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4EFFCB6D208552F2008165FE /* web3swift_local_node_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = web3swift_local_node_Tests.swift; sourceTree = ""; }; @@ -479,6 +485,7 @@ children = ( 81909D4E21887658007D2AE5 /* web3swift_Eventloop_Tests.swift */, 81A7B2892143DF1D004CD2C7 /* web3swift_EIP681_Tests.swift */, + 3A0CD5E8223B865000D0A4FE /* web3swift_ST20AndSecurityToken_Tests.swift */, 81A7B2782143C978004CD2C7 /* web3swift_ENS_Tests.swift */, 8159C50D2135901700197B91 /* web3swift_ERC20_Class_Tests.swift */, 81FECD5D211AEFCE006DA367 /* web3swift_ObjC_Tests.swift */, @@ -501,6 +508,15 @@ path = web3swiftTests; sourceTree = ""; }; + 3AC3BD7B222EA6EF00656EC7 /* ST20 */ = { + isa = PBXGroup; + children = ( + 3AC3BD7C222EA70500656EC7 /* Web3+ST20.swift */, + 3AC3BD7F222EC6C900656EC7 /* Web3+SecurityToken.swift */, + ); + path = ST20; + sourceTree = ""; + }; 78D101CC419F27D142F6E9AE /* Frameworks */ = { isa = PBXGroup; children = ( @@ -604,6 +620,7 @@ 8159C50921343EF900197B91 /* PrecompiledContracts */ = { isa = PBXGroup; children = ( + 3AC3BD7B222EA6EF00656EC7 /* ST20 */, E2F8083421CB140E00B6BF15 /* ERC1376 */, E2F8083121CB0ED400B6BF15 /* ERC1155 */, E2F8082E21CB095A00B6BF15 /* ERC721x */, @@ -1220,7 +1237,6 @@ 8160E5CE20B8245A0070070B /* IBAN.swift in Sources */, 81909D1521862D37007D2AE5 /* Web3+MutatingTransaction.swift in Sources */, 810B0F9C1FEC520500CF0DA2 /* Web3+Methods.swift in Sources */, - 81ED4EA82190D922003E932E /* NonceMiddleware.swift in Sources */, 81A1822520D678590016741F /* Promise+Web3+Eth+GetGasPrice.swift in Sources */, 8113DE7C1FD8514400CD8DF1 /* NSRegularExpressionExtension.swift in Sources */, E2F8082A21CAA07000B6BF15 /* Web3+ERC1643.swift in Sources */, @@ -1235,6 +1251,7 @@ 81DDECCF1FDF004E0063684A /* Web3.swift in Sources */, 81A1823420D6E1FD0016741F /* Promise+Web3+Eth+GetBlockByNumber.swift in Sources */, 81A1822820D678BF0016741F /* Promise+Web3+Eth+GetAccounts.swift in Sources */, + 3AC3BD80222EC6C900656EC7 /* Web3+SecurityToken.swift in Sources */, E2F8081F21CA90BA00B6BF15 /* Web3+ERC1410.swift in Sources */, 81A7B2512143C3A8004CD2C7 /* NameHash.swift in Sources */, E2F8083021CB096D00B6BF15 /* Web3+ERC721x.swift in Sources */, @@ -1244,6 +1261,7 @@ E2F8082D21CB009A00B6BF15 /* Web3+ERC1633.swift in Sources */, 813FFF8D1FD82EEB006379A2 /* String+Extension.swift in Sources */, 81A1823120D68A110016741F /* Promise+Batching.swift in Sources */, + 3AC3BD7D222EA70500656EC7 /* Web3+ST20.swift in Sources */, 817EBB162004FE4200E02EAA /* Web3+HttpProvider.swift in Sources */, 8103BBCC2077B84400499769 /* PlainKeystore.swift in Sources */, 81DFB3FF210775320011DC85 /* Web3+Infura.swift in Sources */, @@ -1271,6 +1289,7 @@ 9196A68821B9EFF100852ED0 /* web3swift_EIP67_Tests.swift in Sources */, 9196A68721B9EFDA00852ED0 /* web3swift_EIP681_Tests.swift in Sources */, E2DCA653218C875100F94FBA /* web3swift_ENS_Tests.swift in Sources */, + 3A0CD5E9223B865000D0A4FE /* web3swift_ST20AndSecurityToken_Tests.swift in Sources */, 81909D292188504D007D2AE5 /* web3swift_AdvancedABIv2_Tests.swift in Sources */, 00E5FE8220EA3FF40030E0D6 /* web3swift_infura_Tests.swift in Sources */, 81909D2A21885067007D2AE5 /* web3swift_rinkeby_personalSignature_Tests.swift in Sources */, @@ -1347,7 +1366,6 @@ 41948131203630530065A83B /* BIP32KeystoreJSONStructure.swift in Sources */, 41948132203630530065A83B /* BIP32HDNode.swift in Sources */, 81A7B2522143C3A8004CD2C7 /* NameHash.swift in Sources */, - 81ED4EA92190D922003E932E /* NonceMiddleware.swift in Sources */, 81A1824920D7DDA20016741F /* Promise+Web3+Personal+Sign.swift in Sources */, 81A1822620D678590016741F /* Promise+Web3+Eth+GetGasPrice.swift in Sources */, 81909D1621862D37007D2AE5 /* Web3+MutatingTransaction.swift in Sources */, @@ -1356,6 +1374,7 @@ 81A1824320D7AA750016741F /* Promise+Web3+Eth+SendTransaction.swift in Sources */, 41948134203630530065A83B /* BIP39+WordLists.swift in Sources */, 41948135203630530065A83B /* EthereumKeystoreV3.swift in Sources */, + 3AC3BD81222EC6C900656EC7 /* Web3+SecurityToken.swift in Sources */, 41948136203630530065A83B /* KeystoreV3JSONStructure.swift in Sources */, 8116666420455E33008D8AD0 /* Web3+Wallet.swift in Sources */, 81909D2221884893007D2AE5 /* BigUInt+Extensions.swift in Sources */, @@ -1365,6 +1384,7 @@ 81A1823520D6E1FD0016741F /* Promise+Web3+Eth+GetBlockByNumber.swift in Sources */, 81A1822920D678BF0016741F /* Promise+Web3+Eth+GetAccounts.swift in Sources */, 81C5DA322074EC1E00424CD6 /* ContractProtocol.swift in Sources */, + 3AC3BD7E222EA70500656EC7 /* Web3+ST20.swift in Sources */, 81A1824C20D7DF1B0016741F /* Promise+Web3+Personal+UnlockAccount.swift in Sources */, 4194813B203630530065A83B /* NSRegularExpressionExtension.swift in Sources */, 81C0FD042044A8A700D82FAF /* Web3+Options.swift in Sources */, diff --git a/web3swift/PrecompiledContracts/ST20/Web3+ST20.swift b/web3swift/PrecompiledContracts/ST20/Web3+ST20.swift new file mode 100644 index 000000000..933dc32db --- /dev/null +++ b/web3swift/PrecompiledContracts/ST20/Web3+ST20.swift @@ -0,0 +1,314 @@ +// +// Web3+ST20.swift +// web3swift +// +// Created by Anton on 05/03/2019. +// Copyright © 2019 The Matter Inc. All rights reserved. +// + +import Foundation +import BigInt +import PromiseKit +import EthereumAddress + +//NPolymath Token Standard +protocol IST20: IERC20 { + // off-chain hash + func tokenDetails() throws -> [UInt32] + + //transfer, transferFrom must respect the result of verifyTransfer + func verifyTransfer(from: EthereumAddress, originalOwner: EthereumAddress, to: EthereumAddress, amount: String) throws -> WriteTransaction + + //used to create tokens + func mint(from: EthereumAddress, investor: EthereumAddress, amount: String) throws -> WriteTransaction + + //Burn function used to burn the securityToken + func burn(from: EthereumAddress, amount: String) throws -> WriteTransaction +} + +// This namespace contains functions to work with ST-20 tokens. +// can be imperatively read and saved +public class ST20: IST20 { + + private var _name: String? = nil + private var _symbol: String? = nil + private var _decimals: UInt8? = nil + + private var _hasReadProperties: Bool = false + + public var transactionOptions: TransactionOptions + public var web3: web3 + public var provider: Web3Provider + public var address: EthereumAddress + public var abi: String + + lazy var contract: web3.web3contract = { + let contract = self.web3.contract(self.abi, at: self.address, abiVersion: 2) + precondition(contract != nil) + return contract! + }() + + public init(web3: web3, provider: Web3Provider, address: EthereumAddress, abi: String = Web3.Utils.st20ABI) { + self.web3 = web3 + self.provider = provider + self.address = address + var mergedOptions = web3.transactionOptions + mergedOptions.to = address + self.abi = abi + self.transactionOptions = mergedOptions + } + + public var name: String { + self.readProperties() + if self._name != nil { + return self._name! + } + return "" + } + + public var symbol: String { + self.readProperties() + if self._symbol != nil { + return self._symbol! + } + return "" + } + + /// Must be 18! + public var decimals: UInt8 { + self.readProperties() + if self._decimals != nil { + return self._decimals! + } + return 18 + } + + public func readProperties() { + if self._hasReadProperties { + return + } + let contract = self.contract + guard contract.contract.address != nil else {return} + var transactionOptions = TransactionOptions.defaultOptions + transactionOptions.callOnBlock = .latest + guard let namePromise = contract.read("name", parameters: [] as [AnyObject], extraData: Data(), transactionOptions: transactionOptions)?.callPromise() else {return} + + guard let symbolPromise = contract.read("symbol", parameters: [] as [AnyObject], extraData: Data(), transactionOptions: transactionOptions)?.callPromise() else {return} + + guard let decimalPromise = contract.read("decimals", parameters: [] as [AnyObject], extraData: Data(), transactionOptions: transactionOptions)?.callPromise() else {return} + + let allPromises = [namePromise, symbolPromise, decimalPromise] + let queue = self.web3.requestDispatcher.queue + when(resolved: allPromises).map(on: queue) { (resolvedPromises) -> Void in + guard case .fulfilled(let nameResult) = resolvedPromises[0] else {return} + guard let name = nameResult["0"] as? String else {return} + self._name = name + + guard case .fulfilled(let symbolResult) = resolvedPromises[1] else {return} + guard let symbol = symbolResult["0"] as? String else {return} + self._symbol = symbol + + guard case .fulfilled(let decimalsResult) = resolvedPromises[2] else {return} + guard let decimals = decimalsResult["0"] as? BigUInt else {return} + self._decimals = UInt8(decimals) + + self._hasReadProperties = true + }.wait() + } + + func tokenDetails() throws -> [UInt32] { + let contract = self.contract + var transactionOptions = TransactionOptions() + transactionOptions.callOnBlock = .latest + let result = try contract.read("tokenDetails", parameters: [] as [AnyObject], extraData: Data(), transactionOptions: self.transactionOptions)!.call(transactionOptions: transactionOptions) + guard let res = result["0"] as? [UInt32] else {throw Web3Error.processingError(desc: "Failed to get result of expected type from the Ethereum node")} + return res + } + + func verifyTransfer(from: EthereumAddress, originalOwner: EthereumAddress, to: EthereumAddress, amount: String) throws -> WriteTransaction { + let contract = self.contract + var basicOptions = TransactionOptions() + basicOptions.from = from + basicOptions.to = self.address + basicOptions.callOnBlock = .latest + + // get the decimals manually + let callResult = try contract.read("decimals", transactionOptions: basicOptions)!.call() + var decimals = BigUInt(0) + guard let dec = callResult["0"], let decTyped = dec as? BigUInt else { + throw Web3Error.inputError(desc: "Contract may be not ERC20 compatible, can not get decimals")} + decimals = decTyped + + let intDecimals = Int(decimals) + guard let value = Web3.Utils.parseToBigUInt(amount, decimals: intDecimals) else { + throw Web3Error.inputError(desc: "Can not parse inputted amount") + } + + let tx = contract.write("verifyTransfer", parameters: [originalOwner, to, value] as [AnyObject], transactionOptions: basicOptions)! + return tx + } + + func mint(from: EthereumAddress, investor: EthereumAddress, amount: String) throws -> WriteTransaction { + let contract = self.contract + var basicOptions = TransactionOptions() + basicOptions.from = from + basicOptions.to = self.address + basicOptions.callOnBlock = .latest + + // get the decimals manually + let callResult = try contract.read("decimals", transactionOptions: basicOptions)!.call() + var decimals = BigUInt(0) + guard let dec = callResult["0"], let decTyped = dec as? BigUInt else { + throw Web3Error.inputError(desc: "Contract may be not ERC20 compatible, can not get decimals")} + decimals = decTyped + + let intDecimals = Int(decimals) + guard let value = Web3.Utils.parseToBigUInt(amount, decimals: intDecimals) else { + throw Web3Error.inputError(desc: "Can not parse inputted amount") + } + + let tx = contract.write("mint", parameters: [investor, value] as [AnyObject], transactionOptions: basicOptions)! + return tx + } + + public func burn(from: EthereumAddress, amount: String) throws -> WriteTransaction { + let contract = self.contract + var basicOptions = TransactionOptions() + basicOptions.from = from + basicOptions.to = self.address + basicOptions.callOnBlock = .latest + + // get the decimals manually + let callResult = try contract.read("decimals", transactionOptions: basicOptions)!.call() + var decimals = BigUInt(0) + guard let dec = callResult["0"], let decTyped = dec as? BigUInt else { + throw Web3Error.inputError(desc: "Contract may be not ERC20 compatible, can not get decimals")} + decimals = decTyped + + let intDecimals = Int(decimals) + guard let value = Web3.Utils.parseToBigUInt(amount, decimals: intDecimals) else { + throw Web3Error.inputError(desc: "Can not parse inputted amount") + } + let tx = contract.write("burn", parameters: [value] as [AnyObject], transactionOptions: basicOptions)! + return tx + } + + public func getBalance(account: EthereumAddress) throws -> BigUInt { + let contract = self.contract + var transactionOptions = TransactionOptions() + transactionOptions.callOnBlock = .latest + let result = try contract.read("balanceOf", parameters: [account] as [AnyObject], extraData: Data(), transactionOptions: self.transactionOptions)!.call(transactionOptions: transactionOptions) + guard let res = result["0"] as? BigUInt else {throw Web3Error.processingError(desc: "Failed to get result of expected type from the Ethereum node")} + return res + } + + public func getAllowance(originalOwner: EthereumAddress, delegate: EthereumAddress) throws -> BigUInt { + let contract = self.contract + var transactionOptions = TransactionOptions() + transactionOptions.callOnBlock = .latest + let result = try contract.read("allowance", parameters: [originalOwner, delegate] as [AnyObject], extraData: Data(), transactionOptions: self.transactionOptions)!.call(transactionOptions: transactionOptions) + guard let res = result["0"] as? BigUInt else {throw Web3Error.processingError(desc: "Failed to get result of expected type from the Ethereum node")} + return res + } + + public func transfer(from: EthereumAddress, to: EthereumAddress, amount: String) throws -> WriteTransaction { + let contract = self.contract + var basicOptions = TransactionOptions() + basicOptions.from = from + basicOptions.to = self.address + basicOptions.callOnBlock = .latest + + // get the decimals manually + let callResult = try contract.read("decimals", transactionOptions: basicOptions)!.call() + var decimals = BigUInt(0) + guard let dec = callResult["0"], let decTyped = dec as? BigUInt else { + throw Web3Error.inputError(desc: "Contract may be not ERC20 compatible, can not get decimals")} + decimals = decTyped + + let intDecimals = Int(decimals) + guard let value = Web3.Utils.parseToBigUInt(amount, decimals: intDecimals) else { + throw Web3Error.inputError(desc: "Can not parse inputted amount") + } + let tx = contract.write("transfer", parameters: [to, value] as [AnyObject], transactionOptions: basicOptions)! + return tx + } + + public func transferFrom(from: EthereumAddress, to: EthereumAddress, originalOwner: EthereumAddress, amount: String) throws -> WriteTransaction { + let contract = self.contract + var basicOptions = TransactionOptions() + basicOptions.from = from + basicOptions.to = self.address + basicOptions.callOnBlock = .latest + + // get the decimals manually + let callResult = try contract.read("decimals", transactionOptions: basicOptions)!.call() + var decimals = BigUInt(0) + guard let dec = callResult["0"], let decTyped = dec as? BigUInt else { + throw Web3Error.inputError(desc: "Contract may be not ERC20 compatible, can not get decimals")} + decimals = decTyped + + let intDecimals = Int(decimals) + guard let value = Web3.Utils.parseToBigUInt(amount, decimals: intDecimals) else { + throw Web3Error.inputError(desc: "Can not parse inputted amount") + } + + let tx = contract.write("transferFrom", parameters: [originalOwner, to, value] as [AnyObject], transactionOptions: basicOptions)! + return tx + } + + public func setAllowance(from: EthereumAddress, to: EthereumAddress, newAmount: String) throws -> WriteTransaction { + let contract = self.contract + var basicOptions = TransactionOptions() + basicOptions.from = from + basicOptions.to = self.address + basicOptions.callOnBlock = .latest + + // get the decimals manually + let callResult = try contract.read("decimals", transactionOptions: basicOptions)!.call() + var decimals = BigUInt(0) + guard let dec = callResult["0"], let decTyped = dec as? BigUInt else { + throw Web3Error.inputError(desc: "Contract may be not ERC20 compatible, can not get decimals")} + decimals = decTyped + + let intDecimals = Int(decimals) + guard let value = Web3.Utils.parseToBigUInt(newAmount, decimals: intDecimals) else { + throw Web3Error.inputError(desc: "Can not parse inputted amount") + } + + let tx = contract.write("setAllowance", parameters: [to, value] as [AnyObject], transactionOptions: basicOptions)! + return tx + } + + public func approve(from: EthereumAddress, spender: EthereumAddress, amount: String) throws -> WriteTransaction { + let contract = self.contract + var basicOptions = TransactionOptions() + basicOptions.from = from + basicOptions.to = self.address + basicOptions.callOnBlock = .latest + + // get the decimals manually + let callResult = try contract.read("decimals", transactionOptions: basicOptions)!.call() + var decimals = BigUInt(0) + guard let dec = callResult["0"], let decTyped = dec as? BigUInt else { + throw Web3Error.inputError(desc: "Contract may be not ERC20 compatible, can not get decimals")} + decimals = decTyped + + let intDecimals = Int(decimals) + guard let value = Web3.Utils.parseToBigUInt(amount, decimals: intDecimals) else { + throw Web3Error.inputError(desc: "Can not parse inputted amount") + } + + let tx = contract.write("approve", parameters: [spender, value] as [AnyObject], transactionOptions: basicOptions)! + return tx + } + + public func totalSupply() throws -> BigUInt { + let contract = self.contract + var transactionOptions = TransactionOptions() + transactionOptions.callOnBlock = .latest + let result = try contract.read("totalSupply", parameters: [AnyObject](), extraData: Data(), transactionOptions: self.transactionOptions)!.call(transactionOptions: transactionOptions) + guard let res = result["0"] as? BigUInt else {throw Web3Error.processingError(desc: "Failed to get result of expected type from the Ethereum node")} + return res + } + +} diff --git a/web3swift/PrecompiledContracts/ST20/Web3+SecurityToken.swift b/web3swift/PrecompiledContracts/ST20/Web3+SecurityToken.swift new file mode 100644 index 000000000..3a60b5ac1 --- /dev/null +++ b/web3swift/PrecompiledContracts/ST20/Web3+SecurityToken.swift @@ -0,0 +1,473 @@ +// +// Web3+SecurityToken.swift +// web3swift +// +// Created by Anton on 05/03/2019. +// Copyright © 2019 The Matter Inc. All rights reserved. +// + +import Foundation +import BigInt +import PromiseKit +import EthereumAddress + +//The Ownable contract has an owner address, and provides basic authorization control functions, this simplifies the implementation of "user permissions". +protocol IOwnable { + //Allows the current owner to relinquish control of the contract. + func renounceOwnership(from: EthereumAddress) throws -> WriteTransaction + + //Allows the current owner to transfer control of the contract to a newOwner. + func transferOwnership(from: EthereumAddress, newOwner: EthereumAddress) throws -> WriteTransaction +} + +//Security token interface +protocol ISecurityToken: IST20, IOwnable { + // Value of current checkpoint + func currentCheckpointId() throws -> BigUInt + + func getGranularity() throws -> BigUInt + + // Total number of non-zero token holders + func investorCount() throws -> BigUInt + + // List of token holders + func investors() throws -> [EthereumAddress] + + // Permissions this to a Permission module, which has a key of 1 + // If no Permission return false - note that IModule withPerm will allow ST owner all permissions anyway + // this allows individual modules to override this logic if needed (to not allow ST owner all permissions) + func checkPermission(delegate: EthereumAddress, module: EthereumAddress, perm: [UInt32]) throws -> Bool + + //returns module list for a module type + //params: + //- moduleType is which type of module we are trying to remove + //- moduleIndex is the index of the module within the chosen type + func getModule(moduleType: UInt8, moduleIndex: UInt8) throws -> ([UInt32], EthereumAddress) + + //returns module list for a module name - will return first match + //params: + //- moduleType is which type of module we are trying to remove + //- name is the name of the module within the chosen type + func getModuleByName(moduleType: UInt8, name: [UInt32]) throws -> ([UInt32], EthereumAddress) + + //Queries totalSupply as of a defined checkpoint + func totalSupplyAt(checkpointId: BigUInt) throws -> BigUInt + + //Queries balances as of a defined checkpoint + func balanceOfAt(investor: EthereumAddress, checkpointId: BigUInt) throws -> BigUInt + + //Creates a checkpoint that can be used to query historical balances / totalSuppy + func createCheckpoint(from: EthereumAddress) throws -> WriteTransaction + + //gets length of investors array + func getInvestorsLength() throws -> BigUInt +} + +public class SecurityToken: ISecurityToken { + + private var _name: String? = nil + private var _symbol: String? = nil + private var _decimals: UInt8? = nil + + private var _hasReadProperties: Bool = false + + public var transactionOptions: TransactionOptions + public var web3: web3 + public var provider: Web3Provider + public var address: EthereumAddress + public var abi: String + + lazy var contract: web3.web3contract = { + let contract = self.web3.contract(self.abi, at: self.address, abiVersion: 2) + precondition(contract != nil) + return contract! + }() + + public init(web3: web3, provider: Web3Provider, address: EthereumAddress, abi: String = Web3.Utils.st20ABI) { + self.web3 = web3 + self.provider = provider + self.address = address + var mergedOptions = web3.transactionOptions + mergedOptions.to = address + self.abi = abi + self.transactionOptions = mergedOptions + } + + public var name: String { + self.readProperties() + if self._name != nil { + return self._name! + } + return "" + } + + public var symbol: String { + self.readProperties() + if self._symbol != nil { + return self._symbol! + } + return "" + } + + /// Must be 18! + public var decimals: UInt8 { + self.readProperties() + if self._decimals != nil { + return self._decimals! + } + return 18 + } + + public func readProperties() { + if self._hasReadProperties { + return + } + let contract = self.contract + guard contract.contract.address != nil else {return} + var transactionOptions = TransactionOptions.defaultOptions + transactionOptions.callOnBlock = .latest + guard let namePromise = contract.read("name", parameters: [] as [AnyObject], extraData: Data(), transactionOptions: transactionOptions)?.callPromise() else {return} + + guard let symbolPromise = contract.read("symbol", parameters: [] as [AnyObject], extraData: Data(), transactionOptions: transactionOptions)?.callPromise() else {return} + + guard let decimalPromise = contract.read("decimals", parameters: [] as [AnyObject], extraData: Data(), transactionOptions: transactionOptions)?.callPromise() else {return} + + let allPromises = [namePromise, symbolPromise, decimalPromise] + let queue = self.web3.requestDispatcher.queue + when(resolved: allPromises).map(on: queue) { (resolvedPromises) -> Void in + guard case .fulfilled(let nameResult) = resolvedPromises[0] else {return} + guard let name = nameResult["0"] as? String else {return} + self._name = name + + guard case .fulfilled(let symbolResult) = resolvedPromises[1] else {return} + guard let symbol = symbolResult["0"] as? String else {return} + self._symbol = symbol + + guard case .fulfilled(let decimalsResult) = resolvedPromises[2] else {return} + guard let decimals = decimalsResult["0"] as? BigUInt else {return} + self._decimals = UInt8(decimals) + + self._hasReadProperties = true + }.wait() + } + + func tokenDetails() throws -> [UInt32] { + let contract = self.contract + var transactionOptions = TransactionOptions() + transactionOptions.callOnBlock = .latest + let result = try contract.read("tokenDetails", parameters: [] as [AnyObject], extraData: Data(), transactionOptions: self.transactionOptions)!.call(transactionOptions: transactionOptions) + guard let res = result["0"] as? [UInt32] else {throw Web3Error.processingError(desc: "Failed to get result of expected type from the Ethereum node")} + return res + } + + func verifyTransfer(from: EthereumAddress, originalOwner: EthereumAddress, to: EthereumAddress, amount: String) throws -> WriteTransaction { + let contract = self.contract + var basicOptions = TransactionOptions() + basicOptions.from = from + basicOptions.to = self.address + basicOptions.callOnBlock = .latest + + // get the decimals manually + let callResult = try contract.read("decimals", transactionOptions: basicOptions)!.call() + var decimals = BigUInt(0) + guard let dec = callResult["0"], let decTyped = dec as? BigUInt else { + throw Web3Error.inputError(desc: "Contract may be not ERC20 compatible, can not get decimals")} + decimals = decTyped + + let intDecimals = Int(decimals) + guard let value = Web3.Utils.parseToBigUInt(amount, decimals: intDecimals) else { + throw Web3Error.inputError(desc: "Can not parse inputted amount") + } + + let tx = contract.write("verifyTransfer", parameters: [originalOwner, to, value] as [AnyObject], transactionOptions: basicOptions)! + return tx + } + + func mint(from: EthereumAddress, investor: EthereumAddress, amount: String) throws -> WriteTransaction { + let contract = self.contract + var basicOptions = TransactionOptions() + basicOptions.from = from + basicOptions.to = self.address + basicOptions.callOnBlock = .latest + + // get the decimals manually + let callResult = try contract.read("decimals", transactionOptions: basicOptions)!.call() + var decimals = BigUInt(0) + guard let dec = callResult["0"], let decTyped = dec as? BigUInt else { + throw Web3Error.inputError(desc: "Contract may be not ERC20 compatible, can not get decimals")} + decimals = decTyped + + let intDecimals = Int(decimals) + guard let value = Web3.Utils.parseToBigUInt(amount, decimals: intDecimals) else { + throw Web3Error.inputError(desc: "Can not parse inputted amount") + } + + let tx = contract.write("mint", parameters: [investor, value] as [AnyObject], transactionOptions: basicOptions)! + return tx + } + + public func burn(from: EthereumAddress, amount: String) throws -> WriteTransaction { + let contract = self.contract + var basicOptions = TransactionOptions() + basicOptions.from = from + basicOptions.to = self.address + basicOptions.callOnBlock = .latest + + // get the decimals manually + let callResult = try contract.read("decimals", transactionOptions: basicOptions)!.call() + var decimals = BigUInt(0) + guard let dec = callResult["0"], let decTyped = dec as? BigUInt else { + throw Web3Error.inputError(desc: "Contract may be not ERC20 compatible, can not get decimals")} + decimals = decTyped + + let intDecimals = Int(decimals) + guard let value = Web3.Utils.parseToBigUInt(amount, decimals: intDecimals) else { + throw Web3Error.inputError(desc: "Can not parse inputted amount") + } + let tx = contract.write("burn", parameters: [value] as [AnyObject], transactionOptions: basicOptions)! + return tx + } + + public func getBalance(account: EthereumAddress) throws -> BigUInt { + let contract = self.contract + var transactionOptions = TransactionOptions() + transactionOptions.callOnBlock = .latest + let result = try contract.read("balanceOf", parameters: [account] as [AnyObject], extraData: Data(), transactionOptions: self.transactionOptions)!.call(transactionOptions: transactionOptions) + guard let res = result["0"] as? BigUInt else {throw Web3Error.processingError(desc: "Failed to get result of expected type from the Ethereum node")} + return res + } + + public func getAllowance(originalOwner: EthereumAddress, delegate: EthereumAddress) throws -> BigUInt { + let contract = self.contract + var transactionOptions = TransactionOptions() + transactionOptions.callOnBlock = .latest + let result = try contract.read("allowance", parameters: [originalOwner, delegate] as [AnyObject], extraData: Data(), transactionOptions: self.transactionOptions)!.call(transactionOptions: transactionOptions) + guard let res = result["0"] as? BigUInt else {throw Web3Error.processingError(desc: "Failed to get result of expected type from the Ethereum node")} + return res + } + + public func transfer(from: EthereumAddress, to: EthereumAddress, amount: String) throws -> WriteTransaction { + let contract = self.contract + var basicOptions = TransactionOptions() + basicOptions.from = from + basicOptions.to = self.address + basicOptions.callOnBlock = .latest + + // get the decimals manually + let callResult = try contract.read("decimals", transactionOptions: basicOptions)!.call() + var decimals = BigUInt(0) + guard let dec = callResult["0"], let decTyped = dec as? BigUInt else { + throw Web3Error.inputError(desc: "Contract may be not ERC20 compatible, can not get decimals")} + decimals = decTyped + + let intDecimals = Int(decimals) + guard let value = Web3.Utils.parseToBigUInt(amount, decimals: intDecimals) else { + throw Web3Error.inputError(desc: "Can not parse inputted amount") + } + let tx = contract.write("transfer", parameters: [to, value] as [AnyObject], transactionOptions: basicOptions)! + return tx + } + + public func transferFrom(from: EthereumAddress, to: EthereumAddress, originalOwner: EthereumAddress, amount: String) throws -> WriteTransaction { + let contract = self.contract + var basicOptions = TransactionOptions() + basicOptions.from = from + basicOptions.to = self.address + basicOptions.callOnBlock = .latest + + // get the decimals manually + let callResult = try contract.read("decimals", transactionOptions: basicOptions)!.call() + var decimals = BigUInt(0) + guard let dec = callResult["0"], let decTyped = dec as? BigUInt else { + throw Web3Error.inputError(desc: "Contract may be not ERC20 compatible, can not get decimals")} + decimals = decTyped + + let intDecimals = Int(decimals) + guard let value = Web3.Utils.parseToBigUInt(amount, decimals: intDecimals) else { + throw Web3Error.inputError(desc: "Can not parse inputted amount") + } + + let tx = contract.write("transferFrom", parameters: [originalOwner, to, value] as [AnyObject], transactionOptions: basicOptions)! + return tx + } + + public func setAllowance(from: EthereumAddress, to: EthereumAddress, newAmount: String) throws -> WriteTransaction { + let contract = self.contract + var basicOptions = TransactionOptions() + basicOptions.from = from + basicOptions.to = self.address + basicOptions.callOnBlock = .latest + + // get the decimals manually + let callResult = try contract.read("decimals", transactionOptions: basicOptions)!.call() + var decimals = BigUInt(0) + guard let dec = callResult["0"], let decTyped = dec as? BigUInt else { + throw Web3Error.inputError(desc: "Contract may be not ERC20 compatible, can not get decimals")} + decimals = decTyped + + let intDecimals = Int(decimals) + guard let value = Web3.Utils.parseToBigUInt(newAmount, decimals: intDecimals) else { + throw Web3Error.inputError(desc: "Can not parse inputted amount") + } + + let tx = contract.write("setAllowance", parameters: [to, value] as [AnyObject], transactionOptions: basicOptions)! + return tx + } + + public func approve(from: EthereumAddress, spender: EthereumAddress, amount: String) throws -> WriteTransaction { + let contract = self.contract + var basicOptions = TransactionOptions() + basicOptions.from = from + basicOptions.to = self.address + basicOptions.callOnBlock = .latest + + // get the decimals manually + let callResult = try contract.read("decimals", transactionOptions: basicOptions)!.call() + var decimals = BigUInt(0) + guard let dec = callResult["0"], let decTyped = dec as? BigUInt else { + throw Web3Error.inputError(desc: "Contract may be not ERC20 compatible, can not get decimals")} + decimals = decTyped + + let intDecimals = Int(decimals) + guard let value = Web3.Utils.parseToBigUInt(amount, decimals: intDecimals) else { + throw Web3Error.inputError(desc: "Can not parse inputted amount") + } + + let tx = contract.write("approve", parameters: [spender, value] as [AnyObject], transactionOptions: basicOptions)! + return tx + } + + public func totalSupply() throws -> BigUInt { + let contract = self.contract + var transactionOptions = TransactionOptions() + transactionOptions.callOnBlock = .latest + let result = try contract.read("totalSupply", parameters: [AnyObject](), extraData: Data(), transactionOptions: self.transactionOptions)!.call(transactionOptions: transactionOptions) + guard let res = result["0"] as? BigUInt else {throw Web3Error.processingError(desc: "Failed to get result of expected type from the Ethereum node")} + return res + } + + public func renounceOwnership(from: EthereumAddress) throws -> WriteTransaction { + let contract = self.contract + var basicOptions = TransactionOptions() + basicOptions.from = from + basicOptions.to = self.address + basicOptions.callOnBlock = .latest + + let tx = contract.write("renounceOwnership", parameters: [AnyObject](), transactionOptions: basicOptions)! + return tx + } + + public func transferOwnership(from: EthereumAddress, newOwner: EthereumAddress) throws -> WriteTransaction { + let contract = self.contract + var basicOptions = TransactionOptions() + basicOptions.from = from + basicOptions.to = self.address + basicOptions.callOnBlock = .latest + + let tx = contract.write("transferOwnership", parameters: [newOwner] as [AnyObject], transactionOptions: basicOptions)! + return tx + } + + public func currentCheckpointId() throws -> BigUInt { + let contract = self.contract + var transactionOptions = TransactionOptions() + transactionOptions.callOnBlock = .latest + let result = try contract.read("currentCheckpointId", parameters: [AnyObject](), extraData: Data(), transactionOptions: self.transactionOptions)!.call(transactionOptions: transactionOptions) + guard let res = result["0"] as? BigUInt else {throw Web3Error.processingError(desc: "Failed to get result of expected type from the Ethereum node")} + return res + } + + public func getGranularity() throws -> BigUInt { + let contract = self.contract + var transactionOptions = TransactionOptions() + transactionOptions.callOnBlock = .latest + let result = try contract.read("granularity", parameters: [AnyObject](), extraData: Data(), transactionOptions: self.transactionOptions)!.call(transactionOptions: transactionOptions) + guard let res = result["0"] as? BigUInt else {throw Web3Error.processingError(desc: "Failed to get result of expected type from the Ethereum node")} + return res + } + + public func investorCount() throws -> BigUInt { + let contract = self.contract + var transactionOptions = TransactionOptions() + transactionOptions.callOnBlock = .latest + let result = try contract.read("investorCount", parameters: [AnyObject](), extraData: Data(), transactionOptions: self.transactionOptions)!.call(transactionOptions: transactionOptions) + guard let res = result["0"] as? BigUInt else {throw Web3Error.processingError(desc: "Failed to get result of expected type from the Ethereum node")} + return res + } + + public func investors() throws -> [EthereumAddress] { + let contract = self.contract + var transactionOptions = TransactionOptions() + transactionOptions.callOnBlock = .latest + let result = try contract.read("investors", parameters: [AnyObject](), extraData: Data(), transactionOptions: self.transactionOptions)!.call(transactionOptions: transactionOptions) + guard let res = result["0"] as? [EthereumAddress] else {throw Web3Error.processingError(desc: "Failed to get result of expected type from the Ethereum node")} + return res + } + + public func checkPermission(delegate: EthereumAddress, module: EthereumAddress, perm: [UInt32]) throws -> Bool { + let contract = self.contract + var transactionOptions = TransactionOptions() + transactionOptions.callOnBlock = .latest + let result = try contract.read("checkPermission", parameters: [delegate, module, perm] as [AnyObject], extraData: Data(), transactionOptions: self.transactionOptions)!.call(transactionOptions: transactionOptions) + guard let res = result["0"] as? Bool else {throw Web3Error.processingError(desc: "Failed to get result of expected type from the Ethereum node")} + return res + } + + public func getModule(moduleType: UInt8, moduleIndex: UInt8) throws -> ([UInt32], EthereumAddress) { + let contract = self.contract + var transactionOptions = TransactionOptions() + transactionOptions.callOnBlock = .latest + let result = try contract.read("getModule", parameters: [moduleType, moduleIndex] as [AnyObject], extraData: Data(), transactionOptions: self.transactionOptions)!.call(transactionOptions: transactionOptions) + guard let moduleList = result["0"] as? [UInt32] else {throw Web3Error.processingError(desc: "Failed to get result of expected type from the Ethereum node")} + guard let moduleAddress = result["1"] as? EthereumAddress else {throw Web3Error.processingError(desc: "Failed to get result of expected type from the Ethereum node")} + return (moduleList, moduleAddress) + } + + public func getModuleByName(moduleType: UInt8, name: [UInt32]) throws -> ([UInt32], EthereumAddress) { + let contract = self.contract + var transactionOptions = TransactionOptions() + transactionOptions.callOnBlock = .latest + let result = try contract.read("getModuleByName", parameters: [moduleType, name] as [AnyObject], extraData: Data(), transactionOptions: self.transactionOptions)!.call(transactionOptions: transactionOptions) + guard let moduleList = result["0"] as? [UInt32] else {throw Web3Error.processingError(desc: "Failed to get result of expected type from the Ethereum node")} + guard let moduleAddress = result["1"] as? EthereumAddress else {throw Web3Error.processingError(desc: "Failed to get result of expected type from the Ethereum node")} + return (moduleList, moduleAddress) + } + + public func totalSupplyAt(checkpointId: BigUInt) throws -> BigUInt { + let contract = self.contract + var transactionOptions = TransactionOptions() + transactionOptions.callOnBlock = .latest + let result = try contract.read("totalSupplyAt", parameters: [checkpointId] as [AnyObject], extraData: Data(), transactionOptions: self.transactionOptions)!.call(transactionOptions: transactionOptions) + guard let res = result["0"] as? BigUInt else {throw Web3Error.processingError(desc: "Failed to get result of expected type from the Ethereum node")} + return res + } + + public func balanceOfAt(investor: EthereumAddress, checkpointId: BigUInt) throws -> BigUInt { + let contract = self.contract + var transactionOptions = TransactionOptions() + transactionOptions.callOnBlock = .latest + let result = try contract.read("balanceOfAt", parameters: [investor, checkpointId] as [AnyObject], extraData: Data(), transactionOptions: self.transactionOptions)!.call(transactionOptions: transactionOptions) + guard let res = result["0"] as? BigUInt else {throw Web3Error.processingError(desc: "Failed to get result of expected type from the Ethereum node")} + return res + } + + public func createCheckpoint(from: EthereumAddress) throws -> WriteTransaction { + let contract = self.contract + var basicOptions = TransactionOptions() + basicOptions.from = from + basicOptions.to = self.address + basicOptions.callOnBlock = .latest + + let tx = contract.write("createCheckpoint", parameters: [AnyObject](), transactionOptions: basicOptions)! + return tx + } + + public func getInvestorsLength() throws -> BigUInt { + let contract = self.contract + var transactionOptions = TransactionOptions() + transactionOptions.callOnBlock = .latest + let result = try contract.read("getInvestorsLength", parameters: [AnyObject](), extraData: Data(), transactionOptions: self.transactionOptions)!.call(transactionOptions: transactionOptions) + guard let res = result["0"] as? BigUInt else {throw Web3Error.processingError(desc: "Failed to get result of expected type from the Ethereum node")} + return res + } +} diff --git a/web3swift/Promises/Classes/Promise+Batching.swift b/web3swift/Promises/Classes/Promise+Batching.swift index 982efa0e9..010b3cc02 100755 --- a/web3swift/Promises/Classes/Promise+Batching.swift +++ b/web3swift/Promises/Classes/Promise+Batching.swift @@ -16,7 +16,7 @@ public class JSONRPCrequestDispatcher { private var lockQueue: DispatchQueue private var batches: [Batch] = [Batch]() - init(provider: Web3Provider, queue: DispatchQueue, policy: DispatchPolicy) { + public init(provider: Web3Provider, queue: DispatchQueue, policy: DispatchPolicy) { self.provider = provider self.queue = queue self.policy = policy diff --git a/web3swift/Web3/Classes/Web3+JSONRPC.swift b/web3swift/Web3/Classes/Web3+JSONRPC.swift index 37d6d9a86..652c03132 100755 --- a/web3swift/Web3/Classes/Web3+JSONRPC.swift +++ b/web3swift/Web3/Classes/Web3+JSONRPC.swift @@ -24,10 +24,10 @@ public struct Counter { /// JSON RPC request structure for serialization and deserialization purposes. public struct JSONRPCrequest: Encodable { - var jsonrpc: String = "2.0" - var method: JSONRPCmethod? - var params: JSONRPCparams? - var id: UInt64 = Counter.increment() + public var jsonrpc: String = "2.0" + public var method: JSONRPCmethod? + public var params: JSONRPCparams? + public var id: UInt64 = Counter.increment() enum CodingKeys: String, CodingKey { case jsonrpc diff --git a/web3swift/Web3/Classes/Web3+Utils.swift b/web3swift/Web3/Classes/Web3+Utils.swift index ade6f7b48..9484c61b2 100755 --- a/web3swift/Web3/Classes/Web3+Utils.swift +++ b/web3swift/Web3/Classes/Web3+Utils.swift @@ -74,6 +74,11 @@ extension Web3.Utils { /// Precoded "cold wallet" (private key controlled) address. Basically - only a payable fallback function. public static var coldWalletABI = "[{\"payable\":true,\"type\":\"fallback\"}]" + /// Precoded ST20 contracts ABI. Output parameters are named for ease of use. + public static var st20ABI = """ +[{"constant":false,"inputs":[],"name":"freezeTransfers","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"investorListed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_moduleType","type":"uint8"},{"name":"_moduleIndex","type":"uint8"}],"name":"removeModule","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"finishMintingSTO","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_granularity","type":"uint256"}],"name":"changeGranularity","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"finishedSTOMinting","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"tokenBurner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"tickerRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"unfreezeTransfers","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"securityTokenVersion","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"investors","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_investor","type":"address"},{"name":"_amount","type":"uint256"}],"name":"mint","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"uint256"}],"name":"burn","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_moduleType","type":"uint8"},{"name":"_moduleIndex","type":"uint256"}],"name":"getModule","outputs":[{"name":"","type":"bytes32"},{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_investors","type":"address[]"},{"name":"_amounts","type":"uint256[]"}],"name":"mintMulti","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_investor","type":"address"},{"name":"_checkpointId","type":"uint256"}],"name":"balanceOfAt","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"currentCheckpointId","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"granularity","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MAX_MODULES","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_moduleType","type":"uint8"},{"name":"_moduleIndex","type":"uint8"},{"name":"_budget","type":"uint256"}],"name":"changeModuleBudget","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"freeze","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"STO_KEY","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseApproval","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"polyToken","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"renounceOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint8"},{"name":"","type":"uint256"}],"name":"modules","outputs":[{"name":"name","type":"bytes32"},{"name":"moduleAddress","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_newTokenDetails","type":"string"}],"name":"updateTokenDetails","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"polymathRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"checkpointTotalSupply","outputs":[{"name":"checkpointId","type":"uint256"},{"name":"value","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_delegate","type":"address"},{"name":"_module","type":"address"},{"name":"_perm","type":"bytes32"}],"name":"checkPermission","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"TRANSFERMANAGER_KEY","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_checkpointId","type":"uint256"}],"name":"totalSupplyAt","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"}],"name":"verifyTransfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"finishedIssuerMinting","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_moduleType","type":"uint8"},{"name":"_name","type":"bytes32"}],"name":"getModuleByName","outputs":[{"name":"","type":"bytes32"},{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"finishMintingIssuer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"PERMISSIONMANAGER_KEY","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_tokenBurner","type":"address"}],"name":"setTokenBurner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"moduleRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"CHECKPOINT_KEY","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_start","type":"uint256"},{"name":"_iters","type":"uint256"}],"name":"pruneInvestors","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"securityTokenRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"tokenDetails","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseApproval","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"investorCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInvestorsLength","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"updateFromRegistry","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_moduleFactory","type":"address"},{"name":"_data","type":"bytes"},{"name":"_maxCost","type":"uint256"},{"name":"_budget","type":"uint256"}],"name":"addModule","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"withdrawPoly","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"uint256"}],"name":"checkpointBalances","outputs":[{"name":"checkpointId","type":"uint256"},{"name":"value","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"createCheckpoint","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_name","type":"string"},{"name":"_symbol","type":"string"},{"name":"_decimals","type":"uint8"},{"name":"_granularity","type":"uint256"},{"name":"_tokenDetails","type":"string"},{"name":"_polymathRegistry","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_type","type":"uint8"},{"indexed":false,"name":"_name","type":"bytes32"},{"indexed":false,"name":"_moduleFactory","type":"address"},{"indexed":false,"name":"_module","type":"address"},{"indexed":false,"name":"_moduleCost","type":"uint256"},{"indexed":false,"name":"_budget","type":"uint256"},{"indexed":false,"name":"_timestamp","type":"uint256"}],"name":"LogModuleAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_oldDetails","type":"string"},{"indexed":false,"name":"_newDetails","type":"string"}],"name":"LogUpdateTokenDetails","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_oldGranularity","type":"uint256"},{"indexed":false,"name":"_newGranularity","type":"uint256"}],"name":"LogGranularityChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_type","type":"uint8"},{"indexed":false,"name":"_module","type":"address"},{"indexed":false,"name":"_timestamp","type":"uint256"}],"name":"LogModuleRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_moduleType","type":"uint8"},{"indexed":false,"name":"_module","type":"address"},{"indexed":false,"name":"_budget","type":"uint256"}],"name":"LogModuleBudgetChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_freeze","type":"bool"},{"indexed":false,"name":"_timestamp","type":"uint256"}],"name":"LogFreezeTransfers","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_checkpointId","type":"uint256"},{"indexed":false,"name":"_timestamp","type":"uint256"}],"name":"LogCheckpointCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_timestamp","type":"uint256"}],"name":"LogFinishMintingIssuer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_timestamp","type":"uint256"}],"name":"LogFinishMintingSTO","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_oldAddress","type":"address"},{"indexed":true,"name":"_newAddress","type":"address"}],"name":"LogChangeSTRAddress","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"}],"name":"OwnershipRenounced","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Minted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_burner","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Burnt","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}] +""" + /// TODO: - Need to make it right /// Precoded ERC888 contracts ABI. Output parameters are named for ease of use. public static var erc888ABI = "[{\"constant\":true,\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_spender\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_from\",\"type\":\"address\"},{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"name\":\"\",\"type\":\"uint8\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"name\":\"balance\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_spender\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"},{\"name\":\"_extraData\",\"type\":\"bytes\"}],\"name\":\"approveAndCall\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\"},{\"name\":\"_spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"name\":\"remaining\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"inputs\":[{\"name\":\"_initialAmount\",\"type\":\"uint256\"},{\"name\":\"_tokenName\",\"type\":\"string\"},{\"name\":\"_decimalUnits\",\"type\":\"uint8\"},{\"name\":\"_tokenSymbol\",\"type\":\"string\"}],\"type\":\"constructor\"},{\"payable\":false,\"type\":\"fallback\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"_from\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"_to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"_owner\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"_spender\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},]" diff --git a/web3swiftTests/web3swift_Eventloop_Tests.swift b/web3swiftTests/web3swift_Eventloop_Tests.swift index 6ca05f5ad..e54c44612 100644 --- a/web3swiftTests/web3swift_Eventloop_Tests.swift +++ b/web3swiftTests/web3swift_Eventloop_Tests.swift @@ -35,33 +35,33 @@ class web3swift_Eventloop_Tests: XCTestCase { waitForExpectations(timeout: 60, handler: nil) } - func testNonceMiddleware() { - let web3 = Web3.InfuraRinkebyWeb3() - let middleware = Web3.Utils.NonceMiddleware() - middleware.attach(web3) - - let sendToAddress = EthereumAddress("0xe22b8979739D724343bd002F9f432F5990879901")! - let ksData = getKeystoreData() - let tempKeystore = EthereumKeystoreV3(ksData!) - let keystoreManager = KeystoreManager([tempKeystore!]) - web3.addKeystoreManager(keystoreManager) - - var tx = web3.eth.sendETH(to: sendToAddress, amount: 1000) - tx!.transactionOptions.from = tempKeystore!.addresses!.first! - var result = try! tx!.send(password: "web3swift") - let newNonce = result.transaction.nonce - sleep(1) - let hookNewNonce = middleware.nonceLookups[tempKeystore!.addresses!.first!]! - XCTAssertEqual(newNonce, hookNewNonce) - - tx = web3.eth.sendETH(to: sendToAddress, amount: 1000) - tx!.transactionOptions.from = tempKeystore!.addresses!.first! - result = try! tx!.send(password: "web3swift") - sleep(1) - let newNonce2 = result.transaction.nonce - let hookNewNonce2 = middleware.nonceLookups[tempKeystore!.addresses!.first!]! - XCTAssert(newNonce2 == hookNewNonce2) - } +// func testNonceMiddleware() { +// let web3 = Web3.InfuraRinkebyWeb3() +// let middleware = Web3.Utils.NonceMiddleware() +// middleware.attach(web3) +// +// let sendToAddress = EthereumAddress("0xe22b8979739D724343bd002F9f432F5990879901")! +// let ksData = getKeystoreData() +// let tempKeystore = EthereumKeystoreV3(ksData!) +// let keystoreManager = KeystoreManager([tempKeystore!]) +// web3.addKeystoreManager(keystoreManager) +// +// var tx = web3.eth.sendETH(to: sendToAddress, amount: 1000) +// tx!.transactionOptions.from = tempKeystore!.addresses!.first! +// var result = try! tx!.send(password: "web3swift") +// let newNonce = result.transaction.nonce +// sleep(1) +// let hookNewNonce = middleware.nonceLookups[tempKeystore!.addresses!.first!]! +// XCTAssertEqual(newNonce, hookNewNonce) +// +// tx = web3.eth.sendETH(to: sendToAddress, amount: 1000) +// tx!.transactionOptions.from = tempKeystore!.addresses!.first! +// result = try! tx!.send(password: "web3swift") +// sleep(1) +// let newNonce2 = result.transaction.nonce +// let hookNewNonce2 = middleware.nonceLookups[tempKeystore!.addresses!.first!]! +// XCTAssert(newNonce2 == hookNewNonce2) +// } func getKeystoreData() -> Data? { diff --git a/web3swiftTests/web3swift_ST20AndSecurityToken_Tests.swift b/web3swiftTests/web3swift_ST20AndSecurityToken_Tests.swift new file mode 100644 index 000000000..1b03fbae9 --- /dev/null +++ b/web3swiftTests/web3swift_ST20AndSecurityToken_Tests.swift @@ -0,0 +1,55 @@ +// +// web3swift_ST20_Tests.swift +// web3swift-iOS_Tests +// +// Created by Anton on 15/03/2019. +// Copyright © 2019 The Matter Inc. All rights reserved. +// + +import XCTest +import BigInt +import EthereumAddress + +@testable import web3swift_iOS + +class web3swift_ST20AndSecurityToken_Tests: XCTestCase { + + func testERC20TokenCreation() { + let web3 = Web3.new(URL(string: "https://kovan.infura.io")!)! + let w3sTokenAddress = EthereumAddress("0x2dD33957C90880bE4Ee9fd5F703110BDA2E579EC")! + let st20token = ST20.init(web3: web3, provider: web3.provider, address: w3sTokenAddress) + st20token.readProperties() + XCTAssert(st20token.symbol == "MIMI") + XCTAssert(st20token.name == "Mimi") + XCTAssert(st20token.decimals == 18) + } + + func testST20tokenBalanceAndAllowance() throws { + let web3 = Web3.new(URL(string: "https://kovan.infura.io")!)! + let w3sTokenAddress = EthereumAddress("0x2dD33957C90880bE4Ee9fd5F703110BDA2E579EC")! + let st20token = ST20.init(web3: web3, provider: web3.provider, address: w3sTokenAddress) + let userAddress = EthereumAddress("0xe22b8979739D724343bd002F9f432F5990879901")! + let balance = try st20token.getBalance(account: userAddress) + let allowance = try st20token.getAllowance(originalOwner: userAddress, delegate: userAddress) + XCTAssert(String(balance) == "0") + XCTAssert(allowance == 0) + } + + func testSecurityTokenInvestors() throws { + let web3 = Web3.new(URL(string: "https://kovan.infura.io")!)! + let w3sTokenAddress = EthereumAddress("0x2dD33957C90880bE4Ee9fd5F703110BDA2E579EC")! + let stoken = SecurityToken.init(web3: web3, provider: web3.provider, address: w3sTokenAddress) + let investorsCount = try stoken.investorCount() + let stringInvestorsCount = String(investorsCount) + XCTAssert(stringInvestorsCount == "0") + } + + func testSecurityTokenGranularity() throws { + let web3 = Web3.new(URL(string: "https://kovan.infura.io")!)! + let w3sTokenAddress = EthereumAddress("0x2dD33957C90880bE4Ee9fd5F703110BDA2E579EC")! + let stoken = SecurityToken.init(web3: web3, provider: web3.provider, address: w3sTokenAddress) + let granularity = try stoken.getGranularity() + let stringGranularity = String(granularity) + XCTAssert(stringGranularity == "1000000000000000000") + } +}