Stable coin Token
Description
The code below is an example and requires further refinement for specific business tasks.
The contract below has the following capabilities
Possibility to increase the number of tokens in circulation - additional issue
Possibility of reducing the number of tokens in circulation (not less than zero) - burning
The ability to block tokens on a specific account for a specific time
The ability to block a certain number of tokens on a certain account for a certain time or until a certain time
Complete stopping of the ability to transfer the token (all addresses)
package com.example.contract;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import javax.management.RuntimeErrorException;
import java.util.Date;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import static java.math.BigDecimal.ZERO;
import com.credits.scapi.v3.SmartContract;
import com.credits.scapi.v0.ExtensionStandard;
public class StableCoinSharedTokenContract extends SmartContract implements ExtensionStandard {
private final String owner;
private final int decimal;
HashMap<String, BigDecimal> balances;
private String name;
private String symbol;
private BigDecimal totalCoins;
private HashMap<String, Map<String, BigDecimal>> allowed;
private boolean frozen;
private HashMap<String, Date> frozenAccounts;
private HashMap<String, PartialFreeze> frozenSums;
private HashSet<String> burnable;
private HashSet<String> permit_burn;
private HashSet<String> permit_emit;
class PartialFreeze implements Serializable {
long until;
BigDecimal sum;
PartialFreeze(long until_ms, BigDecimal amount) {
until = until_ms;
sum = amount;
}
}
public StableCoinSharedTokenContract() {
super();
// deploy time settings
name = "Credits USD";
symbol = "CUSD";
decimal = 2;
long initial = 1000L;
totalCoins = new BigDecimal(initial).setScale(decimal, RoundingMode.FLOOR);
owner = initiator;
allowed = new HashMap<>();
balances = new HashMap<>();
balances.put(owner, totalCoins);
frozen = false;
frozenAccounts = new HashMap<String, Date>();
frozenSums = new HashMap<String, PartialFreeze>();
permit_burn = new HashSet<String>();
permit_burn.add(owner);
permit_emit = new HashSet<String>();
permit_emit.add(owner);
burnable = new HashSet<String>();
}
@Override
public int getDecimal() {
return decimal;
}
@Override
public void register() {
ensureIsNotFrozen(initiator);
balances.putIfAbsent(initiator, ZERO.setScale(decimal, RoundingMode.FLOOR));
}
@Override
public boolean setFrozen(boolean isFrozen) {
if (!isOwner()) {
throw new RuntimeException("only owner can change frozen state");
}
this.frozen = isFrozen;
return true;
}
@Override
public String getName() {
return name;
}
@Override
public String getSymbol() {
return symbol;
}
@Override
public String totalSupply() {
return totalCoins.toString();
}
@Override
public String balanceOf(String account) {
return getTokensBalance(account).toString();
}
@Override
public String allowance(String owner, String spender) {
if (allowed.get(owner) == null) {
return "0";
}
BigDecimal amount = allowed.get(owner).get(spender);
return amount != null ? amount.toString() : "0";
}
@Override
public boolean transfer(String to, String amount) {
contractIsNotFrozen();
ensureIsNotFrozen(initiator);
ensureIsNotFrozen(to);
if (!to.equals(initiator)) {
BigDecimal decimalAmount = toBigDecimal(amount);
ensureAllowedTransferFrom(initiator, decimalAmount);
BigDecimal sourceBalance = getTokensBalance(initiator);
BigDecimal targetTokensBalance = getTokensBalance(to);
if(targetTokensBalance == null) {
targetTokensBalance = ZERO.setScale(decimal, RoundingMode.FLOOR);
}
balances.put(initiator, sourceBalance.subtract(decimalAmount));
balances.put(to, targetTokensBalance.add(decimalAmount));
}
return true;
}
@Override
public boolean transferFrom(String from, String to, String amount) {
contractIsNotFrozen();
ensureIsNotFrozen(initiator);
ensureIsNotFrozen(to);
ensureIsNotFrozen(from);
if (!from.equals(to)) {
BigDecimal fromBalance = getTokensBalance(from);
if(fromBalance == null ) {
throw new RuntimeException(from + " is not a holder");
}
BigDecimal toBalance = getTokensBalance(to);
if(toBalance == null) {
toBalance = ZERO.setScale(decimal, RoundingMode.FLOOR);
}
BigDecimal decimalAmount = toBigDecimal(amount);
ensureAllowedTransferFrom(from, decimalAmount);
Map<String, BigDecimal> spender = allowed.get(from);
if (spender == null || !spender.containsKey(initiator)) {
throw new RuntimeException(initiator + " require allowance from " + from + " to transfer tokens");
}
BigDecimal allowTokens = spender.get(initiator);
if (allowTokens.compareTo(decimalAmount) < 0) {
throw new RuntimeException("maximum " + allowTokens + " tokens are allowed to transfer");
}
spender.put(initiator, allowTokens.subtract(decimalAmount));
balances.put(from, fromBalance.subtract(decimalAmount));
balances.put(to, toBalance.add(decimalAmount));
}
return true;
}
@Override
public void approve(String spender, String amount) {
ensureIsNotFrozen(initiator);
ensureIsNotFrozen(spender);
initiatorIsRegistered();
BigDecimal decimalAmount = toBigDecimal(amount);
Map<String, BigDecimal> initiatorSpenders = allowed.get(initiator);
if (initiatorSpenders == null) {
Map<String, BigDecimal> newSpender = new HashMap<>();
newSpender.put(spender, decimalAmount);
allowed.put(initiator, newSpender);
} else {
BigDecimal spenderAmount = initiatorSpenders.get(spender);
initiatorSpenders.put(spender, spenderAmount == null ? decimalAmount : spenderAmount.add(decimalAmount));
}
}
@Override
public boolean burn(String amount) {
if (!isOwner())
throw new RuntimeException("can not burn tokens, only owner can");
BigDecimal decimalAmount = toBigDecimal(amount);
BigDecimal burnable = getTokensBalance(owner);
if(burnable.compareTo(decimalAmount) < 0) {
throw new RuntimeException("unable to burn " + amount + " tokens but only " + burnable);
}
totalCoins = totalCoins.subtract(decimalAmount);
balances.put(owner, balances.get(owner).subtract(decimalAmount));
return true;
}
public String getBurnAvail() {
return getTokensBalance(owner).toString();
}
public void emit(String amount) {
if (!isOwner())
throw new RuntimeException("only owner can emit tokens");
BigDecimal emitted = toBigDecimal(amount);
if(emitted.compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("amount must be positive");
}
totalCoins = totalCoins.add(emitted);
balances.put(owner, balances.get(owner).add(emitted));
}
public void payable(String amount, String currency) {
throw new RuntimeException("unsupported operation: buy tokens");
}
@Override
public String payable(BigDecimal amount, byte [] data) {
throw new RuntimeException("unsupported method: obsolete");
}
@Override
public boolean buyTokens(String amount) {
throw new RuntimeException("unsupported operation: buy tokens");
}
private void contractIsNotFrozen() {
if (frozen) throw new RuntimeException("unavailable action! The smart-contract is frozen");
}
private void initiatorIsRegistered() {
if (!balances.containsKey(initiator))
throw new RuntimeException("operation rejected, " + initiator + " is not a holder");
}
private BigDecimal toBigDecimal(String stringValue) {
return new BigDecimal(stringValue).setScale(decimal, RoundingMode.FLOOR);
}
private BigDecimal getTokensBalance(String account) {
if(!balances.containsKey(account)) {
return ZERO;
}
return balances.get(account);
}
// extensions
private boolean isOwner() {
return initiator.equals(owner);
}
private void ensureIsNotFrozen(String account) {
if (isAccountFrozen(account)) {
throw new RuntimeException("account is frozen");
}
}
public void freezeAccount(String account, long unix_time) {
testFreezeBy(initiator);
testFreezeOf(account);
frozenAccounts.put(account, new Date(unix_time * 1000L));
}
public void defrostAccount(String account) {
testFreezeBy(initiator);
testFreezeOf(account);
if(frozenAccounts.containsKey(account)) {
frozenAccounts.remove(account);
}
}
public boolean isAccountFrozen(String account) {
if(! frozenAccounts.containsKey(account)) {
return false;
}
Date now = new Date(getBlockchainTimeMills());
if(frozenAccounts.get(account).before(now)) {
frozenAccounts.remove(account);
return false;
}
return true;
}
public String getAccountDefrostDate(String account) {
if(! frozenAccounts.containsKey(account)) {
return "is not frozen";
}
return printDate(frozenAccounts.get(account));
}
public void freezeSum(String account, long unix_time, String amount) {
testFreezeBy(initiator);
testFreezeOf(account);
BigDecimal sum = toBigDecimal(amount);
if(sum.compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("amount must be positive");
}
frozenSums.put(account, new PartialFreeze(unix_time * 1000L, sum));
}
private void ensureAllowedTransferFrom(String account, BigDecimal sum) {
BigDecimal total = getTokensBalance(account);
if(frozenSums.containsKey(account)) {
PartialFreeze p = frozenSums.get(account);
if(getBlockchainTimeMills() <= p.until) {
total = total.subtract(p.sum);
}
}
if(total.compareTo(sum) < 0) {
throw new RuntimeException("insufficient tokens to transfer from");
}
}
public String getFrozenSumValue(String account) {
PartialFreeze p = frozenSums.get(account);
if(p == null) {
return "";
}
return p.sum.toString();
}
public String getFrozenSumDate(String account) {
PartialFreeze p = frozenSums.get(account);
if(p == null) {
return "";
}
return printDate(new Date(p.until));
}
public void optimizeFrozenTokens() {
long now = getBlockchainTimeMills();
frozenSums.values().removeIf((p) -> { return p.until < now; });
}
private String printDate(Date d) {
SimpleDateFormat fmt = new SimpleDateFormat("dd.MM.yyyy HH:mm::ss");
return fmt.format(d);
}
// support pernmissions to burn and emit tokens
private void updateSet(HashSet<String> set, String key, boolean on) {
if(on) {
if(!set.contains(key)) {
set.add(key);
}
}
else {
if(set.contains(key)) {
set.remove(key);
}
}
}
public void permitBurnOn(String account, boolean allow) {
if(!isOwner()) {
throw new RuntimeException("Only owner can do this");
}
updateSet(burnable, account, allow);
}
public void permitBurnBy(String account, boolean allow) {
if(!isOwner()) {
throw new RuntimeException("Only owner can do this");
}
updateSet(permit_burn, account, allow);
}
public void permitEmit(String account, boolean allow) {
if(!isOwner()) {
throw new RuntimeException("Only owner can do this");
}
updateSet(permit_emit, account, allow);
}
private void testBurnOn(String account) {
if(!burnable.contains(account)) {
throw new RuntimeException("Burning on this account is not permitted");
}
}
private void testBurnBy(String account) {
if(isOwner()) {
return;
}
if(!permit_burn.contains(account)) {
throw new RuntimeException("Burning by this account is not permitted");
}
}
private void testEmitBy(String account) {
if(isOwner()) {
return;
}
if(!permit_emit.contains(account)) {
throw new RuntimeException("Emit by this account is not permitted");
}
}
private void testFreezeOf(String account) {
if(isOwner()) {
return;
}
if(!burnable.contains(account)) {
throw new RuntimeException("Freezing this account is not permitted");
}
}
private void testFreezeBy(String account) {
if(isOwner()) {
return;
}
if(!permit_burn.contains(account)) {
throw new RuntimeException("Freezing by this account is not permitted");
}
}
public void burnOn(String account, String amount) {
testBurnBy(initiator);
testBurnOn(account);
BigDecimal decimalAmount = toBigDecimal(amount);
BigDecimal burnable = getTokensBalance(account);
if(burnable.compareTo(decimalAmount) < 0) {
throw new RuntimeException("unable to burn " + amount + " tokens but only " + burnable);
}
totalCoins = totalCoins.subtract(decimalAmount);
balances.put(account, balances.get(account).subtract(decimalAmount));
}
public void emitSelf(String amount) {
testEmitBy(initiator);
BigDecimal emitted = toBigDecimal(amount);
if(emitted.compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("amount must be positive");
}
totalCoins = totalCoins.add(emitted);
if(balances.containsKey(initiator)) {
emitted = emitted.add(balances.get(initiator));
}
balances.put(initiator, emitted);
}
}
Last updated