Skip to content
This repository has been archived by the owner on Dec 2, 2024. It is now read-only.

Commit

Permalink
IOS-7391 Tron allowance (#800)
Browse files Browse the repository at this point in the history
  • Loading branch information
tureck1y authored Aug 15, 2024
1 parent 0aa69ee commit 6e570e6
Show file tree
Hide file tree
Showing 12 changed files with 211 additions and 95 deletions.
8 changes: 8 additions & 0 deletions BlockchainSdk.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,8 @@
DC5E65392B16566100E81AA5 /* OpCodeFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC5E65382B16566100E81AA5 /* OpCodeFactory.swift */; };
DC5E653B2B1663C800E81AA5 /* ScriptChunkHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC5E653A2B1663C800E81AA5 /* ScriptChunkHelper.swift */; };
DC5E653D2B16641300E81AA5 /* BitcoinScriptBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC5E653C2B16641300E81AA5 /* BitcoinScriptBuilder.swift */; };
DC6336692C6BB9AC0007D167 /* TronNetworkProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC6336682C6BB9AC0007D167 /* TronNetworkProvider.swift */; };
DC63366D2C6BC3820007D167 /* TronTransactionDataBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC63366C2C6BC3820007D167 /* TronTransactionDataBuilder.swift */; };
DC63F266293F243200F953EF /* Credentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC63F265293F243200F953EF /* Credentials.swift */; };
DC667F062C401B97000842D0 /* StakeKitTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC667F052C401B97000842D0 /* StakeKitTransaction.swift */; };
DC667F082C4022CF000842D0 /* SolanaStakeKitTransactionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC667F072C4022CF000842D0 /* SolanaStakeKitTransactionHelper.swift */; };
Expand Down Expand Up @@ -1606,6 +1608,8 @@
DC5E65382B16566100E81AA5 /* OpCodeFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpCodeFactory.swift; sourceTree = "<group>"; };
DC5E653A2B1663C800E81AA5 /* ScriptChunkHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptChunkHelper.swift; sourceTree = "<group>"; };
DC5E653C2B16641300E81AA5 /* BitcoinScriptBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitcoinScriptBuilder.swift; sourceTree = "<group>"; };
DC6336682C6BB9AC0007D167 /* TronNetworkProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TronNetworkProvider.swift; sourceTree = "<group>"; };
DC63366C2C6BC3820007D167 /* TronTransactionDataBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TronTransactionDataBuilder.swift; sourceTree = "<group>"; };
DC63F265293F243200F953EF /* Credentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Credentials.swift; sourceTree = "<group>"; };
DC667F052C401B97000842D0 /* StakeKitTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakeKitTransaction.swift; sourceTree = "<group>"; };
DC667F072C4022CF000842D0 /* SolanaStakeKitTransactionHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SolanaStakeKitTransactionHelper.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3505,6 +3509,8 @@
2DFD3AA32BE560BD00FCA7DC /* TronUtils.swift */,
2DDE5B9C29C4F8D200A5B708 /* TronWalletAssembly.swift */,
DA9F76EB27EC9A2900F0665C /* TronWalletManager.swift */,
DC6336682C6BB9AC0007D167 /* TronNetworkProvider.swift */,
DC63366C2C6BC3820007D167 /* TronTransactionDataBuilder.swift */,
DA3081222817AEB800DE41F1 /* protobuf */,
);
path = Tron;
Expand Down Expand Up @@ -5230,6 +5236,7 @@
DAC7C04127A3BB17002F7252 /* PolkadotTransactionBuilder.swift in Sources */,
B62A12062AE308DA0080BAEC /* NEARTransactionParams.swift in Sources */,
5D88838B27D3BBDA008744E1 /* BaseManager.swift in Sources */,
DC63366D2C6BC3820007D167 /* TronTransactionDataBuilder.swift in Sources */,
DC5E64E02B1650F400E81AA5 /* OP_CODESEPARATOR.swift in Sources */,
EF5C753E2BAC4D6B00811198 /* URLSessionTask.State+CustomStringConvertible.swift in Sources */,
B00B58C62BCEB93A007475F7 /* TONAPIResolver.swift in Sources */,
Expand Down Expand Up @@ -5281,6 +5288,7 @@
EFD717D52A26330F00E5430D /* WalletFactory.swift in Sources */,
DC5E65202B1650F400E81AA5 /* OP_DUP.swift in Sources */,
DA63B0A429CB402A00AC6E49 /* KaspaWalletAssembly.swift in Sources */,
DC6336692C6BB9AC0007D167 /* TronNetworkProvider.swift in Sources */,
EF32FEDB2A38C288002ED43F /* BitcoinScriptAddressProvider.swift in Sources */,
DA82433D27A2B06500CFC2C0 /* PolkadotJsonRpcProvider.swift in Sources */,
EF57BEDA2A1E625400C2A493 /* DerivationConfigV2.swift in Sources */,
Expand Down
1 change: 1 addition & 0 deletions BlockchainSdk/Blockchains/Tron/TronFunction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import CryptoSwift
enum TronFunction: String {
case transfer = "transfer(address,uint256)"
case approve = "approve(address,uint256)"
case allowance = "allowance(address,address)"
case balanceOf = "balanceOf(address)"

var prefix: Data {
Expand Down
8 changes: 6 additions & 2 deletions BlockchainSdk/Blockchains/Tron/TronJsonRpcProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,12 @@ class TronJsonRpcProvider: HostProvider {
func transactionInfo(id: String) -> AnyPublisher<TronTransactionInfoResponse, Error> {
requestPublisher(for: .getTransactionInfoById(transactionID: id))
}

private func requestPublisher<T: Codable>(for target: TronTarget.TronTargetType) -> AnyPublisher<T, Error> {

func getAllowance(sourceAddress: String, contractAddress: String, parameter: String) -> AnyPublisher<TronTriggerSmartContractResponse, Error> {
requestPublisher(for: .getAllowance(sourceAddress: sourceAddress, contractAddress: contractAddress, parameter: parameter))
}

private func requestPublisher<T: Decodable>(for target: TronTarget.TronTargetType) -> AnyPublisher<T, Error> {
return provider.requestPublisher(TronTarget(node: node, target))
.filterSuccessfulStatusAndRedirectCodes()
.map(T.self)
Expand Down
30 changes: 15 additions & 15 deletions BlockchainSdk/Blockchains/Tron/TronNetworkModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

import Foundation

struct TronGetChainParametersResponse: Codable {
struct TronChainParameter: Codable {
struct TronGetChainParametersResponse: Decodable {
struct TronChainParameter: Decodable {
let key: String
let value: Int?
}
Expand All @@ -29,34 +29,34 @@ struct TronAccountInfo {
let confirmedTransactionIDs: [String]
}

struct TronGetAccountRequest: Codable {
struct TronGetAccountRequest: Encodable {
let address: String
let visible: Bool
}

struct TronGetAccountResponse: Codable {
struct TronGetAccountResponse: Decodable {
let balance: UInt64?
// We don't use this field but we can't have just one optional `balance` field
// Otherwise an empty JSON will conform to this structure
let address: String
}

struct TronGetAccountResourceResponse: Codable {
struct TronGetAccountResourceResponse: Decodable {
let freeNetUsed: Int?
let freeNetLimit: Int
}

struct TronTransactionInfoRequest: Codable {
struct TronTransactionInfoRequest: Encodable {
let value: String
}

struct TronTransactionInfoResponse: Codable {
struct TronTransactionInfoResponse: Decodable {
let id: String
}

struct TronBlock: Codable {
struct BlockHeader: Codable {
struct RawData: Codable {
struct TronBlock: Decodable {
struct BlockHeader: Decodable {
struct RawData: Decodable {
let number: Int64
let txTrieRoot: String
let witness_address: String
Expand All @@ -71,16 +71,16 @@ struct TronBlock: Codable {
let block_header: BlockHeader
}

struct TronBroadcastRequest: Codable {
struct TronBroadcastRequest: Encodable {
let transaction: String
}

struct TronBroadcastResponse: Codable {
struct TronBroadcastResponse: Decodable {
let result: Bool
let txid: String
}

struct TronTriggerSmartContractRequest: Codable {
struct TronTriggerSmartContractRequest: Encodable {
let owner_address: String
let contract_address: String
let function_selector: String
Expand All @@ -89,10 +89,10 @@ struct TronTriggerSmartContractRequest: Codable {
let visible: Bool
}

struct TronTriggerSmartContractResponse: Codable {
struct TronTriggerSmartContractResponse: Decodable {
let constant_result: [String]
}

struct TronContractEnergyUsageResponse: Codable {
struct TronContractEnergyUsageResponse: Decodable {
let energy_used: Int
}
14 changes: 14 additions & 0 deletions BlockchainSdk/Blockchains/Tron/TronNetworkProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// TronNetworkProvider.swift
// BlockchainSdk
//
// Created by Alexander Osokin on 13.08.2024.
// Copyright © 2024 Tangem AG. All rights reserved.
//

import Foundation
import Combine

public protocol TronNetworkProvider {
func getAllowance(owner: String, spender: String, contractAddress: String) -> AnyPublisher<Decimal, Error>
}
69 changes: 40 additions & 29 deletions BlockchainSdk/Blockchains/Tron/TronNetworkService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ class TronNetworkService: MultiNetworkProvider {
}
}

func accountInfo(for address: String, tokens: [Token], transactionIDs: [String], encodedAddress: String) -> AnyPublisher<TronAccountInfo, Error> {
func accountInfo(for address: String, tokens: [Token], transactionIDs: [String]) -> AnyPublisher<TronAccountInfo, Error> {
Publishers.Zip3(
getAccount(for: address),
tokenBalances(address: address, tokens: tokens, parameter: encodedAddress),
tokenBalances(address: address, tokens: tokens),
confirmedTransactionIDs(ids: transactionIDs)
)
.map { [blockchain] (accountInfo, tokenBalances, confirmedTransactionIDs) in
Expand Down Expand Up @@ -112,14 +112,28 @@ class TronNetworkService: MultiNetworkProvider {
}
}

func contractEnergyUsage(sourceAddress: String, contractAddress: String, parameter: String) -> AnyPublisher<Int, Error> {
func contractEnergyUsage(sourceAddress: String, contractAddress: String, contractEnergyUsageData: String) -> AnyPublisher<Int, Error> {
providerPublisher {
$0.contractEnergyUsage(sourceAddress: sourceAddress, contractAddress: contractAddress, parameter: parameter)
$0.contractEnergyUsage(sourceAddress: sourceAddress, contractAddress: contractAddress, parameter: contractEnergyUsageData)
.map(\.energy_used)
.eraseToAnyPublisher()
}
}


func getAllowance(owner: String, contractAddress: String, allowanceData: String) -> AnyPublisher<Decimal, Error> {
providerPublisher {
$0.getAllowance(sourceAddress: owner, contractAddress: contractAddress, parameter: allowanceData)
.tryMap { response in
// TODO: Use EthereumUtils instead?
try TronUtils().parseBalance(
response: response.constant_result,
decimals: 0
)
}
.eraseToAnyPublisher()
}
}

// MARK: - Private Implementation

private func getAccount(for address: String) -> AnyPublisher<TronGetAccountResponse, Error> {
Expand All @@ -137,16 +151,23 @@ class TronNetworkService: MultiNetworkProvider {
}
}

private func tokenBalances(address: String, tokens: [Token], parameter: String) -> AnyPublisher<[Token: Decimal], Error> {
tokens
private func tokenBalances(address: String, tokens: [Token]) -> AnyPublisher<[Token: Decimal], Error> {
let encodedAddressDataPublisher = Result {
try TronUtils().convertAddressToBytesPadded(address).hexString.lowercased()
}
.publisher

let tokenPublisher = tokens
.publisher
.setFailureType(to: Error.self)
.flatMap { [weak self] token -> AnyPublisher<(Token, Decimal), Error> in
guard let self = self else {
return .anyFail(error: WalletError.empty)
}
return self
.tokenBalance(address: address, token: token, parameter: parameter)

return encodedAddressDataPublisher.zip(tokenPublisher)
.withWeakCaptureOf(self)
.flatMap { args -> AnyPublisher<(Token, Decimal), Error> in
let (service, (encodedAddressData, token)) = args

return service
.tokenBalance(address: address, token: token, encodedAddressData: encodedAddressData)
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
}
Expand All @@ -159,25 +180,15 @@ class TronNetworkService: MultiNetworkProvider {
.eraseToAnyPublisher()
}

private func tokenBalance(address: String, token: Token, parameter: String) -> AnyPublisher<(Token, Decimal), Never> {
private func tokenBalance(address: String, token: Token, encodedAddressData: String) -> AnyPublisher<(Token, Decimal), Never> {
providerPublisher {
$0.tokenBalance(address: address, contractAddress: token.contractAddress, parameter: parameter)
$0.tokenBalance(address: address, contractAddress: token.contractAddress, parameter: encodedAddressData)
.tryMap { response in
let bigUIntValue = try TronUtils().combineBigUIntValueAtBalance(response: response.constant_result)

let formatted = EthereumUtils.formatToPrecision(
bigUIntValue,
numberDecimals: token.decimalCount,
formattingDecimals: token.decimalCount,
decimalSeparator: ".",
fallbackToScientific: false
let value = try TronUtils().parseBalance(
response: response.constant_result,
decimals: token.decimalCount
)

guard let decimalValue = Decimal(stringValue: formatted) else {
throw WalletError.failedToParseNetworkResponse()
}

return (token, decimalValue)
return (token, value)
}
.eraseToAnyPublisher()
}
Expand Down
13 changes: 12 additions & 1 deletion BlockchainSdk/Blockchains/Tron/TronTarget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ struct TronTarget: TargetType {
case tokenBalance(address: String, contractAddress: String, parameter: String)
case contractEnergyUsage(sourceAddress: String, contractAddress: String, parameter: String)
case getTransactionInfoById(transactionID: String)
case getAllowance(sourceAddress: String, contractAddress: String, parameter: String)
}

let node: NodeInfo
Expand All @@ -45,7 +46,7 @@ struct TronTarget: TargetType {
return "/wallet/getnowblock"
case .broadcastHex:
return "/wallet/broadcasthex"
case .tokenBalance, .contractEnergyUsage:
case .tokenBalance, .contractEnergyUsage, .getAllowance:
return "/wallet/triggerconstantcontract"
case .getTransactionInfoById:
return "/walletsolidity/gettransactioninfobyid"
Expand Down Expand Up @@ -89,6 +90,16 @@ struct TronTarget: TargetType {
case .getTransactionInfoById(let transactionID):
let request = TronTransactionInfoRequest(value: transactionID)
return .requestJSONEncodable(request)
case .getAllowance(let sourceAddress, let contractAddress, let parameter):
let request = TronTriggerSmartContractRequest(
owner_address: sourceAddress,
contract_address: contractAddress,
function_selector: TronFunction.allowance.rawValue,
parameter: parameter,
visible: true
)
return .requestJSONEncodable(request)

}
}

Expand Down
35 changes: 23 additions & 12 deletions BlockchainSdk/Blockchains/Tron/TronTransactionBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,31 @@ class TronTransactionBuilder {
return try transaction.serializedData()
}

func buildContractEnergyUsageParameter(amount: Amount, destinationAddress: String) throws -> String {
let addressData = try utils.convertAddressToBytes(destinationAddress).leadingZeroPadding(toLength: 32)
func buildContractEnergyUsageData(amount: Amount, destinationAddress: String) throws -> String {
let addressData = try utils.convertAddressToBytesPadded(destinationAddress)
let amountData = try utils.convertAmountPadded(amount)

guard let amountData = amount.encoded?.leadingZeroPadding(toLength: 32) else {
throw WalletError.failedToGetFee
}
let data = (addressData + amountData).hexString.lowercased()
return data
}

// MARK: - Transaction data builder

func buildForApprove(spender: String, amount: Amount) throws -> Data {
let spenderData = try utils.convertAddressToBytesPadded(spender)
let amountData = try utils.convertAmountPadded(amount)

return spenderData + amountData
}

let parameter = (addressData + amountData).hexString.lowercased()
return parameter
func buildForAllowance(owner: String, spender: String) throws -> String {
let ownerAddress = try TronUtils().convertAddressToBytesPadded(owner)
let spenderAddress = try TronUtils().convertAddressToBytesPadded(spender)
return (ownerAddress + spenderAddress).hexString.lowercased()
}

// MARK: - Private

private func contract(transaction: Transaction) throws -> Protocol_Transaction.Contract {
let amount = transaction.amount
let sourceAddress = transaction.sourceAddress
Expand Down Expand Up @@ -121,11 +135,8 @@ class TronTransactionBuilder {
}

private func buildTransferContractData(amount: Amount, destinationAddress: String) throws -> Data {
guard let amountData = amount.encoded?.leadingZeroPadding(toLength: 32) else {
throw WalletError.failedToBuildTx
}

let destinationData = try utils.convertAddressToBytes(destinationAddress).leadingZeroPadding(toLength: 32)
let amountData = try utils.convertAmountPadded(amount)
let destinationData = try utils.convertAddressToBytesPadded(destinationAddress)

let contractData = TronFunction.transfer.prefix + destinationData + amountData
return contractData
Expand Down
13 changes: 13 additions & 0 deletions BlockchainSdk/Blockchains/Tron/TronTransactionDataBuilder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// TronTransactionDataBuilder.swift
// BlockchainSdk
//
// Created by Alexander Osokin on 13.08.2024.
// Copyright © 2024 Tangem AG. All rights reserved.
//

import Foundation

public protocol TronTransactionDataBuilder {
func buildForApprove(spender: String, amount: Amount) throws -> Data
}
Loading

0 comments on commit 6e570e6

Please sign in to comment.