diff --git a/web3swift.xcodeproj/project.pbxproj b/web3swift.xcodeproj/project.pbxproj index 2ebdfddd4..fcad911f6 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 */; }; @@ -252,6 +257,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 +487,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 +510,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 +622,7 @@ 8159C50921343EF900197B91 /* PrecompiledContracts */ = { isa = PBXGroup; children = ( + 3AC3BD7B222EA6EF00656EC7 /* ST20 */, E2F8083421CB140E00B6BF15 /* ERC1376 */, E2F8083121CB0ED400B6BF15 /* ERC1155 */, E2F8082E21CB095A00B6BF15 /* ERC721x */, @@ -1235,6 +1254,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 +1264,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 +1292,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 */, @@ -1356,6 +1378,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 +1388,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/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_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") + } +}