diff --git a/Sources/KukaiCoreSwift/Models/NetworkConstants.swift b/Sources/KukaiCoreSwift/Models/NetworkConstants.swift index c2270bba..bd919476 100644 --- a/Sources/KukaiCoreSwift/Models/NetworkConstants.swift +++ b/Sources/KukaiCoreSwift/Models/NetworkConstants.swift @@ -34,7 +34,7 @@ public struct NetworkConstants: Codable { } public func maxGasPerBlock() -> Int { - return Int(hard_gas_limit_per_block) ?? 2600000 + return Int(hard_gas_limit_per_block) ?? 1733333 } public func maxStoragePerOperation() -> Int { diff --git a/Sources/KukaiCoreSwift/Models/Operation/OperationResponse.swift b/Sources/KukaiCoreSwift/Models/Operation/OperationResponse.swift index d1de0c07..64ce62e5 100644 --- a/Sources/KukaiCoreSwift/Models/Operation/OperationResponse.swift +++ b/Sources/KukaiCoreSwift/Models/Operation/OperationResponse.swift @@ -86,6 +86,12 @@ public struct BalanceUpdate: Codable { let change: String let delegate: String? let cycle: Int? + let staker: BalanceUpdateStake? +} + +public struct BalanceUpdateStake: Codable { + let contract: String + let delegate: String } /// The inner `result` key from the `OeprationResponse` diff --git a/Sources/KukaiCoreSwift/Services/FeeEstimatorService.swift b/Sources/KukaiCoreSwift/Services/FeeEstimatorService.swift index 0ff3f790..cc1e66a5 100644 --- a/Sources/KukaiCoreSwift/Services/FeeEstimatorService.swift +++ b/Sources/KukaiCoreSwift/Services/FeeEstimatorService.swift @@ -95,7 +95,7 @@ public class FeeEstimatorService { // To handle issues with sending max Tez, and simulation API not ignoring burn fees etc // modify the operationPayload contents to send 1 mutez instead of real amount // This won't effect the returned operations later, as we've made a deep copy first and will use that afte rthe estimation - if $0.operationKind == .transaction, let transOp = $0 as? OperationTransaction, (transOp.destination.prefix(3) != "KT1" && ($0 as? OperationTransaction)?.parameters == nil) { + if $0.operationKind == .transaction, let transOp = $0 as? OperationTransaction, (transOp.destination.prefix(3) != "KT1" && transOp.parameters == nil && transOp.destination != walletAddress) { transOp.amount = "1" // rpc representation of 1 mutez } } @@ -236,7 +236,7 @@ public class FeeEstimatorService { } for balanceUpdate in content.metadata.operationResult?.balanceUpdates ?? [] { - if balanceUpdate.contract == address { + if balanceUpdate.contract == address || balanceUpdate.staker?.contract == address { opStorage -= Decimal(string: balanceUpdate.change) ?? 0 } } @@ -261,6 +261,7 @@ public class FeeEstimatorService { opGas = Decimal(FeeEstimatorService.addGasSafetyMarginTo(gasUsed: opGas.intValue())) + // Convert storage to bytes opStorage = opStorage / (constants.xtzPerByte().toRpcDecimal() ?? 250) @@ -275,7 +276,9 @@ public class FeeEstimatorService { } } - // Check whether suggested or estimated gas / sotrage is higher and pick that + + + // Check whether suggested or estimated gas / storage is higher and pick that let indexToCheck = index + suggestedCompareIndex if indexToCheck > -1 { let op = originalRemoteOps[indexToCheck] @@ -290,6 +293,8 @@ public class FeeEstimatorService { } + + // Sum totals for later totalGas += opGas totalStorage += opStorage diff --git a/Tests/KukaiCoreSwiftTests/MockConstants.swift b/Tests/KukaiCoreSwiftTests/MockConstants.swift index 72d88d3d..03139954 100644 --- a/Tests/KukaiCoreSwiftTests/MockConstants.swift +++ b/Tests/KukaiCoreSwiftTests/MockConstants.swift @@ -253,6 +253,10 @@ public struct MockConstants { (MockConstants.jsonStub(fromFilename: "simulate_operation-crunchy-swap-response"), MockConstants.http200), MockPostUrlKey(url: simulateURL1, requestData: MockConstants.jsonStub(fromFilename: "simulate_operation-high-gas-low-storage-request")): (MockConstants.jsonStub(fromFilename: "simulate_operation-high-gas-low-storage-response"), MockConstants.http200), + MockPostUrlKey(url: simulateURL1, requestData: MockConstants.jsonStub(fromFilename: "simulate_operation-stake-request")): + (MockConstants.jsonStub(fromFilename: "simulate_operation-stake-response"), MockConstants.http200), + MockPostUrlKey(url: simulateURL1, requestData: MockConstants.jsonStub(fromFilename: "simulate_operation-unstake-request")): + (MockConstants.jsonStub(fromFilename: "simulate_operation-unstake-response"), MockConstants.http200), ] config.urlSession = mockURLSession diff --git a/Tests/KukaiCoreSwiftTests/Services/FeeEstimatorServiceTests.swift b/Tests/KukaiCoreSwiftTests/Services/FeeEstimatorServiceTests.swift index d79d7bfd..1bee0bc4 100644 --- a/Tests/KukaiCoreSwiftTests/Services/FeeEstimatorServiceTests.swift +++ b/Tests/KukaiCoreSwiftTests/Services/FeeEstimatorServiceTests.swift @@ -161,8 +161,6 @@ class FeeEstimatorServiceTests: XCTestCase { wait(for: [expectation1], timeout: 120) } - // TODO: add teset with and without reveal - // Test that if a dApp suggests a high gas amount, but a low storage amount. That we take the gas, but use our own higher estimated storage func testJSONPayload3() { let decoder = JSONDecoder() @@ -206,4 +204,80 @@ class FeeEstimatorServiceTests: XCTestCase { wait(for: [expectation1], timeout: 120) } + + // Test stake + func testJSONPayload4() { + let decoder = JSONDecoder() + + let jsonDataRequest1 = MockConstants.jsonStub(fromFilename: "simulate_operation-stake-operations") + let jsonRequestOps1 = (try? decoder.decode([OperationTransaction].self, from: jsonDataRequest1)) ?? [] + XCTAssert(jsonRequestOps1.count != 0) + + let expectation1 = XCTestExpectation(description: "Estimation service") + let address = MockConstants.defaultHdWallet.address + let key = MockConstants.defaultHdWallet.publicKeyBase58encoded() + estimationService.estimate(operations: jsonRequestOps1, operationMetadata: MockConstants.operationMetadata, constants: MockConstants.networkConstants, walletAddress: address, base58EncodedPublicKey: key) { result in + switch result { + case .success(let result): + XCTAssert(result.operations.count == 1, result.operations.count.description) + XCTAssert(result.operations[0].operationFees.gasLimit == 3703, result.operations[0].operationFees.gasLimit.description) + XCTAssert(result.operations[0].operationFees.storageLimit == 2, result.operations[0].operationFees.storageLimit.description) + XCTAssert(result.operations[0].operationFees.allFees().normalisedRepresentation == "0.001144", result.operations[0].operationFees.allFees().normalisedRepresentation) + + let totalGas = result.operations.map({ $0.operationFees.gasLimit }).reduce(0, +) + XCTAssert(totalGas == 3703, totalGas.description) + + let totalStorage = result.operations.map({ $0.operationFees.storageLimit }).reduce(0, +) + XCTAssert(totalStorage == 2, totalStorage.description) + + let totalFee = result.operations.map({ $0.operationFees.allFees() }).reduce(XTZAmount.zero(), +) + XCTAssert(totalFee.normalisedRepresentation == "0.001144", totalFee.normalisedRepresentation) + + case .failure(let error): + XCTFail(error.description) + } + + expectation1.fulfill() + } + + wait(for: [expectation1], timeout: 120) + } + + // Test unstake + func testJSONPayload5() { + let decoder = JSONDecoder() + + let jsonDataRequest1 = MockConstants.jsonStub(fromFilename: "simulate_operation-unstake-operations") + let jsonRequestOps1 = (try? decoder.decode([OperationTransaction].self, from: jsonDataRequest1)) ?? [] + XCTAssert(jsonRequestOps1.count != 0) + + let expectation1 = XCTestExpectation(description: "Estimation service") + let address = MockConstants.defaultHdWallet.address + let key = MockConstants.defaultHdWallet.publicKeyBase58encoded() + estimationService.estimate(operations: jsonRequestOps1, operationMetadata: MockConstants.operationMetadata, constants: MockConstants.networkConstants, walletAddress: address, base58EncodedPublicKey: key) { result in + switch result { + case .success(let result): + XCTAssert(result.operations.count == 1, result.operations.count.description) + XCTAssert(result.operations[0].operationFees.gasLimit == 4335, result.operations[0].operationFees.gasLimit.description) + XCTAssert(result.operations[0].operationFees.storageLimit == 0, result.operations[0].operationFees.storageLimit.description) + XCTAssert(result.operations[0].operationFees.allFees().normalisedRepresentation == "0.000704", result.operations[0].operationFees.allFees().normalisedRepresentation) + + let totalGas = result.operations.map({ $0.operationFees.gasLimit }).reduce(0, +) + XCTAssert(totalGas == 4335, totalGas.description) + + let totalStorage = result.operations.map({ $0.operationFees.storageLimit }).reduce(0, +) + XCTAssert(totalStorage == 0, totalStorage.description) + + let totalFee = result.operations.map({ $0.operationFees.allFees() }).reduce(XTZAmount.zero(), +) + XCTAssert(totalFee.normalisedRepresentation == "0.000704", totalFee.normalisedRepresentation) + + case .failure(let error): + XCTFail(error.description) + } + + expectation1.fulfill() + } + + wait(for: [expectation1], timeout: 120) + } } diff --git a/Tests/KukaiCoreSwiftTests/Stubs/simulate_operation-stake-operations.json b/Tests/KukaiCoreSwiftTests/Stubs/simulate_operation-stake-operations.json new file mode 100644 index 00000000..a94855ce --- /dev/null +++ b/Tests/KukaiCoreSwiftTests/Stubs/simulate_operation-stake-operations.json @@ -0,0 +1,15 @@ +[ + { + "amount": "100000000", + "counter": "10534537", + "destination": "tz1Ue76bLW7boAcJEZf2kSGcamdBKVi4Kpss", + "kind": "transaction", + "parameters": { + "entrypoint": "stake", + "value": { + "prim": "Unit" + } + }, + "source": "tz1Ue76bLW7boAcJEZf2kSGcamdBKVi4Kpss" + } +] \ No newline at end of file diff --git a/Tests/KukaiCoreSwiftTests/Stubs/simulate_operation-stake-request.json b/Tests/KukaiCoreSwiftTests/Stubs/simulate_operation-stake-request.json new file mode 100644 index 00000000..be23b294 --- /dev/null +++ b/Tests/KukaiCoreSwiftTests/Stubs/simulate_operation-stake-request.json @@ -0,0 +1 @@ +{"chain_id":"NetXxkAx4woPLyu","operation":{"branch":"BLEDGNuADAwZfKK7iZ6PHnu7gZFSXuRPVFXe2PhSnb6aMyKn3mK","contents":[{"amount":"100000000","counter":"143231","destination":"tz1Ue76bLW7boAcJEZf2kSGcamdBKVi4Kpss","fee":"0","gas_limit":"5200000","kind":"transaction","parameters":{"entrypoint":"stake","value":{"prim":"Unit"}},"source":"tz1Ue76bLW7boAcJEZf2kSGcamdBKVi4Kpss","storage_limit":"60000"}],"signature":"edsigtXomBKi5CTRf5cjATJWSyaRvhfYNHqSUGrn4SdbYRcGwQrUGjzEfQDTuqHhuA8b2d8NarZjz8TRf65WkpQmo423BtomS8Q"}} \ No newline at end of file diff --git a/Tests/KukaiCoreSwiftTests/Stubs/simulate_operation-stake-response.json b/Tests/KukaiCoreSwiftTests/Stubs/simulate_operation-stake-response.json new file mode 100644 index 00000000..bdea7de4 --- /dev/null +++ b/Tests/KukaiCoreSwiftTests/Stubs/simulate_operation-stake-response.json @@ -0,0 +1,63 @@ +{ + "contents": [ + { + "amount": "100000000", + "fee": "0", + "kind": "transaction", + "source": "tz1Ue76bLW7boAcJEZf2kSGcamdBKVi4Kpss", + "destination": "tz1Ue76bLW7boAcJEZf2kSGcamdBKVi4Kpss", + "metadata": { + "balance_updates": [ + { + "kind": "contract", + "contract": "tz1Ue76bLW7boAcJEZf2kSGcamdBKVi4Kpss", + "change": "-641", + "origin": "block" + }, + { + "kind": "accumulator", + "category": "block fees", + "change": "641", + "origin": "block" + } + ], + "operation_result": { + "status": "applied", + "balance_updates": [ + { + "kind": "staking", + "category": "delegator_numerator", + "delegator": "tz1Ue76bLW7boAcJEZf2kSGcamdBKVi4Kpss", + "change": "92355529", + "origin": "block" + }, + { + "kind": "staking", + "category": "delegate_denominator", + "delegate": "tz1YgDUQV2eXm8pUWNz3S5aWP86iFzNp4jnD", + "change": "92355529", + "origin": "block" + }, + { + "kind": "contract", + "contract": "tz1Ue76bLW7boAcJEZf2kSGcamdBKVi4Kpss", + "change": "-100000000", + "origin": "block" + }, + { + "kind": "freezer", + "category": "deposits", + "staker": { + "contract": "tz1Ue76bLW7boAcJEZf2kSGcamdBKVi4Kpss", + "delegate": "tz1YgDUQV2eXm8pUWNz3S5aWP86iFzNp4jnD" + }, + "change": "100000000", + "origin": "block" + } + ], + "consumed_milligas": "3629053" + } + } + } + ] +} \ No newline at end of file diff --git a/Tests/KukaiCoreSwiftTests/Stubs/simulate_operation-unstake-operations.json b/Tests/KukaiCoreSwiftTests/Stubs/simulate_operation-unstake-operations.json new file mode 100644 index 00000000..887d7ce2 --- /dev/null +++ b/Tests/KukaiCoreSwiftTests/Stubs/simulate_operation-unstake-operations.json @@ -0,0 +1,14 @@ +[ + { + "kind": "transaction", + "source": "tz1Ue76bLW7boAcJEZf2kSGcamdBKVi4Kpss", + "amount": "1", + "destination": "tz1Ue76bLW7boAcJEZf2kSGcamdBKVi4Kpss", + "parameters": { + "entrypoint": "unstake", + "value": { + "prim": "Unit" + } + } + } +] \ No newline at end of file diff --git a/Tests/KukaiCoreSwiftTests/Stubs/simulate_operation-unstake-request.json b/Tests/KukaiCoreSwiftTests/Stubs/simulate_operation-unstake-request.json new file mode 100644 index 00000000..5bed6570 --- /dev/null +++ b/Tests/KukaiCoreSwiftTests/Stubs/simulate_operation-unstake-request.json @@ -0,0 +1 @@ +{"chain_id":"NetXxkAx4woPLyu","operation":{"branch":"BLEDGNuADAwZfKK7iZ6PHnu7gZFSXuRPVFXe2PhSnb6aMyKn3mK","contents":[{"amount":"1","counter":"143231","destination":"tz1Ue76bLW7boAcJEZf2kSGcamdBKVi4Kpss","fee":"0","gas_limit":"5200000","kind":"transaction","parameters":{"entrypoint":"unstake","value":{"prim":"Unit"}},"source":"tz1Ue76bLW7boAcJEZf2kSGcamdBKVi4Kpss","storage_limit":"60000"}],"signature":"edsigtXomBKi5CTRf5cjATJWSyaRvhfYNHqSUGrn4SdbYRcGwQrUGjzEfQDTuqHhuA8b2d8NarZjz8TRf65WkpQmo423BtomS8Q"}} \ No newline at end of file diff --git a/Tests/KukaiCoreSwiftTests/Stubs/simulate_operation-unstake-response.json b/Tests/KukaiCoreSwiftTests/Stubs/simulate_operation-unstake-response.json new file mode 100644 index 00000000..ece1a0fd --- /dev/null +++ b/Tests/KukaiCoreSwiftTests/Stubs/simulate_operation-unstake-response.json @@ -0,0 +1,64 @@ +{ + "contents": [ + { + "kind": "transaction", + "source": "tz1Ue76bLW7boAcJEZf2kSGcamdBKVi4Kpss", + "fee": "0", + "counter": "11240021", + "gas_limit": "866666", + "storage_limit": "60000", + "amount": "1", + "destination": "tz1Ue76bLW7boAcJEZf2kSGcamdBKVi4Kpss", + "parameters": { + "entrypoint": "unstake", + "value": { + "prim": "Unit" + } + }, + "metadata": { + "operation_result": { + "status": "applied", + "balance_updates": [ + { + "kind": "staking", + "category": "delegate_denominator", + "delegate": "tz1YgDUQV2eXm8pUWNz3S5aWP86iFzNp4jnD", + "change": "-1", + "origin": "block" + }, + { + "kind": "staking", + "category": "delegator_numerator", + "delegator": "tz1Ue76bLW7boAcJEZf2kSGcamdBKVi4Kpss", + "change": "-1", + "origin": "block" + }, + { + "kind": "freezer", + "category": "deposits", + "staker": { + "contract": "tz1Ue76bLW7boAcJEZf2kSGcamdBKVi4Kpss", + "delegate": "tz1YgDUQV2eXm8pUWNz3S5aWP86iFzNp4jnD" + }, + "change": "-1", + "origin": "block" + }, + { + "kind": "freezer", + "category": "unstaked_deposits", + "staker": { + "contract": "tz1Ue76bLW7boAcJEZf2kSGcamdBKVi4Kpss", + "delegate": "tz1YgDUQV2eXm8pUWNz3S5aWP86iFzNp4jnD" + }, + "cycle": 1064, + "change": "1", + "origin": "block" + } + ], + "consumed_milligas": "4249020" + } + } + } + ], + "signature": "edsigtXomBKi5CTRf5cjATJWSyaRvhfYNHqSUGrn4SdbYRcGwQrUGjzEfQDTuqHhuA8b2d8NarZjz8TRf65WkpQmo423BtomS8Q" +} \ No newline at end of file