Skip to content

Commit

Permalink
IOS-7619 [Staking] MATIC token in Ethereum (#853)
Browse files Browse the repository at this point in the history
  • Loading branch information
Balashov152 authored Oct 4, 2024
1 parent d4fe241 commit ade7eb1
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,62 +12,59 @@ import BigInt

struct EthereumStakeKitTransactionHelper {
private let transactionBuilder: EthereumTransactionBuilder

init(transactionBuilder: EthereumTransactionBuilder) {
self.transactionBuilder = transactionBuilder
}

func prepareForSign(
_ stakingTransaction: StakeKitTransaction
) throws -> Data {
let input = try buildSigningInput(
stakingTransaction: stakingTransaction
)

func prepareForSign(_ stakingTransaction: StakeKitTransaction) throws -> Data {
let input = try buildSigningInput(stakingTransaction: stakingTransaction)
let preSigningOutput = try transactionBuilder.buildTxCompilerPreSigningOutput(input: input)
return preSigningOutput.dataHash
}

func prepareForSend(
stakingTransaction: StakeKitTransaction,
signatureInfo: SignatureInfo
) throws -> Data {
let input = try buildSigningInput(
stakingTransaction: stakingTransaction
)
let input = try buildSigningInput(stakingTransaction: stakingTransaction)
let output = try transactionBuilder.buildSigningOutput(input: input, signatureInfo: signatureInfo)
return output.encoded
}

private func buildSigningInput(
stakingTransaction: StakeKitTransaction
) throws -> EthereumSigningInput {
let compiledTransactionData = Data(hex: stakingTransaction.unsignedData)
let compiledTransaction = try JSONDecoder().decode(
EthereumCompiledTransaction.self,
from: compiledTransactionData
)

guard let amountValue = stakingTransaction.amount.bigUIntValue else {
throw EthereumTransactionBuilderError.invalidAmount
guard let compiledTransactionData = stakingTransaction.unsignedData.data(using: .utf8) else {
throw EthereumTransactionBuilderError.invalidStakingTransaction
}

guard let gasLimit = BigUInt(compiledTransaction.gasLimit, radix: 16),
let baseFee = BigUInt(compiledTransaction.maxFeePerGas, radix: 16),
let priorityFee = BigUInt(compiledTransaction.maxPriorityFeePerGas, radix: 16) else {

let compiledTransaction = try JSONDecoder()
.decode(EthereumCompiledTransaction.self, from: compiledTransactionData)

let gasLimit = BigUInt(Data(hex: compiledTransaction.gasLimit))
let baseFee = BigUInt(Data(hex: compiledTransaction.maxFeePerGas))
let priorityFee = BigUInt(Data(hex: compiledTransaction.maxPriorityFeePerGas))

guard gasLimit > 0, baseFee > 0, priorityFee > 0 else {
throw EthereumTransactionBuilderError.feeParametersNotFound
}


let data = Data(hex: compiledTransaction.data)

guard !data.isEmpty else {
throw EthereumTransactionBuilderError.invalidStakingTransaction
}

return try transactionBuilder.buildSigningInput(
// TODO: refactor, 'user' field is used only in erc20Transfer, consider moving elsewhere?
destination: .contract(user: "", contract: compiledTransaction.to, value: amountValue),
destination: compiledTransaction.to,
coinAmount: .zero,
fee: Fee(
stakingTransaction.fee.amount,
parameters: EthereumEIP1559FeeParameters(gasLimit: gasLimit, baseFee: baseFee, priorityFee: priorityFee)
),
parameters: EthereumTransactionParams(
data: Data(hex: compiledTransaction.data),
nonce: compiledTransaction.nonce
)
nonce: compiledTransaction.nonce,
data: data
)
}
}
Expand Down
107 changes: 53 additions & 54 deletions BlockchainSdk/Blockchains/Ethereum/EthereumTransactionBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,24 @@ class EthereumTransactionBuilder {
switch fee.amount.type {
case .coin:
let input = try buildSigningInput(
destination: .user(user: destination, value: valueData),
destination: destination,
coinAmount: valueData,
fee: fee,
// The nonce for the dummy transaction won't be used later, so we can just mock it with any value
parameters: EthereumTransactionParams(data: data, nonce: 1)
nonce: 1,
data: data
)
return try buildTxCompilerPreSigningOutput(input: input).data

case .token(let token):
let data = TransferERC20TokenMethod(destination: destination, amount: valueData).data
let input = try buildSigningInput(
destination: .contract(user: destination, contract: token.contractAddress, value: valueData),
destination: token.contractAddress,
coinAmount: .zero,
fee: fee,
// The nonce for the dummy transaction won't be used later, so we can just mock it with any value
parameters: EthereumTransactionParams(data: data, nonce: 1)
nonce: 1,
data: data
)

return try buildTxCompilerPreSigningOutput(input: input).data
Expand Down Expand Up @@ -78,19 +83,28 @@ class EthereumTransactionBuilder {
return method.data
}

func buildSigningInput(destination: DestinationType, fee: Fee, parameters: EthereumTransactionParams) throws -> EthereumSigningInput {
guard let nonce = parameters.nonce, nonce >= 0 else {
func buildSigningInput(destination: String, coinAmount: BigUInt, fee: Fee, nonce: Int, data: Data?) throws -> EthereumSigningInput {
guard nonce >= 0 else {
throw EthereumTransactionBuilderError.invalidNonce
}

let nonceValue = BigUInt(nonce)

guard let feeParameters = fee.parameters as? EthereumFeeParameters else {
throw EthereumTransactionBuilderError.feeParametersNotFound
}

let input = try EthereumSigningInput.with { input in
input.chainID = BigUInt(chainId).serialize()
input.nonce = nonceValue.serialize()

guard let feeParameters = fee.parameters as? EthereumFeeParameters else {
throw EthereumTransactionBuilderError.feeParametersNotFound
input.nonce = BigUInt(nonce).serialize()
input.toAddress = destination

input.transaction = .with { transaction in
input.toAddress = destination
transaction.contractGeneric = .with {
$0.amount = coinAmount.serialize()
if let data {
$0.data = data
}
}
}

switch feeParameters.parametersType {
Expand All @@ -105,35 +119,6 @@ class EthereumTransactionBuilder {
input.gasLimit = legacyParameters.gasLimit.serialize()
input.gasPrice = legacyParameters.gasPrice.serialize()
}

input.transaction = .with {
switch destination {
case .user(let user, let value):
input.toAddress = user
$0.transfer = .with {
$0.amount = value.serialize()
if let data = parameters.data {
$0.data = data
}
}
case .contract(let user, let contract, let value):
input.toAddress = contract
let amount = value.serialize()

if let data = parameters.data {
$0.contractGeneric = .with {
$0.amount = amount
$0.data = data
}
} else {
// Fallback to plain transfer if there is no payload available
$0.erc20Transfer = .with {
$0.amount = amount
$0.to = user
}
}
}
}
}

return input
Expand Down Expand Up @@ -209,52 +194,64 @@ private extension EthereumTransactionBuilder {
throw EthereumTransactionBuilderError.invalidAmount
}

guard let parameters = transaction.params as? EthereumTransactionParams else {
throw EthereumTransactionBuilderError.transactionParamsNotFound
}

guard let nonce = parameters.nonce else {
throw EthereumTransactionBuilderError.invalidNonce
}

switch transaction.amount.type {
case .coin:
return try buildSigningInput(
destination: .user(user: transaction.destinationAddress, value: amountValue),
destination: transaction.destinationAddress,
coinAmount: amountValue,
fee: transaction.fee,
parameters: transaction.params as? EthereumTransactionParams ?? .empty
nonce: nonce,
data: parameters.data
)
case .token(let token):
let contract = transaction.contractAddress ?? token.contractAddress
let method = TransferERC20TokenMethod(destination: transaction.destinationAddress, amount: amountValue)
let data = parameters.data ?? method.data

return try buildSigningInput(
destination: .contract(
user: transaction.destinationAddress,
contract: transaction.contractAddress ?? token.contractAddress,
value: amountValue
),
destination: contract,
coinAmount: .zero,
fee: transaction.fee,
parameters: transaction.params as? EthereumTransactionParams ?? .empty
nonce: nonce,
data: data
)

case .reserve, .feeResource:
throw BlockchainSdkError.notImplemented
}
}
}

extension EthereumTransactionBuilder {
enum DestinationType: Hashable {
case user(user: String, value: BigUInt)
case contract(user: String, contract: String, value: BigUInt)
}

private enum Constants {
static let signatureSize = 64
}
}

enum EthereumTransactionBuilderError: LocalizedError {
case feeParametersNotFound
case transactionParamsNotFound
case invalidSignatureCount
case invalidAmount
case invalidNonce
case transactionEncodingFailed
case walletCoreError(message: String)
case invalidStakingTransaction

public var errorDescription: String? {
switch self {
case .feeParametersNotFound:
return "feeParametersNotFound"
case .transactionParamsNotFound:
return "transactionParamsNotFound"
case .invalidAmount:
return "invalidAmount"
case .invalidNonce:
Expand All @@ -265,6 +262,8 @@ enum EthereumTransactionBuilderError: LocalizedError {
return "transactionEncodingFailed"
case .walletCoreError(let message):
return "walletCoreError: \(message)"
case .invalidStakingTransaction:
return "invalidStakingTransaction"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,4 @@ extension EthereumTransactionParams {
func with(nonce: Int) -> EthereumTransactionParams {
EthereumTransactionParams(data: data, nonce: nonce)
}

static var empty: EthereumTransactionParams {
EthereumTransactionParams()
}
}

0 comments on commit ade7eb1

Please sign in to comment.