Skip to content

Create JoinStockCompany based on EIP-20 #59

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions ERCs/eip-20/jsc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
node_modules
.env
coverage
coverage.json
typechain
typechain-types

#Hardhat files
cache
artifacts


node_modules
.env
coverage
coverage.json
typechain
typechain-types

#Hardhat files
cache
artifacts

192 changes: 192 additions & 0 deletions ERCs/eip-20/jsc/.openzeppelin/unknown-31337.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
{
"manifestVersion": "3.2",
"admin": {
"address": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
"txHash": "0xfb68b8894e29ce484b5cddd4f387cd8af79b3b6cf9e9df17a363aed4a4e23705"
},
"proxies": [
{
"address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0",
"txHash": "0x43a7693f2382a3452f2b0cf965ff29a9f6dbbaf8ceb7118982242fac2ad15d0d",
"kind": "transparent"
}
],
"impls": {
"133533f85f007949b76aa5b5a360a3df2df8ef1890d83e2b14ac72290d46dc70": {
"address": "0x5FbDB2315678afecb367f032d93F642f64180aa3",
"txHash": "0x7e8c03190a3edbbe1d3497ea218f1a83877c36aed27476b4ebb62653f1c8000c",
"layout": {
"solcVersion": "0.8.17",
"storage": [
{
"label": "_initialized",
"offset": 0,
"slot": "0",
"type": "t_uint8",
"contract": "Initializable",
"src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63",
"retypedFrom": "bool"
},
{
"label": "_initializing",
"offset": 1,
"slot": "0",
"type": "t_bool",
"contract": "Initializable",
"src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68"
},
{
"label": "__gap",
"offset": 0,
"slot": "1",
"type": "t_array(t_uint256)50_storage",
"contract": "ContextUpgradeable",
"src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36"
},
{
"label": "_balances",
"offset": 0,
"slot": "51",
"type": "t_mapping(t_address,t_uint256)",
"contract": "ERC20Upgradeable",
"src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:40"
},
{
"label": "_allowances",
"offset": 0,
"slot": "52",
"type": "t_mapping(t_address,t_mapping(t_address,t_uint256))",
"contract": "ERC20Upgradeable",
"src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:42"
},
{
"label": "_totalSupply",
"offset": 0,
"slot": "53",
"type": "t_uint256",
"contract": "ERC20Upgradeable",
"src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:44"
},
{
"label": "_name",
"offset": 0,
"slot": "54",
"type": "t_string_storage",
"contract": "ERC20Upgradeable",
"src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:46"
},
{
"label": "_symbol",
"offset": 0,
"slot": "55",
"type": "t_string_storage",
"contract": "ERC20Upgradeable",
"src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:47"
},
{
"label": "__gap",
"offset": 0,
"slot": "56",
"type": "t_array(t_uint256)45_storage",
"contract": "ERC20Upgradeable",
"src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:376"
},
{
"label": "_supportTokens",
"offset": 0,
"slot": "101",
"type": "t_array(t_contract(IERC1363Upgradeable)97)dyn_storage",
"contract": "AJointStockCompany",
"src": "contracts/AJointStockCompany.sol:40"
},
{
"label": "_virtualWithdrawnEarningsByOwner",
"offset": 0,
"slot": "102",
"type": "t_mapping(t_address,t_mapping(t_contract(IERC1363Upgradeable)97,t_uint256))",
"contract": "AJointStockCompany",
"src": "contracts/AJointStockCompany.sol:41"
},
{
"label": "_virtualTotalWithdrawnEarnings",
"offset": 0,
"slot": "103",
"type": "t_mapping(t_contract(IERC1363Upgradeable)97,t_uint256)",
"contract": "AJointStockCompany",
"src": "contracts/AJointStockCompany.sol:42"
},
{
"label": "_virtualTotalRetainedEarnings",
"offset": 0,
"slot": "104",
"type": "t_mapping(t_contract(IERC1363Upgradeable)97,t_uint256)",
"contract": "AJointStockCompany",
"src": "contracts/AJointStockCompany.sol:43"
},
{
"label": "__gap",
"offset": 0,
"slot": "105",
"type": "t_array(t_uint256)50_storage",
"contract": "AJointStockCompany",
"src": "contracts/AJointStockCompany.sol:150"
}
],
"types": {
"t_address": {
"label": "address",
"numberOfBytes": "20"
},
"t_array(t_contract(IERC1363Upgradeable)97)dyn_storage": {
"label": "contract IERC1363Upgradeable[]",
"numberOfBytes": "32"
},
"t_array(t_uint256)45_storage": {
"label": "uint256[45]",
"numberOfBytes": "1440"
},
"t_array(t_uint256)50_storage": {
"label": "uint256[50]",
"numberOfBytes": "1600"
},
"t_bool": {
"label": "bool",
"numberOfBytes": "1"
},
"t_contract(IERC1363Upgradeable)97": {
"label": "contract IERC1363Upgradeable",
"numberOfBytes": "20"
},
"t_mapping(t_address,t_mapping(t_address,t_uint256))": {
"label": "mapping(address => mapping(address => uint256))",
"numberOfBytes": "32"
},
"t_mapping(t_address,t_mapping(t_contract(IERC1363Upgradeable)97,t_uint256))": {
"label": "mapping(address => mapping(contract IERC1363Upgradeable => uint256))",
"numberOfBytes": "32"
},
"t_mapping(t_address,t_uint256)": {
"label": "mapping(address => uint256)",
"numberOfBytes": "32"
},
"t_mapping(t_contract(IERC1363Upgradeable)97,t_uint256)": {
"label": "mapping(contract IERC1363Upgradeable => uint256)",
"numberOfBytes": "32"
},
"t_string_storage": {
"label": "string",
"numberOfBytes": "32"
},
"t_uint256": {
"label": "uint256",
"numberOfBytes": "32"
},
"t_uint8": {
"label": "uint8",
"numberOfBytes": "1"
}
}
}
}
}
}
12 changes: 12 additions & 0 deletions ERCs/eip-20/jsc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# ERC5453 RefImpl

Reference implementation for ERC5453.

## Script

```sh
npx hardhat test
```

## TODO
- [ ] Formalize the format how to identify a function or go with regular EIP-1271 type.
151 changes: 151 additions & 0 deletions ERCs/eip-20/jsc/contracts/AJointStockCompany.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/interfaces/IERC1363Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/interfaces/IERC1363ReceiverUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

/**
* @title JointStockCompany
* @author Zainan Victor Zhou
* @notice A JointStockCompany is a company with a defined "stock" that represents a share of the
* future revenue of a company.
* The company can issue new shares to investors, and investors can withdraw their earnings
* from the company.
* The company can support multiple tokens, and investors can withdraw their earnings in
* any of the supported tokens.
* Unlike a traditional stock when an income is received it's the company's discretion to
* decidew when and how much of the income to distribute to the shareholders, in this
* contract the income is distributed to under the name of the shareholders immediately.
*
* The company can also issue new shares to new shareholders to share the future income
* with them.
*
* TODO(xinbenlv): we haven't check for math overflow. DO NOT USE IN PRODUCTION.
*/
abstract contract AJointStockCompany is Initializable,
ERC20Upgradeable, IERC1363ReceiverUpgradeable {

event SharesPurchased(address indexed buyer, uint256 amount);
event SupportedTokenAdded(IERC1363Upgradeable indexed token);
// Address = 0 are reserved for the native token of the blockchain
// For mainnnet it's ETH
// For goerli it's goerliETH
// For sepolia its sepoliaETH

/// @dev The tokens that the company supports.
/// specifically, when token address is 0x0, it means the native token of the blockchain
/// that the company is deployed on.
/// For example, Mainnet is ETH, Goerli is goerliETH, Sepolia is sepoliaETH
IERC1363Upgradeable[] internal _supportTokens;
mapping (address => mapping (IERC1363Upgradeable => uint256)) internal _virtualWithdrawnEarningsByOwner;
mapping (IERC1363Upgradeable => uint256) _virtualTotalWithdrawnEarnings;
mapping (IERC1363Upgradeable => uint256) _virtualTotalRetainedEarnings;

function __AJointStockCompany_init(string memory name, string memory symbol) internal onlyInitializing {
__ERC20_init(name, symbol);
}

/* --- Public or External Methods --- */

/**
* @notice Handle the receipt of ERC1363 tokens
* @dev Any ERC1363 smart contract calls this function on the recipient
* after a `transfer` or a `transferFrom`. This function MAY throw to revert and reject the
* transfer. Return of other than the magic value MUST result in the
* transaction being reverted.
* Note: the token contract address is always the message sender.
*
* @param amount uint256 The amount of tokens transferred
* @return `bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))` unless throwing
*/
function onTransferReceived(
address /*operator*/,
address /*from*/,
uint256 amount,
bytes memory /*data*/
) external returns (bytes4) {
_virtualTotalRetainedEarnings[IERC1363Upgradeable(msg.sender)]+= amount;
return 0x4b1d4fbc; // bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))
}

function getShares(address _to) external view returns (uint256) {
return balanceOf(_to);
}

function getSupportTokens() external view returns (IERC1363Upgradeable[] memory) {
return _supportTokens;
}

function getSupportTokenCount() external view returns (uint256) {
return _supportTokens.length;
}

function withdrawableAmount(address _to, IERC1363Upgradeable _token) external view returns (uint256) {
return _withdrawableAmount(_to, _token);
}

/* --- Internal or Private Methods --- */

function _withdrawableAmount(address _to, IERC1363Upgradeable _token) internal view returns (uint256) {
uint256 sharesOfTo = balanceOf(_to);
_validateTotalRetainedToken(_token);
return _virtualTotalRetainedEarnings[_token] / sharesOfTo - _virtualWithdrawnEarningsByOwner[_to][_token];
}

function _addSupportedToken(IERC1363Upgradeable _token) internal {
_supportTokens.push(_token);
emit SupportedTokenAdded(_token);
}

function _balanceOf(address _to) internal view returns (uint256) {
// This was due to the fact we use ERC20Upgradeable from OZ
// Ideally it should be _balances but we don't have access to it.
return balanceOf(_to);
}

function _withdrawEarnings(address payable _to, IERC1363Upgradeable _token, uint256 _amount) internal {
require(_amount > 0, "StockToken: amount must be greater than 0");
_validateTotalRetainedToken(_token);

uint256 localWithdrawableAmount = _withdrawableAmount(_to, _token);
require(localWithdrawableAmount >= _amount, "StockToken: not enough retained amount");

_virtualWithdrawnEarningsByOwner[_to][_token] += _amount;
if (address(_token) == address(0)) _to.transfer(_amount);
else _token.transfer(_to, _amount);
_validateTotalRetainedToken(_token);
}

// Disallow transfer
function _transfer(address /*sender*/, address /*recipient*/, uint256 /*amount*/) internal virtual override {
revert("StockToken: transfer is not supported for now.");
}

function _issueNewShare (address _to, uint256 _share) internal {
// When issueing new share
// It's equivalent that the new share part of retained earnings are withdrawn
for (uint256 i = 0; i < _supportTokens.length; i++) {
IERC1363Upgradeable _token = _supportTokens[i];
_validateTotalRetainedToken(_token);
uint256 _totalVirtualRetainedEarningOfToken = _virtualTotalRetainedEarnings[_token];
uint256 _virtualWithdrawnEarningsByNewShare = _totalVirtualRetainedEarningOfToken / _share;
_virtualWithdrawnEarningsByOwner[_to][_token] = _virtualWithdrawnEarningsByNewShare;
_virtualTotalRetainedEarnings[_token] += _virtualWithdrawnEarningsByNewShare;
_virtualTotalWithdrawnEarnings[_token] += _virtualWithdrawnEarningsByNewShare;
_validateTotalRetainedToken(_token);
}
_mint(_to, _share);
}

function _validateTotalRetainedToken(IERC1363Upgradeable _token) view internal {
uint256 expectedBalance =
_virtualTotalRetainedEarnings[_token] -
_virtualTotalWithdrawnEarnings[_token];
require(
(address(_token) != address(0) ? _token.balanceOf(address(this)) : address(this).balance) >= expectedBalance,
"StockToken: Company has insufficient balance");
}

uint256[50] private __gap;
}
Loading