Add BankHub contract and fix IDRCoin contract
This commit is contained in:
parent
0d021958d5
commit
b4196c8b26
125
src/BankHub.sol
125
src/BankHub.sol
@ -8,6 +8,8 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/U
|
|||||||
contract BankHub is UUPSUpgradeable {
|
contract BankHub is UUPSUpgradeable {
|
||||||
// constants
|
// constants
|
||||||
uint32 public constant MIN_INTEREST_RATE = 5;
|
uint32 public constant MIN_INTEREST_RATE = 5;
|
||||||
|
uint32 public constant DENOMINATOR = 100;
|
||||||
|
uint256 public constant MIN_LOAN_AMOUNT = 10e18;
|
||||||
|
|
||||||
// state variables
|
// state variables
|
||||||
address public owner;
|
address public owner;
|
||||||
@ -15,13 +17,37 @@ contract BankHub is UUPSUpgradeable {
|
|||||||
|
|
||||||
// mappings
|
// mappings
|
||||||
mapping(address => bool) public whiteListed;
|
mapping(address => bool) public whiteListed;
|
||||||
mapping(address => bool) public approved;
|
|
||||||
mapping(address => uint32) public interestRate;
|
mapping(address => uint32) public interestRate;
|
||||||
mapping(address => uint256) public depositTimestamp;
|
mapping(address => uint256) public depositTimestamp;
|
||||||
mapping(address => uint256) public savingAmount;
|
mapping(address => uint256) public savingAmount;
|
||||||
|
|
||||||
|
// modifier
|
||||||
|
modifier onlyOwner() {
|
||||||
|
if (msg.sender != owner) {
|
||||||
|
revert notOwner();
|
||||||
|
}
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier onlyWhiteListed() {
|
||||||
|
if (!whiteListed[msg.sender]) {
|
||||||
|
revert notWhiteListed();
|
||||||
|
}
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// error
|
||||||
|
error notOwner();
|
||||||
|
error notWhiteListed();
|
||||||
|
error insufficientLoanAmount();
|
||||||
|
error invalidInterestRate();
|
||||||
|
|
||||||
|
// event
|
||||||
|
event Deposit(address indexed user, address indexed bank, uint256 amount);
|
||||||
|
event Withdraw(address indexed user, address indexed bank, uint256 amount);
|
||||||
|
event Approved(address indexed bank);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// initialize
|
|
||||||
_disableInitializers();
|
_disableInitializers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,10 +56,15 @@ contract BankHub is UUPSUpgradeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// user function
|
// user function
|
||||||
function deposit(uint256 _amount, address _toBank) public {
|
// depositing IDRCoin to whitelisted bank, user would then have saving account with interest
|
||||||
require(whiteListed[_toBank], "bank not whitelisted");
|
function depositToBank(uint256 _amount, address _toBank) public {
|
||||||
require(approved[_toBank], "bank not approved");
|
if (!whiteListed[_toBank]) {
|
||||||
require(idrcoin.balanceOf(msg.sender) >= _amount, "insufficient balance");
|
revert notWhiteListed();
|
||||||
|
}
|
||||||
|
require(
|
||||||
|
idrcoin.balanceOf(msg.sender) >= _amount,
|
||||||
|
"insufficient balance"
|
||||||
|
);
|
||||||
|
|
||||||
// transfer user IDRCoin to bank
|
// transfer user IDRCoin to bank
|
||||||
idrcoin.transferFrom(msg.sender, _toBank, _amount);
|
idrcoin.transferFrom(msg.sender, _toBank, _amount);
|
||||||
@ -41,8 +72,11 @@ contract BankHub is UUPSUpgradeable {
|
|||||||
// update user deposit timestamp and saving amount
|
// update user deposit timestamp and saving amount
|
||||||
depositTimestamp[msg.sender] = block.timestamp;
|
depositTimestamp[msg.sender] = block.timestamp;
|
||||||
savingAmount[msg.sender] += _amount;
|
savingAmount[msg.sender] += _amount;
|
||||||
|
emit Deposit(msg.sender, _toBank, _amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// withdraw IDRCoin from saving account
|
||||||
|
// user's interest would be applied here
|
||||||
function withdraw(uint256 _amount, address _fromBank) public {
|
function withdraw(uint256 _amount, address _fromBank) public {
|
||||||
require(whiteListed[_fromBank], "bank not whitelisted");
|
require(whiteListed[_fromBank], "bank not whitelisted");
|
||||||
require(savingAmount[msg.sender] >= _amount, "insufficient balance");
|
require(savingAmount[msg.sender] >= _amount, "insufficient balance");
|
||||||
@ -50,44 +84,60 @@ contract BankHub is UUPSUpgradeable {
|
|||||||
// calculate interest
|
// calculate interest
|
||||||
uint256 timePassed = block.timestamp - depositTimestamp[msg.sender];
|
uint256 timePassed = block.timestamp - depositTimestamp[msg.sender];
|
||||||
uint256 interest = (_amount * timePassed * interestRate[msg.sender]) /
|
uint256 interest = (_amount * timePassed * interestRate[msg.sender]) /
|
||||||
100;
|
DENOMINATOR /
|
||||||
|
365 days;
|
||||||
|
|
||||||
// transfer amount + interest to user
|
// update user savingAmount
|
||||||
idrcoin.transferFrom(
|
// interest is not deducted from user savingAmount because it would underflow
|
||||||
address(_fromBank),
|
savingAmount[msg.sender] -= _amount;
|
||||||
msg.sender,
|
|
||||||
_amount + interest
|
// instead, it would be minted to user
|
||||||
);
|
idrcoin.mint(msg.sender, interest);
|
||||||
|
|
||||||
|
// transfer amount
|
||||||
|
idrcoin.transferFrom(address(_fromBank), msg.sender, _amount);
|
||||||
|
|
||||||
|
emit Withdraw(msg.sender, _fromBank, _amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// bank function
|
// bank function
|
||||||
|
// get IDRCoin for bank reserve
|
||||||
|
function getIDRCoinLoan(
|
||||||
|
address _bank,
|
||||||
|
uint256 _amount
|
||||||
|
) public onlyWhiteListed {
|
||||||
|
require(msg.sender == _bank, "only bank can receive loan from BankHub");
|
||||||
|
if (_amount < MIN_LOAN_AMOUNT) {
|
||||||
|
revert insufficientLoanAmount();
|
||||||
|
}
|
||||||
|
|
||||||
// this function is MANDATORY for bank to call before any deposit can be made
|
idrcoin.mint(_bank, _amount);
|
||||||
function approve() public {
|
}
|
||||||
require(whiteListed[msg.sender], "bank not whitelisted");
|
|
||||||
idrcoin.approve(address(this), idrcoin.balanceOf(msg.sender));
|
|
||||||
approved[msg.sender] = true;
|
|
||||||
|
|
||||||
// send IDRCoin to bank as reserve for operation
|
// set interest rate for saving account
|
||||||
idrcoin.transferFrom(owner, msg.sender, 10 ether);
|
// this function would retroactively apply the new interest rate to all user savingAmount
|
||||||
|
function setInterestRate(uint32 _interestRate) public onlyWhiteListed {
|
||||||
|
interestRate[msg.sender] = _interestRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
// admin function
|
// admin function
|
||||||
function changeOwner(address _newOwner) public {
|
// change owner
|
||||||
require(msg.sender == owner, "only owner can change owner");
|
function changeOwner(address _newOwner) public onlyOwner {
|
||||||
owner = _newOwner;
|
owner = _newOwner;
|
||||||
}
|
}
|
||||||
|
|
||||||
// whitelist partner bank
|
// whitelist partner bank, set interest rate and approve unlimited IDRCoin transfer by this contract
|
||||||
function whiteList(address _bank) public {
|
function whiteList(address _bank) public onlyOwner {
|
||||||
require(msg.sender == owner, "only owner can whitelist");
|
|
||||||
whiteListed[_bank] = true;
|
whiteListed[_bank] = true;
|
||||||
interestRate[_bank] = MIN_INTEREST_RATE;
|
interestRate[_bank] = MIN_INTEREST_RATE;
|
||||||
|
|
||||||
|
idrcoin.setApproval(_bank, type(uint256).max);
|
||||||
|
emit Approved(_bank);
|
||||||
}
|
}
|
||||||
|
|
||||||
// revoke whitelist from partner bank
|
// revoke whitelist from partner bank
|
||||||
function revokeWhiteList(address _bank) public {
|
// collect all IDRCoin from bank
|
||||||
require(msg.sender == owner, "only owner can revoke whitelist");
|
function revokeWhiteList(address _bank) public onlyOwner {
|
||||||
if (idrcoin.balanceOf(_bank) > 0) {
|
if (idrcoin.balanceOf(_bank) > 0) {
|
||||||
idrcoin.transferFrom(_bank, owner, idrcoin.balanceOf(_bank));
|
idrcoin.transferFrom(_bank, owner, idrcoin.balanceOf(_bank));
|
||||||
}
|
}
|
||||||
@ -97,4 +147,25 @@ contract BankHub is UUPSUpgradeable {
|
|||||||
function _authorizeUpgrade(address newImplementation) internal override {
|
function _authorizeUpgrade(address newImplementation) internal override {
|
||||||
require(msg.sender == owner, "only owner can authorize upgrades");
|
require(msg.sender == owner, "only owner can authorize upgrades");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// view function
|
||||||
|
function isWhiteListed(address _bank) public view returns (bool) {
|
||||||
|
return whiteListed[_bank];
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkSavingAmountIncludingInterest(
|
||||||
|
address _user
|
||||||
|
) public view returns (uint256) {
|
||||||
|
uint256 timePassed = block.timestamp - depositTimestamp[_user];
|
||||||
|
uint256 interest = (savingAmount[_user] *
|
||||||
|
timePassed *
|
||||||
|
interestRate[_user]) /
|
||||||
|
DENOMINATOR /
|
||||||
|
365 days;
|
||||||
|
uint256 taxPercent = idrcoin.TAX();
|
||||||
|
uint256 taxDenominator = idrcoin.DENOMINATOR();
|
||||||
|
uint256 tax = (interest * taxPercent) / taxDenominator;
|
||||||
|
interest -= tax;
|
||||||
|
return savingAmount[_user] + interest;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
pragma solidity 0.8.28;
|
pragma solidity 0.8.28;
|
||||||
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||||
import {IUSDT} from "./interfaces/IUSDT.sol";
|
import {IUSDT} from "./interfaces/IUSDT.sol";
|
||||||
|
import {IBankHub} from "./interfaces/IBankHub.sol";
|
||||||
|
|
||||||
contract IDRCoin is ERC20 {
|
contract IDRCoin is ERC20 {
|
||||||
// mapping
|
// mapping
|
||||||
@ -12,15 +13,16 @@ contract IDRCoin is ERC20 {
|
|||||||
// address state
|
// address state
|
||||||
address admin;
|
address admin;
|
||||||
address taxCollector;
|
address taxCollector;
|
||||||
|
address public bankHub;
|
||||||
IUSDT usdt;
|
IUSDT usdt;
|
||||||
|
|
||||||
// constant
|
// constant
|
||||||
// convertion rate from USDT to IDR
|
// convertion rate from USDT to IDR
|
||||||
uint256 constant CONVERSION_RATE = 16000;
|
uint256 constant public CONVERSION_RATE = 16000;
|
||||||
|
|
||||||
// enforce ppn 12% for ALL transaction involving IDRC token
|
// enforce ppn 12% for ALL MINTING transaction involving IDRC token
|
||||||
uint256 constant TAX = 12;
|
uint256 constant public TAX = 12;
|
||||||
uint256 constant DENOMINATOR = 100;
|
uint256 constant public DENOMINATOR = 100;
|
||||||
|
|
||||||
modifier onlyAdmin() {
|
modifier onlyAdmin() {
|
||||||
if (msg.sender != admin) {
|
if (msg.sender != admin) {
|
||||||
@ -29,13 +31,26 @@ contract IDRCoin is ERC20 {
|
|||||||
_;
|
_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
modifier onlyBankHub() {
|
||||||
|
if (msg.sender != bankHub) {
|
||||||
|
revert notBankHub();
|
||||||
|
}
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
// error
|
// error
|
||||||
error notAdmin();
|
error notAdmin();
|
||||||
|
error notBankHub();
|
||||||
error insufficientBalance();
|
error insufficientBalance();
|
||||||
error insufficientAllowances();
|
error insufficientAllowances();
|
||||||
error addressZero();
|
error addressZero();
|
||||||
|
error BankCannotManualApprove();
|
||||||
|
|
||||||
// event
|
// event
|
||||||
|
event IDRC_Transfer(address indexed from, address indexed to, uint256 value);
|
||||||
|
event IDRC_Approval(address indexed owner, address indexed spender, uint256 value);
|
||||||
|
event IDRC_Mint(address indexed to, uint256 value);
|
||||||
|
event IDRC_Burn(address indexed from, uint256 value);
|
||||||
|
|
||||||
// constructor
|
// constructor
|
||||||
constructor(
|
constructor(
|
||||||
@ -46,22 +61,52 @@ contract IDRCoin is ERC20 {
|
|||||||
taxCollector = _taxCollector;
|
taxCollector = _taxCollector;
|
||||||
}
|
}
|
||||||
|
|
||||||
// external/public function
|
// bankHub function
|
||||||
|
// this function is called by bankHub to set allowances
|
||||||
|
// to allow bankHub spend the IDRC held by _bank
|
||||||
|
function setApproval(address _bank, uint256 amount) external onlyBankHub {
|
||||||
|
allowances[_bank][msg.sender] = amount;
|
||||||
|
|
||||||
|
emit IDRC_Approval(_bank, msg.sender, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// this mint function can be called only by bankHub
|
||||||
|
// mint new IDRC to _addr, this should be populated by whitelisted bank address
|
||||||
|
function mint(address _addr, uint256 amount) external onlyBankHub {
|
||||||
|
mint_(_addr, amount);
|
||||||
|
|
||||||
|
emit IDRC_Mint(_addr, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// external/public function
|
||||||
|
// anyone can buy IDRC with USDT with fixed conversion rate
|
||||||
function convertUSDtoIDR(uint256 amountInUSD) external {
|
function convertUSDtoIDR(uint256 amountInUSD) external {
|
||||||
usdt.transfer(address(this), amountInUSD);
|
usdt.transfer(address(this), amountInUSD);
|
||||||
uint256 amountInIDR = amountInUSD * CONVERSION_RATE * decimals();
|
uint256 amountInIDR = amountInUSD * CONVERSION_RATE * decimals();
|
||||||
_mint(msg.sender, amountInIDR);
|
_mint(msg.sender, amountInIDR);
|
||||||
|
|
||||||
|
emit IDRC_Mint(msg.sender, amountInIDR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// function to approve the _spender to spend _amount of IDRC on behalf of msg.sender
|
||||||
function approve(
|
function approve(
|
||||||
address _spender,
|
address _spender,
|
||||||
uint256 amount
|
uint256 _amount
|
||||||
) public override returns (bool) {
|
) public override returns (bool) {
|
||||||
allowances[msg.sender][_spender] = amount;
|
// we dont want the bank to manually approve
|
||||||
|
// because it can be exploited by the bank, they can set _amount to 0 and the BankHub
|
||||||
|
// cannot spend the loaned IDRC
|
||||||
|
if (IBankHub(bankHub).isWhiteListed(msg.sender)) {
|
||||||
|
revert BankCannotManualApprove();
|
||||||
|
}
|
||||||
|
allowances[msg.sender][_spender] = _amount;
|
||||||
|
|
||||||
|
emit IDRC_Approval(msg.sender, _spender, _amount);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// function to transfer the IDRC to _receiver by _amount
|
||||||
function transfer(
|
function transfer(
|
||||||
address _receiver,
|
address _receiver,
|
||||||
uint256 _amount
|
uint256 _amount
|
||||||
@ -73,26 +118,33 @@ contract IDRCoin is ERC20 {
|
|||||||
revert addressZero();
|
revert addressZero();
|
||||||
}
|
}
|
||||||
transfer_(msg.sender, _receiver, _amount);
|
transfer_(msg.sender, _receiver, _amount);
|
||||||
|
|
||||||
|
emit IDRC_Transfer(msg.sender, _receiver, _amount);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// function to transfer the IDRC from _owner to _receiver by _amount
|
||||||
|
// msg.sender must be approved by _owner to spend the _amount of IDRC
|
||||||
function transferFrom(
|
function transferFrom(
|
||||||
address _owner,
|
address _owner,
|
||||||
address _receiver,
|
address _receiver,
|
||||||
uint256 amount
|
uint256 amount
|
||||||
) public override returns (bool) {
|
) public override returns (bool) {
|
||||||
if (allowances[msg.sender][_owner] < amount) {
|
if (allowances[_owner][msg.sender] < amount) {
|
||||||
revert insufficientAllowances();
|
revert insufficientAllowances();
|
||||||
}
|
}
|
||||||
if (_receiver == address(0)) {
|
if (_receiver == address(0)) {
|
||||||
revert addressZero();
|
revert addressZero();
|
||||||
}
|
}
|
||||||
transfer_(_owner, _receiver, amount);
|
transfer_(_owner, _receiver, amount);
|
||||||
|
|
||||||
|
emit IDRC_Transfer(_owner, _receiver, amount);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// internal function
|
// internal function
|
||||||
|
|
||||||
function mint_(address _addr, uint256 amount) internal {
|
function mint_(address _addr, uint256 amount) internal {
|
||||||
// calculating the tax amount and then collect it
|
// calculating the tax amount and then collect it
|
||||||
uint256 tax = (amount * TAX) / DENOMINATOR;
|
uint256 tax = (amount * TAX) / DENOMINATOR;
|
||||||
@ -120,7 +172,6 @@ contract IDRCoin is ERC20 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// view function
|
// view function
|
||||||
|
|
||||||
function balanceOf(
|
function balanceOf(
|
||||||
address _addr
|
address _addr
|
||||||
) public view override returns (uint256 amount) {
|
) public view override returns (uint256 amount) {
|
||||||
@ -128,17 +179,27 @@ contract IDRCoin is ERC20 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setter/admin function
|
// setter/admin function
|
||||||
|
// set the bankHub address
|
||||||
|
function setBankHub(address _bankHub) external onlyAdmin {
|
||||||
|
bankHub = _bankHub;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the USDT address
|
||||||
function setUSDT(address _usdt) external onlyAdmin {
|
function setUSDT(address _usdt) external onlyAdmin {
|
||||||
usdt = IUSDT(_usdt);
|
usdt = IUSDT(_usdt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// change owner address
|
||||||
function changeOwner(address admin) external onlyAdmin {
|
function changeOwner(address admin) external onlyAdmin {
|
||||||
admin = admin;
|
admin = admin;
|
||||||
}
|
}
|
||||||
|
|
||||||
// collect USDT for public goods purpose
|
// change taxCollector address
|
||||||
|
function changeTaxCollector(address _taxCollector) external onlyAdmin {
|
||||||
|
taxCollector = _taxCollector;
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect USDT for public goods purpose
|
||||||
function withdrawUSDT(address _addr) external onlyAdmin {
|
function withdrawUSDT(address _addr) external onlyAdmin {
|
||||||
// transfer usdt to admin specified address
|
// transfer usdt to admin specified address
|
||||||
uint256 amount = usdt.balanceOf(address(this));
|
uint256 amount = usdt.balanceOf(address(this));
|
||||||
@ -148,5 +209,7 @@ contract IDRCoin is ERC20 {
|
|||||||
// burn ALL corruptor IDRC with this function
|
// burn ALL corruptor IDRC with this function
|
||||||
function burn(address _account, uint256 _amount) public onlyAdmin {
|
function burn(address _account, uint256 _amount) public onlyAdmin {
|
||||||
transfer_(_account, address(0), _amount);
|
transfer_(_account, address(0), _amount);
|
||||||
|
|
||||||
|
emit IDRC_Burn(_account, _amount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
src/interfaces/IBankHub.sol
Normal file
7
src/interfaces/IBankHub.sol
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity 0.8.28;
|
||||||
|
|
||||||
|
interface IBankHub {
|
||||||
|
function isWhiteListed(address _address) external view returns (bool);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user