public void Claim(Address from, Address stakeAddress) { Runtime.Expect(IsWitness(from), "witness failed"); var unclaimedAmount = GetUnclaimed(stakeAddress); Runtime.Expect(unclaimedAmount > 0, "nothing unclaimed"); var fuelAmount = unclaimedAmount; // distribute to proxy list var list = _proxyStakersMap.Get <Address, StorageList>(stakeAddress); var count = list.Count(); // if the transaction comes from someone other than the stake owner, must be registred in proxy list if (from != stakeAddress) { bool found = false; for (int i = 0; i < count; i++) { var proxy = list.Get <EnergyProxy>(i); if (proxy.address == from) { found = true; break; } } Runtime.Expect(found, "invalid permissions"); } BigInteger sum = 0; BigInteger availableAmount = fuelAmount; for (int i = 0; i < count; i++) { var proxy = list.Get <EnergyProxy>(i); sum += proxy.percentage; var proxyAmount = (fuelAmount * proxy.percentage) / 100; if (proxyAmount > 0) { Runtime.Expect(availableAmount >= proxyAmount, "unsuficient amount for proxy distribution"); Runtime.Expect(Runtime.Nexus.MintTokens(Nexus.FuelTokenSymbol, this.Storage, Runtime.Chain, proxy.address, proxyAmount), "proxy fuel minting failed"); availableAmount -= proxyAmount; } } Runtime.Expect(availableAmount >= 0, "unsuficient leftovers"); Runtime.Expect(Runtime.Nexus.MintTokens(Nexus.FuelTokenSymbol, this.Storage, Runtime.Chain, stakeAddress, availableAmount), "fuel minting failed"); // NOTE here we set the full staked amount instead of claimed amount, to avoid infinite claims loophole var stake = _stakes.Get <Address, EnergyAction>(stakeAddress); if (stake.totalAmount == 0 && GetLastAction(stakeAddress).unclaimedPartials == 0) { _stakes.Remove(from); } var action = new EnergyAction() { unclaimedPartials = 0, totalAmount = stake.totalAmount, timestamp = Runtime.Time }; _claims.Set <Address, EnergyAction>(stakeAddress, action); Runtime.Notify(EventKind.TokenClaim, from, new TokenEventData() { chainAddress = Runtime.Chain.Address, symbol = Nexus.StakingTokenSymbol, value = unclaimedAmount }); Runtime.Notify(EventKind.TokenMint, stakeAddress, new TokenEventData() { chainAddress = Runtime.Chain.Address, symbol = Nexus.FuelTokenSymbol, value = fuelAmount }); }
public BigInteger Unstake(Address from, BigInteger amount) { Runtime.Expect(IsWitness(from), "witness failed"); if (!_stakes.ContainsKey <Address>(from)) { return(0); } var stake = _stakes.Get <Address, EnergyAction>(from); if (stake.timestamp.Value == 0) // failsafe, should never happen { return(0); } var diff = Runtime.Time - stake.timestamp; var days = diff / 86400; // convert seconds to days Runtime.Expect(days >= 1, "waiting period required"); var token = Runtime.Nexus.GetTokenInfo(Nexus.StakingTokenSymbol); var balances = new BalanceSheet(token.Symbol); var balance = balances.Get(this.Storage, Runtime.Chain.Address); Runtime.Expect(balance >= amount, "not enough balance"); Runtime.Expect(stake.totalAmount >= amount, "tried to unstake more than what was staked"); Runtime.Expect(balances.Subtract(this.Storage, Runtime.Chain.Address, amount), "balance subtract failed"); Runtime.Expect(balances.Add(this.Storage, from, amount), "balance add failed"); stake.totalAmount -= amount; var unclaimedPartials = GetLastAction(from).unclaimedPartials; if (stake.totalAmount == 0 && unclaimedPartials == 0) { _stakes.Remove(from); _voteHistory.Remove(from); } else { var entry = new EnergyAction() { unclaimedPartials = unclaimedPartials, totalAmount = stake.totalAmount, timestamp = this.Runtime.Time, }; _stakes.Set(from, entry); RemoveVotingPower(from, amount); } if (stake.totalAmount < MasterAccountThreshold) { var count = _mastersList.Count(); var index = -1; for (int i = 0; i < count; i++) { var master = _mastersList.Get <EnergyMaster>(i); if (master.address == from) { index = i; break; } } if (index >= 0) { var penalizationDate = GetMasterClaimDateFromReference(1, _mastersList.Get <EnergyMaster>(index).claimDate); _mastersList.RemoveAt <EnergyMaster>(index); Runtime.Notify(EventKind.MasterDemote, from, penalizationDate); } } Runtime.Notify(EventKind.TokenUnstake, from, new TokenEventData() { chainAddress = Runtime.Chain.Address, symbol = token.Symbol, value = amount }); return(amount); }