Skip to content

Commit 30fa28f

Browse files
authored
Merge pull request #497 from AltudePlatform/Stakepool-program
Stakepool program
2 parents 2f2019b + da1be54 commit 30fa28f

20 files changed

+5329
-1
lines changed

src/Solnet.Programs/Solnet.Programs.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,9 @@
1313
<Folder Include="Models\TokenSwap\" />
1414
</ItemGroup>
1515

16+
<ItemGroup>
17+
<PackageReference Include="System.Runtime.Extensions" Version="4.3.1" />
18+
</ItemGroup>
19+
1620
<Import Project="..\..\SharedBuildProperties.props" />
1721
</Project>

src/Solnet.Programs/Stake/StakeProgram.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public static class StakeProgram
2828
/// <summary>
2929
/// Stake Config ID
3030
/// </summary>
31-
public static readonly PublicKey ConfigKey = new("StakeConfig11111111111111111111111111111111");
31+
public static readonly PublicKey ConfigKey = new("StakeConfig11111111111111111111111111111111");
3232
/// <summary>
3333
/// The program's name.
3434
/// </summary>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace Solnet.Programs.StakePool.Models
8+
{
9+
/// <summary>
10+
/// Enum representing the account type managed by the program.
11+
/// </summary>
12+
public enum AccountType : byte
13+
{
14+
/// <summary>
15+
/// If the account has not been initialized, the enum will be 0.
16+
/// </summary>
17+
Uninitialized = 0,
18+
19+
/// <summary>
20+
/// Stake pool.
21+
/// </summary>
22+
StakePool = 1,
23+
24+
/// <summary>
25+
/// Validator stake list.
26+
/// </summary>
27+
ValidatorList = 2
28+
}
29+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
using System.Numerics;
2+
3+
namespace Solnet.Programs.StakePool.Models
4+
{
5+
/// <summary>
6+
/// Fee rate as a ratio, minted on UpdateStakePoolBalance as a proportion of the rewards.
7+
/// If either the numerator or the denominator is 0, the fee is considered to be 0.
8+
/// </summary>
9+
public class Fee
10+
{
11+
/// <summary>
12+
/// Denominator of the fee ratio.
13+
/// </summary>
14+
public ulong Denominator { get; set; }
15+
16+
/// <summary>
17+
/// Numerator of the fee ratio.
18+
/// </summary>
19+
public ulong Numerator { get; set; }
20+
21+
/// <summary>
22+
/// Returns true if the fee is considered zero (either numerator or denominator is zero).
23+
/// </summary>
24+
public bool IsZero => Denominator == 0 || Numerator == 0;
25+
26+
/// <summary>
27+
/// Initializes a new instance of the <see cref="Fee"/> class.
28+
/// </summary>
29+
public Fee() { }
30+
31+
/// <summary>
32+
/// Initializes a new instance of the <see cref="Fee"/> class with the specified numerator and denominator.
33+
/// </summary>
34+
/// <param name="numerator"></param>
35+
/// <param name="denominator"></param>
36+
public Fee(ulong numerator, ulong denominator)
37+
{
38+
Numerator = numerator;
39+
Denominator = denominator;
40+
}
41+
42+
/// <summary>
43+
/// Applies the fee's rates to a given amount, returning the amount to be subtracted as fees.
44+
/// Returns 0 if denominator is 0 or amount is 0, or null if overflow occurs.
45+
/// </summary>
46+
public ulong? Apply(ulong amount)
47+
{
48+
if (Denominator == 0 || amount == 0)
49+
return 0;
50+
51+
try
52+
{
53+
// Use BigInteger to avoid overflow
54+
BigInteger amt = new BigInteger(amount);
55+
BigInteger numerator = new BigInteger(Numerator);
56+
BigInteger denominator = new BigInteger(Denominator);
57+
58+
BigInteger feeNumerator = amt * numerator;
59+
// Ceiling division: (feeNumerator + denominator - 1) / denominator
60+
BigInteger result = (feeNumerator + denominator - 1) / denominator;
61+
62+
if (result < 0 || result > ulong.MaxValue)
63+
return null;
64+
65+
return (ulong)result;
66+
}
67+
catch
68+
{
69+
return null;
70+
}
71+
}
72+
73+
/// <summary>
74+
/// Checks withdrawal fee restrictions, throws StakePoolFeeException if not met.
75+
/// </summary>
76+
public void CheckWithdrawal(Fee oldWithdrawalFee)
77+
{
78+
// Constants as per SPL Stake Pool program
79+
var WITHDRAWAL_BASELINE_FEE = new Fee(1, 1000); // 0.1%
80+
var MAX_WITHDRAWAL_FEE_INCREASE = new Fee(3, 2); // 1.5x
81+
82+
ulong oldNum, oldDenom;
83+
if (oldWithdrawalFee.Denominator == 0 || oldWithdrawalFee.Numerator == 0)
84+
{
85+
oldNum = WITHDRAWAL_BASELINE_FEE.Numerator;
86+
oldDenom = WITHDRAWAL_BASELINE_FEE.Denominator;
87+
}
88+
else
89+
{
90+
oldNum = oldWithdrawalFee.Numerator;
91+
oldDenom = oldWithdrawalFee.Denominator;
92+
}
93+
94+
// Check that new_fee / old_fee <= MAX_WITHDRAWAL_FEE_INCREASE
95+
try
96+
{
97+
BigInteger left = (BigInteger)oldNum * Denominator * MAX_WITHDRAWAL_FEE_INCREASE.Numerator;
98+
BigInteger right = (BigInteger)Numerator * oldDenom * MAX_WITHDRAWAL_FEE_INCREASE.Denominator;
99+
100+
if (left < right)
101+
{
102+
throw new StakePoolFeeException("Fee increase exceeds maximum allowed.");
103+
}
104+
}
105+
catch
106+
{
107+
throw new StakePoolFeeException("Calculation failure in withdrawal fee check.");
108+
}
109+
}
110+
111+
/// <summary>
112+
/// Returns a string representation of the fee.
113+
/// </summary>
114+
public override string ToString()
115+
{
116+
if (Numerator > 0 && Denominator > 0)
117+
return $"{Numerator}/{Denominator}";
118+
return "none";
119+
}
120+
}
121+
122+
/// <summary>
123+
/// Exception for stake pool fee errors.
124+
/// </summary>
125+
public class StakePoolFeeException : System.Exception
126+
{
127+
/// <summary>
128+
/// Initializes a new instance of the <see cref="StakePoolFeeException"/> class with a specified error message.
129+
/// </summary>
130+
/// <param name="message"></param>
131+
public StakePoolFeeException(string message) : base(message) { }
132+
}
133+
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
using System;
2+
3+
namespace Solnet.Programs.StakePool.Models
4+
{
5+
/// <summary>
6+
/// The type of fees that can be set on the stake pool.
7+
/// </summary>
8+
public abstract class FeeType
9+
{
10+
/// <summary>
11+
/// Represents a referral fee type with a specified percentage.
12+
/// </summary>
13+
/// <remarks>This class is used to define a referral fee as a percentage value. The percentage is
14+
/// immutable and must be specified at the time of instantiation.</remarks>
15+
public class SolReferral : FeeType
16+
{
17+
/// <summary>
18+
/// Gets the percentage value represented as a byte.
19+
/// </summary>
20+
public byte Percentage { get; }
21+
/// <summary>
22+
/// Initializes a new instance of the <see cref="SolReferral"/> class with the specified referral
23+
/// percentage.
24+
/// </summary>
25+
/// <param name="percentage">The referral percentage to be applied. Must be a value between 0 and 100, inclusive.</param>
26+
public SolReferral(byte percentage) => Percentage = percentage;
27+
}
28+
29+
/// <summary>
30+
/// Represents a referral fee type with a specified percentage for staking rewards.
31+
/// </summary>
32+
/// <remarks>This class is used to define a referral fee as a percentage of staking rewards. The
33+
/// percentage value is immutable and must be specified at the time of instantiation.</remarks>
34+
public class StakeReferral : FeeType
35+
{
36+
/// <summary>
37+
/// Gets the percentage value represented as a byte.
38+
/// </summary>
39+
public byte Percentage { get; }
40+
/// <summary>
41+
/// Represents a referral with a specified stake percentage.
42+
/// </summary>
43+
/// <remarks>The <paramref name="percentage"/> parameter defines the proportion of the
44+
/// stake allocated to the referral.</remarks>
45+
/// <param name="percentage">The percentage of the stake associated with the referral. Must be a value between 0 and 100.</param>
46+
public StakeReferral(byte percentage) => Percentage = percentage;
47+
}
48+
49+
/// <summary>
50+
/// Represents an epoch in the fee structure, associated with a specific fee.
51+
/// </summary>
52+
/// <remarks>This class is used to define a specific epoch and its corresponding fee. It inherits
53+
/// from the <see cref="FeeType"/> base class.</remarks>
54+
public class Epoch : FeeType
55+
{
56+
/// <summary>
57+
/// Gets the fee associated with the transaction.
58+
/// </summary>
59+
public Fee Fee { get; }
60+
/// <summary>
61+
/// Represents a specific epoch with an associated fee.
62+
/// </summary>
63+
/// <param name="fee">The fee associated with the epoch. Cannot be null.</param>
64+
public Epoch(Fee fee) => Fee = fee;
65+
}
66+
67+
/// <summary>
68+
/// Represents a withdrawal of staked funds, including an associated fee.
69+
/// </summary>
70+
/// <remarks>This class encapsulates the details of a stake withdrawal operation, including the
71+
/// fee applied to the withdrawal. It inherits from <see cref="FeeType"/>.</remarks>
72+
public class StakeWithdrawal : FeeType
73+
{
74+
/// <summary>
75+
/// Gets the fee associated with the transaction.
76+
/// </summary>
77+
public Fee Fee { get; }
78+
/// <summary>
79+
/// Initializes a new instance of the <see cref="StakeWithdrawal"/> class with the specified fee.
80+
/// </summary>
81+
/// <param name="fee">The fee associated with the stake withdrawal. This value cannot be null.</param>
82+
public StakeWithdrawal(Fee fee) => Fee = fee;
83+
}
84+
85+
/// <summary>
86+
/// Represents a deposit fee type specific to Solana transactions.
87+
/// </summary>
88+
/// <remarks>This class encapsulates the fee information for a Solana deposit transaction. It
89+
/// inherits from the <see cref="FeeType"/> base class.</remarks>
90+
public class SolDeposit : FeeType
91+
{
92+
/// <summary>
93+
/// Gets the fee associated with the transaction.
94+
/// </summary>
95+
public Fee Fee { get; }
96+
/// <summary>
97+
/// Initializes a new instance of the <see cref="SolDeposit"/> class with the specified fee.
98+
/// </summary>
99+
/// <param name="fee">The fee associated with the deposit. This value cannot be null.</param>
100+
public SolDeposit(Fee fee) => Fee = fee;
101+
}
102+
103+
/// <summary>
104+
/// Represents a deposit required for staking, associated with a specific fee.
105+
/// </summary>
106+
/// <remarks>This class encapsulates the concept of a staking deposit, which includes a fee that
107+
/// must be paid. It inherits from <see cref="FeeType"/>, providing additional context for fee-related
108+
/// operations.</remarks>
109+
public class StakeDeposit : FeeType
110+
{
111+
/// <summary>
112+
/// Gets the fee associated with the transaction.
113+
/// </summary>
114+
public Fee Fee { get; }
115+
/// <summary>
116+
/// Initializes a new instance of the <see cref="StakeDeposit"/> class with the specified fee.
117+
/// </summary>
118+
/// <param name="fee">The fee associated with the stake deposit. Cannot be null.</param>
119+
public StakeDeposit(Fee fee) => Fee = fee;
120+
}
121+
122+
/// <summary>
123+
/// Represents a withdrawal operation for Solana (SOL) that includes an associated fee.
124+
/// </summary>
125+
/// <remarks>This class encapsulates the details of a Solana withdrawal, including the fee
126+
/// required for the transaction.</remarks>
127+
public class SolWithdrawal : FeeType
128+
{
129+
/// <summary>
130+
/// Gets the fee associated with the transaction.
131+
/// </summary>
132+
public Fee Fee { get; }
133+
/// <summary>
134+
/// Initializes a new instance of the <see cref="SolWithdrawal"/> class with the specified fee.
135+
/// </summary>
136+
/// <param name="fee">The fee associated with the withdrawal. This value cannot be null.</param>
137+
public SolWithdrawal(Fee fee) => Fee = fee;
138+
}
139+
140+
/// <summary>
141+
/// Checks if the provided fee is too high.
142+
/// </summary>
143+
public bool IsTooHigh()
144+
{
145+
return this switch
146+
{
147+
SolReferral s => s.Percentage > 100,
148+
StakeReferral s => s.Percentage > 100,
149+
Epoch e => e.Fee.Numerator > e.Fee.Denominator,
150+
StakeWithdrawal s => s.Fee.Numerator > s.Fee.Denominator,
151+
SolWithdrawal s => s.Fee.Numerator > s.Fee.Denominator,
152+
SolDeposit s => s.Fee.Numerator > s.Fee.Denominator,
153+
StakeDeposit s => s.Fee.Numerator > s.Fee.Denominator,
154+
_ => false
155+
};
156+
}
157+
158+
/// <summary>
159+
/// Returns true if the contained fee can only be updated earliest on the next epoch.
160+
/// </summary>
161+
public bool CanOnlyChangeNextEpoch()
162+
{
163+
return this is StakeWithdrawal or SolWithdrawal or Epoch;
164+
}
165+
}
166+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace Solnet.Programs.StakePool.Models
8+
{
9+
/// <summary>
10+
/// Represents the type of funding operation in the stake pool.
11+
/// </summary>
12+
public enum FundingType
13+
{
14+
/// <summary>
15+
/// A deposit of a stake account.
16+
/// </summary>
17+
StakeDeposit = 0,
18+
19+
/// <summary>
20+
/// A deposit of SOL tokens.
21+
/// </summary>
22+
SolDeposit = 1,
23+
24+
/// <summary>
25+
/// A withdrawal of SOL tokens.
26+
/// </summary>
27+
SolWithdraw = 2,
28+
}
29+
}

0 commit comments

Comments
 (0)