Core System Contract

Treasury Contract

0x1Ee7E501039E0bd75475DD9da69EE744951CaB8B

Treasury Contract는 라운드의 확장 및 축소 상태를 결정하고 KAI의 신규 발행, bKAI 상환 등 중앙은행과 같은 중심적인 역할을 합니다.

[주요 역할]

  • PriceOracle에서 제공되는 KAI의 가격에 따른 확장/축소 상태 결정

  • 확장/축소/부스트 결과에 따른 에어드랍 비율 결정

  • 확장 상태에서 KAI 신규 발행 및 분배

  • bKAI 상환을 위한 KAI 보유 및 지급

다음은 확장 상태에서 KAI 추가 발행을 위해 Mint 하는 Code 입니다.

function allocateSeigniorage() external onlyOneBlock checkCondition checkEpoch checkOperator {
        _updateKAIPrice();
        previousRoundKAIPrice = getKAIPrice(); // KAI 가격을 얻어옴
        uint256 kaiSupply = IERC20(kai).totalSupply().sub(seigniorageSaved);
        if (round < bootstrapRounds) { // 초기 21라운드는 3% 고정
            _sendToBoardRoom(kaiSupply.mul(bootstrapSupplyExpansionPercent).div(10000));
        } else {
            if (previousRoundKAIPrice > kaiPriceOne) { // KAI 가격이 $1 초과하면 확장
uint256 bkaiSupply = IERC20(bkai).totalSupply();
                uint256 _percentage = previousRoundKAIPrice.sub(kaiPriceOne).mul(seigniorageExpansionRate).div(10000);
                uint256 _savedForBKAI;
                uint256 _savedForBoardRoom;
                // bKAI 의 발행량 이상으로 Treasury 가 KAI 를 보유한 경우 일반적인 확장 진행
                if (seigniorageSaved >= bkaiSupply.mul(bkaiDepletionFloorPercent).div(10000)) 
                    uint256 _mse = maxSupplyExpansionPercent.mul(1e14);
                    if (_percentage > _mse) {
                        _percentage = _mse;
                    }
                    _savedForBoardRoom = kaiSupply.mul(_percentage).div(1e18);
                } else {
                    // bKAI 의 발행량 만큼 Treasury 가 보유하고 있지 않다면, 이는 Debt Phase 로 전환되어 더 많은 KAI 가 Treasury 에 적립됨
                    _percentage = maxSupplyExpansionPercentInDebtPhase.mul(1e14);
                    uint256 _seigniorage = kaiSupply.mul(_percentage).div(1e18);
                    _savedForBoardRoom = _seigniorage.mul(seigniorageExpansionFloorPercent).div(10000);
                    _savedForBKAI = _seigniorage.sub(_savedForBoardRoom);
                    if (mintingFactorForPayingDebt > 0) {
                        _savedForBKAI = _savedForBKAI.mul(mintingFactorForPayingDebt).div(10000);
                    }
                }
                if (_savedForBoardRoom > 0) {
                    // 보드룸으로 KAI 생성 요청
                    _sendToBoardRoom(_savedForBoardRoom);
                }
                if (_savedForBKAI > 0) {
                    seigniorageSaved = seigniorageSaved.add(_savedForBKAI);
                    IKAIAsset(kai).mint(address(this), _savedForBKAI);
                    emit TreasuryFunded(now, _savedForBKAI);
                }
            }
        }
        // 바이백 펀드 KAI 생성
        if (previousRoundKAIPrice > kaiPriceOne) {
            uint256 _buyBackRate = previousRoundKAIPrice.sub(kaiPriceOne).mul(buyBackFundExpansionRate).div(10000);
            uint256 _maxBuyBackRate = maxBuyBackFundExpansion.mul(1e14);
            if (_buyBackRate > _maxBuyBackRate) {
                _buyBackRate = _maxBuyBackRate;
            }
            uint256 _savedForBuyBackFund = kaiSupply.mul(_buyBackRate).div(1e18);
            if (_savedForBuyBackFund > 0) {
                IKAIAsset(kai).mint(address(buyBackFund), _savedForBuyBackFund);
                emit BuyBackFunded(now, _savedForBuyBackFund);
            }
        }
        if (allocateSeigniorageSalary > 0) {
            IKAIAsset(kai).mint(address(admin), allocateSeigniorageSalary);
        }
    }

Mint된 후 _sendToBoardRoom function 을 통해 Boardroom Contract 및 Team Fund에게 KAI가 분배됩니다.

function _sendToBoardRoom(uint256 _amount) internal {
        IKAIAsset(kai).mint(address(this), _amount);
        // 10%의 물량은 팀펀드로 적립되며, 해당 물량은 마케팅 및 운영비로 사용됨
        if (teamFundSharedPercent > 0) {
            uint256 _teamFundSharedAmount = _amount.mul(teamFundSharedPercent).div(10000);
            IERC20(kai).transfer(teamFund, _teamFundSharedAmount);
            emit TeamFundFunded(now, _teamFundSharedAmount);
            _amount = _amount.sub(_teamFundSharedAmount);
        }
        IERC20(kai).safeApprove(boardroom, 0);
        IERC20(kai).safeApprove(boardroom, _amount);
        // 보드룸에 나머지 물량을 보내서 단일예치자들에게 배분
        IBoardroom(boardroom).allocateSeigniorage(_amount);
        emit BoardroomFunded(now, _amount);
    }

Boardroom Contract

0x931579Fa23580CB2214fBE89aB487f6AEDE8a352

Boardroom Contract는 sKAI 단일 예치를 관리하고 확장 상태에서 Treasury Contract로 부터 받은 KAI를 sKAI 단일 예치 사용자에게 분배하는 일반 은행과 같은 역할을 합니다.

[주요 역할]

  • sKAI 단일 예치

  • 추가 발행된 KAI 분배

  • 발행된 vKAI 분배

BBFund Contract - Buyback Fund

0x451033434Fb739a538DA0a77d3DcA23Bf0801255

bKAI를 통한 KAI의 $ 1 페깅 알고리즘의 보완 장치로 다음 라운드 예상 가격(TWAP)이 $ 1미만일때 KAI를 바이백해서 가격을 안정화할 수 있습니다. KAI 유통량 및 바이백 펀드 현황에 따라 바이백된 KAI가 소각될 수 있습니다.

바이백 펀드 조성은 KAI, vKAI로 진행되며 KUSDT, KDAI로 변환해서 적립합니다.

[주요 기능]

  • KAI 가격이 $ 1 초과일때 KAI -> KUSDT 또는 KAI -> KDAI 교환

  • KAI 가격이 $ 1 미만일때 KUSDT -> KAI 또는 KDAI -> KAI교환

Buyback Fund Contract는 Transfer function이 불가능하며 오직 KAI 가격 유지를 위한 Sell, Buy, Burn function으로만 구성되어 있습니다.

function setKAIPriceToSell(uint256 _priceToSell) external onlyStrategist {
        require(_priceToSell > 1.0 ether, "out of range"); // 1불을 초과해야 매도 가능
        kaiPriceToSell = _priceToSell;
}
 
function setKAIPriceToBuy(uint256 _priceToBuy) external onlyStrategist {
        require(_priceToBuy < 1.0 ether, "out of range"); // 1 불 미만일 때 매수 가능
        kaiPriceToBuy = _priceToBuy;
}

function forceSell(address _buyingToken, uint256 _amount) external onlyStrategist {
        require(getKAIUpdatedPrice() >= kaiPriceToSell, "price is too low to sell");
        _swapToken(kai, _buyingToken, _amount);
}
 
function forceBuy(address _sellingToken, uint256 _amount) external onlyStrategist {
        require(getKAIUpdatedPrice() <= kaiPriceToBuy, "price is too high to buy");
        _swapToken(_sellingToken, kai, _amount);
}
 
function _swapToken(address _inputToken, address _outputToken, uint256 _amount) internal {
        if (_amount == 0) return;
        uint256 _maxAmount = maxAmountToTrade[_inputToken];
 
        if (_maxAmount > 0 && _maxAmount < _amount) {
            _amount = _maxAmount;
        }
 
        address[] memory _path;
        IERC20(_inputToken).safeIncreaseAllowance(address(klayswapFactory), _amount);
        IKlayswapFactory(klayswapFactory).exchangeKctPos(_inputToken, _amount, _outputToken, 1, _path);
}

Last updated