All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.wavesenterprise.wrc.wrc20.impl.WRC20FTokenContractImpl.kt Maven / Gradle / Ivy

package com.wavesenterprise.wrc.wrc20.impl

import com.wavesenterprise.sdk.contract.api.annotation.ContractHandler
import com.wavesenterprise.sdk.contract.api.domain.ContractCall
import com.wavesenterprise.sdk.contract.api.state.ContractState
import com.wavesenterprise.sdk.contract.api.state.mapping.Mapping
import com.wavesenterprise.wrc.wrc10.WRC10RoleBasedAccessControl
import com.wavesenterprise.wrc.wrc10.impl.WRC10RoleBasedAccessControlImpl
import com.wavesenterprise.wrc.wrc10.impl.hasPermission
import com.wavesenterprise.wrc.wrc20.KEY_BURNABLE
import com.wavesenterprise.wrc.wrc20.KEY_CONTRACT_ID
import com.wavesenterprise.wrc.wrc20.KEY_DECIMALS
import com.wavesenterprise.wrc.wrc20.KEY_MINTABLE
import com.wavesenterprise.wrc.wrc20.KEY_NAME
import com.wavesenterprise.wrc.wrc20.KEY_TOTAL_SUPPLY
import com.wavesenterprise.wrc.wrc20.MAPPING_ALLOWANCES
import com.wavesenterprise.wrc.wrc20.MAPPING_BALANCES
import com.wavesenterprise.wrc.wrc20.PERM_MINTER
import com.wavesenterprise.wrc.wrc20.WRC20FTokenContract

@ContractHandler
class WRC20FTokenContractImpl private constructor(
    private val state: ContractState,
    private val call: ContractCall,
    private val rbac: WRC10RoleBasedAccessControlImpl,
    private val balance: Mapping,
    private val allowances: Mapping
) : WRC20FTokenContract, WRC10RoleBasedAccessControl by rbac {
//    AsyncContract by AsyncContractImpl(state) ToDo: Extend AsyncContractImpl WTCH-186, WTCH-187

    // TODO: 02.02.2021 Java reflection not works well with kotlin default arguments
    // see https://stackoverflow.com/questions/46245206/how-can-i-instantiate-an-object-using-default-constructor-parameter-values-in-ko
    constructor(
        state: ContractState,
        call: ContractCall
    ) : this(
        state,
        call,
        WRC10RoleBasedAccessControlImpl(state, call),
        state.getMapping(Long::class.java, MAPPING_BALANCES),
        state.getMapping(Long::class.java, MAPPING_ALLOWANCES)
    )

    override fun create(name: String) {
        createFull(
            name = name,
            burnable = true,
            mintable = true,
            supply = 0,
            supplyHolder = "",
            decimals = 2
        )
    }

    override fun createFull(
        name: String,
        burnable: Boolean,
        mintable: Boolean,
        supply: Long,
        supplyHolder: String,
        decimals: Int
    ) {
        state.put(KEY_NAME, name)
        state.put(KEY_TOTAL_SUPPLY, 0L)
        state.put(KEY_DECIMALS, decimals)
        state.put(KEY_MINTABLE, mintable)
        state.put(KEY_BURNABLE, burnable)
        state.put(KEY_CONTRACT_ID, call.id)

        rbac.init()
        if (mintable) {
            rbac.grant(call.caller, PERM_MINTER)
        }

        if (supply > 0) {
            mint(supplyHolder, supply)
        }
    }

    override fun transfer(to: String, amount: Long) {
        require(balanceOf(call.caller) >= amount) { "INSUFFICIENT_BALANCE" }
        modifyBalance(call.caller) { it - amount }
        modifyBalance(to) { it + amount }
    }

    override fun approve(to: String, amount: Long) {
        modifyAllowance(call.caller, to) { amount }
    }

    override fun transferFrom(from: String, to: String, amount: Long) {
        require(allowance(from, call.caller) >= amount) { "INSUFFICIENT_ALLOWANCE" }
        require(balanceOf(from) >= amount) { "INSUFFICIENT_BALANCE" }
        modifyBalance(from) { it - amount }
        modifyBalance(to) { it + amount }
        modifyAllowance(from, call.caller) { it - amount }
    }

    override fun burn(amount: Long) {
        require(isBurnable()) { "NOT_BURNABLE" }
        require(balanceOf(call.caller) >= amount) { "INSUFFICIENT_BALANCE" }
        modifyBalance(call.caller) { it - amount }
        modifyTotalSupply { it - amount }
    }

    override fun mint(to: String, amount: Long) {
        require(isMintable()) { "NON_MINTABLE" }
        require(rbac.hasPermission(call.caller, PERM_MINTER)) { "NOT_PERMITTED" }
        modifyBalance(to) { it + amount }
        modifyTotalSupply { it + amount }
    }

    fun balanceOf(address: String) = balance.tryGet(address).orElse(0)!!

    fun allowance(from: String, to: String) = allowances.tryGet(allowanceKey(from, to)).orElse(0)!!

    fun allowanceKey(from: String, to: String) = "${from}_$to"

    fun modifyBalance(address: String, fn: (Long) -> Long) {
        balance.put(address, fn(balanceOf(address)))
    }

    fun modifyAllowance(from: String, to: String, fn: (Long) -> Long) {
        allowances.put(allowanceKey(from, to), fn(allowance(from, to)))
    }

    fun modifyTotalSupply(fn: (Long) -> Long) {
        val totalSupply = state.tryGet(KEY_TOTAL_SUPPLY, Long::class.java).orElse(0)
        state.put(KEY_TOTAL_SUPPLY, fn(totalSupply))
    }

    fun isBurnable() = state.getBoolean(KEY_BURNABLE)

    fun isMintable() = state.getBoolean(KEY_MINTABLE)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy