-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathOpdexMinedToken.cs
More file actions
341 lines (270 loc) · 11.6 KB
/
Copy pathOpdexMinedToken.cs
File metadata and controls
341 lines (270 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
using Stratis.SmartContracts;
/// <summary>
/// Mined token contract distributed through a Mining Governance and Vault smart contract.
/// Used for staking in Opdex liquidity pools to participate in governance for liquidity mining pool selection.
/// </summary>
[Deploy]
public class OpdexMinedToken : SmartContract, IOpdexMinedToken
{
/// <summary>
/// Constructor initializing opdex token contract.
/// </summary>
/// <param name="state">Smart contract state.</param>
/// <param name="name">The name of the token.</param>
/// <param name="symbol">The token's ticker symbol.</param>
/// <param name="vaultDistribution">Serialized UInt256 array of vault distribution amounts.</param>
/// <param name="miningDistribution">Serialized UInt256 array of mining distribution amounts.</param>
/// <param name="periodDuration">The number of blocks between token distributions.</param>
/// <param name="vaultTotalPledgeMinimum">The minimum total number of tokens pledged to a proposal to move to a vote.</param>
/// <param name="vaultTotalVoteMinimum">The minimum total number of tokens voted on a proposal to have a chance to be approved.</param>
public OpdexMinedToken(ISmartContractState state, string name, string symbol, byte[] vaultDistribution,
byte[] miningDistribution, ulong periodDuration, ulong vaultTotalPledgeMinimum, ulong vaultTotalVoteMinimum) : base(state)
{
var vaultSchedule = Serializer.ToArray<UInt256>(vaultDistribution);
var miningSchedule = Serializer.ToArray<UInt256>(miningDistribution);
Assert(vaultSchedule.Length > 1 && vaultSchedule.Length == miningSchedule.Length, "OPDEX: INVALID_DISTRIBUTION_SCHEDULE");
Name = name;
Symbol = symbol;
Decimals = 8;
Creator = Message.Sender;
VaultSchedule = vaultSchedule;
MiningSchedule = miningSchedule;
PeriodDuration = periodDuration;
MiningGovernance = InitializeMiningGovernance(periodDuration);
Vault = InitializeVault(periodDuration, vaultTotalPledgeMinimum, vaultTotalVoteMinimum);
}
/// <inheritdoc />
public string Symbol
{
get => State.GetString(TokenStateKeys.Symbol);
private set => State.SetString(TokenStateKeys.Symbol, value);
}
/// <inheritdoc />
public string Name
{
get => State.GetString(TokenStateKeys.Name);
private set => State.SetString(TokenStateKeys.Name, value);
}
/// <inheritdoc />
public byte Decimals
{
get => State.GetBytes(TokenStateKeys.Decimals)[0];
private set => State.SetBytes(TokenStateKeys.Decimals, new [] {value});
}
/// <inheritdoc />
public UInt256 TotalSupply
{
get => State.GetUInt256(TokenStateKeys.TotalSupply);
private set => State.SetUInt256(TokenStateKeys.TotalSupply, value);
}
/// <inheritdoc />
public Address Creator
{
get => State.GetAddress(TokenStateKeys.Creator);
private set => State.SetAddress(TokenStateKeys.Creator, value);
}
/// <inheritdoc />
public Address MiningGovernance
{
get => State.GetAddress(TokenStateKeys.MiningGovernance);
private set => State.SetAddress(TokenStateKeys.MiningGovernance, value);
}
/// <inheritdoc />
public Address Vault
{
get => State.GetAddress(TokenStateKeys.Vault);
private set => State.SetAddress(TokenStateKeys.Vault, value);
}
/// <inheritdoc />
public UInt256[] VaultSchedule
{
get => State.GetArray<UInt256>(TokenStateKeys.VaultSchedule);
private set => State.SetArray(TokenStateKeys.VaultSchedule, value);
}
/// <inheritdoc />
public UInt256[] MiningSchedule
{
get => State.GetArray<UInt256>(TokenStateKeys.MiningSchedule);
private set => State.SetArray(TokenStateKeys.MiningSchedule, value);
}
/// <inheritdoc />
public ulong Genesis
{
get => State.GetUInt64(TokenStateKeys.Genesis);
private set => State.SetUInt64(TokenStateKeys.Genesis, value);
}
/// <inheritdoc />
public uint PeriodIndex
{
get => State.GetUInt32(TokenStateKeys.PeriodIndex);
private set => State.SetUInt32(TokenStateKeys.PeriodIndex, value);
}
/// <inheritdoc />
public ulong PeriodDuration
{
get => State.GetUInt64(TokenStateKeys.PeriodDuration);
private set => State.SetUInt64(TokenStateKeys.PeriodDuration, value);
}
/// <inheritdoc />
public ulong NextDistributionBlock
{
get => State.GetUInt64(TokenStateKeys.NextDistributionBlock);
private set => State.SetUInt64(TokenStateKeys.NextDistributionBlock, value);
}
/// <inheritdoc />
public UInt256 GetBalance(Address address)
{
return State.GetUInt256($"{TokenStateKeys.Balance}:{address}");
}
private void SetBalance(Address address, UInt256 value)
{
State.SetUInt256($"{TokenStateKeys.Balance}:{address}", value);
}
private void SetApproval(Address owner, Address spender, UInt256 value)
{
State.SetUInt256($"{TokenStateKeys.Allowance}:{owner}:{spender}", value);
}
/// <inheritdoc />
public UInt256 Allowance(Address owner, Address spender)
{
return State.GetUInt256($"{TokenStateKeys.Allowance}:{owner}:{spender}");
}
/// <inheritdoc />
public void NominateLiquidityPool()
{
Assert(State.IsContract(Message.Sender), "OPDEX: INVALID_SENDER");
var balance = GetBalance(Message.Sender);
if (balance == 0) return;
// Intentionally ignore the response
Call(MiningGovernance, 0ul, nameof(IOpdexMiningGovernance.NominateLiquidityPool), new object[] {Message.Sender, balance});
}
/// <inheritdoc />
public void DistributeGenesis(Address firstNomination, Address secondNomination, Address thirdNomination, Address fourthNomination)
{
var periodIndex = PeriodIndex;
Assert(periodIndex == 0, "OPDEX: INVALID_DISTRIBUTION_PERIOD");
Assert(Message.Sender == Creator, "OPDEX: UNAUTHORIZED");
var nominations = new [] {firstNomination, secondNomination, thirdNomination, fourthNomination};
for (var i = 0; i < nominations.Length; i++)
{
var nomination = nominations[i];
Assert(nomination != Address.Zero && State.IsContract(nomination), "OPDEX: INVALID_NOMINATION");
var next = i + 1;
while (next < nominations.Length)
{
Assert(nomination != nominations[next], "OPDEX: DUPLICATE_NOMINATION");
next++;
}
}
Genesis = Block.Number;
DistributeExecute(periodIndex, nominations);
}
/// <inheritdoc />
public void Distribute()
{
var periodIndex = PeriodIndex;
Assert(periodIndex > 0, "OPDEX: INVALID_DISTRIBUTION_PERIOD");
// 4 Addresses to send
DistributeExecute(periodIndex, new Address[4]);
}
/// <inheritdoc />
public bool TransferTo(Address to, UInt256 amount)
{
if (to == Address.Zero) return false;
if (amount == 0)
{
Log(new TransferLog { From = Message.Sender, To = to, Amount = 0 });
return true;
}
var senderBalance = GetBalance(Message.Sender);
if (senderBalance < amount) return false;
SetBalance(Message.Sender, senderBalance - amount);
SetBalance(to, GetBalance(to) + amount);
Log(new TransferLog { From = Message.Sender, To = to, Amount = amount });
return true;
}
/// <inheritdoc />
public bool TransferFrom(Address from, Address to, UInt256 amount)
{
if (to == Address.Zero) return false;
if (amount == 0)
{
Log(new TransferLog { From = from, To = to, Amount = 0 });
return true;
}
var senderAllowance = Allowance(from, Message.Sender);
var fromBalance = GetBalance(from);
if (senderAllowance < amount || fromBalance < amount) return false;
SetApproval(from, Message.Sender, senderAllowance - amount);
SetBalance(from, fromBalance - amount);
SetBalance(to, GetBalance(to) + amount);
Log(new TransferLog { From = from, To = to, Amount = amount });
return true;
}
/// <inheritdoc />
public bool Approve(Address spender, UInt256 currentAmount, UInt256 amount)
{
if (Allowance(Message.Sender, spender) != currentAmount) return false;
SetApproval(Message.Sender, spender, amount);
Log(new ApprovalLog { Owner = Message.Sender, Spender = spender, Amount = amount, OldAmount = currentAmount });
return true;
}
private static ulong GetNextDistributionBlock(ulong periodDuration, uint periodIndex, ulong genesis)
{
return (periodDuration * periodIndex) + genesis;
}
private void DistributeExecute(uint periodIndex, Address[] nominations)
{
Assert(Block.Number >= NextDistributionBlock, "OPDEX: DISTRIBUTION_NOT_READY");
var miningGov = MiningGovernance;
var vault = Vault;
var miningSchedule = MiningSchedule;
var vaultSchedule = VaultSchedule;
var totalSupply = TotalSupply;
var inflationIndex = (uint)vaultSchedule.Length - 1;
var scheduleIndex = periodIndex < inflationIndex ? periodIndex : inflationIndex;
var miningTokens = miningSchedule[scheduleIndex];
var vaultTokens = vaultSchedule[scheduleIndex];
var supplyIncrease = miningTokens + vaultTokens;
if (miningTokens > 0)
{
SetBalance(miningGov, GetBalance(miningGov) + miningTokens);
var nominationParams = new object[] {nominations[0], nominations[1], nominations[2], nominations[3]};
var governanceNotification = Call(miningGov, 0ul, nameof(IOpdexMiningGovernance.NotifyDistribution), nominationParams);
Assert(governanceNotification.Success, "OPDEX: FAILED_GOVERNANCE_DISTRIBUTION");
}
if (vaultTokens > 0)
{
SetBalance(vault, GetBalance(vault) + vaultTokens);
var vaultNotification = Call(vault, 0, nameof(IOpdexVault.NotifyDistribution), new object[] { vaultTokens });
Assert(vaultNotification.Success, "OPDEX: FAILED_VAULT_DISTRIBUTION");
}
totalSupply += supplyIncrease;
TotalSupply = totalSupply;
var nextPeriodIndex = periodIndex + 1;
PeriodIndex = nextPeriodIndex;
var nextDistributionBlock = GetNextDistributionBlock(PeriodDuration, nextPeriodIndex, Genesis);
NextDistributionBlock = nextDistributionBlock;
Log(new DistributionLog
{
MiningAmount = miningTokens,
VaultAmount = vaultTokens,
PeriodIndex = periodIndex,
TotalSupply = totalSupply,
NextDistributionBlock = nextDistributionBlock
});
}
private Address InitializeMiningGovernance(ulong periodDuration)
{
var miningDuration = periodDuration / 12;
var miningGovernanceResponse = Create<OpdexMiningGovernance>(0, new object[] {Address, miningDuration});
Assert(miningGovernanceResponse.Success, "OPDEX: INVALID_MINING_GOVERNANCE_ADDRESS");
return miningGovernanceResponse.NewContractAddress;
}
private Address InitializeVault(ulong periodDuration, ulong vaultTotalPledgeMinimum, ulong vaultTotalVoteMinimum)
{
var vaultResponse = Create<OpdexVault>(0, new object[] { Address, periodDuration, vaultTotalPledgeMinimum, vaultTotalVoteMinimum });
Assert(vaultResponse.Success, "OPDEX: INVALID_VAULT_ADDRESS");
return vaultResponse.NewContractAddress;
}
}