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

Commit

Permalink
Merge pull request #394 from jopmiddelkamp/feature/fee_bump_result_su…
Browse files Browse the repository at this point in the history
…pport

Fix for missing fee bump result (issue 388)
  • Loading branch information
elucidsoft authored Jun 20, 2023
2 parents 529722a + 2401341 commit 6972ad6
Show file tree
Hide file tree
Showing 7 changed files with 299 additions and 13 deletions.
213 changes: 213 additions & 0 deletions stellar-dotnet-sdk-test/operations/FeeBumpOperationTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using stellar_dotnet_sdk;
using xdrSDK = stellar_dotnet_sdk.xdr;
using System.Threading.Tasks;
using stellar_dotnet_sdk.responses;
using stellar_dotnet_sdk.responses.results;

namespace stellar_dotnet_sdk_test.operations;

[TestClass]
public class FeeBumpOperationTest
{
[TestMethod]
public async Task TransactionResultSuccess()
{
Network.UseTestNetwork();
using var server = new Server("https://horizon-testnet.stellar.org");

var sourceKeyPair = KeyPair.Random();
var sponsorKeyPair = KeyPair.Random();

await Task.WhenAll(
server.TestNetFriendBot
.FundAccount(sourceKeyPair.AccountId)
.Execute(),
server.TestNetFriendBot
.FundAccount(sponsorKeyPair.AccountId)
.Execute()
);

var sourceAccount = await server.Accounts.Account(sourceKeyPair.AccountId);

var transactionBuilder = new TransactionBuilder(sourceAccount);

var paymentOperationBuilder = new PaymentOperation.Builder(
sponsorKeyPair,
new AssetTypeNative(),
"10"
);
transactionBuilder.AddOperation(
paymentOperationBuilder.Build()
);
var transaction = transactionBuilder.Build();
transaction.Sign(sourceKeyPair);

var feeBumpTransaction = TransactionBuilder.BuildFeeBumpTransaction(
sponsorKeyPair,
transaction
);
feeBumpTransaction.Sign(sponsorKeyPair);

var response = await server.SubmitTransaction(feeBumpTransaction);

Assert.IsTrue(response.IsSuccess());
Assert.IsFalse(string.IsNullOrEmpty(response.Hash));
Assert.IsInstanceOfType(response.Result, typeof(FeeBumpTransactionResultSuccess));
}

[TestMethod]
public async Task TransactionResultFailed()
{
Network.UseTestNetwork();
using var server = new Server("https://horizon-testnet.stellar.org");

var sourceKeyPair = KeyPair.Random();
var sponsorKeyPair = KeyPair.Random();

await Task.WhenAll(
server.TestNetFriendBot
.FundAccount(sourceKeyPair.AccountId)
.Execute(),
server.TestNetFriendBot
.FundAccount(sponsorKeyPair.AccountId)
.Execute()
);

var sourceAccount = await server.Accounts.Account(sourceKeyPair.AccountId);

var transactionBuilder = new TransactionBuilder(sourceAccount);

var paymentOperationBuilder = new PaymentOperation.Builder(
sponsorKeyPair,
new AssetTypeNative(),
"100000"
);
transactionBuilder.AddOperation(
paymentOperationBuilder.Build()
);
var transaction = transactionBuilder.Build();
transaction.Sign(sourceKeyPair);

var feeBumpTransaction = TransactionBuilder.BuildFeeBumpTransaction(
sponsorKeyPair,
transaction
);
feeBumpTransaction.Sign(sponsorKeyPair);

var response = await server.SubmitTransaction(feeBumpTransaction);

Assert.IsFalse(response.IsSuccess());

var result = response.Result as FeeBumpTransactionResultFailed;
Assert.IsNotNull(result);

var innerResult = result.InnerResultPair.Result as TransactionResultFailed;
Assert.IsNotNull(innerResult);
Assert.IsInstanceOfType(innerResult.Results[0], typeof(PaymentUnderfunded));
}

[TestMethod]
public async Task InsufficientFee()
{
Network.UseTestNetwork();
using var server = new Server("https://horizon-testnet.stellar.org");

var sourceKeyPair = KeyPair.Random();
var sponsorKeyPair = KeyPair.Random();

await Task.WhenAll(
server.TestNetFriendBot
.FundAccount(sourceKeyPair.AccountId)
.Execute(),
server.TestNetFriendBot
.FundAccount(sponsorKeyPair.AccountId)
.Execute()
);

var sourceAccount = await server.Accounts.Account(sourceKeyPair.AccountId);

const uint maxFee = 2000000u;

var transactionBuilder = new TransactionBuilder(sourceAccount);
transactionBuilder.SetFee(maxFee);

var paymentOperationBuilder = new PaymentOperation.Builder(
sponsorKeyPair,
new AssetTypeNative(),
"1000"
);
transactionBuilder.AddOperation(
paymentOperationBuilder.Build()
);
transactionBuilder.AddOperation(
paymentOperationBuilder.Build()
);
var transaction = transactionBuilder.Build();
transaction.Sign(sourceKeyPair);

var exception = Assert.ThrowsException<Exception>(() => {
TransactionBuilder.BuildFeeBumpTransaction(
sponsorKeyPair,
transaction,
maxFee / 2
);
});

Assert.AreEqual($"Invalid fee, it should be at least {maxFee} stroops", exception.Message);
}

[TestMethod]
public async Task SufficientFee()
{
Network.UseTestNetwork();
using var server = new Server("https://horizon-testnet.stellar.org");

var sourceKeyPair = KeyPair.Random();
var sponsorKeyPair = KeyPair.Random();

await Task.WhenAll(
server.TestNetFriendBot
.FundAccount(sourceKeyPair.AccountId)
.Execute(),
server.TestNetFriendBot
.FundAccount(sponsorKeyPair.AccountId)
.Execute()
);

var sourceAccount = await server.Accounts.Account(sourceKeyPair.AccountId);

const uint maxFee = 2000000u;

var transactionBuilder = new TransactionBuilder(sourceAccount);
transactionBuilder.SetFee(maxFee);

var paymentOperationBuilder = new PaymentOperation.Builder(
sponsorKeyPair,
new AssetTypeNative(),
"1000"
);
transactionBuilder.AddOperation(
paymentOperationBuilder.Build()
);
transactionBuilder.AddOperation(
paymentOperationBuilder.Build()
);
var transaction = transactionBuilder.Build();
transaction.Sign(sourceKeyPair);

var feeBumpTransaction = TransactionBuilder.BuildFeeBumpTransaction(
sponsorKeyPair,
transaction,
maxFee
);
feeBumpTransaction.Sign(sponsorKeyPair);

var response = await server.SubmitTransaction(feeBumpTransaction);

Assert.IsTrue(response.IsSuccess());
Assert.IsFalse(string.IsNullOrEmpty(response.Hash));
Assert.IsInstanceOfType(response.Result, typeof(FeeBumpTransactionResultSuccess));
}
}
29 changes: 18 additions & 11 deletions stellar-dotnet-sdk/TransactionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,33 @@ public TransactionBuilder(ITransactionBuilderAccount sourceAccount)
_preconditions = new TransactionPreconditions();
}

public static FeeBumpTransaction BuildFeeBumpTransaction(IAccountId feeSource, Transaction inner, long fee)
public static FeeBumpTransaction BuildFeeBumpTransaction(IAccountId feeSource, Transaction inner)
{
long innerOps = inner.Operations.Length;
long innerBaseFeeRate = inner.Fee;
if (innerOps > 0)
if (inner.Operations.Length == 0)
{
innerBaseFeeRate = innerBaseFeeRate / innerOps;
throw new Exception("Invalid fee bump transaction, it should contain at least one operation");
}

long innerFee = inner.Fee / inner.Operations.Length;
var feeBumpFee = checked(innerFee * (inner.Operations.Length + 1));

return new FeeBumpTransaction(feeSource, inner, feeBumpFee);
}

if (fee < innerBaseFeeRate)
public static FeeBumpTransaction BuildFeeBumpTransaction(IAccountId feeSource, Transaction inner, long fee)
{
if (inner.Operations.Length == 0)
{
throw new Exception($"Invalid fee, it should be at least {innerBaseFeeRate} stroops");
throw new Exception("Invalid fee bump transaction, it should contain at least one operation");
}

if (fee < BaseFee)

long innerFee = inner.Fee / inner.Operations.Length;
if (fee < innerFee)
{
throw new Exception($"Invalid fee, it should be at least {BaseFee} stroops");
throw new Exception($"Invalid fee, it should be at least {innerFee} stroops");
}

var feeBumpFee = checked(fee * (innerOps + 1));
var feeBumpFee = checked(fee * (inner.Operations.Length + 1));

return new FeeBumpTransaction(feeSource, inner, feeBumpFee);
}
Expand Down
11 changes: 11 additions & 0 deletions stellar-dotnet-sdk/responses/FeeBumpTransactionResultFailed.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace stellar_dotnet_sdk.responses;

/// <summary>
/// One of the operations in the inner transaction failed (none were applied).
/// </summary>
public class FeeBumpTransactionResultFailed : TransactionResult
{
public override bool IsSuccess => false;

public InnerTransactionResultPair InnerResultPair { get; set; }
}
11 changes: 11 additions & 0 deletions stellar-dotnet-sdk/responses/FeeBumpTransactionResultSuccess.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace stellar_dotnet_sdk.responses;

/// <summary>
/// All operations in the inner transaction succeeded.
/// </summary>
public class FeeBumpTransactionResultSuccess : TransactionResult
{
public override bool IsSuccess => true;

public InnerTransactionResultPair InnerResultPair { get; set; }
}
31 changes: 31 additions & 0 deletions stellar-dotnet-sdk/responses/InnerTransactionResultPair.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using stellar_dotnet_sdk.xdr;

namespace stellar_dotnet_sdk.responses;

public class InnerTransactionResultPair
{
public string TransactionHash { get; set; }

public TransactionResult Result { get; set; }

public static InnerTransactionResultPair FromXdr(string encoded)
{
byte[] bytes = Convert.FromBase64String(encoded);
var result = xdr.InnerTransactionResultPair.Decode(new xdr.XdrDataInputStream(bytes));
return FromXdr(result);
}

public static InnerTransactionResultPair FromXdr(xdr.InnerTransactionResultPair result)
{
var writer = new XdrDataOutputStream();
xdr.InnerTransactionResult.Encode(writer, result.Result);
var xdrTransaction = Convert.ToBase64String(writer.ToArray());

return new InnerTransactionResultPair
{
TransactionHash = Convert.ToBase64String(result.TransactionHash.InnerValue),
Result = TransactionResult.FromXdr(xdrTransaction),
};
}
}
15 changes: 14 additions & 1 deletion stellar-dotnet-sdk/responses/TransactionResult.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using stellar_dotnet_sdk.responses.results;
using stellar_dotnet_sdk.xdr;
using OperationResult = stellar_dotnet_sdk.responses.results.OperationResult;

namespace stellar_dotnet_sdk.responses
{
Expand Down Expand Up @@ -89,6 +90,18 @@ public static TransactionResult FromXdr(xdr.TransactionResult result)
{
FeeCharged = feeCharged
};
case xdr.TransactionResultCode.TransactionResultCodeEnum.txFEE_BUMP_INNER_SUCCESS:
return new FeeBumpTransactionResultSuccess
{
FeeCharged = feeCharged,
InnerResultPair = InnerTransactionResultPair.FromXdr(result.Result.InnerResultPair),
};
case xdr.TransactionResultCode.TransactionResultCodeEnum.txFEE_BUMP_INNER_FAILED:
return new FeeBumpTransactionResultFailed
{
FeeCharged = feeCharged,
InnerResultPair = InnerTransactionResultPair.FromXdr(result.Result.InnerResultPair),
};
default:
throw new SystemException("Unknown TransactionResult type");
}
Expand Down
2 changes: 1 addition & 1 deletion stellar-dotnet-sdk/responses/TransactionResultSuccess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace stellar_dotnet_sdk.responses
{
/// <summary>
/// All operations succeded.
/// All operations succeeded.
/// </summary>
public class TransactionResultSuccess : TransactionResult
{
Expand Down

0 comments on commit 6972ad6

Please sign in to comment.