// SPDX-License-Identifier: Non-Profit Open Software License 3.0 /////// [Smart SIB] version 2.0 from [Pan-Impact Korea] /////// /////// ..................................Jae-Hoon Kwak /////// pragma solidity >=0.8.1; interface Contract { function balanceOf(address owner) external view returns (uint256); function clearBalance(address owner) external; function migrate(address owner, uint256 quantity) external; function transfer(address receiver, uint256 quantity) external; function transferDividend( address receiver, uint256 quantity, uint256 divPerToken) external; } ///// Main Contract for SIB Securities(Tokens) Management ///// contract AssetTokens { address payable private superuser; string public name; string public symbol; uint256 public totalSupply; mapping (address => uint256) public balanceOf; mapping (address => SecureTrading) private secureTrade; address[] private ownerAddr; struct SecureTrading { bool online; uint256 quantity; uint256 amount; uint256 startTime; } event Construct(uint256 totalSupply); event Transfer(address indexed from, address indexed to, uint256 quantity); event SecureTrade( bool offer, bool bid, address indexed trader, uint256 tokenQuantity, uint256 totalPriceInEther ); event SetDividend( uint16 indexed year, uint256 sumInEther, address indexed dividendAddr ); event EarnDividend( address indexed from, address indexed to, uint256 quantity, uint256 dividendPerToken ); event Verify( address indexed activator, uint256 balanceOf, uint8 option, bytes32 indexed verifier, string raw ); event CmdStr(uint8 command); event CmdUintAddr( uint16 command, uint256 uintInput, address indexed addrInput ); event CleanAddr(uint256 previous, uint256 current); constructor(string memory _name, string memory _symbol, uint256 _supply) { superuser = payable(msg.sender); name = _name; symbol = _symbol; totalSupply = _supply; balanceOf[superuser] = _supply; emit Construct(totalSupply); } modifier bySuperuser() { require(msg.sender == superuser); _; } modifier byOwner() { require(balanceOf[msg.sender] > 0); _; } modifier onlyBy(address authorized) { require(msg.sender == authorized); _; } function transfer(address receiver, uint256 quantity) public { /* The function that enables transferring SIB tokens. */ require(!suspended); require(balanceOf[msg.sender] >= quantity); require(balanceOf[receiver] + quantity >= balanceOf[receiver]); if (balanceOf[receiver] == 0) { ownerAddr.push(receiver); } balanceOf[msg.sender] -= quantity; balanceOf[receiver] += quantity; emit Transfer(msg.sender, receiver, quantity); } uint256 private timeLimit = 43200; function secureOffer( uint256 tokenQuantity, uint256 totalPriceInEther ) public byOwner { /* Seller can initiate secure trading by writing the quantity of selling and price sum in ether. 'securePurchase' should be executed in 12 hours (43200 seconds) by default. */ require(!suspended); require(balanceOf[msg.sender] >= tokenQuantity); // Seller can cancel secureOffer by setting tokenQuantity as 0. if (tokenQuantity == 0) { secureTrade[msg.sender].online = false; emit SecureTrade(false, false, msg.sender, 0, 0); return; } // Starts secure trading. secureTrade[msg.sender].online = true; secureTrade[msg.sender].quantity = tokenQuantity; secureTrade[msg.sender].amount = totalPriceInEther * 1 ether; secureTrade[msg.sender].startTime = block.timestamp; emit SecureTrade( true, false, msg.sender, tokenQuantity, totalPriceInEther ); } function securePurchase( address payable seller, uint256 tokenQuantity ) public payable { /* Buyers can receive SIB tokens safely if they use securePurchase. */ require(!suspended); require(balanceOf[seller] >= secureTrade[seller].quantity); require(balanceOf[msg.sender] + tokenQuantity >= balanceOf[msg.sender]); // Secure trading execution condtions. require(secureTrade[seller].online == true); require(secureTrade[seller].quantity == tokenQuantity); require(block.timestamp - secureTrade[seller].startTime <= timeLimit); // Buyer can get offered tokens when bid price is the same or more than // the offer price. require(msg.value >= secureTrade[seller].amount); if (balanceOf[msg.sender] == 0) { ownerAddr.push(msg.sender); } balanceOf[seller] -= tokenQuantity; balanceOf[msg.sender] += tokenQuantity; emit SecureTrade( false, true, msg.sender, tokenQuantity, msg.value / 1 ether ); emit Transfer(seller, msg.sender, tokenQuantity); secureTrade[seller] = SecureTrading(false, 0, 0, 0); seller.transfer(msg.value); } uint256 private maxHolders = 999999999; uint256 public numberOfHolders = 0; function cmdUint(uint8 command, uint256 input) public bySuperuser { /* Optional function setting a maximum number of SIB holders. This function can suspend transfer functions. When SIB investment contracts are made through private placement, there is a limit on the number of investors depending on the country. To use this function, several commands as the example below must be applied to every transaction of SIB tokens. if ( numberOfHolders == maxHolders && balanceOf[receiver] == 0 && balanceOf[msg.sender] > quantity) { revert(); } if (balanceOf[receiver] == 0) { numberOfHolders++; } balanceOf[msg.sender] -= quantity; balanceOf[receiver] += quantity; if (balanceOf[msg.sender] == 0) { numberOfHolders--; */ if (command == 1) { maxHolders = input; return; } if (command == 2) { timeLimit = input; return; } else { revert(); } } mapping (uint16 => Contract) private divContract; // uint16 => Contract: Map [year] to the interface 'Contract'. mapping (uint16 => uint256) private annualDiv; mapping (address => mapping (uint16 => DividendInfo)) private divInfo; struct DividendInfo { uint256 quantity; bool withdrawn; } function setDividend( uint16 year, uint256 sumInEther, address dividendAddr ) private bySuperuser { /* Optional function to execute repayments to SIB holders annually if needed. It is named as 'dividend' for easy understanding, and SIB works differently from the actual stock dividend. This function confirms total dividends for a year and save the address of dividend contract. */ // Ethers must be sent to the dividend contract in advance. require(dividendAddr.balance >= sumInEther * 1 ether); // Fix the claimable quantity for each address. for (uint256 i = 0; i < ownerAddr.length; i++) { if (balanceOf[ownerAddr[i]] > 0) { divInfo[ownerAddr[i]][year].quantity = balanceOf[ownerAddr[i]]; } } // Set dividend per token. annualDiv[year] = sumInEther * 1 ether / totalSupply; divContract[year] = Contract(dividendAddr); emit SetDividend(year, sumInEther, dividendAddr); } function earnDividend(uint16 year) public { /* Investors who have rights for dividends can withdraw their portion. */ require(divInfo[msg.sender][year].quantity > 0); require(!divInfo[msg.sender][year].withdrawn); // It is necessary to explicitly convert values of contract type to // addresses before using an address member. require( address(divContract[year]).balance >= divInfo[msg.sender][year].quantity * annualDiv[year]); divInfo[msg.sender][year].withdrawn = true; emit EarnDividend( address(this), msg.sender, divInfo[msg.sender][year].quantity, annualDiv[year]); divContract[year].transferDividend( msg.sender, divInfo[msg.sender][year].quantity, annualDiv[year]); } function verify(uint8 option, string memory input) public byOwner { /* Verification of SIB owners for any purpose. */ bytes32 verifier = sha256(abi.encodePacked(input)); if (option == 1) { emit Verify( msg.sender, balanceOf[msg.sender], option, verifier, "" ); return; } if (option == 2) { emit Verify( msg.sender, balanceOf[msg.sender], option, verifier, input ); return; } else { revert(); } } address private crowdsaleContract; function clearBalance(address owner) external onlyBy(crowdsaleContract) { /* Clear tokens when owner withdraws fund from crowdsale. Crowdsale of SIB tokens is optional. */ balanceOf[superuser] += balanceOf[owner]; balanceOf[owner] = 0; } uint32 public incomePerUnit; function projectResult( uint32 targetNumber, uint32 successNumber, uint32 spentExpenses ) public bySuperuser { /* SIB intermediary operating Smart SIB should make a function according to the conditions of the project. Each SIB evaluation criterion has its own detailed logic, which can be algorithmized and coded. Therefore, evaluation results can be automatically derived according to measurement results, and returns for each investor can be automatically calculated. This part must be totally different among SIB projects according to each contract. This function is a very simplified and short example which should not be used in the same way. */ uint32 outcome; uint32 coefficient; uint32 outcomePayout; outcome = successNumber / targetNumber; /* Conditional sentences are recommended here according to the real conditions of a project. The code below is just an example how it could look. uint32 payout; if (condition 1) { coefficient = 0; } else if (condition 2) { payout = some formula; coefficient = payout * some formula; } else if (condition 3) { payout = some formula; coefficient = payout * some formula; } else if (condition 4) { payout = some formula; coefficient = payout * some formula; } else { coefficient = 1000000; } */ outcomePayout = spentExpenses * coefficient; incomePerUnit = outcomePayout; } string[3] private inputArray; bool private suspended; function cmdStr(uint8 command, string memory input) public bySuperuser { /* Command function for the superuser(SIB intermediary). To simplify smart contract interface, the functions for superuser are gathered in one function. When making abi specification public, the functions only for superuser can be hidden. This 'cmdStr' function receives command number and string type inputs. 1: companyInfo, 2: officialMessage, 3: documents, 4: change the name of this contract, 5: stop trading, 6: enable trading, 7: clean empty accounts in ownerAddr. */ if (command >= 1 && command <= 3) { inputArray[command - 1] = input; emit CmdStr(command); return; } if (command == 4) { name = input; emit CmdStr(command); return; } bytes32 hashedInput = sha256(abi.encodePacked(input)); // It is a confirmation process requiring correct commands. // Encrypted strings are safe as no one can know them until they are // executed for the first time. So, the method is double protectd. // Only superuser can access to the command, and the accessor should // know the key. If superuser enters the suspensionKey, trading is // suspended. The example key string in this code is "sample key 1". // Make a new key, convert it into hash value, and modify the code. bytes32 suspensionKey = hex"46ddc0ad377f60a40c40e3e12fa2f4c927b0b39edfcbc67c7c6dc6ecfb8d66a8"; if (command == 5 && hashedInput == suspensionKey) { suspended = true; emit CmdStr(command); return; } // If superuser enters the resumptionKey, trading is enabled. // "sample key 2" bytes32 resumptionKey = hex"839130414a656c620595a0890635ac46dd5ce2a12c03f38cafb7760a6d31fa9a"; if (command == 6 && hashedInput == resumptionKey) { suspended = false; emit CmdStr(command); return; } // If superuser enters the cleanKey, 0 balance addresses are deleted. // "sample key 3" bytes32 cleanKey = hex"5f2bbeaa6e69f10781cb17ec6ec45eee645bb2dce938ec536c274a1bd4df2fed"; if (command == 7 && hashedInput == cleanKey) { uint256 previous = ownerAddr.length; for (uint256 i = 0; i < ownerAddr.length; i++) { if (balanceOf[ownerAddr[i]] == 0) { delete ownerAddr[i]; ownerAddr[i] = ownerAddr[ownerAddr.length - 1]; ownerAddr.pop(); } } emit CmdStr(command); emit CleanAddr(previous, ownerAddr.length); return; } else { revert(); } } function cmdUintAddr( uint16 command, uint256 uintInput, address payable addrInput ) public bySuperuser { /* Command function for the superuser(SIB intermediary). To simplify smart contract interface, the functions for superuser are gathered in one function. When making abi specification public, the functions only for superuser can be hidden. This 'cmdUnitAddr' function receives command number, unsigned integer, and address. Even where the address is unnecessary, there are parts it is required to prevent running command mistakes. 1: set SIB tokens crowdsale, 2: mint SIB tokens, 3: burn SIB tokens, 4: change securePurchase timeLimit, 5: change superuser, 6: command for future upgarade of the contract, command > 2000 : set dividends for the year 'command'. */ // 1, crowdsale supply, the address of crowdsale contract. if (command == 1) { transfer(addrInput, uintInput); crowdsaleContract = addrInput; emit CmdUintAddr(command, uintInput, addrInput); return; } // 2, nubmer of SIB tokens to mint, address of current superuser. if (command == 2 && addrInput == superuser) { balanceOf[superuser] += uintInput; totalSupply += uintInput; emit CmdUintAddr(command, uintInput, addrInput); return; } // 3, nubmer of SIB tokens to burn, address of current superuser. if (command == 3 && addrInput == superuser) { balanceOf[superuser] -= uintInput; totalSupply -= uintInput; emit CmdUintAddr(command, uintInput, addrInput); return; } // Modify timeLimit in seconds for securePurchase. // 4, new timeLimit, address of current superuser. if (command == 4 && addrInput == superuser) { timeLimit = uintInput; emit CmdUintAddr(command, uintInput, addrInput); return; } // Change superuser. // 5, 0, address of new superuser. if (command == 5 && uintInput == 0) { superuser = addrInput; emit CmdUintAddr(command, uintInput, addrInput); return; } // A command reserved for future contract upgradability. // If contract upgrade is required, owners can move to a new contract // copying their balances to that. The function of a new contract, // 'migrate' must avoid adding up the same addresses multiple times. // Superuser should suspend trading before migration. // 6, 0, address of new contract. if (command == 6 && uintInput == 0) { Contract newContract; newContract = Contract(addrInput); for (uint256 i = 0; i < ownerAddr.length; i++) { if (balanceOf[ownerAddr[i]] > 0) { newContract.migrate(ownerAddr[i], balanceOf[ownerAddr[i]]); } } emit CmdUintAddr(command, uintInput, addrInput); return; } // Check owners at a specific time and confirm dividends. // year, total dividend in ether, address of dividend contract. if (command > 2000) { setDividend(command, uintInput, addrInput); return; } else { revert(); } } function companyInfo() public view returns (string memory) { /* SIB intermediary information. */ return inputArray[0]; } function officialMessage() public view returns (string memory) { /* Official message to investors. */ return inputArray[1]; } function documents() public view returns (string memory) { /* Useful documents link about the project. */ return inputArray[2]; } function dividendInfo(address owner, uint16 year) public view returns ( /* Dividend information. */ uint256 quantity, uint256 dividendPerToken, bool withdrawn) { quantity = divInfo[owner][year].quantity; dividendPerToken = annualDiv[year]; withdrawn = divInfo[owner][year].withdrawn; return (quantity, dividendPerToken, withdrawn); } function finalRepayment() public view returns (uint64 income) { /* Income for a SIB Token. */ income = incomePerUnit; return (income); } } ///// SIB Tokens Crowdsale ///// contract Crowdsale { address payable private superuser; string public name; string public symbol; uint256 private crowdsaleSupply; uint256 private softCap; uint256 private tokenPriceInWei; mapping (address => AccountInfo) public accountInfo; Contract public assetContract; struct AccountInfo { uint256 balance; uint256 inWei; uint256 inRoundedDownETH; } event Construct(uint256 supply, uint256 softCap, uint256 hardCap); event FundTransfer(address indexed investor, uint256 amount); event FundingCompleted(uint256 tokensSold, uint256 totalAmountRaised); event Withdraw( address indexed withdrawer, uint256 amount, bool fundingSuccess ); event CmdStr(uint8 command); constructor( string memory _name, string memory _symbol, uint256 _crowdsaleSupply, uint256 _softCap, uint256 _hardCap, address _assetAddress ) { /* Before initiating crowdsale, tokens just as much as crowdsaleSuppy must be transferred to this contract from AssetTokens contract. It is possible to sell a part of tokens from AssetTokens and keep the rest for superuser. */ superuser = payable(msg.sender); name = _name; symbol = _symbol; crowdsaleSupply = _crowdsaleSupply; softCap = _softCap * 1 ether; tokenPriceInWei = (_hardCap * 1 ether) / _crowdsaleSupply; assetContract = Contract(_assetAddress); emit Construct(_crowdsaleSupply, _softCap, _hardCap); } modifier bySuperuser() { require(msg.sender == superuser); _; } bool private saleOpened; uint256 private amountRaised; receive() external payable { /* Fallback function. */ require(saleOpened == true); uint256 saleQuantity = msg.value / tokenPriceInWei; // When investor pays more than remaining value, only remaining tokens // will be sent, and the change will be transfered to the investor. if (assetContract.balanceOf(address(this)) <= saleQuantity) { uint256 remainingValue = assetContract.balanceOf(address(this)) * tokenPriceInWei; amountRaised += remainingValue; saleOpened = false; emit FundTransfer(msg.sender, remainingValue); emit FundingCompleted(crowdsaleSupply, amountRaised); accountInfo[msg.sender].inWei += remainingValue; accountInfo[msg.sender].balance += assetContract.balanceOf(address(this)); assetContract.transfer( msg.sender, assetContract.balanceOf(address(this)) ); payable(msg.sender).transfer(msg.value - remainingValue); } else { amountRaised += msg.value; emit FundTransfer(msg.sender, msg.value); accountInfo[msg.sender].inWei += msg.value; accountInfo[msg.sender].balance += saleQuantity; assetContract.transfer(msg.sender, saleQuantity); } accountInfo[msg.sender].inRoundedDownETH = accountInfo[msg.sender].inWei / 1 ether; } function getFund(string memory input) public bySuperuser { /* When crowdsale reached its soft cap, then SIB intermediary gets amount raised. Intermediary has to enter the passKey to get the fund. */ require(!saleOpened); require(amountRaised >= softCap); bytes32 hashedInput = sha256(abi.encodePacked(input)); // "sample key 4". bytes32 passKey = hex"34d8446e9e9cd4383e2f2a0521d42d152841734f82902662f01894d6cc938649"; require(hashedInput == passKey); emit Withdraw(msg.sender, amountRaised, true); superuser.transfer(amountRaised); } function withdrawFund() public { /* When crowdsale did not reach its soft cap, it returns investors' funds. */ require( accountInfo[msg.sender].inWei > 0 && accountInfo[msg.sender].balance > 0 ); require(!saleOpened); require(amountRaised < softCap); emit Withdraw(msg.sender, accountInfo[msg.sender].inWei, false); assetContract.clearBalance(msg.sender); payable(msg.sender).transfer(accountInfo[msg.sender].inWei); accountInfo[msg.sender] = AccountInfo(0, 0, 0); } string[3] private inputArray; function cmdStr(uint8 command, string memory input) public bySuperuser { /* Commands function in strings for the superuser. 1: officialMessage, 2: documents, 3: token price in ether, 4: change the name of this contract, 5: open crowdsale, 6: close crowdsale. */ if (command >= 1 && command <= 3) { inputArray[command - 1] = input; emit CmdStr(command); return; } if (command == 4) { name = input; emit CmdStr(command); return; } bytes32 hashedInput = sha256(abi.encodePacked(input)); // If superuser enters the openKey in string, crowdsale is opened. // "sample key 5" bytes32 openKey = hex"7101d0260d3fdc13b0123178666798b628359e2ae61bf03218d8f8c441a53c0c"; if (command == 5 && hashedInput == openKey) { require(assetContract.balanceOf(address(this)) == crowdsaleSupply); require(softCap > 0 && tokenPriceInWei > 0); saleOpened = true; emit CmdStr(command); return; } // If superuser enters the closekey in string, crowdsale is closed. // "sample key 6" bytes32 closeKey = hex"a766b47c3c5064e6ca449ec7125599f25342e74d3c459e3332fbc8094406fdac"; if (command == 6 && hashedInput == closeKey) { saleOpened = false; emit CmdStr(command); emit FundingCompleted( crowdsaleSupply - assetContract.balanceOf(address(this)), amountRaised ); return; } // If there are unsold tokens, return them to superuser. // "sample key 8" bytes32 returnKey = hex"8ef0bf8e52b14af9f67a3ff62f0fd2db60858970c17b73fc0223d1da4ca00a3f"; if (command == 7 && hashedInput == returnKey) { assetContract.transfer( msg.sender, assetContract.balanceOf(address(this)) ); emit CmdStr(command); return; } else { revert(); } } /// Pure and view functions. /// function issuerInfo() public pure returns (string memory) { /* Return the name of SIB intermediary. */ string memory information = "Pan-Impact Korea"; return information; } function officialMessage() public view returns (string memory) { /* Return official message. */ return inputArray[0]; } function documents() public view returns (string memory) { /* Return document link. */ return inputArray[1]; } function tokenPrice() public view returns ( uint256 inWei, string memory inEther ) { /* Return SIB token price. */ inWei = tokenPriceInWei; inEther = inputArray[2]; return (inWei, inEther); } function crowdsaleInfo() public view returns ( uint256 totalSupply, bool crowdsaleOpened, uint256 fundRaised, uint256 remainingTokens) { /* Return crowdsale information. */ totalSupply = crowdsaleSupply; crowdsaleOpened = saleOpened; fundRaised = amountRaised; remainingTokens = assetContract.balanceOf(address(this)); return (totalSupply, crowdsaleOpened, fundRaised, remainingTokens); } } ///// Annual Repayment for SIB Investors ///// contract Dividend { string public name; address private superuser; address public assetContract; event Construct(address assetContract); event TransferDividend( address indexed from, address indexed to, uint256 amount ); event CmdUint(uint8 command, uint256 input); event CmdAddr(uint8 command, address indexed input); event Deposit(uint256 amount); constructor(string memory _name, address _assetContract) payable { /* Dividend contracts are installed every year and each contract keeps its annual dividend information. */ superuser = msg.sender; name = _name; assetContract = _assetContract; emit Construct(_assetContract); } modifier onlyBy(address authorized) { require(msg.sender == authorized); _; } function transferDividend( address payable receiver, uint256 quantity, uint256 divPerToken ) external onlyBy(assetContract) { /* The function that transfers dividends to investors. */ uint256 amount = quantity * divPerToken; emit TransferDividend(address(this), receiver, amount); receiver.transfer(amount); } function cmdAddr(uint8 command, address input) public onlyBy(superuser) { /* Commands function in address for the superuser. This 'cmdAddr' function receives command number and address. Although command number looks unnecessary in this function, it is required to prevent easy mistakes by entering only an address. 1: change superuser. */ if (command == 1) { superuser = input; emit CmdAddr(command, input); return; } else { return; } } function cmdUint( uint8 command, uint256 input ) public payable onlyBy(superuser) { /* Commands function in unsigned integers for the superuser. 1: increase ether deposits to modify balance, 2: decrease ether deposits to modify balance. */ // To increase deposits, the superuser need to enter 1, 0, and send // ethers to the contract. if (command == 1 && input == 0 && msg.value > 0) { emit CmdUint(command, input); emit Deposit(msg.value); return; } // To decrease deposits, the superuser need to enter 2, and the amount // of ether to withdraw. if (command == 2 && input > 0 && input <= address(this).balance) { emit CmdUint(command, input); payable(msg.sender).transfer(input * 1 ether); return; } else { revert(); } } }