├── .eslintrc.json ├── .gitignore ├── .jshintrc ├── .nvmrc ├── .travis.yml ├── README.md ├── contracts ├── Administratable.sol ├── CardstackToken.sol ├── Configurable.sol ├── CstLedger.sol ├── CstLibrary.sol ├── Displayable.sol ├── ERC20.sol ├── ExternalStorage.sol ├── Freezable.sol ├── IStorable.sol ├── ITokenLedger.sol ├── Migrations.sol ├── Registry.sol └── upgradeability ├── lib ├── constants.js ├── ledger.js ├── proxy.js ├── time.js └── utils.js ├── migrations ├── 1_initial_migration.js ├── 2_deploy_registry.js ├── 3_deploy_storage.js └── 4_deploy_cst_contract.js ├── package.json ├── scripts ├── add-admin.js ├── add-super-admin.js ├── cst-buy-info.js ├── cst-configure.js ├── cst-deposit-info.js ├── cst-freeze-account.js ├── cst-freeze-token.js ├── cst-grant-tokens-list.js ├── cst-grant-tokens.js ├── cst-grant-vesting-tokens.js ├── cst-mint-tokens.js ├── cst-register-ledger.js ├── cst-register.js ├── cst-release-info.js ├── cst-release-vested-tokens.js ├── cst-remove-whitelisted-transferer.js ├── cst-resume-purchases.js ├── cst-revoke-vesting.js ├── cst-suspend-purchases.js ├── cst-transfer-info.js ├── cst-unfreeze-account.js ├── cst-unfreeze-token.js ├── cst-vesting-info.js ├── cst-withdraw-info.js ├── deploy-cst.js ├── deploy-ledger.js ├── deploy-registry.js ├── ledger-info.js ├── registry-add-storage.js ├── remove-admin.js ├── remove-super-admin.js ├── send-eth-list.js ├── system-info.js └── testing │ ├── create-addresses.js │ ├── ledger-export-checksum.js │ └── sign-message.js ├── test ├── cardstack-token-allowance-test.js ├── cardstack-token-buy-test.js ├── cardstack-token-freeze-test.js ├── cardstack-token-test.js ├── cardstack-token-transfer-test.js ├── cardstack-token-upgrade-test.js ├── cardstack-token-vesting-test.js ├── contracts │ ├── TestingCardstackToken.sol │ ├── TestingCstLedger.sol │ ├── TestingRegistry.sol │ └── mocks │ │ ├── Token_v1.sol │ │ └── Token_v2.sol ├── external-storage-test.js ├── ledger-test.js ├── registry-test.js └── utils.js ├── token-abi.json ├── truffle.js └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 2017 4 | }, 5 | "extends": "eslint:recommended", 6 | "env": { 7 | "node": true, 8 | "mocha": true 9 | }, 10 | "globals": { 11 | "web3": true, 12 | "contract": true, 13 | "artifacts": true, 14 | "assert": true, 15 | "Promise": true 16 | }, 17 | "rules": { 18 | "no-console": "warn", 19 | "semi": "error" 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /build 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # misc 12 | npm-debug.log 13 | testem.log 14 | .DS_Store 15 | .node-* 16 | accounts.txt 17 | *.csv 18 | *.numbers 19 | *.log 20 | 21 | .zos.session 22 | zos.*.json 23 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 7 3 | } 4 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 8.8 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | 4 | node_js: 5 | - "8.7" 6 | 7 | cache: 8 | yarn: true 9 | directories: 10 | - node_modules 11 | 12 | before_install: 13 | - curl -o- -L https://yarnpkg.com/install.sh | bash 14 | - export PATH=$HOME/.yarn/bin:$PATH 15 | 16 | install: 17 | - yarn install 18 | - yarn run ganache > /dev/null & 19 | - sleep 10 20 | 21 | script: 22 | - yarn test 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cardstack-token [![Build Status](https://travis-ci.org/cardstack/cardstack-token.svg?branch=master)](https://travis-ci.org/cardstack/cardstack-token) 2 | This project contains the smart contracts that govern the [Cardstack](https://cardstack.com) token. 3 | 4 | The official Cardstack token, with the ERC-20 symbol `CARD` is located on mainnet at the ENS resolved name [cardstack.eth](https://etherscan.io/address/cardstack.eth). Which is currently `0x954b890704693af242613edEf1B603825afcD708`. 5 | 6 | ## Prerequisites 7 | * node 8+ 8 | 9 | * yarn 10 | 11 | * [truffle](http://truffleframework.com/) 12 | ``` 13 | yarn global add truffle 14 | ``` 15 | 16 | * [geth](https://github.com/ethereum/go-ethereum/wiki/Installation-Instructions-for-Mac) to use as CLI ethereum wallet that truffle can manipulate. 17 | ``` 18 | brew tap ethereum/ethereum 19 | brew install ethereum 20 | ``` 21 | After installing, run `geth account new` to create an account on your node. 22 | 23 | ## Installing 24 | This project leverages node 8+, please make sure to install node 8+ (or use nvm to manage your node versions). Additionally, this project leverages yarn. 25 | 26 | ``` 27 | yarn install 28 | ``` 29 | 30 | 31 | ## Testing 32 | For testing we leverage a local private blockchain [ganache](https://github.com/trufflesuite/ganache). You must first start your private blockchain: 33 | ``` 34 | yarn ganache 35 | ``` 36 | 37 | then execute the tests: 38 | ``` 39 | yarn test 40 | ``` 41 | 42 | 43 | ## Deploying 44 | 45 | ### testrpc 46 | To deploy the CST contracts to testrpc, start the testrpc blockchain. Make sure to not run the Mist client or Ethereum wallet connected to the testrpc when you perform the migration--testrpc is not good at walking and chewing gum at the same time. 47 | ``` 48 | yarn ganache 49 | ``` 50 | 51 | Then execute: 52 | ``` 53 | truffle migrate --reset --network=testrpc 54 | ``` 55 | 56 | Make a note of the address of the `Registry` and of the `CardstackToken` contract. Make sure not to lose the address of the Registry, the registry address is specified as a parameter for all contract ops commands. 57 | 58 | Register the `CardstackToken` contract with the `Registry`: 59 | ``` 60 | truffle exec ./scripts/cst-register.js --cst= --registry= --network=testrpc 61 | ``` 62 | 63 | You can view the CST system info by executing: 64 | ``` 65 | truffle exec ./scripts/system-info.js --network=testrpc -r 66 | ``` 67 | 68 | You can configure the price and details around CST by executing: 69 | ``` 70 | truffle exec ./scripts/cst-configure.js --tokenName="Cardstack Token" --tokenSymbol="CST" --buyPriceEth=0.005 --circulationCap=50000000 --maxBalance=1000 --foundation="" -r "" --network=testrpc 71 | ``` 72 | 73 | You can mint new CST's (which must exist in order for people to buy) by executing: 74 | ``` 75 | truffle exec ./scripts/cst-mint-tokens.js --amount=1000000000 -r --network=testrpc 76 | ``` 77 | 78 | You will need to whitelist buyers of CST by executing: 79 | ``` 80 | truffle exec ./scripts/cst-add-buyer.js --address= -r --network=testrpc 81 | ``` 82 | 83 | You can execute this script to get the purchase information for CST: 84 | ``` 85 | truffle exec ./scripts/cst-buy-info.js --network=testrpc -r 86 | ``` 87 | 88 | You can execute this script to get information on how to release vested tokens for CST: 89 | ``` 90 | truffle exec ./scripts/cst-release-info.js --network=testrpc -r 91 | ``` 92 | 93 | The token contract is initialized in a frozen state (same for upgraded tokens). You must unfreeze the token before people can buy tokens: 94 | ``` 95 | truffle exec ./scripts/cst-unfreeze-token.js -r "" --network=testrpc 96 | ``` 97 | 98 | ### Rinkeby 99 | To deploy the CST contracts on Rinkeby, make sure that your wallet's main account is funded. Copy your wallet's main account address into the clipboard. Close the Mist or Ethereum wallet apps if they are open (geth cannot run when Mist is running and vice versa). Then from the commandline execute: 100 | ``` 101 | geth --rinkeby --rpc --rpcapi db,eth,net,web3,personal --unlock="main account's address" 102 | ``` 103 | 104 | Enter the password for your wallet when prompted, and then wait for the latest block in Rinkeby to download (you can double check the block number here at https://www.rinkeby.io/). After you see that the latest blocks have downloaded execute the following: 105 | 106 | 107 | Then execute: 108 | ``` 109 | truffle migrate --reset --network=rinkeby 110 | ``` 111 | The deploy will make many minutes to run depending on Rinkeby network stats and gas price. 112 | 113 | Make a note of the address of the Registry and of the CardstackToken contract. Make sure not to lose the address of the Registry, the registry address is specified as a parameter for all contract ops commands. 114 | Register the `CardstackToken` contract with the `Registry`: 115 | ``` 116 | truffle exec ./scripts/cst-register.js --cst= --registry= --network=rinkeby 117 | ``` 118 | 119 | You can view the CST system info by executing: 120 | ``` 121 | truffle exec ./scripts/system-info.js --network=rinkeby -r 122 | ``` 123 | 124 | You can execute this script to get the purchase information for CST (make sure to set the price and mint tokens first before sharing this information, though): 125 | ``` 126 | truffle exec ./scripts/cst-buy-info.js --network=rinkeby -r 127 | ``` 128 | 129 | From there you can execute other scripts to configure the CST contract and/or mint tokens, etc. 130 | -------------------------------------------------------------------------------- /contracts/Administratable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 4 | 5 | contract Administratable { 6 | using SafeMath for uint256; 7 | 8 | // zOS requires that the variables are never removed nor order changed 9 | // Since this is a parent contract, no new variables can be added here 10 | address internal constant primaryInitializer = 0x0AEaF8c2Fe778797CD5464E7EB8351d28da2E823; 11 | address internal constant stagingInitializer = 0x1E65F71b024937b988fdba09814d60049e0Fc59d; 12 | address public owner; 13 | bool public adminsInitialized; 14 | address[] public adminsForIndex; 15 | address[] public superAdminsForIndex; 16 | mapping (address => bool) public admins; 17 | mapping (address => bool) public superAdmins; 18 | mapping (address => bool) private processedAdmin; 19 | mapping (address => bool) private processedSuperAdmin; 20 | 21 | event AddAdmin(address indexed admin); 22 | event RemoveAdmin(address indexed admin); 23 | event AddSuperAdmin(address indexed admin); 24 | event RemoveSuperAdmin(address indexed admin); 25 | event OwnershipTransferred( 26 | address indexed previousOwner, 27 | address indexed newOwner 28 | ); 29 | 30 | modifier onlyInitializers() { 31 | require(msg.sender == primaryInitializer || 32 | msg.sender == stagingInitializer); 33 | _; 34 | } 35 | 36 | modifier onlyOwner() { 37 | require(msg.sender == owner); 38 | _; 39 | } 40 | 41 | modifier onlyAdmins { 42 | if (msg.sender != owner && !superAdmins[msg.sender] && !admins[msg.sender]) revert(); 43 | _; 44 | } 45 | 46 | modifier onlySuperAdmins { 47 | if (msg.sender != owner && !superAdmins[msg.sender]) revert(); 48 | _; 49 | } 50 | 51 | constructor() public { 52 | owner = msg.sender; 53 | } 54 | 55 | function addSuperAdmin(address admin) public onlySuperAdmins { 56 | _addSuperAdmin(admin); 57 | 58 | emit AddSuperAdmin(admin); 59 | } 60 | 61 | function removeSuperAdmin(address admin) public onlySuperAdmins { 62 | require(admin != address(0)); 63 | superAdmins[admin] = false; 64 | 65 | emit RemoveSuperAdmin(admin); 66 | } 67 | 68 | function addAdmin(address admin) public onlySuperAdmins { 69 | require(admin != address(0)); 70 | admins[admin] = true; 71 | if (!processedAdmin[admin]) { 72 | adminsForIndex.push(admin); 73 | processedAdmin[admin] = true; 74 | } 75 | 76 | emit AddAdmin(admin); 77 | } 78 | 79 | function removeAdmin(address admin) public onlySuperAdmins { 80 | require(admin != address(0)); 81 | admins[admin] = false; 82 | 83 | emit RemoveAdmin(admin); 84 | } 85 | 86 | function transferOwnership(address _newOwner) public onlyOwner { 87 | require(_newOwner != address(0)); 88 | emit OwnershipTransferred(owner, _newOwner); 89 | owner = _newOwner; 90 | } 91 | 92 | function totalSuperAdminsMapping() public view returns (uint256) { 93 | return superAdminsForIndex.length; 94 | } 95 | 96 | function totalAdminsMapping() public view returns (uint256) { 97 | return adminsForIndex.length; 98 | } 99 | 100 | // Accepting a function param to set initial owner/admins 101 | // is a potential security vulnerability when using initialize 102 | // pattern (which is just a public function). So we hard-code 103 | // our initial admin addresses and use zOS to manage this list. 104 | function initializeAdmins() internal { 105 | require(!adminsInitialized, "Contract instance has already initialized the admins"); 106 | 107 | owner = primaryInitializer; 108 | _addSuperAdmin(stagingInitializer); 109 | 110 | adminsInitialized = true; 111 | } 112 | 113 | function _addSuperAdmin(address admin) internal { 114 | require(admin != address(0)); 115 | superAdmins[admin] = true; 116 | if (!processedSuperAdmin[admin]) { 117 | superAdminsForIndex.push(admin); 118 | processedSuperAdmin[admin] = true; 119 | } 120 | } 121 | 122 | 123 | } 124 | -------------------------------------------------------------------------------- /contracts/Configurable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | contract Configurable { 4 | function configureFromStorage() public returns (bool); 5 | } 6 | -------------------------------------------------------------------------------- /contracts/CstLedger.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | import "./Administratable.sol"; 4 | import "./ITokenLedger.sol"; 5 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 6 | import "zos-lib/contracts/migrations/Initializable.sol"; 7 | 8 | contract CstLedger is ITokenLedger, Initializable, Administratable { 9 | 10 | using SafeMath for uint256; 11 | 12 | /* zOS requires that the variables are never removed nor order changed 13 | /* BEGIN VARIABLES */ 14 | string public constant version = "2"; 15 | 16 | uint256 private _totalInCirculation; // warning this does not take into account unvested nor vested-unreleased tokens into consideration 17 | uint256 private _totalTokens; 18 | mapping (address => uint256) private _balanceOf; 19 | mapping (address => bool) private accounts; 20 | /* END VARIABLES */ 21 | 22 | function transfer(address sender, address recipient, uint256 amount) external onlyAdmins { 23 | require(sender != address(0)); 24 | require(recipient != address(0)); 25 | require(_balanceOf[sender] >= amount); 26 | 27 | _balanceOf[sender] = _balanceOf[sender].sub(amount); 28 | _balanceOf[recipient] = _balanceOf[recipient].add(amount); 29 | } 30 | 31 | function creditAccount(address account, uint256 amount) external onlyAdmins { // remove tokens 32 | require(account != address(0)); 33 | require(_balanceOf[account] >= amount); 34 | 35 | _totalInCirculation = _totalInCirculation.sub(amount); 36 | _balanceOf[account] = _balanceOf[account].sub(amount); 37 | } 38 | 39 | function debitAccount(address account, uint256 amount) external onlyAdmins { // add tokens 40 | require(account != address(0)); 41 | _totalInCirculation = _totalInCirculation.add(amount); 42 | _balanceOf[account] = _balanceOf[account].add(amount); 43 | } 44 | 45 | function totalTokens() external view returns (uint256) { 46 | return _totalTokens; 47 | } 48 | 49 | function totalInCirculation() external view returns (uint256) { 50 | return _totalInCirculation; 51 | } 52 | 53 | function balanceOf(address account) external view returns (uint256) { 54 | return _balanceOf[account]; 55 | } 56 | 57 | function mintTokens(uint256 amount) external onlyAdmins { 58 | _totalTokens = _totalTokens.add(amount); 59 | } 60 | 61 | function initialize() public onlyInitializers isInitializer { 62 | initializeAdmins(); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /contracts/CstLibrary.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | import "./ExternalStorage.sol"; 4 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 5 | 6 | library CstLibrary { 7 | using SafeMath for uint256; 8 | 9 | function getTokenName(address _storage) public view returns(bytes32) { 10 | return ExternalStorage(_storage).getBytes32Value("cstTokenName"); 11 | } 12 | 13 | function setTokenName(address _storage, bytes32 tokenName) public { 14 | ExternalStorage(_storage).setBytes32Value("cstTokenName", tokenName); 15 | } 16 | 17 | function getTokenSymbol(address _storage) public view returns(bytes32) { 18 | return ExternalStorage(_storage).getBytes32Value("cstTokenSymbol"); 19 | } 20 | 21 | function setTokenSymbol(address _storage, bytes32 tokenName) public { 22 | ExternalStorage(_storage).setBytes32Value("cstTokenSymbol", tokenName); 23 | } 24 | 25 | function getBuyPrice(address _storage) public view returns(uint256) { 26 | return ExternalStorage(_storage).getUIntValue("cstBuyPrice"); 27 | } 28 | 29 | function setBuyPrice(address _storage, uint256 value) public { 30 | ExternalStorage(_storage).setUIntValue("cstBuyPrice", value); 31 | } 32 | 33 | function getCirculationCap(address _storage) public view returns(uint256) { 34 | return ExternalStorage(_storage).getUIntValue("cstCirculationCap"); 35 | } 36 | 37 | function setCirculationCap(address _storage, uint256 value) public { 38 | ExternalStorage(_storage).setUIntValue("cstCirculationCap", value); 39 | } 40 | 41 | function getBalanceLimit(address _storage) public view returns(uint256) { 42 | return ExternalStorage(_storage).getUIntValue("cstBalanceLimit"); 43 | } 44 | 45 | function setBalanceLimit(address _storage, uint256 value) public { 46 | ExternalStorage(_storage).setUIntValue("cstBalanceLimit", value); 47 | } 48 | 49 | function getFoundation(address _storage) public view returns(address) { 50 | return ExternalStorage(_storage).getAddressValue("cstFoundation"); 51 | } 52 | 53 | function setFoundation(address _storage, address value) public { 54 | ExternalStorage(_storage).setAddressValue("cstFoundation", value); 55 | } 56 | 57 | function getAllowance(address _storage, address account, address spender) public view returns (uint256) { 58 | return ExternalStorage(_storage).getMultiLedgerValue("cstAllowance", account, spender); 59 | } 60 | 61 | function setAllowance(address _storage, address account, address spender, uint256 allowance) public { 62 | ExternalStorage(_storage).setMultiLedgerValue("cstAllowance", account, spender, allowance); 63 | } 64 | 65 | function getCustomBuyerLimit(address _storage, address buyer) public view returns (uint256) { 66 | return ExternalStorage(_storage).getLedgerValue("cstCustomBuyerLimit", buyer); 67 | } 68 | 69 | function setCustomBuyerLimit(address _storage, address buyer, uint256 value) public { 70 | ExternalStorage(_storage).setLedgerValue("cstCustomBuyerLimit", buyer, value); 71 | } 72 | 73 | function getCustomBuyerForIndex(address _storage, uint256 index) public view returns (address) { 74 | return ExternalStorage(_storage).ledgerEntryForIndex(keccak256("cstCustomBuyerLimit"), index); 75 | } 76 | 77 | function getCustomBuyerMappingCount(address _storage) public view returns(uint256) { 78 | return ExternalStorage(_storage).getLedgerCount("cstCustomBuyerLimit"); 79 | } 80 | 81 | function getApprovedBuyer(address _storage, address buyer) public view returns (bool) { 82 | return ExternalStorage(_storage).getBooleanMapValue("cstApprovedBuyer", buyer); 83 | } 84 | 85 | function setApprovedBuyer(address _storage, address buyer, bool value) public { 86 | ExternalStorage(_storage).setBooleanMapValue("cstApprovedBuyer", buyer, value); 87 | } 88 | 89 | function getApprovedBuyerForIndex(address _storage, uint256 index) public view returns (address) { 90 | return ExternalStorage(_storage).booleanMapEntryForIndex(keccak256("cstApprovedBuyer"), index); 91 | } 92 | 93 | function getApprovedBuyerMappingCount(address _storage) public view returns(uint256) { 94 | return ExternalStorage(_storage).getBooleanMapCount("cstApprovedBuyer"); 95 | } 96 | 97 | function getTotalUnvestedAndUnreleasedTokens(address _storage) public view returns(uint256) { 98 | return ExternalStorage(_storage).getUIntValue("cstUnvestedAndUnreleasedTokens"); 99 | } 100 | 101 | function setTotalUnvestedAndUnreleasedTokens(address _storage, uint256 value) public { 102 | ExternalStorage(_storage).setUIntValue("cstUnvestedAndUnreleasedTokens", value); 103 | } 104 | 105 | function vestingMappingSize(address _storage) public view returns(uint256) { 106 | return ExternalStorage(_storage).getLedgerCount("cstFullyVestedAmount"); 107 | } 108 | 109 | function vestingBeneficiaryForIndex(address _storage, uint256 index) public view returns(address) { 110 | return ExternalStorage(_storage).ledgerEntryForIndex(keccak256("cstFullyVestedAmount"), index); 111 | } 112 | 113 | function releasableAmount(address _storage, address beneficiary) public view returns (uint256) { 114 | uint256 releasedAmount = getVestingReleasedAmount(_storage, beneficiary); 115 | return vestedAvailableAmount(_storage, beneficiary).sub(releasedAmount); 116 | } 117 | 118 | function vestedAvailableAmount(address _storage, address beneficiary) public view returns (uint256) { 119 | uint256 start = getVestingStart(_storage, beneficiary); 120 | uint256 fullyVestedAmount = getFullyVestedAmount(_storage, beneficiary); 121 | 122 | if (start == 0 || fullyVestedAmount == 0) { 123 | return 0; 124 | } 125 | 126 | uint256 duration = getVestingDuration(_storage, beneficiary); 127 | if (duration == 0) { 128 | return 0; 129 | } 130 | uint256 cliff = getVestingCliff(_storage, beneficiary); 131 | uint256 revokeDate = getVestingRevokeDate(_storage, beneficiary); 132 | 133 | if (now < cliff || (revokeDate > 0 && revokeDate < cliff)) { 134 | return 0; 135 | } else if (revokeDate > 0 && revokeDate > cliff) { 136 | return fullyVestedAmount.mul(revokeDate.sub(start)).div(duration); 137 | } else if (now >= start.add(duration)) { 138 | return fullyVestedAmount; 139 | } else { 140 | return fullyVestedAmount.mul(now.sub(start)).div(duration); 141 | } 142 | } 143 | 144 | function vestedAmount(address _storage, address beneficiary) public view returns (uint256) { 145 | uint256 start = getVestingStart(_storage, beneficiary); 146 | uint256 fullyVestedAmount = getFullyVestedAmount(_storage, beneficiary); 147 | 148 | if (start == 0 || fullyVestedAmount == 0) { 149 | return 0; 150 | } 151 | 152 | uint256 duration = getVestingDuration(_storage, beneficiary); 153 | if (duration == 0) { 154 | return 0; 155 | } 156 | 157 | uint256 revokeDate = getVestingRevokeDate(_storage, beneficiary); 158 | 159 | if (now <= start) { 160 | return 0; 161 | } else if (revokeDate > 0) { 162 | return fullyVestedAmount.mul(revokeDate.sub(start)).div(duration); 163 | } else if (now >= start.add(duration)) { 164 | return fullyVestedAmount; 165 | } else { 166 | return fullyVestedAmount.mul(now.sub(start)).div(duration); 167 | } 168 | } 169 | 170 | function canGrantVestedTokens(address _storage, address beneficiary) public view returns (bool) { 171 | uint256 existingFullyVestedAmount = getFullyVestedAmount(_storage, beneficiary); 172 | if (existingFullyVestedAmount == 0) { 173 | return true; 174 | } 175 | 176 | uint256 existingVestedAmount = vestedAvailableAmount(_storage, beneficiary); 177 | uint256 existingReleasedAmount = getVestingReleasedAmount(_storage, beneficiary); 178 | uint256 revokeDate = getVestingRevokeDate(_storage, beneficiary); 179 | 180 | if (revokeDate > 0 || 181 | (existingVestedAmount == existingFullyVestedAmount && 182 | existingReleasedAmount == existingFullyVestedAmount)) { 183 | return true; 184 | } 185 | 186 | return false; 187 | } 188 | 189 | function canRevokeVesting(address _storage, address beneficiary) public view returns (bool) { 190 | bool isRevocable = getVestingRevocable(_storage, beneficiary); 191 | uint256 revokeDate = getVestingRevokeDate(_storage, beneficiary); 192 | uint256 start = getVestingStart(_storage, beneficiary); 193 | uint256 duration = getVestingDuration(_storage, beneficiary); 194 | 195 | return start > 0 && 196 | isRevocable && 197 | revokeDate == 0 && 198 | now < start.add(duration); 199 | } 200 | 201 | function revokeVesting(address _storage, address beneficiary) public { 202 | require(canRevokeVesting(_storage, beneficiary)); 203 | 204 | uint256 totalUnvestedAndUnreleasedAmount = getTotalUnvestedAndUnreleasedTokens(_storage); 205 | uint256 unvestedAmount = getFullyVestedAmount(_storage, beneficiary).sub(vestedAvailableAmount(_storage, beneficiary)); 206 | 207 | setVestingRevokeDate(_storage, beneficiary, now); 208 | setTotalUnvestedAndUnreleasedTokens(_storage, totalUnvestedAndUnreleasedAmount.sub(unvestedAmount)); 209 | } 210 | 211 | function getVestingSchedule(address _storage, address _beneficiary) public 212 | view returns (uint256 startDate, 213 | uint256 cliffDate, 214 | uint256 durationSec, 215 | uint256 fullyVestedAmount, 216 | uint256 releasedAmount, 217 | uint256 revokeDate, 218 | bool isRevocable) { 219 | startDate = getVestingStart(_storage, _beneficiary); 220 | cliffDate = getVestingCliff(_storage, _beneficiary); 221 | durationSec = getVestingDuration(_storage, _beneficiary); 222 | fullyVestedAmount = getFullyVestedAmount(_storage, _beneficiary); 223 | releasedAmount = getVestingReleasedAmount(_storage, _beneficiary); 224 | revokeDate = getVestingRevokeDate(_storage, _beneficiary); 225 | isRevocable = getVestingRevocable(_storage, _beneficiary); 226 | } 227 | 228 | function setVestingSchedule(address _storage, 229 | address beneficiary, 230 | uint256 fullyVestedAmount, 231 | uint256 startDate, 232 | uint256 cliffDate, 233 | uint256 duration, 234 | bool isRevocable) public { 235 | require(canGrantVestedTokens(_storage, beneficiary)); 236 | 237 | uint256 totalUnvestedAndUnreleasedAmount = getTotalUnvestedAndUnreleasedTokens(_storage); 238 | setTotalUnvestedAndUnreleasedTokens(_storage, totalUnvestedAndUnreleasedAmount.add(fullyVestedAmount)); 239 | 240 | ExternalStorage(_storage).setLedgerValue("cstVestingStart", beneficiary, startDate); 241 | ExternalStorage(_storage).setLedgerValue("cstVestingCliff", beneficiary, cliffDate); 242 | ExternalStorage(_storage).setLedgerValue("cstVestingDuration", beneficiary, duration); 243 | ExternalStorage(_storage).setLedgerValue("cstFullyVestedAmount", beneficiary, fullyVestedAmount); 244 | ExternalStorage(_storage).setBooleanMapValue("cstVestingRevocable", beneficiary, isRevocable); 245 | 246 | setVestingRevokeDate(_storage, beneficiary, 0); 247 | setVestingReleasedAmount(_storage, beneficiary, 0); 248 | } 249 | 250 | function releaseVestedTokens(address _storage, address beneficiary) public { 251 | uint256 unreleased = releasableAmount(_storage, beneficiary); 252 | uint256 releasedAmount = getVestingReleasedAmount(_storage, beneficiary); 253 | uint256 totalUnvestedAndUnreleasedAmount = getTotalUnvestedAndUnreleasedTokens(_storage); 254 | 255 | releasedAmount = releasedAmount.add(unreleased); 256 | setVestingReleasedAmount(_storage, beneficiary, releasedAmount); 257 | setTotalUnvestedAndUnreleasedTokens(_storage, totalUnvestedAndUnreleasedAmount.sub(unreleased)); 258 | } 259 | 260 | function getVestingStart(address _storage, address beneficiary) public view returns(uint256) { 261 | return ExternalStorage(_storage).getLedgerValue("cstVestingStart", beneficiary); 262 | } 263 | 264 | function getVestingCliff(address _storage, address beneficiary) public view returns(uint256) { 265 | return ExternalStorage(_storage).getLedgerValue("cstVestingCliff", beneficiary); 266 | } 267 | 268 | function getVestingDuration(address _storage, address beneficiary) public view returns(uint256) { 269 | return ExternalStorage(_storage).getLedgerValue("cstVestingDuration", beneficiary); 270 | } 271 | 272 | function getFullyVestedAmount(address _storage, address beneficiary) public view returns(uint256) { 273 | return ExternalStorage(_storage).getLedgerValue("cstFullyVestedAmount", beneficiary); 274 | } 275 | 276 | function getVestingRevocable(address _storage, address beneficiary) public view returns(bool) { 277 | return ExternalStorage(_storage).getBooleanMapValue("cstVestingRevocable", beneficiary); 278 | } 279 | 280 | function setVestingReleasedAmount(address _storage, address beneficiary, uint256 value) public { 281 | ExternalStorage(_storage).setLedgerValue("cstVestingReleasedAmount", beneficiary, value); 282 | } 283 | 284 | function getVestingReleasedAmount(address _storage, address beneficiary) public view returns(uint256) { 285 | return ExternalStorage(_storage).getLedgerValue("cstVestingReleasedAmount", beneficiary); 286 | } 287 | 288 | function setVestingRevokeDate(address _storage, address beneficiary, uint256 value) public { 289 | ExternalStorage(_storage).setLedgerValue("cstVestingRevokeDate", beneficiary, value); 290 | } 291 | 292 | function getVestingRevokeDate(address _storage, address beneficiary) public view returns(uint256) { 293 | return ExternalStorage(_storage).getLedgerValue("cstVestingRevokeDate", beneficiary); 294 | } 295 | 296 | function getRewardsContractHash(address _storage) public view returns (bytes32) { 297 | return ExternalStorage(_storage).getBytes32Value("cstRewardsContractHash"); 298 | } 299 | 300 | function setRewardsContractHash(address _storage, bytes32 rewardsContractHash) public { 301 | ExternalStorage(_storage).setBytes32Value("cstRewardsContractHash", rewardsContractHash); 302 | } 303 | 304 | } 305 | -------------------------------------------------------------------------------- /contracts/Displayable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | contract Displayable { 4 | function bytes32ToString(bytes32 x) public pure returns (string) { 5 | bytes memory bytesString = new bytes(32); 6 | uint256 charCount = 0; 7 | for (uint256 j = 0; j < 32; j++) { 8 | if (x[j] != 0) { 9 | bytesString[charCount] = x[j]; 10 | charCount++; 11 | } 12 | } 13 | bytes memory bytesStringTrimmed = new bytes(charCount); 14 | for (j = 0; j < charCount; j++) { 15 | bytesStringTrimmed[j] = bytesString[j]; 16 | } 17 | return string(bytesStringTrimmed); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contracts/ERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | contract ERC20 { 4 | function allowance(address owner, address spender) public view returns (uint256); 5 | function transferFrom(address from, address to, uint256 value) public returns (bool); 6 | function approve(address spender, uint256 value) public returns (bool); 7 | function totalSupply() public view returns (uint256); 8 | function balanceOf(address account) public view returns (uint256); 9 | function transfer(address to, uint256 value) public returns (bool); 10 | event Transfer(address indexed _from, address indexed _to, uint256 _value); 11 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); 12 | } 13 | -------------------------------------------------------------------------------- /contracts/ExternalStorage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 4 | import "./Administratable.sol"; 5 | 6 | contract ExternalStorage is Administratable { 7 | using SafeMath for uint256; 8 | 9 | mapping(bytes32 => address[]) public primaryLedgerEntryForIndex; 10 | mapping(bytes32 => mapping(address => address[])) public secondaryLedgerEntryForIndex; 11 | mapping(bytes32 => address[]) public ledgerEntryForIndex; 12 | mapping(bytes32 => address[]) public booleanMapEntryForIndex; 13 | 14 | mapping(bytes32 => mapping(address => mapping(address => uint256))) private MultiLedgerStorage; 15 | mapping(bytes32 => mapping(address => bool)) private ledgerPrimaryEntries; 16 | mapping(bytes32 => mapping(address => mapping(address => bool))) private ledgerSecondaryEntries; 17 | mapping(bytes32 => mapping(address => uint256)) private LedgerStorage; 18 | mapping(bytes32 => mapping(address => bool)) private ledgerAccounts; 19 | mapping(bytes32 => mapping(address => bool)) private BooleanMapStorage; 20 | mapping(bytes32 => mapping(address => bool)) private booleanMapAccounts; 21 | mapping(bytes32 => uint256) private UIntStorage; 22 | mapping(bytes32 => bytes32) private Bytes32Storage; 23 | mapping(bytes32 => address) private AddressStorage; 24 | mapping(bytes32 => bytes) private BytesStorage; 25 | mapping(bytes32 => bool) private BooleanStorage; 26 | mapping(bytes32 => int256) private IntStorage; 27 | 28 | function getMultiLedgerValue(string record, address primaryAddress, address secondaryAddress) external view returns (uint256) { 29 | return MultiLedgerStorage[keccak256(abi.encodePacked(record))][primaryAddress][secondaryAddress]; 30 | } 31 | 32 | function primaryLedgerCount(string record) external view returns (uint256) { 33 | return primaryLedgerEntryForIndex[keccak256(abi.encodePacked(record))].length; 34 | } 35 | 36 | function secondaryLedgerCount(string record, address primaryAddress) external view returns (uint256) { 37 | return secondaryLedgerEntryForIndex[keccak256(abi.encodePacked(record))][primaryAddress].length; 38 | } 39 | 40 | function setMultiLedgerValue(string record, address primaryAddress, address secondaryAddress, uint256 value) external onlyAdmins { 41 | bytes32 hash = keccak256(abi.encodePacked(record)); 42 | if (!ledgerSecondaryEntries[hash][primaryAddress][secondaryAddress]) { 43 | secondaryLedgerEntryForIndex[hash][primaryAddress].push(secondaryAddress); 44 | ledgerSecondaryEntries[hash][primaryAddress][secondaryAddress] = true; 45 | 46 | if (!ledgerPrimaryEntries[hash][primaryAddress]) { 47 | primaryLedgerEntryForIndex[hash].push(primaryAddress); 48 | ledgerPrimaryEntries[hash][primaryAddress] = true; 49 | } 50 | } 51 | 52 | MultiLedgerStorage[hash][primaryAddress][secondaryAddress] = value; 53 | } 54 | 55 | function getLedgerValue(string record, address _address) external view returns (uint256) { 56 | return LedgerStorage[keccak256(abi.encodePacked(record))][_address]; 57 | } 58 | 59 | function getLedgerCount(string record) external view returns (uint256) { 60 | return ledgerEntryForIndex[keccak256(abi.encodePacked(record))].length; 61 | } 62 | 63 | function setLedgerValue(string record, address _address, uint256 value) external onlyAdmins { 64 | bytes32 hash = keccak256(abi.encodePacked(record)); 65 | if (!ledgerAccounts[hash][_address]) { 66 | ledgerEntryForIndex[hash].push(_address); 67 | ledgerAccounts[hash][_address] = true; 68 | } 69 | 70 | LedgerStorage[hash][_address] = value; 71 | } 72 | 73 | function getBooleanMapValue(string record, address _address) external view returns (bool) { 74 | return BooleanMapStorage[keccak256(abi.encodePacked(record))][_address]; 75 | } 76 | 77 | function getBooleanMapCount(string record) external view returns (uint256) { 78 | return booleanMapEntryForIndex[keccak256(abi.encodePacked(record))].length; 79 | } 80 | 81 | function setBooleanMapValue(string record, address _address, bool value) external onlyAdmins { 82 | bytes32 hash = keccak256(abi.encodePacked(record)); 83 | if (!booleanMapAccounts[hash][_address]) { 84 | booleanMapEntryForIndex[hash].push(_address); 85 | booleanMapAccounts[hash][_address] = true; 86 | } 87 | 88 | BooleanMapStorage[hash][_address] = value; 89 | } 90 | 91 | function getUIntValue(string record) external view returns (uint256) { 92 | return UIntStorage[keccak256(abi.encodePacked(record))]; 93 | } 94 | 95 | function setUIntValue(string record, uint256 value) external onlyAdmins { 96 | UIntStorage[keccak256(abi.encodePacked(record))] = value; 97 | } 98 | 99 | function getBytes32Value(string record) external view returns (bytes32) { 100 | return Bytes32Storage[keccak256(abi.encodePacked(record))]; 101 | } 102 | 103 | function setBytes32Value(string record, bytes32 value) external onlyAdmins { 104 | Bytes32Storage[keccak256(abi.encodePacked(record))] = value; 105 | } 106 | 107 | function getAddressValue(string record) external view returns (address) { 108 | return AddressStorage[keccak256(abi.encodePacked(record))]; 109 | } 110 | 111 | function setAddressValue(string record, address value) external onlyAdmins { 112 | AddressStorage[keccak256(abi.encodePacked(record))] = value; 113 | } 114 | 115 | function getBytesValue(string record) external view returns (bytes) { 116 | return BytesStorage[keccak256(abi.encodePacked(record))]; 117 | } 118 | 119 | function setBytesValue(string record, bytes value) external onlyAdmins { 120 | BytesStorage[keccak256(abi.encodePacked(record))] = value; 121 | } 122 | 123 | function getBooleanValue(string record) external view returns (bool) { 124 | return BooleanStorage[keccak256(abi.encodePacked(record))]; 125 | } 126 | 127 | function setBooleanValue(string record, bool value) external onlyAdmins { 128 | BooleanStorage[keccak256(abi.encodePacked(record))] = value; 129 | } 130 | 131 | function getIntValue(string record) external view returns (int256) { 132 | return IntStorage[keccak256(abi.encodePacked(record))]; 133 | } 134 | 135 | function setIntValue(string record, int256 value) external onlyAdmins { 136 | IntStorage[keccak256(abi.encodePacked(record))] = value; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /contracts/Freezable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | import "./Administratable.sol"; 4 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 5 | 6 | contract Freezable is Administratable { 7 | using SafeMath for uint256; 8 | 9 | // zOS requires that the variables are never removed nor order changed 10 | // Since this is a parent contract, no new variables can be added here 11 | bool public frozenToken; 12 | // TODO move this into external storage 13 | address[] public frozenAccountForIndex; 14 | mapping (address => bool) public frozenAccount; 15 | mapping (address => bool) private processedAccount; 16 | 17 | event FrozenFunds(address indexed target, bool frozen); 18 | event FrozenToken(bool frozen); 19 | 20 | modifier unlessFrozen { 21 | require(!frozenToken); 22 | require(!frozenAccount[msg.sender]); 23 | _; 24 | } 25 | 26 | function freezeAccount(address target, bool freeze) public onlySuperAdmins { 27 | frozenAccount[target] = freeze; 28 | if (!processedAccount[target]) { 29 | frozenAccountForIndex.push(target); 30 | processedAccount[target] = true; 31 | } 32 | emit FrozenFunds(target, freeze); 33 | } 34 | 35 | function freezeToken(bool freeze) public onlySuperAdmins { 36 | frozenToken = freeze; 37 | emit FrozenToken(frozenToken); 38 | } 39 | 40 | function totalFrozenAccountsMapping() public view returns(uint256) { 41 | return frozenAccountForIndex.length; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /contracts/IStorable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | contract IStorable { 4 | function getLedgerNameHash() external view returns (bytes32); 5 | function getStorageNameHash() external view returns (bytes32); 6 | } 7 | -------------------------------------------------------------------------------- /contracts/ITokenLedger.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | interface ITokenLedger { 4 | function mintTokens(uint256 amount) external; 5 | function transfer(address sender, address reciever, uint256 amount) external; 6 | function creditAccount(address account, uint256 amount) external; 7 | function debitAccount(address account, uint256 amount) external; 8 | function addAdmin(address admin) external; 9 | function removeAdmin(address admin) external; 10 | function totalTokens() external view returns (uint256); 11 | function totalInCirculation() external view returns (uint256); 12 | function balanceOf(address account) external view returns (uint256); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint256 public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | constructor() public { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint256 completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/Registry.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 4 | import "zos-lib/contracts/migrations/Initializable.sol"; 5 | import "./ExternalStorage.sol"; 6 | import "./CstLedger.sol"; 7 | import "./Administratable.sol"; 8 | import "./Configurable.sol"; 9 | import "./IStorable.sol"; 10 | import "./Freezable.sol"; 11 | import "./ERC20.sol"; 12 | 13 | contract Registry is Initializable, Administratable { 14 | using SafeMath for uint256; 15 | 16 | /* zOS requires that the variables are never removed nor order changed 17 | /* BEGIN VARIABLES */ 18 | string public constant version = "2"; 19 | bytes4 private constant INTERFACE_META_ID = 0x01ffc9a7; 20 | bytes4 private constant ADDR_INTERFACE_ID = 0x3b3b57de; 21 | bytes32 private constant BARE_DOMAIN_NAMEHASH = 0x794941fae74d6435d1b29ee1c08cc39941ba78470872e6afd0693c7eeb63025c; // namehash for "cardstack.eth" 22 | mapping(bytes32 => address) public storageForHash; 23 | mapping(bytes32 => address) public contractForHash; 24 | mapping(bytes32 => bytes32) public hashForNamehash; 25 | mapping(bytes32 => bytes32) public namehashForHash; 26 | string[] public contractNameForIndex; 27 | /* END VARIABLES */ 28 | 29 | event ContractRegistered(address indexed _contract, string _name, bytes32 namehash); 30 | event StorageAdded(address indexed storageAddress, string name); 31 | event StorageRemoved(address indexed storageAddress, string name); 32 | event AddrChanged(bytes32 indexed node, address a); 33 | 34 | function() public { 35 | revert(); 36 | } 37 | 38 | function setNamehash(string contractName, bytes32 namehash) external onlySuperAdmins returns (bool) { 39 | require(namehash != 0x0); 40 | 41 | bytes32 hash = keccak256(abi.encodePacked(contractName)); 42 | address contractAddress = contractForHash[hash]; 43 | 44 | require(contractAddress != 0x0); 45 | require(hashForNamehash[namehash] == 0x0); 46 | 47 | hashForNamehash[namehash] = hash; 48 | namehashForHash[hash] = namehash; 49 | 50 | emit AddrChanged(namehash, contractAddress); 51 | } 52 | 53 | function register(string name, address contractAddress, bytes32 namehash) external onlySuperAdmins returns (bool) { 54 | bytes32 hash = keccak256(abi.encodePacked(name)); 55 | require(bytes(name).length > 0); 56 | require(contractAddress != 0x0); 57 | require(contractForHash[hash] == 0x0); 58 | require(hashForNamehash[namehash] == 0x0); 59 | 60 | contractNameForIndex.push(name); 61 | contractForHash[hash] = contractAddress; 62 | 63 | if (namehash != 0x0) { 64 | hashForNamehash[namehash] = hash; 65 | namehashForHash[hash] = namehash; 66 | } 67 | 68 | address storageAddress = storageForHash[IStorable(contractAddress).getStorageNameHash()]; 69 | address ledgerAddress = storageForHash[IStorable(contractAddress).getLedgerNameHash()]; 70 | 71 | if (storageAddress != 0x0) { 72 | ExternalStorage(storageAddress).addAdmin(contractAddress); 73 | } 74 | if (ledgerAddress != 0x0) { 75 | CstLedger(ledgerAddress).addAdmin(contractAddress); 76 | } 77 | 78 | Configurable(contractAddress).configureFromStorage(); 79 | 80 | emit ContractRegistered(contractAddress, name, namehash); 81 | 82 | if (namehash != 0x0) { 83 | emit AddrChanged(namehash, contractAddress); 84 | } 85 | 86 | return true; 87 | } 88 | 89 | function addStorage(string name, address storageAddress) external onlySuperAdmins { 90 | require(storageAddress != address(0)); 91 | bytes32 hash = keccak256(abi.encodePacked(name)); 92 | storageForHash[hash] = storageAddress; 93 | 94 | emit StorageAdded(storageAddress, name); 95 | } 96 | 97 | function initialize() public onlyInitializers isInitializer { 98 | initializeAdmins(); 99 | } 100 | 101 | function removeStorage(string name) public onlySuperAdmins { 102 | address storageAddress = storageForHash[keccak256(abi.encodePacked(name))]; 103 | delete storageForHash[keccak256(abi.encodePacked(name))]; 104 | 105 | emit StorageRemoved(storageAddress, name); 106 | } 107 | 108 | function getStorage(string name) public view returns (address) { 109 | return storageForHash[keccak256(abi.encodePacked(name))]; 110 | } 111 | 112 | function addr(bytes32 node) public view returns (address) { 113 | return contractForHash[hashForNamehash[node]]; 114 | } 115 | 116 | function numContracts() public view returns(uint256) { 117 | return contractNameForIndex.length; 118 | } 119 | 120 | function getContractHash(string name) public pure returns (bytes32) { 121 | return keccak256(abi.encodePacked(name)); 122 | } 123 | 124 | function supportsInterface(bytes4 interfaceId) public pure returns (bool) { 125 | return interfaceId == ADDR_INTERFACE_ID || 126 | interfaceId == INTERFACE_META_ID; 127 | } 128 | } 129 | 130 | -------------------------------------------------------------------------------- /contracts/upgradeability: -------------------------------------------------------------------------------- 1 | ../node_modules/zos-lib/contracts/upgradeability -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | const CST_NAME = 'cst'; 2 | const CST_STORAGE_NAME = 'cstStorage'; 3 | const CST_LEDGER_NAME = 'ledger-CARD'; 4 | const ROUNDING_ERROR_WEI = 20000; 5 | const MAX_FAILED_TXN_GAS = 6721975.0001; 6 | const CST_DEPLOY_GAS_LIMIT = 6800000; 7 | const CST_BUY_GAS_LIMIT = 200000; 8 | const NULL_ADDRESS = "0x0000000000000000000000000000000000000000"; 9 | const CARDSTACK_NAMEHASH = "0x794941fae74d6435d1b29ee1c08cc39941ba78470872e6afd0693c7eeb63025c"; // namehash for "cardstack.eth" 10 | 11 | module.exports = { 12 | CST_NAME, 13 | CST_STORAGE_NAME, 14 | CST_LEDGER_NAME, 15 | CST_DEPLOY_GAS_LIMIT, 16 | CST_BUY_GAS_LIMIT, 17 | CARDSTACK_NAMEHASH, 18 | ROUNDING_ERROR_WEI, 19 | MAX_FAILED_TXN_GAS, 20 | NULL_ADDRESS, 21 | }; 22 | -------------------------------------------------------------------------------- /lib/ledger.js: -------------------------------------------------------------------------------- 1 | const { NULL_ADDRESS } = require("./constants"); 2 | 3 | function constructLedger(web3, transactions, contractAddress) { 4 | let ledger = {}; 5 | for (let txn of transactions) { 6 | let { _to:to, _from:from, _value:amount } = txn.args; 7 | 8 | if (from.toLowerCase() !== contractAddress.toLowerCase() && from !== NULL_ADDRESS) { 9 | let balance = new web3.BigNumber(ledger[from] || 0); 10 | ledger[from] = balance.sub(new web3.BigNumber(amount)); 11 | } 12 | 13 | if (to.toLowerCase() !== contractAddress.toLowerCase() && to !== NULL_ADDRESS) { 14 | let balance = new web3.BigNumber(ledger[to] || 0); 15 | ledger[to] = balance.add(new web3.BigNumber(amount)); 16 | } 17 | } 18 | 19 | for (let account of Object.keys(ledger)) { 20 | if (ledger[account].isZero()) { 21 | delete ledger[account]; 22 | } else if (ledger[account].isNegative()) { 23 | throw new Error(`account ${account} has a negative balance ${ledger[account].toString()}`); 24 | } 25 | } 26 | 27 | return ledger; 28 | } 29 | 30 | async function getLedger(web3, contractInstance) { 31 | let ledger = await new Promise((res, rej) => { 32 | contractInstance.contract.Transfer({}, { fromBlock: 0, toBlock: 'latest'}).get((err, events) => { 33 | console.log(`Processing ${events.length} Transfer events...`); 34 | if (err) { 35 | rej(err); 36 | } else { 37 | res(constructLedger(web3, events, contractInstance.address)); 38 | } 39 | }); 40 | }); 41 | 42 | ledger = ledger || {}; 43 | 44 | return ledger; 45 | } 46 | 47 | module.exports = { 48 | getLedger 49 | }; 50 | -------------------------------------------------------------------------------- /lib/proxy.js: -------------------------------------------------------------------------------- 1 | async function proxyContract(AdminUpgradeabilityProxy, artifact, proxyAdmin, ...args) { 2 | let opts; 3 | if (args && args.length && typeof args[args.length -1] === 'object') { 4 | opts = args.pop(); 5 | } 6 | 7 | console.log(`Deploying ${artifact.contractName} implementation...`); 8 | 9 | let implementation = await artifact.new(opts); 10 | console.log(`${artifact.contractName} implementation deployed to ${implementation.address}`); 11 | 12 | console.log(`Deploying ${artifact.contractName} proxy...`); 13 | let proxy = await AdminUpgradeabilityProxy.new(implementation.address); 14 | console.log(`${artifact.contractName} proxy deployed to ${proxy.address}`); 15 | 16 | console.log(`Setting proxyAdmin to ${proxyAdmin}...`); 17 | await proxy.changeAdmin(proxyAdmin); 18 | 19 | let contract = await artifact.at(proxy.address); 20 | 21 | console.log(`Initializing ${artifact.contractName} proxy with args ${JSON.stringify(args)}...`); 22 | await contract.initialize.apply(this, args); 23 | 24 | return { contract, implementation, proxy }; 25 | } 26 | 27 | async function upgradeContract(AdminUpgradeabilityProxy, artifact, proxyAdmin, proxyAddress, opts) { 28 | console.log(`Deploying ${artifact.contractName} implementation...`); 29 | let implementation = await artifact.new(opts); 30 | console.log(`${artifact.contractName} implementation deployed to ${implementation.address}`); 31 | 32 | let proxy = await AdminUpgradeabilityProxy.at(proxyAddress); 33 | 34 | console.log(`Upgrading ${artifact.contractName} proxy ${proxyAddress} to new implementation...`); 35 | await proxy.upgradeTo(implementation.address, { from: proxyAdmin }); 36 | let contract = await artifact.at(proxyAddress); 37 | 38 | return { contract, proxy, implementation }; 39 | } 40 | 41 | module.exports = { 42 | proxyContract, 43 | upgradeContract 44 | }; 45 | -------------------------------------------------------------------------------- /lib/time.js: -------------------------------------------------------------------------------- 1 | const Bluebird = require('bluebird'); 2 | 3 | function latestTime () { 4 | return web3.eth.getBlock('latest').timestamp; 5 | } 6 | 7 | // Increases testrpc time by the passed duration in seconds 8 | function increaseTime (duration) { 9 | const id = Date.now(); 10 | 11 | return new Bluebird.Promise((resolve, reject) => { 12 | web3.currentProvider.sendAsync({ 13 | jsonrpc: '2.0', 14 | method: 'evm_increaseTime', 15 | params: [duration], 16 | id: id, 17 | }, err1 => { 18 | if (err1) return reject(err1); 19 | 20 | web3.currentProvider.sendAsync({ 21 | jsonrpc: '2.0', 22 | method: 'evm_mine', 23 | id: id + 1, 24 | }, (err2, res) => { 25 | return err2 ? reject(err2) : resolve(res); 26 | }); 27 | }); 28 | }); 29 | } 30 | 31 | /** 32 | * Beware that due to the need of calling two separate testrpc methods and rpc calls overhead 33 | * it's hard to increase time precisely to a target point so design your test to tolerate 34 | * small fluctuations from time to time. 35 | * 36 | * @param target time in seconds 37 | */ 38 | function increaseTimeTo (target) { 39 | let now = latestTime(); 40 | if (target < now) throw Error(`Cannot increase current time(${now}) to a moment in the past(${target})`); 41 | let diff = target - now; 42 | return increaseTime(diff); 43 | } 44 | 45 | const duration = { 46 | seconds: function (val) { return val; }, 47 | minutes: function (val) { return val * this.seconds(60); }, 48 | hours: function (val) { return val * this.minutes(60); }, 49 | days: function (val) { return val * this.hours(24); }, 50 | weeks: function (val) { return val * this.days(7); }, 51 | years: function (val) { return val * this.days(365); }, 52 | }; 53 | 54 | module.exports = { 55 | duration, 56 | latestTime, 57 | increaseTimeTo, 58 | increaseTime 59 | }; 60 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | const { 2 | CST_NAME, 3 | CST_STORAGE_NAME, 4 | CST_LEDGER_NAME, 5 | CST_DEPLOY_GAS_LIMIT, 6 | CST_BUY_GAS_LIMIT, 7 | CARDSTACK_NAMEHASH, 8 | ROUNDING_ERROR_WEI, 9 | MAX_FAILED_TXN_GAS, 10 | NULL_ADDRESS, 11 | } = require("./constants"); 12 | 13 | const Bluebird = require("bluebird"); 14 | const GAS_PRICE = web3.toWei(20, "gwei"); 15 | const DEFAULT_EVENT_WATCH_TIMEOUT_SEC = 60; 16 | 17 | function asInt(contractValue) { 18 | if (!contractValue) { throw new Error("Cannot convert to int ", JSON.stringify(contractValue)); } 19 | if (typeof contractValue.toNumber === "function") { 20 | return contractValue.toNumber(); 21 | } 22 | 23 | return parseInt(contractValue.toString(), 10); 24 | } 25 | 26 | function wait(sec) { 27 | return new Bluebird.Promise(res => setTimeout(() => res(), sec * 1000)); 28 | } 29 | 30 | async function checkBalance(account, minBalanceEth) { 31 | let balanceEth = await web3.eth.getBalance(account); 32 | balanceEth = parseInt(web3.fromWei(balanceEth.toString(), 'ether'), 10); 33 | 34 | if (balanceEth < minBalanceEth) { 35 | throw new Error(`Not enough ether in address ${account} to perform test--restart testrpc to top-off balance`); 36 | } 37 | } 38 | 39 | function waitForContractEvent(contract, eventName, timeoutSec) { 40 | timeoutSec = timeoutSec || DEFAULT_EVENT_WATCH_TIMEOUT_SEC; 41 | return new Bluebird.Promise((resolve, reject) => { 42 | let event = contract[eventName](); 43 | let timeout = setTimeout(() => { 44 | event.stopWatching(); 45 | reject(new Error(`timeout waiting for '${eventName}' after ${timeoutSec} seconds`)); 46 | }, timeoutSec * 1000); 47 | 48 | event.watch((error, log) => { 49 | clearTimeout(timeout); 50 | event.stopWatching(); 51 | resolve(log); 52 | }); 53 | }); 54 | } 55 | 56 | async function assertRevert(block, msg) { 57 | let err; 58 | try { 59 | await block(); 60 | } catch (e) { 61 | err = e; 62 | } 63 | 64 | if (!err) { return assert.isOk(err, "Revert should have been fired, instead no error fired"); } 65 | 66 | if (msg) { 67 | return assert.isOk(err.message.search(msg) > -1, 68 | msg + " should have been fired, instead:" + err.message); 69 | } else { 70 | return assert.isOk(err.message.search("revert") > -1, 71 | "revert should have been fired, instead:" + err.message); 72 | } 73 | } 74 | 75 | module.exports = { 76 | GAS_PRICE, 77 | CST_DEPLOY_GAS_LIMIT, 78 | ROUNDING_ERROR_WEI, 79 | MAX_FAILED_TXN_GAS, 80 | NULL_ADDRESS, 81 | CST_NAME, 82 | CST_STORAGE_NAME, 83 | CST_LEDGER_NAME, 84 | CST_BUY_GAS_LIMIT, 85 | CARDSTACK_NAMEHASH, 86 | assertRevert, 87 | asInt, 88 | checkBalance, 89 | wait, 90 | waitForContractEvent 91 | }; 92 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/2_deploy_registry.js: -------------------------------------------------------------------------------- 1 | let RegistryContract = artifacts.require("./Registry.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(RegistryContract); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/3_deploy_storage.js: -------------------------------------------------------------------------------- 1 | const { CST_STORAGE_NAME, CST_LEDGER_NAME } = require("../lib/constants"); 2 | let RegistryContract = artifacts.require("./Registry.sol"); 3 | let ExternalStorage = artifacts.require("./ExternalStorage.sol"); 4 | let CstLedger = artifacts.require("./CstLedger.sol"); 5 | 6 | module.exports = async function(deployer) { 7 | await deployer.deploy(ExternalStorage); 8 | await deployer.deploy(CstLedger); 9 | 10 | let ledger = await CstLedger.deployed(); 11 | let storage = await ExternalStorage.deployed(); 12 | let registry = await RegistryContract.deployed(); 13 | 14 | await storage.addSuperAdmin(registry.address); 15 | await ledger.addSuperAdmin(registry.address); 16 | 17 | await registry.addStorage(CST_STORAGE_NAME, storage.address); 18 | await registry.addStorage(CST_LEDGER_NAME, ledger.address); 19 | }; 20 | -------------------------------------------------------------------------------- /migrations/4_deploy_cst_contract.js: -------------------------------------------------------------------------------- 1 | const { CST_DEPLOY_GAS_LIMIT, CST_STORAGE_NAME, CST_LEDGER_NAME } = require("../lib/constants.js"); 2 | let RegistryContract = artifacts.require("./Registry.sol"); 3 | let CstLibrary = artifacts.require("./CstLibrary.sol"); 4 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 5 | let TestingCardstackToken = artifacts.require("./TestingCardstackToken.sol"); 6 | let Token_v1 = artifacts.require("./Token_v1.sol"); 7 | let Token_v2 = artifacts.require("./Token_v2.sol"); 8 | 9 | module.exports = async function(deployer) { 10 | if (deployer.network === 'development') { 11 | await deployer.deploy(CstLibrary); 12 | await deployer.link(CstLibrary, CardstackToken); 13 | 14 | // for tests only 15 | await deployer.link(CstLibrary, TestingCardstackToken); 16 | await deployer.link(CstLibrary, Token_v1); 17 | await deployer.link(CstLibrary, Token_v2); 18 | } else { 19 | let registry = await RegistryContract.deployed(); 20 | await deployer.deploy(CstLibrary); 21 | await deployer.link(CstLibrary, CardstackToken); 22 | await deployer.deploy(CardstackToken, 23 | registry.address, 24 | CST_STORAGE_NAME, 25 | CST_LEDGER_NAME, 26 | { gas: CST_DEPLOY_GAS_LIMIT }); // need as much gas as possible--this is a big contract 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cardstack-token", 3 | "version": "0.0.1", 4 | "description": "Cardstack token", 5 | "directories": { 6 | "test": "test" 7 | }, 8 | "scripts": { 9 | "build": "rm -rf build && ./node_modules/.bin/truffle compile", 10 | "test": "rm -rf build && ./node_modules/.bin/truffle test", 11 | "ganache": "ganache-cli --gasLimit 6900000 --gasPrice 20000000000 --account='0x4214fecc01d87f5d3559230db253d5935a4ebe1a024f4977b6f6effd8a29dbaf,1000000000000000000000000' --account='0x0d7878ec27ddcc412d2ebec971191e99d81a91a40369fa878629aa4e3f5d8d21,100000000000000000000' --account='0x18f83cc4ecaa95d23d5c45deaf86a7c0be1c625c183a13c0ac56dfba8ada8a2d,100000000000000000000' --account='0x51b78b161754df3836b56741696cb1c5a4ece077dee6d645a8bd79b3f909e0be,100000000000000000000' --account='0x5d6af7022deb42d9d54cd76a55f9e8f24c710038557c2619e343a9899a3bfd65,100000000000000000000' --account='0x7739672ccf4cfaa17a5bd59f59dc1e8156b714191d4fdcd671ca4bc005dba80a,100000000000000000000' --account='0xcd7346e90d43ef02eea94d5652bb56c8523e7b09d0f6cfef228061884cb6ed61,100000000000000000000' --account='0x806257be5fb3e3b63c73da1a6694231c43db92c5490c0a64f734acf2a80f891c,100000000000000000000' --account='0x627d21c0faa0acb2243acf2a0eaac1bb9353a762f65d33d00943d3c51930e6b0,100000000000000000000' --account='0xf401c7170d55f6fe2a825cf1ce3bad978b3368d8bfcbe5e176b0868428373b4b,100000000000000000000' --account='0xeb5c18531a02ea83eab89b22eee7e2151a3058b10032b6fd27bd758ec3155526,100000000000000000000' --account='0x2b7a2f17825d81156bc1ad81b5b27a134ee838833c21b3d1dfa0536c29607eaa,100000000000000000000' --account='0xb267d6e53de614c0938db983a8ba7a71d64f086ed2b987cdece5e0a99852849d,100000000000000000000' --account='0x544f16e09741c3f2235d89bd68074618b9fece624c07c12f777c73534aa13cc2,100000000000000000000' --account='0x8afde16ac2389e233988f326875fcb008654f6e8810b44f3d05f0a2028e466df,100000000000000000000' --account='0x4cdeae4a12d0bd2a889ada30b5ed315fe871b927b7482d2aed62fb58e3df2855,100000000000000000000' --account='0x18054e21801ec1910fadb19c12e985af4dfa9d0613797314a5103477749349a1,100000000000000000000' --account='0x058284ec2d7867f1927840867e3c76749e5c4055e35e170d52b7ecdb191e25a7,100000000000000000000' --account='0xf44d63499c553d663384ea436673a872c835ee67b64aad7dc37a215b3435d26c,100000000000000000000' --account='0x7e64b4c395b73684a1bb443d993e743bae1f4b83a7eff94e9551e490b2141d9f,100000000000000000000' --account='0xa54c95fb5154d4b5f92f65afb05de3bb52a1c60d619f62d385cb7040e035b8e0,100000000000000000000' --account='0x7e0dc45fd9519749a1d6dc772ae630a74ff7e433544fd7026f898a89646ebb97,100000000000000000000' --account='0x9857f25bac59467992295d38d7e519b544979eb4a5781568cb60dd2259adc47e,100000000000000000000' --account='0xecb692afa96ea349ad4736aa3a1dab5ec6a5e3fdfed2d2f9ad124d678c6c9338,100000000000000000000' --account='0xc8fc04a2ac1795a17258ad280447cd48f79631ece23f49ca3f8406a88c119cc8,100000000000000000000' --account='0x6f3a72c0190f3032c3868c9b2a6852f6c3f4900afc64e0679a779dfd9864a362,100000000000000000000' --account='0x14d01e6525fa0cda85ab44b77962a56de8c04b61e1f701b5e05b42bb0cc16158,100000000000000000000' --account='0xac451a58faec2212a5d735135c180eb85a08256f68306a322618dac679c79612,100000000000000000000' --account='0x691fab28cac41f9d90fd55583e46e01d0a30504cd59bc994f166bd8b9fd98f1f,100000000000000000000' --account='0x1276a37be67e0d275dbacfaf7d1696856d57c67138db9c03bf209d2e79fb996d,100000000000000000000' --account='0x921644b68906b497dbc462d69973adfbb1f00f5759359c11e471f74414543928,100000000000000000000' --account='0x964d4f1e44ec23a7f72eb2b47e7fdeecc5d325d63fb469a36a63537aa4040a0c,100000000000000000000' --account='0xab27c218f960fb5b7611232dbae49b266fcbb481143ad2b8507bdf3e1893b92e,100000000000000000000' --account='0x8fac47743467c90b43dde06db463db9fc60978c254eea8544678cd5247ea7b5a,100000000000000000000' --account='0x94a08b727ba6c99ffb9b1607508a2573a9615d173e3b376437ff7414355fa3be,100000000000000000000' --account='0x475dafb6945b31adb0329c127fced49d2a266d8f933e44375fd8ef623e9cee8f,100000000000000000000' --account='0x5bbb2a6ed5854cef27753ad1909365d36bafbc60124225677d9ab92953554410,100000000000000000000' --account='0x601e11bbcfce0b3640e35f69a23dafe0bac1a9587c8d12d9bc5d29d05521d71b,100000000000000000000' --account='0x99c8b467c336012669766dc7924b9c43be2a0a2ebb28a305c0176cdaec09dcbb,100000000000000000000' --account='0xcf899b031411d305158bb4af10e97b0af6362d468ece6601e91b44e1d543630f,100000000000000000000' --account='0x051d218f60ba0431fe6478d5976459a80a3162f2bd3f935e8dc079b7304ab76e,100000000000000000000' --account='0x6e7dcb217dc10a19d905b802f3beea231639bc4072c4b2afeb9b2e775d0c67d2,100000000000000000000' --account='0xb8e983ca3ce47eec836f2370aebb40b415e261e9e1604281b0fb587e92f87f58,100000000000000000000' --account='0xd6408e06542a4f1503bbdd7fe5d5a2fb45d79040c97f5e68253f73f944aa9380,100000000000000000000' --account='0x4e396f7f1e04ac2894e55f0d08a645b54dd4cdd59cbcee094b76f33133b6c5b9,100000000000000000000' --account='0x1f0941fa8da1f9f8660cef1099e2fde21b441fbb24a5d8bf216e3d3c8f0d2a2f,100000000000000000000' --account='0xfe52ce2ef9f94d98786aa06a1df3f15c72a2a6d1d1ea1bec72c31d72e529fff5,100000000000000000000' --account='0xbf9a2048901090c3f70defbf53af4d7d176e1399834f3c4aabd70a29a68bef63,100000000000000000000' --account='0x7a72e9f3b1c9c2f312812500fe8e88de27f410ad1aa0cc46a465f4d7984358fb,100000000000000000000' --account='0xad0238822521d44c58eb60a80b9afcdc1dc4ee4cbb089a00b31720d5b9286c60,100000000000000000000'" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "async-parallel": "^1.2.3", 17 | "bluebird": "^3.5.0", 18 | "command-line-args": "^4.0.7", 19 | "command-line-usage": "^4.0.1", 20 | "eth-ens-namehash": "^2.0.8", 21 | "ganache-cli": "^6.1.8", 22 | "lodash": "^4.17.4", 23 | "moment": "^2.19.4", 24 | "openzeppelin-solidity": "^1.12.0", 25 | "request": "^2.81.0", 26 | "request-promise": "^4.2.1", 27 | "solc": "^0.4.24", 28 | "truffle": "^4.1.14", 29 | "zos": "^1.4.1", 30 | "zos-lib": "^1.4.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /scripts/add-admin.js: -------------------------------------------------------------------------------- 1 | const { CST_NAME, CST_STORAGE_NAME, CST_LEDGER_NAME } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | 5 | let RegistryContract = artifacts.require("./Registry.sol"); 6 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 7 | let ExternalStorage = artifacts.require("./ExternalStorage.sol"); 8 | let CstLedger = artifacts.require("./CstLedger.sol"); 9 | 10 | const optionsDefs = [ 11 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 12 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 13 | { name: "address", alias: "a", type: String, description: "The address to grant admin permissions"}, 14 | { name: "name", alias: "n", type: String, description: "The registered name of the contract/storage to grant admin permissions"}, 15 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 16 | { name: "data", alias: "d", type: Boolean, description: "Display the data necessary to invoke the transaction instead of actually invoking the transaction" } 17 | ]; 18 | 19 | const usage = [ 20 | { 21 | header: "add-admin", 22 | content: "This script adds an admin to a contract" 23 | },{ 24 | header: "Options", 25 | optionList: optionsDefs 26 | } 27 | ]; 28 | 29 | module.exports = async function(callback) { 30 | const options = commandLineArgs(optionsDefs); 31 | 32 | if (!options.address || !options.name || (!options.network && !options.data) || options.help || options.registry) { 33 | console.log(getUsage(usage)); 34 | callback(); 35 | return; 36 | } 37 | 38 | let registryAddress = options.registry; 39 | 40 | let registry = registryAddress ? await RegistryContract.at(registryAddress) : await RegistryContract.deployed(); 41 | 42 | console.log(`Using registry at ${registry.address}`); 43 | 44 | let { address, name } = options; 45 | let cstAddress = await registry.contractForHash(web3.sha3(CST_NAME)); 46 | let cst = await CardstackToken.at(cstAddress); 47 | let storageAddress = await registry.storageForHash(web3.sha3(CST_STORAGE_NAME)); 48 | let ledgerAddress = await registry.storageForHash(web3.sha3(CST_LEDGER_NAME)); 49 | let storage = await ExternalStorage.at(storageAddress); 50 | let ledger = await CstLedger.at(ledgerAddress); 51 | 52 | let contract; 53 | switch (name) { 54 | case CST_NAME: 55 | contract = cst; 56 | break; 57 | case CST_LEDGER_NAME: 58 | contract = ledger; 59 | break; 60 | case CST_STORAGE_NAME: 61 | contract = storage; 62 | break; 63 | } 64 | 65 | if (!contract) { 66 | console.log(`Could not find contract for ${name}`); 67 | callback(); 68 | return; 69 | } 70 | 71 | if (options.data) { 72 | let data = contract.contract.addAdmin.getData(address); 73 | let estimatedGas = web3.eth.estimateGas({ 74 | to: contract.address, 75 | data 76 | }); 77 | console.log(`Data for adding "${address}" as admin for ${name} (${contract.address}):`); 78 | console.log(`\nAddress: ${contract.address}`); 79 | console.log(`Data: ${data}`); 80 | console.log(`Estimated gas: ${estimatedGas}`); 81 | callback(); 82 | return; 83 | } 84 | 85 | try { 86 | console.log(`Adding "${address}" as admin for ${name} (${contract.address})...`); 87 | await contract.addAdmin(address); 88 | console.log('done'); 89 | } catch (err) { 90 | console.error(`Error encountered adding admin for ${name} (${contract.address}), ${err.message}`); 91 | } 92 | 93 | callback(); 94 | }; 95 | -------------------------------------------------------------------------------- /scripts/add-super-admin.js: -------------------------------------------------------------------------------- 1 | const { CST_NAME } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | let RegistryContract = artifacts.require("./Registry.sol"); 5 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 6 | 7 | const optionsDefs = [ 8 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 9 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 10 | { name: "address", alias: "a", type: String, description: "The address to grant admin permissions"}, 11 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 12 | { name: "data", alias: "d", type: Boolean, description: "Display the data necessary to invoke the transaction instead of actually invoking the transaction" } 13 | ]; 14 | 15 | const usage = [ 16 | { 17 | header: "add-super-admin", 18 | content: "This script adds a super admin to the CST and Registry." 19 | },{ 20 | header: "Options", 21 | optionList: optionsDefs 22 | } 23 | ]; 24 | 25 | module.exports = async function(callback) { 26 | const options = commandLineArgs(optionsDefs); 27 | 28 | if (!options.address || (!options.network && !options.data) || options.help || !options.registry) { 29 | console.log(getUsage(usage)); 30 | callback(); 31 | return; 32 | } 33 | 34 | let registryAddress = options.registry; 35 | 36 | let registry = registryAddress ? await RegistryContract.at(registryAddress) : await RegistryContract.deployed(); 37 | 38 | console.log(`Using registry at ${registry.address}`); 39 | let cstAddress = await registry.contractForHash(web3.sha3(CST_NAME)); 40 | 41 | let cst = await CardstackToken.at(cstAddress); 42 | 43 | let address = options.address; 44 | 45 | if (options.data) { 46 | let data = cst.contract.addSuperAdmin.getData(address); 47 | let estimatedGas = web3.eth.estimateGas({ 48 | to: cst.address, 49 | data 50 | }); 51 | console.log(`Data for adding "${address}" as super admin for CST ${cst.address}...`); 52 | console.log(`\nAddress: ${cst.address}`); 53 | console.log(`Data: ${data}`); 54 | console.log(`Estimated gas: ${estimatedGas}`); 55 | 56 | data = registry.contract.addSuperAdmin.getData(address); 57 | estimatedGas = web3.eth.estimateGas({ 58 | to: registry.address, 59 | data 60 | }); 61 | console.log(`\n Data for adding "${address}" as super admin for Regsitry ${registry.address}...`); 62 | console.log(`\nAddress: ${registry.address}`); 63 | console.log(`Data: ${data}`); 64 | console.log(`Estimated gas: ${estimatedGas}`); 65 | callback(); 66 | return; 67 | } 68 | 69 | try { 70 | console.log(`Adding "${address}" as super admin for CST ${cst.address}...`); 71 | await cst.addSuperAdmin(address); 72 | console.log(`Adding "${address}" as super admin for Registry ${registry.address}...`); 73 | await registry.addSuperAdmin(address); 74 | console.log('done'); 75 | } catch (err) { 76 | console.error(`Error encountered adding super admin, ${err.message}`); 77 | } 78 | 79 | callback(); 80 | }; 81 | -------------------------------------------------------------------------------- /scripts/cst-buy-info.js: -------------------------------------------------------------------------------- 1 | const { CST_BUY_GAS_LIMIT, CST_NAME } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | let RegistryContract = artifacts.require("./Registry.sol"); 5 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 6 | 7 | const optionsDefs = [ 8 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 9 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 10 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 11 | ]; 12 | 13 | const usage = [ 14 | { 15 | header: "cst-buy-info", 16 | content: "This script display purchase information that instructs how to buy CST." 17 | },{ 18 | header: "Options", 19 | optionList: optionsDefs 20 | } 21 | ]; 22 | 23 | module.exports = async function(callback) { 24 | const options = commandLineArgs(optionsDefs); 25 | 26 | if (!options.network || options.help || !options.registry) { 27 | console.log(getUsage(usage)); 28 | callback(); 29 | return; 30 | } 31 | 32 | let registryAddress = options.registry; 33 | 34 | let registry = registryAddress ? await RegistryContract.at(registryAddress) : await RegistryContract.deployed(); 35 | 36 | console.log(`Using registry at ${registry.address}`); 37 | let cstAddress = await registry.contractForHash(web3.sha3(CST_NAME)); 38 | 39 | let cst = await CardstackToken.at(cstAddress); 40 | let symbol = await cst.symbol(); 41 | 42 | let data = cst.contract.buy.getData(); 43 | console.log(`\nTo purchase ${symbol} send ETH to the following address with the following data:`); 44 | console.log(`Address: ${cst.address}`); 45 | console.log(`Data: ${data}`); 46 | console.log(`Estimated gas: ${CST_BUY_GAS_LIMIT}`); 47 | 48 | callback(); 49 | }; 50 | -------------------------------------------------------------------------------- /scripts/cst-configure.js: -------------------------------------------------------------------------------- 1 | const { CST_NAME, NULL_ADDRESS } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | let RegistryContract = artifacts.require("./Registry.sol"); 5 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 6 | 7 | function adjustForDecimals(value, decimals) { 8 | let decimalsFactor = new web3.BigNumber('1'.padEnd(decimals.toNumber() + 1, '0')); 9 | return (new web3.BigNumber(value)).mul(decimalsFactor); 10 | } 11 | 12 | const optionsDefs = [ 13 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 14 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 15 | { name: "tokenName", type: String, description: "The ERC-20 token full name." }, 16 | { name: "tokenSymbol", type: String, description: "The ERC-20 token symbol." }, 17 | { name: "buyPriceCardPerEth", type: Number, description: "The price of CST expressed as the amount of CST that you can purchase for 1 ETH"}, 18 | { name: "circulationCap", type: Number, description: "The maximum number of CST that is allowed to be in circluation at any point in time (this includes unvested tokens)" }, 19 | { name: "maxBalance", type: Number, description: "this is the maximum amount of CST that an account is allowed to posses expressed as number of CST" }, 20 | { name: "foundation", type: String, description: "(optional) The address of the CST Foundation, which has the ability to deposit and withdraw ETH against the CST contract." }, 21 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 22 | { name: "force", type: Boolean, description: "(optional) Specify the `--force` parameter to configure an unfrozen contract. For price changes, the token must be frozen in order to change the price." }, 23 | { name: "data", alias: "d", type: Boolean, description: "Display the data necessary to invoke the transaction instead of actually invoking the transaction" } 24 | ]; 25 | 26 | const usage = [ 27 | { 28 | header: "cst-configure", 29 | content: "This script configures the CST token and makes it available for purchase." 30 | },{ 31 | header: "Options", 32 | optionList: optionsDefs 33 | } 34 | ]; 35 | module.exports = async function(callback) { 36 | const options = commandLineArgs(optionsDefs); 37 | let { tokenName, 38 | tokenSymbol, 39 | buyPriceCardPerEth, 40 | maxBalance, 41 | force, 42 | circulationCap, 43 | foundation } = options; 44 | 45 | if (!tokenName || 46 | !tokenSymbol || 47 | !buyPriceCardPerEth || 48 | !circulationCap || 49 | !options.network || 50 | !maxBalance || 51 | options.help || 52 | !options.registry) { 53 | console.log(getUsage(usage)); 54 | callback(); 55 | return; 56 | } 57 | 58 | let registryAddress = options.registry; 59 | foundation = foundation || NULL_ADDRESS; 60 | 61 | let registry = registryAddress ? await RegistryContract.at(registryAddress) : await RegistryContract.deployed(); 62 | 63 | console.log(`Using registry at ${registry.address}`); 64 | let cstAddress = await registry.contractForHash(web3.sha3(CST_NAME)); 65 | 66 | let cst = await CardstackToken.at(cstAddress); 67 | let isFrozen = await cst.frozenToken(); 68 | let decimals = await cst.decimals(); 69 | 70 | if (!isFrozen) { 71 | if (!force) { 72 | console.log("WARNING: Token is not frozen. Token must be frozen in order to make price changes. Use `--force` to configure the token without freezing it."); 73 | callback(); 74 | return; 75 | } else { 76 | console.log("WARNING: Token is not frozen, force-changing the token configuration. Note that price changes are not allowed when the token is not frozen.\n"); 77 | } 78 | } 79 | 80 | console.log(`Configuring CST token: 81 | token name: ${tokenName} 82 | token symbol: ${tokenSymbol} 83 | buy price: ${tokenSymbol} per ETH: ${buyPriceCardPerEth} 84 | circulation cap: ${circulationCap} 85 | maximum balance: ${maxBalance} 86 | foundation address: ${foundation}`); 87 | 88 | if (options.data) { 89 | let data = cst.contract.configure.getData(web3.toHex(tokenName), 90 | web3.toHex(tokenSymbol), 91 | buyPriceCardPerEth, // since decimals is 18, this eactly matches cardPerWei when decimals is taken into account 92 | adjustForDecimals(circulationCap, decimals), 93 | adjustForDecimals(maxBalance, decimals), 94 | foundation); 95 | let estimatedGas = web3.eth.estimateGas({ 96 | to: cst.address, 97 | data 98 | }); 99 | console.log(`\nData for configuring token CST(${cst.address}):`); 100 | console.log(`\nAddress: ${cst.address}`); 101 | console.log(`Data: ${data}`); 102 | console.log(`Estimated gas: ${estimatedGas}`); 103 | callback(); 104 | return; 105 | } 106 | 107 | console.log("\n...\n"); 108 | try { 109 | await cst.configure(web3.toHex(tokenName), 110 | web3.toHex(tokenSymbol), 111 | buyPriceCardPerEth, // since decimals is 18, this eactly matches cardPerWei when decimals is taken into account 112 | adjustForDecimals(circulationCap, decimals), 113 | adjustForDecimals(maxBalance, decimals), 114 | foundation); 115 | console.log("done"); 116 | } catch (err) { 117 | console.error(`\nError encountered initializing CST (${cst.address}), ${err.message}`); 118 | } 119 | 120 | callback(); 121 | }; 122 | -------------------------------------------------------------------------------- /scripts/cst-deposit-info.js: -------------------------------------------------------------------------------- 1 | const commandLineArgs = require('command-line-args'); 2 | const getUsage = require('command-line-usage'); 3 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 4 | 5 | const optionsDefs = [ 6 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 7 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 8 | { name: "cst", alias: "c", type: String, description: "The token contract address (note we do not use the registry for this)" } 9 | ]; 10 | 11 | const usage = [ 12 | { 13 | header: "cst-buy-info", 14 | content: "This script displays ETH deposit information that instructs how the Cardstack Foundation can deposit ETH into the CST contract for the purposes of buying back CST." 15 | },{ 16 | header: "Options", 17 | optionList: optionsDefs 18 | } 19 | ]; 20 | 21 | module.exports = async function(callback) { 22 | const options = commandLineArgs(optionsDefs); 23 | 24 | if (!options.network || options.help || !options.cst) { 25 | console.log(getUsage(usage)); 26 | callback(); 27 | return; 28 | } 29 | 30 | let { cst:cstAddress } = options; 31 | 32 | console.log(`Using token contract at ${cstAddress}`); 33 | let cst = await CardstackToken.at(cstAddress); 34 | 35 | let data = cst.contract.foundationDeposit.getData(); 36 | let estimatedGas = web3.eth.estimateGas({ 37 | to: cst.address, 38 | data 39 | }); 40 | console.log(`\nTo deposit ETH into the CST contract, send ETH to the following address with the following data:`); 41 | console.log(`Address: ${cst.address}`); 42 | console.log(`Data: ${data}`); 43 | console.log(`Estimated gas: ${estimatedGas}`); 44 | 45 | callback(); 46 | }; 47 | -------------------------------------------------------------------------------- /scripts/cst-freeze-account.js: -------------------------------------------------------------------------------- 1 | const { CST_NAME } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | let RegistryContract = artifacts.require("./Registry.sol"); 5 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 6 | 7 | const optionsDefs = [ 8 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 9 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 10 | { name: "address", alias: "a", type: String, description: "The address to grant admin permissions"}, 11 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 12 | { name: "data", alias: "d", type: Boolean, description: "Display the data necessary to invoke the transaction instead of actually invoking the transaction" } 13 | ]; 14 | 15 | const usage = [ 16 | { 17 | header: "cst-freeze-account", 18 | content: "This script freezes a CST account." 19 | },{ 20 | header: "Options", 21 | optionList: optionsDefs 22 | } 23 | ]; 24 | 25 | module.exports = async function(callback) { 26 | const options = commandLineArgs(optionsDefs); 27 | 28 | if (!options.address || !options.network || options.help || !options.registry) { 29 | console.log(getUsage(usage)); 30 | callback(); 31 | return; 32 | } 33 | 34 | let registryAddress = options.registry; 35 | 36 | let registry = registryAddress ? await RegistryContract.at(registryAddress) : await RegistryContract.deployed(); 37 | 38 | console.log(`Using registry at ${registry.address}`); 39 | let cstAddress = await registry.contractForHash(web3.sha3(CST_NAME)); 40 | 41 | let cst = await CardstackToken.at(cstAddress); 42 | 43 | let address = options.address; 44 | 45 | if (options.data) { 46 | let data = cst.contract.freezeAccount.getData(address, true); 47 | let estimatedGas = web3.eth.estimateGas({ 48 | to: cst.address, 49 | data 50 | }); 51 | console.log(`Data for freezing account ${address} for CST (${cst.address}):`); 52 | console.log(`\nAddress: ${cst.address}`); 53 | console.log(`Data: ${data}`); 54 | console.log(`Estimated gas: ${estimatedGas}`); 55 | callback(); 56 | return; 57 | } 58 | try { 59 | console.log(`Freezing account ${address} for CST (${cst.address})...`); 60 | await cst.freezeAccount(address, true); 61 | console.log('done'); 62 | } catch (err) { 63 | console.error(`Error freezing account for CST (${cst.address}, ${err.message}`); 64 | } 65 | 66 | callback(); 67 | }; 68 | -------------------------------------------------------------------------------- /scripts/cst-freeze-token.js: -------------------------------------------------------------------------------- 1 | const { CST_NAME } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | 5 | let RegistryContract = artifacts.require("./Registry.sol"); 6 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 7 | 8 | const optionsDefs = [ 9 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 10 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 11 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 12 | { name: "data", alias: "d", type: Boolean, description: "Display the data necessary to invoke the transaction instead of actually invoking the transaction" } 13 | ]; 14 | 15 | const usage = [ 16 | { 17 | header: "cst-freeze-token", 18 | content: "This script freezes the CST token." 19 | },{ 20 | header: "Options", 21 | optionList: optionsDefs 22 | } 23 | ]; 24 | module.exports = async function(callback) { 25 | const options = commandLineArgs(optionsDefs); 26 | 27 | if (!options.network || options.help || !options.registry) { 28 | console.log(getUsage(usage)); 29 | callback(); 30 | return; 31 | } 32 | 33 | let registryAddress = options.registry; 34 | 35 | let registry = registryAddress ? await RegistryContract.at(registryAddress) : await RegistryContract.deployed(); 36 | 37 | console.log(`Using registry at ${registry.address}`); 38 | let cstAddress = await registry.contractForHash(web3.sha3(CST_NAME)); 39 | 40 | let cst = await CardstackToken.at(cstAddress); 41 | 42 | if (options.data) { 43 | let data = cst.contract.freezeToken.getData(true); 44 | let estimatedGas = web3.eth.estimateGas({ 45 | to: cst.address, 46 | data 47 | }); 48 | console.log(`Data for freezing token for CST (${cst.address}):`); 49 | console.log(`\nAddress: ${cst.address}`); 50 | console.log(`Data: ${data}`); 51 | console.log(`Estimated gas: ${estimatedGas}`); 52 | callback(); 53 | return; 54 | } 55 | 56 | try { 57 | console.log(`Freezing token for CST (${cst.address})...`); 58 | await cst.freezeToken(true); 59 | console.log('done'); 60 | } catch (err) { 61 | console.error(`Error freezing token for CST (${cst.address}, ${err.message}`); 62 | } 63 | 64 | callback(); 65 | }; 66 | -------------------------------------------------------------------------------- /scripts/cst-grant-tokens-list.js: -------------------------------------------------------------------------------- 1 | const { CST_NAME } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | const fs = require("fs"); 5 | const _ = require("lodash"); 6 | const Parallel = require("async-parallel"); 7 | 8 | let RegistryContract = artifacts.require("./Registry.sol"); 9 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 10 | 11 | function displayBigNumber(number) { 12 | let precision = 50; 13 | let numberAsString = number.toPrecision(precision).toString(); 14 | let [ result ] = numberAsString.split('.'); 15 | return result; 16 | } 17 | const optionsDefs = [ 18 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 19 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 20 | { name: "csv", type: String, description: "The file containing ethereum addresses (each on a separate line)" }, 21 | { name: "raw", type: Boolean, description: "Use the token amounts in the CSV as the actual token amounts without coverting using the contract decimals field"}, 22 | { name: "concurrency", alias: "c", type: Number, description: "(Optional) The number of concurrent transactions to submit to the network at any one time. The default concurrency is 100 simultaneous transactions at a time." }, 23 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 24 | ]; 25 | 26 | const usage = [ 27 | { 28 | header: "cst-grant-tokens-list", 29 | content: "This script grants tokens to a list of ethereum addresses and their grant amounts (expressed in ETH--we do the math to figure out the corresponding amount of CARD)" 30 | },{ 31 | header: "Options", 32 | optionList: optionsDefs 33 | } 34 | ]; 35 | 36 | module.exports = async function(callback) { 37 | const options = commandLineArgs(optionsDefs); 38 | 39 | if (!options.csv || !options.network || options.help || !options.registry) { 40 | console.log(getUsage(usage)); 41 | callback(); 42 | return; 43 | } 44 | 45 | let registryAddress = options.registry; 46 | let { raw } = options; 47 | 48 | let registry = registryAddress ? await RegistryContract.at(registryAddress) : await RegistryContract.deployed(); 49 | 50 | console.log(`Using registry at ${registry.address}`); 51 | let cstAddress = await registry.contractForHash(web3.sha3(CST_NAME)); 52 | 53 | let cst = await CardstackToken.at(cstAddress); 54 | let decimals = await cst.decimals(); 55 | let decimalsFactor = new web3.BigNumber('1'.padEnd(decimals.toNumber() + 1, '0')); 56 | 57 | let { csv, concurrency } = options; 58 | 59 | concurrency = concurrency || 100; 60 | 61 | let fileStr = fs.readFileSync(csv); 62 | let rows = _.compact(fileStr.toString().split("\n")); 63 | 64 | console.log(`Scheduling ${rows.length} addresses to be granted tokens for the CST contract ${cst.address}`); 65 | 66 | let counter = 0; 67 | await Parallel.each(rows, async row => { 68 | let count = ++counter; 69 | if (count % concurrency === 0) { 70 | console.log(`Processing ${count} of ${rows.length}, ${Math.round((count / rows.length) * 100)}% complete...`); 71 | } 72 | 73 | let [ address, amount ] = row.replace(/"/g, "").split(","); 74 | 75 | if (address && amount && amount.trim()) { 76 | if (raw) { 77 | console.log(`Granting ${address} raw token amount ${amount}`); 78 | try { 79 | await cst.grantTokens(address, amount.trim()); 80 | } catch (err) { 81 | console.error(`Error encountered granting tokens ${address}, ${err.message}`); 82 | } 83 | } else { 84 | amount = new web3.BigNumber(amount); 85 | amount = amount.mul(new web3.BigNumber(decimalsFactor)); 86 | console.log(`Granting ${address} raw token amount ${displayBigNumber(amount)}`); 87 | try { 88 | await cst.grantTokens(address, displayBigNumber(amount)); 89 | } catch (err) { 90 | console.error(`Error encountered granting tokens ${address}, ${err.message}`); 91 | } 92 | } 93 | } 94 | }, concurrency); 95 | 96 | console.log("done."); 97 | callback(); 98 | }; 99 | -------------------------------------------------------------------------------- /scripts/cst-grant-tokens.js: -------------------------------------------------------------------------------- 1 | const { CST_NAME } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | 5 | let RegistryContract = artifacts.require("./Registry.sol"); 6 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 7 | 8 | function adjustForDecimals(value, decimals) { 9 | let decimalsFactor = new web3.BigNumber('1'.padEnd(decimals.toNumber() + 1, '0')); 10 | let resultBN = (new web3.BigNumber(value)).mul(decimalsFactor); 11 | let [ result ] = resultBN.toPrecision(40).toString().split('.'); 12 | return result; 13 | } 14 | 15 | const optionsDefs = [ 16 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 17 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 18 | { name: "address", alias: "a", type: String, description: "The address to grant admin permissions"}, 19 | { name: "amount", type: String, description: "The amount of CST tokens to grant." }, 20 | { name: "rawAmount", type: String, description: "The amount of tokens to mint without factoring token decimals" }, 21 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 22 | { name: "data", alias: "d", type: Boolean, description: "Display the data necessary to invoke the transaction instead of actually invoking the transaction" } 23 | ]; 24 | 25 | const usage = [ 26 | { 27 | header: "cst-grant-tokens", 28 | content: "This script grants tokens to the specified address" 29 | },{ 30 | header: "Options", 31 | optionList: optionsDefs 32 | } 33 | ]; 34 | 35 | module.exports = async function(callback) { 36 | const options = commandLineArgs(optionsDefs); 37 | 38 | if (!options.address || (!options.amount && !options.rawAmount) || !options.network || options.help || !options.registry) { 39 | console.log(getUsage(usage)); 40 | callback(); 41 | return; 42 | } 43 | 44 | let registryAddress = options.registry; 45 | 46 | let registry = registryAddress ? await RegistryContract.at(registryAddress) : await RegistryContract.deployed(); 47 | 48 | console.log(`Using registry at ${registry.address}`); 49 | let cstAddress = await registry.contractForHash(web3.sha3(CST_NAME)); 50 | 51 | let cst = await CardstackToken.at(cstAddress); 52 | let symbol = await cst.symbol(); 53 | let decimals = await cst.decimals(); 54 | 55 | let { address, amount, rawAmount } = options; 56 | 57 | if (options.data) { 58 | let data = !rawAmount ? cst.contract.grantTokens.getData(address, adjustForDecimals(amount, decimals)) : 59 | cst.contract.grantTokens.getData(address, rawAmount); 60 | let estimatedGas = web3.eth.estimateGas({ 61 | to: cst.address, 62 | data 63 | }); 64 | console.log(`Data for granting ${rawAmount ? rawAmount + ' (raw token amount)' : amount + ' ' + symbol} to ${address} for CST (${cst.address}):`); 65 | console.log(`\nAddress: ${cst.address}`); 66 | console.log(`Data: ${data}`); 67 | console.log(`Estimated gas: ${estimatedGas}`); 68 | callback(); 69 | return; 70 | } 71 | 72 | try { 73 | console.log(`Granting ${rawAmount ? rawAmount + ' (raw token amount)' : amount + ' ' + symbol} to ${address} for CST (${cst.address})...`); 74 | if (rawAmount) { 75 | await cst.grantTokens(address, rawAmount); 76 | } else { 77 | await cst.grantTokens(address, adjustForDecimals(amount, decimals)); 78 | } 79 | console.log('done'); 80 | } catch (err) { 81 | console.error(`Error granting tokens for CST (${cst.address}, ${err.message}`); 82 | } 83 | 84 | callback(); 85 | }; 86 | -------------------------------------------------------------------------------- /scripts/cst-grant-vesting-tokens.js: -------------------------------------------------------------------------------- 1 | const { CST_NAME } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | const moment = require('moment'); 5 | 6 | function adjustForDecimals(value, decimals) { 7 | let decimalsFactor = new web3.BigNumber('1'.padEnd(decimals.toNumber() + 1, '0')); 8 | let resultBN = (new web3.BigNumber(value)).mul(decimalsFactor); 9 | let [ result ] = resultBN.toPrecision(40).toString().split('.'); 10 | return result; 11 | } 12 | 13 | const dateFormat = "YYYY-MM-DD"; 14 | let RegistryContract = artifacts.require("./Registry.sol"); 15 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 16 | 17 | const optionsDefs = [ 18 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 19 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 20 | { name: "address", alias: "a", type: String, description: "The address to grant admin permissions"}, 21 | { name: "fullyVestedTokenAmount", type: Number, description: "The amount of tokens that will be made available when 100% vested." }, 22 | { name: "startDay", type: String, description: "[Optional] The day, in YYYY-MM-DD format of the local timezone in which to begin the vesting. Note that the date must be in the future. If no date is supplied, then vesting will begin at the time that this transaction is mined." }, 23 | { name: "vestingDurationDays", type: Number, description: "The amount of days over which the vesting will occur, after which the tokens will be 100% vested." }, 24 | { name: "daysUntilCliff", type: Number, description: "The amount of days after the vesting start date that the vesting cliff occurs. Before the cliff date, no tokens will be available. After the cliff date, vested tokens can be released." }, 25 | { name: "nonRevocable", type: Boolean, description: "[Optional] A flag indicating whether the vested token grant is non-revocable. Note that if you create a non-revocable vested token grant, there is no way to recall it. when this flag is not included, vested tokens grants are revocable. When a vested grant is revoked after the cliff date, all the vested tokens are released to the beneficiary, and the unvested tokens are returned to the token contract. If the vesting is revoked before the cliff date, then no tokens are released to the beneficiary, and all the tokens are released back to the token contract. Note that you cannot revoke a fully vested token grant." }, 26 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 27 | { name: "data", alias: "d", type: Boolean, description: "Display the data necessary to invoke the transaction instead of actually invoking the transaction" } 28 | ]; 29 | 30 | const usage = [ 31 | { 32 | header: "cst-grant-vested-tokens", 33 | content: "This script grants vested tokens to the specified address based on the vesting schedule defined in the command line arguments." 34 | },{ 35 | header: "Options", 36 | optionList: optionsDefs 37 | } 38 | ]; 39 | 40 | module.exports = async function(callback) { 41 | let { network, 42 | address, 43 | fullyVestedTokenAmount, 44 | startDay, 45 | vestingDurationDays, 46 | daysUntilCliff, 47 | nonRevocable, 48 | registry, 49 | data, 50 | help } = commandLineArgs(optionsDefs); 51 | 52 | if (!address || 53 | !fullyVestedTokenAmount || 54 | !vestingDurationDays || 55 | !network || 56 | help || 57 | !registry) { 58 | console.log(getUsage(usage)); 59 | callback(); 60 | return; 61 | } 62 | 63 | if (startDay && (!moment(startDay, dateFormat).isValid() || 64 | moment(startDay).isBefore())) { 65 | console.error(`The start date "${startDay}" is not a valid ${dateFormat} date or in the past`); 66 | console.log(getUsage(usage)); 67 | callback(); 68 | return; 69 | } 70 | 71 | if (vestingDurationDays < daysUntilCliff) { 72 | console.error(`The vestingDurationDays "${vestingDurationDays}" cannot be less than the daysUntilCliff "${daysUntilCliff}"`); 73 | console.log(getUsage(usage)); 74 | callback(); 75 | return; 76 | } 77 | 78 | let registryContract = await RegistryContract.at(registry); 79 | 80 | console.log(`Using registry at ${registryContract.address}`); 81 | let cstAddress = await registryContract.contractForHash(web3.sha3(CST_NAME)); 82 | 83 | let cst = await CardstackToken.at(cstAddress); 84 | let symbol = await cst.symbol(); 85 | let decimals = await cst.decimals(); 86 | 87 | let revocable = !nonRevocable; 88 | 89 | let startSec; 90 | if (!startDay) { 91 | startSec = 0; 92 | } else { 93 | startSec = moment(startDay, dateFormat).unix(); 94 | } 95 | let vestingDurationSec = Math.floor(moment.duration(vestingDurationDays, "days").as("seconds")); 96 | let vestingCliffSec = Math.floor(moment.duration(daysUntilCliff, "days").as("seconds")); 97 | 98 | if (data) { 99 | let data = cst.contract.grantVestedTokens.getData(address, 100 | adjustForDecimals(fullyVestedTokenAmount, decimals), 101 | startSec, 102 | vestingCliffSec, 103 | vestingDurationSec, 104 | revocable); 105 | let estimatedGas = web3.eth.estimateGas({ 106 | to: cst.address, 107 | data 108 | }); 109 | console.log(`Data for vested token grant with a fully vested amount of ${fullyVestedTokenAmount} ${symbol} to ${address}, that starts ${startDay ? startDay : "now" } with a vesting duration of ${vestingDurationDays} days and a cliff that occurs ${daysUntilCliff} days after the start day that is ${revocable ? "revocable" : "NOT revocable"} for CST (${cst.address}):`); 110 | console.log(`\nAddress: ${cst.address}`); 111 | console.log(`Data: ${data}`); 112 | console.log(`Estimated gas: ${estimatedGas}`); 113 | callback(); 114 | return; 115 | } 116 | 117 | try { 118 | console.log(`Granting vested tokens with a fully vested amount of ${fullyVestedTokenAmount} ${symbol} to ${address}, that starts ${startDay ? startDay : "now" } with a vesting duration of ${vestingDurationDays} days and a cliff that occurs ${daysUntilCliff} days after the start day that is ${revocable ? "revocable" : "NOT revocable"} for CST (${cst.address}):`); 119 | await cst.grantVestedTokens(address, 120 | adjustForDecimals(fullyVestedTokenAmount, decimals), 121 | startSec, 122 | vestingCliffSec, 123 | vestingDurationSec, 124 | revocable); 125 | console.log('done'); 126 | } catch (err) { 127 | console.error(`Error granting vested tokens for CST (${cst.address}, ${err.message}`); 128 | } 129 | 130 | callback(); 131 | }; 132 | -------------------------------------------------------------------------------- /scripts/cst-mint-tokens.js: -------------------------------------------------------------------------------- 1 | const { CST_NAME } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | 5 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 6 | let RegistryContract = artifacts.require("./Registry.sol"); 7 | 8 | function adjustForDecimals(value, decimals) { 9 | let decimalsFactor = new web3.BigNumber('1'.padEnd(decimals.toNumber() + 1, '0')); 10 | let resultBN = (new web3.BigNumber(value)).mul(decimalsFactor); 11 | let [ result ] = resultBN.toPrecision(40).toString().split('.'); 12 | return result; 13 | } 14 | 15 | const optionsDefs = [ 16 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 17 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 18 | { name: "rawAmount", type: String, description: "The amount of tokens to mint without factoring token decimals" }, 19 | { name: "amount", type: String, description: "The amount of tokens to mint with factoring token decimals" }, 20 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 21 | { name: "data", alias: "d", type: Boolean, description: "Display the data necessary to invoke the transaction instead of actually invoking the transaction" } 22 | ]; 23 | 24 | const usage = [ 25 | { 26 | header: "cst-mint-tokens", 27 | content: "This script mints the specified number of CST tokens." 28 | },{ 29 | header: "Options", 30 | optionList: optionsDefs 31 | } 32 | ]; 33 | 34 | module.exports = async function(callback) { 35 | const options = commandLineArgs(optionsDefs); 36 | 37 | if ((!options.amount && !options.rawAmount) || 38 | !options.network || 39 | options.help || 40 | !options.registry) { 41 | console.log(getUsage(usage)); 42 | callback(); 43 | return; 44 | } 45 | 46 | let registryAddress = options.registry; 47 | 48 | let registry = registryAddress ? await RegistryContract.at(registryAddress) : await RegistryContract.deployed(); 49 | 50 | console.log(`Using registry at ${registry.address}`); 51 | let cstAddress = await registry.contractForHash(web3.sha3(CST_NAME)); 52 | 53 | let cst = await CardstackToken.at(cstAddress); 54 | let symbol = await cst.symbol(); 55 | let decimals = await cst.decimals(); 56 | 57 | let numOfTokens = options.amount; 58 | let rawAmountOfTokens = options.rawAmount; 59 | 60 | if (options.data) { 61 | let data = rawAmountOfTokens ? cst.contract.mintTokens.getData(adjustForDecimals(numOfTokens, decimals)) : 62 | cst.contract.mintTokens.getData(rawAmountOfTokens); 63 | let estimatedGas = web3.eth.estimateGas({ 64 | to: cst.address, 65 | data 66 | }); 67 | console.log(`Data for minting ${rawAmountOfTokens ? rawAmountOfTokens + ' (raw token amount)' : numOfTokens + ' ' + symbol} for CST (${cst.address}):`); 68 | console.log(`\nAddress: ${cst.address}`); 69 | console.log(`Data: ${data}`); 70 | console.log(`Estimated gas: ${estimatedGas}`); 71 | callback(); 72 | return; 73 | } 74 | 75 | try { 76 | console.log(`Minting ${rawAmountOfTokens ? rawAmountOfTokens + ' (raw token amount)' : numOfTokens + ' ' + symbol} for CST (${cst.address})...`); 77 | if (rawAmountOfTokens) { 78 | await cst.mintTokens(rawAmountOfTokens); 79 | } else { 80 | await cst.mintTokens(adjustForDecimals(numOfTokens, decimals)); 81 | } 82 | console.log('done'); 83 | } catch (err) { 84 | console.error(`Error encountered minting tokens for CST (${cst.address}), ${err.message}`); 85 | } 86 | 87 | callback(); 88 | }; 89 | -------------------------------------------------------------------------------- /scripts/cst-register-ledger.js: -------------------------------------------------------------------------------- 1 | const { CST_NAME, CST_STORAGE_NAME } = require("../lib/constants.js"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | let RegistryContract = artifacts.require("./Registry.sol"); 5 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 6 | let CstLedger = artifacts.require("./CstLedger.sol"); 7 | 8 | const optionsDefs = [ 9 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 10 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 11 | { name: "ledgerName", type: String, description: "The name of the new ledger to register" }, 12 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 13 | ]; 14 | 15 | const usage = [ 16 | { 17 | header: "cst-register-ledger", 18 | content: "This script creates a new ledger and attaches it to token" 19 | },{ 20 | header: "Options", 21 | optionList: optionsDefs 22 | } 23 | ]; 24 | 25 | module.exports = async function(callback) { 26 | const options = commandLineArgs(optionsDefs); 27 | 28 | if (!options.network || !options.ledgerName || options.help || !options.registry) { 29 | console.log(getUsage(usage)); 30 | callback(); 31 | return; 32 | } 33 | 34 | let { registry:registryAddress, ledgerName } = options; 35 | 36 | let registry = await RegistryContract.at(registryAddress); 37 | console.log(`Using registry at ${registry.address}`); 38 | let cstAddress = await registry.contractForHash(web3.sha3(CST_NAME)); 39 | let cst = await CardstackToken.at(cstAddress); 40 | 41 | try { 42 | console.log(`\nDeploying Ledger...`); 43 | 44 | let ledger = await CstLedger.new(); 45 | console.log(`\nDeployed ledger to ${ledger.address}`); 46 | 47 | console.log(`\nGranting registry permissions to manage ledger...`); 48 | await ledger.addSuperAdmin(registry.address); 49 | 50 | console.log(`Adding ledger storage to registry with name '${ledgerName}'...`); 51 | await registry.addStorage(ledgerName, ledger.address); 52 | 53 | console.log(`Granting token permissions to use ledger...`); 54 | await ledger.addAdmin(cst.address); 55 | 56 | console.log(`Updating token...`); 57 | await cst.updateStorage(CST_STORAGE_NAME, ledgerName); 58 | 59 | console.log(`\nCompleted registering ledger with token.`); 60 | 61 | } catch(err) { 62 | console.error("Error encountered registering ledger with contract", err); 63 | } 64 | 65 | callback(); 66 | }; 67 | -------------------------------------------------------------------------------- /scripts/cst-register.js: -------------------------------------------------------------------------------- 1 | const { CST_NAME, CARDSTACK_NAMEHASH } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | let RegistryContract = artifacts.require("./Registry.sol"); 5 | 6 | const optionsDefs = [ 7 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 8 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 9 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 10 | { name: "cst", alias: "c",type: String, description: "(optional) The address of the deployed Cardstack token if you dont intend to deploy a new token contract." }, 11 | { name: "data", alias: "d", type: Boolean, description: "Display the data necessary to invoke the transaction instead of actually invoking the transaction" } 12 | ]; 13 | 14 | const usage = [ 15 | { 16 | header: "cst-register", 17 | content: "This script registers a CST contract with the registry." 18 | },{ 19 | header: "Options", 20 | optionList: optionsDefs 21 | } 22 | ]; 23 | 24 | module.exports = async function(callback) { 25 | const options = commandLineArgs(optionsDefs); 26 | 27 | if (!options.cst || !options.network || options.help || !options.registry) { 28 | console.log(getUsage(usage)); 29 | callback(); 30 | return; 31 | } 32 | 33 | let registryAddress = options.registry; 34 | 35 | let registry = registryAddress ? await RegistryContract.at(registryAddress) : await RegistryContract.deployed(); 36 | let cstAddress = options.cst; 37 | 38 | console.log(`Using registry at ${registry.address}`); 39 | 40 | if (options.data) { 41 | let data = registry.contract.register.getData(CST_NAME, cstAddress, CARDSTACK_NAMEHASH); 42 | let estimatedGas = web3.eth.estimateGas({ 43 | to: registry.address, 44 | data 45 | }); 46 | console.log(`Data for registering CST (${cstAddress}) as contract "${CST_NAME}" with registry (${registry.address}):`); 47 | console.log(`\nAddress: ${registry.address}`); 48 | console.log(`Data: ${data}`); 49 | console.log(`Estimated gas: ${estimatedGas}`); 50 | callback(); 51 | return; 52 | } 53 | 54 | console.log(`Registering contract ${cstAddress} ...`); 55 | 56 | try { 57 | await registry.register(CST_NAME, cstAddress, CARDSTACK_NAMEHASH); 58 | console.log(`\nRegistered CST (${cstAddress}) as contract "${CST_NAME}" with registry (${registry.address})`); 59 | } catch (err) { 60 | console.error(`\nError registering CST contract with registry ${registry.address}, ${err.message}`); 61 | } 62 | 63 | callback(); 64 | }; 65 | -------------------------------------------------------------------------------- /scripts/cst-release-info.js: -------------------------------------------------------------------------------- 1 | const { CST_BUY_GAS_LIMIT, CST_NAME } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | let RegistryContract = artifacts.require("./Registry.sol"); 5 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 6 | 7 | const optionsDefs = [ 8 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 9 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 10 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 11 | ]; 12 | 13 | const usage = [ 14 | { 15 | header: "cst-release-info", 16 | content: "This script display vesting release information that instructs how to send an ethereum transaction to release vested CST for the sender of the transaction." 17 | },{ 18 | header: "Options", 19 | optionList: optionsDefs 20 | } 21 | ]; 22 | 23 | module.exports = async function(callback) { 24 | const { network, help, registry } = commandLineArgs(optionsDefs); 25 | 26 | if (!network || help || !registry) { 27 | console.log(getUsage(usage)); 28 | callback(); 29 | return; 30 | } 31 | 32 | let registryContract = await RegistryContract.at(registry); 33 | 34 | console.log(`Using registry at ${registryContract.address}`); 35 | let cstAddress = await registryContract.contractForHash(web3.sha3(CST_NAME)); 36 | 37 | let cst = await CardstackToken.at(cstAddress); 38 | let symbol = await cst.symbol(); 39 | 40 | let data = cst.contract.releaseVestedTokens.getData(); 41 | 42 | console.log(`\nTo release your vested ${symbol} send 0 ETH (not including gas) to the following address with the following data:`); 43 | console.log(`Address: ${cst.address}`); 44 | console.log(`Data: ${data}`); 45 | console.log(`Estimated gas: ${CST_BUY_GAS_LIMIT}`); 46 | 47 | callback(); 48 | }; 49 | -------------------------------------------------------------------------------- /scripts/cst-release-vested-tokens.js: -------------------------------------------------------------------------------- 1 | const { CST_NAME } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | 5 | let RegistryContract = artifacts.require("./Registry.sol"); 6 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 7 | 8 | const optionsDefs = [ 9 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 10 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 11 | { name: "address", alias: "a", type: String, description: "The address to grant admin permissions"}, 12 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 13 | { name: "data", alias: "d", type: Boolean, description: "Display the data necessary to invoke the transaction instead of actually invoking the transaction" } 14 | ]; 15 | 16 | const usage = [ 17 | { 18 | header: "cst-release-vested-tokens", 19 | content: "This script releases vested tokens for the specified beneficiary. Any vested tokens that have not yet been placed into the benefieicary's account will be transfered to the beneficary's account aafter this script is run." 20 | },{ 21 | header: "Options", 22 | optionList: optionsDefs 23 | } 24 | ]; 25 | 26 | module.exports = async function(callback) { 27 | let { network, 28 | address, 29 | registry, 30 | data, 31 | help } = commandLineArgs(optionsDefs); 32 | 33 | if (!address || 34 | !network || 35 | help || 36 | !registry) { 37 | console.log(getUsage(usage)); 38 | callback(); 39 | return; 40 | } 41 | 42 | let registryContract = await RegistryContract.at(registry); 43 | 44 | console.log(`Using registry at ${registryContract.address}`); 45 | let cstAddress = await registryContract.contractForHash(web3.sha3(CST_NAME)); 46 | 47 | let cst = await CardstackToken.at(cstAddress); 48 | 49 | if (data) { 50 | let data = cst.contract.releaseVestedTokensForBeneficiary.getData(address); 51 | let estimatedGas = web3.eth.estimateGas({ 52 | to: cst.address, 53 | data 54 | }); 55 | 56 | // the estimated gas calculations for this txn are way off... 57 | estimatedGas = 200000; 58 | 59 | console.log(`Data for releasing vested tokens for the beneficiary ${address}, for CST (${cst.address}):`); 60 | console.log(`\nAddress: ${cst.address}`); 61 | console.log(`Data: ${data}`); 62 | console.log(`Estimated gas: ${estimatedGas}`); 63 | callback(); 64 | return; 65 | } 66 | 67 | try { 68 | console.log(`Releasing vested tokens for the beneficiary ${address}, for CST (${cst.address}):`); 69 | await cst.releaseVestedTokensForBeneficiary(address); 70 | console.log('done'); 71 | } catch (err) { 72 | console.error(`Error releasing vested tokens for CST (${cst.address}, ${err.message}`); 73 | } 74 | 75 | callback(); 76 | }; 77 | -------------------------------------------------------------------------------- /scripts/cst-remove-whitelisted-transferer.js: -------------------------------------------------------------------------------- 1 | const { CST_NAME } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | let RegistryContract = artifacts.require("./Registry.sol"); 5 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 6 | 7 | const optionsDefs = [ 8 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 9 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 10 | { name: "address", alias: "a", type: String, description: "The address to grant admin permissions"}, 11 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 12 | { name: "data", alias: "d", type: Boolean, description: "Display the data necessary to invoke the transaction instead of actually invoking the transaction" } 13 | ]; 14 | 15 | const usage = [ 16 | { 17 | header: "cst-remove-whitelisted-transferer", 18 | content: "This script removes an address from the transfer whitelist" 19 | },{ 20 | header: "Options", 21 | optionList: optionsDefs 22 | } 23 | ]; 24 | 25 | module.exports = async function(callback) { 26 | const options = commandLineArgs(optionsDefs); 27 | 28 | if (!options.address || (!options.network && !options.data) || options.help || !options.registry) { 29 | console.log(getUsage(usage)); 30 | callback(); 31 | return; 32 | } 33 | 34 | let registryAddress = options.registry; 35 | 36 | let registry = registryAddress ? await RegistryContract.at(registryAddress) : await RegistryContract.deployed(); 37 | 38 | console.log(`Using registry at ${registry.address}`); 39 | let cstAddress = await registry.contractForHash(web3.sha3(CST_NAME)); 40 | 41 | let cst = await CardstackToken.at(cstAddress); 42 | 43 | let { address } = options; 44 | 45 | if (options.data) { 46 | let data = cst.contract.setWhitelistedTransferer.getData(address, false); 47 | let estimatedGas = web3.eth.estimateGas({ 48 | to: cst.address, 49 | data 50 | }); 51 | console.log(`Data for removing whitelisting of transferer "${address}":`); 52 | console.log(`\nAddress: ${cst.address}`); 53 | console.log(`Data: ${data}`); 54 | console.log(`Estimated gas: ${estimatedGas}`); 55 | 56 | callback(); 57 | return; 58 | } 59 | 60 | try { 61 | console.log(`Removing whitelisted transferer "${address}" for CST ${cst.address}...`); 62 | await cst.setWhitelistedTransferer(address, false); 63 | console.log('done'); 64 | } catch (err) { 65 | console.error(`Error encountered removing whitelisting transferer, ${err.message}`); 66 | } 67 | 68 | callback(); 69 | }; 70 | -------------------------------------------------------------------------------- /scripts/cst-resume-purchases.js: -------------------------------------------------------------------------------- 1 | const { CST_NAME } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | 5 | let RegistryContract = artifacts.require("./Registry.sol"); 6 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 7 | 8 | const optionsDefs = [ 9 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 10 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 11 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 12 | { name: "data", alias: "d", type: Boolean, description: "Display the data necessary to invoke the transaction instead of actually invoking the transaction" } 13 | ]; 14 | 15 | const usage = [ 16 | { 17 | header: "cst-resume-purchase", 18 | content: "This script resumes the purchase of CST tokens" 19 | },{ 20 | header: "Options", 21 | optionList: optionsDefs 22 | } 23 | ]; 24 | module.exports = async function(callback) { 25 | const options = commandLineArgs(optionsDefs); 26 | 27 | if (!options.network || options.help || !options.registry) { 28 | console.log(getUsage(usage)); 29 | callback(); 30 | return; 31 | } 32 | 33 | let registryAddress = options.registry; 34 | 35 | let registry = registryAddress ? await RegistryContract.at(registryAddress) : await RegistryContract.deployed(); 36 | 37 | console.log(`Using registry at ${registry.address}`); 38 | let cstAddress = await registry.contractForHash(web3.sha3(CST_NAME)); 39 | 40 | let cst = await CardstackToken.at(cstAddress); 41 | 42 | if (options.data) { 43 | let data = cst.contract.setHaltPurchase.getData(false); 44 | let estimatedGas = web3.eth.estimateGas({ 45 | to: cst.address, 46 | data 47 | }); 48 | console.log(`Data for resuming purchase of CST (${cst.address}):`); 49 | console.log(`\nAddress: ${cst.address}`); 50 | console.log(`Data: ${data}`); 51 | console.log(`Estimated gas: ${estimatedGas}`); 52 | callback(); 53 | return; 54 | } 55 | 56 | try { 57 | console.log(`Resuming purchase of CST (${cst.address})...`); 58 | await cst.setHaltPurchase(false); 59 | console.log('done'); 60 | } catch (err) { 61 | console.error(`Error resuming purchase of CST (${cst.address}, ${err.message}`); 62 | } 63 | 64 | callback(); 65 | }; 66 | -------------------------------------------------------------------------------- /scripts/cst-revoke-vesting.js: -------------------------------------------------------------------------------- 1 | const { CST_NAME } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | 5 | let RegistryContract = artifacts.require("./Registry.sol"); 6 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 7 | 8 | const optionsDefs = [ 9 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 10 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 11 | { name: "address", alias: "a", type: String, description: "The address to grant admin permissions"}, 12 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 13 | { name: "data", alias: "d", type: Boolean, description: "Display the data necessary to invoke the transaction instead of actually invoking the transaction" } 14 | ]; 15 | 16 | const usage = [ 17 | { 18 | header: "cst-revoke-vesting", 19 | content: "This script revokes vesting of CST for the beneficiary the specified address. When a vested grant is revoked after the cliff date, all the vested tokens are released to the beneficiary, and the unvested tokens are returned to the token contract. If the vesting is revoked before the cliff date, then no tokens are released to the beneficiary, and all the tokens are released back to the token contract. Note that you cannot revoke a fully vested token grant." 20 | },{ 21 | header: "Options", 22 | optionList: optionsDefs 23 | } 24 | ]; 25 | 26 | module.exports = async function(callback) { 27 | let { network, 28 | address, 29 | registry, 30 | data, 31 | help } = commandLineArgs(optionsDefs); 32 | 33 | if (!address || 34 | !network || 35 | help || 36 | !registry) { 37 | console.log(getUsage(usage)); 38 | callback(); 39 | return; 40 | } 41 | 42 | let registryContract = await RegistryContract.at(registry); 43 | 44 | console.log(`Using registry at ${registryContract.address}`); 45 | let cstAddress = await registryContract.contractForHash(web3.sha3(CST_NAME)); 46 | 47 | let cst = await CardstackToken.at(cstAddress); 48 | 49 | if (data) { 50 | let data = cst.contract.revokeVesting.getData(address); 51 | let estimatedGas = web3.eth.estimateGas({ 52 | to: cst.address, 53 | data 54 | }); 55 | console.log(`Data for revoking vested token grant for the beneficiary ${address}, for CST (${cst.address}):`); 56 | console.log(`\nAddress: ${cst.address}`); 57 | console.log(`Data: ${data}`); 58 | console.log(`Estimated gas: ${estimatedGas}`); 59 | callback(); 60 | return; 61 | } 62 | 63 | try { 64 | console.log(`Revoking vested token grant for the beneficiary ${address}, for CST (${cst.address}):`); 65 | await cst.revokeVesting(address); 66 | console.log('done'); 67 | } catch (err) { 68 | console.error(`Error revoking vesting for CST (${cst.address}, ${err.message}`); 69 | } 70 | 71 | callback(); 72 | }; 73 | -------------------------------------------------------------------------------- /scripts/cst-suspend-purchases.js: -------------------------------------------------------------------------------- 1 | const { CST_NAME } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | 5 | let RegistryContract = artifacts.require("./Registry.sol"); 6 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 7 | 8 | const optionsDefs = [ 9 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 10 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 11 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 12 | { name: "data", alias: "d", type: Boolean, description: "Display the data necessary to invoke the transaction instead of actually invoking the transaction" } 13 | ]; 14 | 15 | const usage = [ 16 | { 17 | header: "cst-suspend-purchases", 18 | content: "This script suspends the purchase of CST tokens" 19 | },{ 20 | header: "Options", 21 | optionList: optionsDefs 22 | } 23 | ]; 24 | module.exports = async function(callback) { 25 | const options = commandLineArgs(optionsDefs); 26 | 27 | if (!options.network || options.help || !options.registry) { 28 | console.log(getUsage(usage)); 29 | callback(); 30 | return; 31 | } 32 | 33 | let registryAddress = options.registry; 34 | 35 | let registry = registryAddress ? await RegistryContract.at(registryAddress) : await RegistryContract.deployed(); 36 | 37 | console.log(`Using registry at ${registry.address}`); 38 | let cstAddress = await registry.contractForHash(web3.sha3(CST_NAME)); 39 | 40 | let cst = await CardstackToken.at(cstAddress); 41 | 42 | if (options.data) { 43 | let data = cst.contract.setHaltPurchase.getData(true); 44 | let estimatedGas = web3.eth.estimateGas({ 45 | to: cst.address, 46 | data 47 | }); 48 | console.log(`Data for suspending purchase of CST (${cst.address}):`); 49 | console.log(`\nAddress: ${cst.address}`); 50 | console.log(`Data: ${data}`); 51 | console.log(`Estimated gas: ${estimatedGas}`); 52 | callback(); 53 | return; 54 | } 55 | 56 | try { 57 | console.log(`Suspending purchase of CST (${cst.address})...`); 58 | await cst.setHaltPurchase(true); 59 | console.log('done'); 60 | } catch (err) { 61 | console.error(`Error suspending purchase of CST (${cst.address}, ${err.message}`); 62 | } 63 | 64 | callback(); 65 | }; 66 | -------------------------------------------------------------------------------- /scripts/cst-transfer-info.js: -------------------------------------------------------------------------------- 1 | const { CST_BUY_GAS_LIMIT, CST_NAME } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | let RegistryContract = artifacts.require("./Registry.sol"); 5 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 6 | 7 | const optionsDefs = [ 8 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 9 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 10 | { name: "address", alias: "a", type: String, description: "The address to grant admin permissions"}, 11 | { name: "amount", type: String, description: "The amount of tokens to transfer" }, 12 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 13 | ]; 14 | 15 | const usage = [ 16 | { 17 | header: "cst-transfer-info", 18 | content: "This script display transfer information that instructs how to transfer CST." 19 | },{ 20 | header: "Options", 21 | optionList: optionsDefs 22 | } 23 | ]; 24 | 25 | module.exports = async function(callback) { 26 | const options = commandLineArgs(optionsDefs); 27 | 28 | if (!options.network || options.help || !options.amount || !options.address || !options.registry) { 29 | console.log(getUsage(usage)); 30 | callback(); 31 | return; 32 | } 33 | 34 | let { address, amount } = options; 35 | let registryAddress = options.registry; 36 | 37 | let registry = registryAddress ? await RegistryContract.at(registryAddress) : await RegistryContract.deployed(); 38 | 39 | console.log(`Using registry at ${registry.address}`); 40 | let cstAddress = await registry.contractForHash(web3.sha3(CST_NAME)); 41 | 42 | let cst = await CardstackToken.at(cstAddress); 43 | let symbol = await cst.symbol(); 44 | 45 | let data = cst.contract.transfer.getData(address, amount); 46 | console.log(`\nTo transfer ${amount} ${symbol} to the recipient ${address}, send 0 ETH (not including gas) to the following address with the following data:`); 47 | console.log(`Address: ${cst.address}`); 48 | console.log(`Data: ${data}`); 49 | console.log(`Estimated gas: ${CST_BUY_GAS_LIMIT}`); 50 | 51 | callback(); 52 | }; 53 | -------------------------------------------------------------------------------- /scripts/cst-unfreeze-account.js: -------------------------------------------------------------------------------- 1 | const { CST_NAME } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | let RegistryContract = artifacts.require("./Registry.sol"); 5 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 6 | 7 | const optionsDefs = [ 8 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 9 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 10 | { name: "address", alias: "a", type: String, description: "The address to grant admin permissions"}, 11 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 12 | { name: "data", alias: "d", type: Boolean, description: "Display the data necessary to invoke the transaction instead of actually invoking the transaction" } 13 | ]; 14 | 15 | const usage = [ 16 | { 17 | header: "cst-unfreeze-account", 18 | content: "This script unfreezes a CST account." 19 | },{ 20 | header: "Options", 21 | optionList: optionsDefs 22 | } 23 | ]; 24 | 25 | module.exports = async function(callback) { 26 | const options = commandLineArgs(optionsDefs); 27 | 28 | if (!options.address || !options.network || options.help || !options.registry) { 29 | console.log(getUsage(usage)); 30 | callback(); 31 | return; 32 | } 33 | 34 | let registryAddress = options.registry; 35 | 36 | let registry = registryAddress ? await RegistryContract.at(registryAddress) : await RegistryContract.deployed(); 37 | 38 | console.log(`Using registry at ${registry.address}`); 39 | let cstAddress = await registry.contractForHash(web3.sha3(CST_NAME)); 40 | 41 | let cst = await CardstackToken.at(cstAddress); 42 | 43 | let address = options.address; 44 | 45 | if (options.data) { 46 | let data = cst.contract.freezeAccount.getData(address, false); 47 | let estimatedGas = web3.eth.estimateGas({ 48 | to: cst.address, 49 | data 50 | }); 51 | console.log(`Data for unfreezing account ${address} for CST (${cst.address}):`); 52 | console.log(`\nAddress: ${cst.address}`); 53 | console.log(`Data: ${data}`); 54 | console.log(`Estimated gas: ${estimatedGas}`); 55 | callback(); 56 | return; 57 | } 58 | 59 | try { 60 | console.log(`Unfreezing account ${address} for CST (${cst.address})...`); 61 | await cst.freezeAccount(address, false); 62 | console.log('done'); 63 | } catch (err) { 64 | console.error(`Error unfreezing account for CST (${cst.address}, ${err.message}`); 65 | } 66 | 67 | callback(); 68 | }; 69 | -------------------------------------------------------------------------------- /scripts/cst-unfreeze-token.js: -------------------------------------------------------------------------------- 1 | const { CST_NAME } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | 5 | let RegistryContract = artifacts.require("./Registry.sol"); 6 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 7 | 8 | const optionsDefs = [ 9 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 10 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 11 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 12 | { name: "data", alias: "d", type: Boolean, description: "Display the data necessary to invoke the transaction instead of actually invoking the transaction" } 13 | ]; 14 | 15 | const usage = [ 16 | { 17 | header: "cst-freeze-untoken", 18 | content: "This script unfreezes the CST token." 19 | },{ 20 | header: "Options", 21 | optionList: optionsDefs 22 | } 23 | ]; 24 | module.exports = async function(callback) { 25 | const options = commandLineArgs(optionsDefs); 26 | 27 | if (!options.network || options.help || !options.registry) { 28 | console.log(getUsage(usage)); 29 | callback(); 30 | return; 31 | } 32 | 33 | let registryAddress = options.registry; 34 | 35 | let registry = registryAddress ? await RegistryContract.at(registryAddress) : await RegistryContract.deployed(); 36 | 37 | console.log(`Using registry at ${registry.address}`); 38 | let cstAddress = await registry.contractForHash(web3.sha3(CST_NAME)); 39 | 40 | let cst = await CardstackToken.at(cstAddress); 41 | 42 | if (options.data) { 43 | let data = cst.contract.freezeToken.getData(false); 44 | let estimatedGas = web3.eth.estimateGas({ 45 | to: cst.address, 46 | data 47 | }); 48 | console.log(`Data for unfreezing token for CST (${cst.address}):`); 49 | console.log(`\nAddress: ${cst.address}`); 50 | console.log(`Data: ${data}`); 51 | console.log(`Estimated gas: ${estimatedGas}`); 52 | callback(); 53 | return; 54 | } 55 | 56 | try { 57 | console.log(`Unfreezing token for CST (${cst.address})...`); 58 | await cst.freezeToken(false); 59 | console.log('done'); 60 | } catch (err) { 61 | console.error(`Error unfreezing token for CST (${cst.address}, ${err.message}`); 62 | } 63 | 64 | callback(); 65 | }; 66 | -------------------------------------------------------------------------------- /scripts/cst-vesting-info.js: -------------------------------------------------------------------------------- 1 | const { CST_NAME } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | const moment = require('moment'); 5 | 6 | const dateFormat = "YYYY-MM-DD HH:mm Z"; 7 | 8 | let RegistryContract = artifacts.require("./Registry.sol"); 9 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 10 | 11 | function adjustForDecimals(value, decimals) { 12 | let decimalsFactor = new web3.BigNumber('1'.padEnd(decimals.toNumber() + 1, '0')); 13 | return (new web3.BigNumber(value)).div(decimalsFactor); 14 | } 15 | 16 | const optionsDefs = [ 17 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 18 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 19 | { name: "address", alias: "a", type: String, description: "The address to grant admin permissions"}, 20 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 21 | ]; 22 | 23 | const usage = [ 24 | { 25 | header: "cst-vesting-info", 26 | content: "This script displays vesting information for the specified beneficiary." 27 | },{ 28 | header: "Options", 29 | optionList: optionsDefs 30 | } 31 | ]; 32 | 33 | module.exports = async function(callback) { 34 | let { network, 35 | address, 36 | registry, 37 | help } = commandLineArgs(optionsDefs); 38 | 39 | if (!address || 40 | !network || 41 | help || 42 | !registry) { 43 | console.log(getUsage(usage)); 44 | callback(); 45 | return; 46 | } 47 | 48 | let registryContract = await RegistryContract.at(registry); 49 | 50 | console.log(`Using registry at ${registryContract.address}`); 51 | let cstAddress = await registryContract.contractForHash(web3.sha3(CST_NAME)); 52 | 53 | let cst = await CardstackToken.at(cstAddress); 54 | 55 | let [ startDate, 56 | cliffDate, 57 | durationSec, 58 | fullyVestedAmount, 59 | vestedAmount, 60 | vestedAvailableAmount, 61 | releasedAmount, 62 | revokeDate, 63 | isRevocable ] = await cst.vestingSchedule(address); 64 | let releasableAmount = await cst.releasableAmount(address); 65 | let cstSymbol = await cst.symbol(); 66 | let decimals = await cst.decimals(); 67 | 68 | console.log(` 69 | Vesting information for beneficiary: ${address} ${revokeDate.toNumber() > 0 ? "Revoked on " + moment.unix(revokeDate.toNumber()).format(dateFormat) : ""} 70 | start date: ${moment.unix(startDate.toNumber()).format(dateFormat)} 71 | cliff date: ${moment.unix(cliffDate.toNumber()).format(dateFormat)} 72 | fully vested date: ${moment.unix(startDate.toNumber() + durationSec.toNumber()).format(dateFormat)} 73 | fully vested amount: ${adjustForDecimals(fullyVestedAmount, decimals)} ${cstSymbol} 74 | vested amount as of now (${moment().format(dateFormat)}): ${adjustForDecimals(vestedAmount, decimals).toFixed(0)} ${cstSymbol} 75 | vested amount available as of now (${moment().format(dateFormat)}): ${adjustForDecimals(vestedAvailableAmount, decimals)} ${cstSymbol} 76 | vested amount already released: ${adjustForDecimals(releasedAmount, decimals)} ${cstSymbol} 77 | vested amount not yet released ${adjustForDecimals(releasableAmount, decimals)} ${cstSymbol} 78 | is revocable: ${isRevocable} 79 | `); 80 | 81 | callback(); 82 | }; 83 | -------------------------------------------------------------------------------- /scripts/cst-withdraw-info.js: -------------------------------------------------------------------------------- 1 | const { CST_BUY_GAS_LIMIT, NULL_ADDRESS } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 5 | 6 | const optionsDefs = [ 7 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 8 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 9 | { name: "amount", type: String, description: "The amount of CST tokens to grant." }, 10 | { name: "cst", alias: "c", type: String, description: "(optional) The address of the deployed Cardstack token if you dont intend to deploy a new token contract." } 11 | ]; 12 | 13 | const usage = [ 14 | { 15 | header: "cst-buy-info", 16 | content: "This script displays ETH deposit information that instructs how the Cardstack Foundation can deposit ETH into the CST contract for the purposes of buying back CST." 17 | },{ 18 | header: "Options", 19 | optionList: optionsDefs 20 | } 21 | ]; 22 | 23 | module.exports = async function(callback) { 24 | const options = commandLineArgs(optionsDefs); 25 | 26 | if (!options.network || !options.amount || options.help || !options.cst) { 27 | console.log(getUsage(usage)); 28 | callback(); 29 | return; 30 | } 31 | 32 | let { cst:cstAddress } = options; 33 | 34 | console.log(`Using token contract at ${cstAddress}`); 35 | 36 | let cst = await CardstackToken.at(cstAddress); 37 | 38 | let foundation = await cst.foundation(); 39 | if (foundation === NULL_ADDRESS) { 40 | console.log(`The foundation address has not been set. Set the foundation address on the CST contract before performing a withdral`); 41 | callback(); 42 | return; 43 | } 44 | 45 | let amount = web3.toWei(options.amount, "ether"); 46 | let balance = await web3.eth.getBalance(cst.address); 47 | 48 | if (amount > balance) { 49 | console.log(`The CST contract does not have a high enough balance to support this transaction. The maximum withdrawal is currently ${web3.fromWei(balance, "ether")} ETH.`); 50 | callback(); 51 | return; 52 | } 53 | 54 | let data = cst.contract.foundationWithdraw.getData(amount); 55 | console.log(`\nTo withdraw ETH from the CST contract, send 0 ETH to the following address with the following data from the wallet with the address ${foundation}:`); 56 | console.log(`Address: ${cst.address}`); 57 | console.log(`Data: ${data}`); 58 | console.log(`Estimated gas: ${CST_BUY_GAS_LIMIT}`); 59 | 60 | callback(); 61 | }; 62 | -------------------------------------------------------------------------------- /scripts/deploy-cst.js: -------------------------------------------------------------------------------- 1 | const { CST_DEPLOY_GAS_LIMIT, CST_STORAGE_NAME, CST_LEDGER_NAME } = require("../lib/constants.js"); 2 | const { upgradeContract, proxyContract } = require('../lib/proxy'); 3 | const commandLineArgs = require('command-line-args'); 4 | const getUsage = require('command-line-usage'); 5 | const CstLibrary = artifacts.require("./CstLibrary.sol"); 6 | const CardstackToken = artifacts.require("./CardstackToken.sol"); 7 | const AdminUpgradeabilityProxy = artifacts.require('AdminUpgradeabilityProxy'); 8 | 9 | const optionsDefs = [ 10 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 11 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 12 | { name: "registry", alias: "r", type: String, description: "This value is necessary then the 'proxy' argument is not supplied. The address of the registry." }, 13 | { name: "admin", type: String, description: "The address of the proxyAdmin. IMPORTANT: the proxyAdmin is not allowed to invoke functions on the implemented contract!" }, 14 | { name: "proxy", type: String, description: "(optional) The address of the cardstack token proxy. If no proxy is provided, then one will be created for you." }, 15 | { name: "library", type: String, description: "(optional) The address of the deployed CST Library if you dont intent to deploy a new library." }, 16 | ]; 17 | 18 | const usage = [ 19 | { 20 | header: "deploy-cst", 21 | content: "This script creates/upgrades the currently deployed token contract with a new contract." 22 | },{ 23 | header: "Options", 24 | optionList: optionsDefs 25 | } 26 | ]; 27 | 28 | module.exports = async function(callback) { 29 | let { network, 30 | help, 31 | registry, 32 | admin:proxyAdmin, 33 | proxy:proxyAddress, 34 | library:libraryAddress } = commandLineArgs(optionsDefs); 35 | 36 | if (!network || !proxyAdmin || !(registry || proxyAdmin) || help) { 37 | console.log(getUsage(usage)); 38 | callback(); 39 | return; 40 | } 41 | 42 | try { 43 | let library; 44 | if (libraryAddress) { 45 | library = await CstLibrary.at(libraryAddress); 46 | console.log(`Using CstLibrary at ${library.address}`); 47 | } else { 48 | console.log(`Deploying CstLibrary...`); 49 | library = await CstLibrary.new({ synchronization_timeout: 0 }); 50 | console.log(`Deployed CstLibrary to ${library.address}`); 51 | } 52 | 53 | await CardstackToken.link('CstLibrary', library.address); 54 | 55 | let cardstackToken; 56 | if (!proxyAddress) { 57 | cardstackToken = await proxyContract( 58 | AdminUpgradeabilityProxy, 59 | CardstackToken, 60 | proxyAdmin, 61 | registry, 62 | CST_STORAGE_NAME, 63 | CST_LEDGER_NAME, 64 | { 65 | synchronization_timeout: 0, 66 | gas: CST_DEPLOY_GAS_LIMIT 67 | }); // need as much gas as possible--this is a big contract 68 | } else { 69 | cardstackToken = await upgradeContract( 70 | AdminUpgradeabilityProxy, 71 | CardstackToken, 72 | proxyAdmin, 73 | proxyAddress, 74 | { 75 | synchronization_timeout: 0, 76 | gas: CST_DEPLOY_GAS_LIMIT 77 | }); // need as much gas as possible--this is a big contract 78 | } 79 | 80 | console.log(`\nCompleted deploying Cardstack token. 81 | Cardstack token implementation address ${cardstackToken.implementation.address} 82 | Cardstack token address ${cardstackToken.proxy.address} (proxy)`); 83 | 84 | } catch(err) { 85 | console.error("Error encountered performing contact upgrade", err); 86 | } 87 | 88 | callback(); 89 | }; 90 | -------------------------------------------------------------------------------- /scripts/deploy-ledger.js: -------------------------------------------------------------------------------- 1 | const commandLineArgs = require('command-line-args'); 2 | const { upgradeContract, proxyContract } = require('../lib/proxy'); 3 | const getUsage = require('command-line-usage'); 4 | const CstLedger = artifacts.require("./CstLedger.sol"); 5 | const AdminUpgradeabilityProxy = artifacts.require('AdminUpgradeabilityProxy'); 6 | 7 | const optionsDefs = [ 8 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 9 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 10 | { name: "admin", type: String, description: "The address of the proxyAdmin. IMPORTANT: the proxyAdmin is not allowed to invoke functions on the implemented contract!" }, 11 | { name: "proxy", type: String, description: "(optional) The address of the ledger proxy. If no proxy is provided, then one will be created for you." }, 12 | ]; 13 | 14 | const usage = [ 15 | { 16 | header: "deploy-ledger", 17 | content: "This script creates/upgrades the currently deployed ledger with a new contract." 18 | },{ 19 | header: "Options", 20 | optionList: optionsDefs 21 | } 22 | ]; 23 | 24 | module.exports = async function(callback) { 25 | let { network, 26 | help, 27 | admin:proxyAdmin, 28 | proxy:proxyAddress, } = commandLineArgs(optionsDefs); 29 | 30 | if (!network || !proxyAdmin || help) { 31 | console.log(getUsage(usage)); 32 | callback(); 33 | return; 34 | } 35 | 36 | try { 37 | let ledger; 38 | if (!proxyAddress) { 39 | ledger = await proxyContract( 40 | AdminUpgradeabilityProxy, 41 | CstLedger, 42 | proxyAdmin, 43 | { 44 | synchronization_timeout: 0, 45 | }); 46 | } else { 47 | ledger = await upgradeContract( 48 | AdminUpgradeabilityProxy, 49 | CstLedger, 50 | proxyAdmin, 51 | proxyAddress, 52 | { 53 | synchronization_timeout: 0, 54 | }); 55 | } 56 | 57 | console.log(`\nCompleted deploying Ledger. 58 | Ledger implementation address ${ledger.implementation.address} 59 | Ledger address ${ledger.proxy.address} (proxy)`); 60 | 61 | } catch(err) { 62 | console.error("Error encountered performing contact upgrade", err); 63 | } 64 | 65 | callback(); 66 | }; 67 | -------------------------------------------------------------------------------- /scripts/deploy-registry.js: -------------------------------------------------------------------------------- 1 | const commandLineArgs = require('command-line-args'); 2 | const { upgradeContract, proxyContract } = require('../lib/proxy'); 3 | const getUsage = require('command-line-usage'); 4 | const Registry = artifacts.require("./Registry.sol"); 5 | const AdminUpgradeabilityProxy = artifacts.require('AdminUpgradeabilityProxy'); 6 | 7 | const optionsDefs = [ 8 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 9 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 10 | { name: "admin", type: String, description: "The address of the proxyAdmin. IMPORTANT: the proxyAdmin is not allowed to invoke functions on the implemented contract!" }, 11 | { name: "proxy", type: String, description: "(optional) The address of the cardstack token proxy. If no proxy is provided, then one will be created for you." }, 12 | ]; 13 | 14 | const usage = [ 15 | { 16 | header: "deploy-registry", 17 | content: "This script creates/upgrades the currently deployed registry with a new contract." 18 | },{ 19 | header: "Options", 20 | optionList: optionsDefs 21 | } 22 | ]; 23 | 24 | module.exports = async function(callback) { 25 | let { network, 26 | help, 27 | admin:proxyAdmin, 28 | proxy:proxyAddress, } = commandLineArgs(optionsDefs); 29 | 30 | if (!network || !proxyAdmin || help) { 31 | console.log(getUsage(usage)); 32 | callback(); 33 | return; 34 | } 35 | 36 | try { 37 | let registry; 38 | if (!proxyAddress) { 39 | registry = await proxyContract( 40 | AdminUpgradeabilityProxy, 41 | Registry, 42 | proxyAdmin, 43 | { 44 | synchronization_timeout: 0, 45 | }); 46 | } else { 47 | registry = await upgradeContract( 48 | AdminUpgradeabilityProxy, 49 | Registry, 50 | proxyAdmin, 51 | proxyAddress, 52 | { 53 | synchronization_timeout: 0, 54 | }); 55 | } 56 | 57 | console.log(`\nCompleted deploying Registry. 58 | Registry implementation address ${registry.implementation.address} 59 | Registry address ${registry.proxy.address} (proxy)`); 60 | 61 | } catch(err) { 62 | console.error("Error encountered performing contact upgrade", err); 63 | } 64 | 65 | callback(); 66 | }; 67 | -------------------------------------------------------------------------------- /scripts/ledger-info.js: -------------------------------------------------------------------------------- 1 | const { CST_NAME } = require("../lib/constants"); 2 | const { getLedger } = require('../lib/ledger'); 3 | const commandLineArgs = require('command-line-args'); 4 | const getUsage = require('command-line-usage'); 5 | const fs = require('fs'); 6 | const CardstackToken = artifacts.require("./CardstackToken.sol"); 7 | const RegistryContract = artifacts.require("./Registry.sol"); 8 | const CstLedger = artifacts.require("./CstLedger.sol"); 9 | 10 | function adjustForDecimals(value, decimals) { 11 | let decimalsFactor = new web3.BigNumber('1'.padEnd(decimals.toNumber() + 1, '0')); 12 | return (new web3.BigNumber(value)).div(decimalsFactor); 13 | } 14 | 15 | const optionsDefs = [ 16 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 17 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 18 | { name: "address", alias: "a", type: String, description: "The address to grant admin permissions"}, 19 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 20 | { name: "csv", type: String, description: "(optional) The CSV file to write the ledger report" }, 21 | { name: "importable", type: Boolean, description: "(optional) output csv format in a manner that can be used in grant token script as raw token values" } 22 | ]; 23 | 24 | const usage = [ 25 | { 26 | header: "ledger-info", 27 | content: "This script dispays information about the ledger used for CST." 28 | },{ 29 | header: "Options", 30 | optionList: optionsDefs 31 | } 32 | ]; 33 | 34 | module.exports = async function(callback) { 35 | const options = commandLineArgs(optionsDefs); 36 | 37 | if (!options.network || options.help || !options.registry) { 38 | console.log(getUsage(usage)); 39 | callback(); 40 | return; 41 | } 42 | 43 | let { registry:registryAddress, 44 | address:queryAddress, 45 | importable, 46 | csv:csvFile } = options; 47 | 48 | let registry = registryAddress ? await RegistryContract.at(registryAddress) : await RegistryContract.deployed(); 49 | 50 | console.log(`Using registry at ${registry.address}`); 51 | let cstAddress = await registry.contractForHash(web3.sha3(CST_NAME)); 52 | let cst = await CardstackToken.at(cstAddress); 53 | let cstLedgerName = await cst.ledgerName(); 54 | let cstSymbol = await cst.symbol(); 55 | let ledgerAddress = await registry.storageForHash(web3.sha3(cstLedgerName.toString())); 56 | let ledgerContract = await CstLedger.at(ledgerAddress); 57 | let totalTokens = await ledgerContract.totalTokens(); 58 | let totalInCirculation = await ledgerContract.totalInCirculation(); 59 | let buyPriceTokensPerWei = await cst.buyPrice(); 60 | let decimals = await cst.decimals(); 61 | let sigDigits = 6; 62 | let version; 63 | try { 64 | version = await ledgerContract.version(); 65 | } catch (e) { 66 | version = "1"; // version function did not exist in initial contract 67 | } 68 | 69 | let ledger = await getLedger(web3, cst); 70 | 71 | if (csvFile) { 72 | console.log(`Writing file ${csvFile}...`); 73 | if (!importable) { 74 | fs.writeFileSync(csvFile, `"address","ETH","${raw ? 'raw token amount' : cstSymbol}"\n`, 'ascii'); 75 | } 76 | let sum = new web3.BigNumber(0); 77 | for (let address of Object.keys(ledger)) { 78 | let balance = ledger[address]; 79 | 80 | let balanceEth = !buyPriceTokensPerWei.toNumber() ? '' : Math.round(balance.div(new web3.BigNumber(('1'.padEnd(decimals.toNumber() + 1, '0')))).div(buyPriceTokensPerWei).toNumber() * 10 ** sigDigits) / 10 ** sigDigits; 81 | 82 | if (importable) { 83 | sum = sum.add(balance); 84 | let [ formattedBalance ] = balance.toPrecision(50).toString().split('.'); 85 | fs.appendFileSync(csvFile, `"${address}","${formattedBalance}"\n`); 86 | } else { 87 | fs.appendFileSync(csvFile, `"${address}","${balanceEth}","${raw ? balance : adjustForDecimals(balance, decimals)}"\n`); 88 | } 89 | } 90 | 91 | if (importable) { 92 | console.log(`Exported ledger for ${Object.keys(ledger).length} token holder addresses`); 93 | console.log(`ledger total: `, adjustForDecimals(totalInCirculation, decimals).toString()); 94 | console.log(`exported ledger total tokens:`, adjustForDecimals(sum, decimals).toString()); 95 | } 96 | 97 | console.log("Done"); 98 | callback(); 99 | return; 100 | } 101 | 102 | console.log(` 103 | Cardstack Token (${cst.address}) 104 | Ledger (${ledger.address}): 105 | version: ${version} 106 | totalTokens: ${adjustForDecimals(totalTokens, decimals)} ${cstSymbol} 107 | totalInCirculation: ${adjustForDecimals(totalInCirculation, decimals)} ${cstSymbol} 108 | number of accounts: ${Object.keys(ledger).length} 109 | 110 | * Note that ETH amounts expressed below are based on the token contract's buy price\n`); 111 | 112 | if (!queryAddress) { 113 | console.log(`Accounts:`); 114 | 115 | for (let address of Object.keys(ledger)) { 116 | let balance = ledger[address]; 117 | let balanceEth = !buyPriceTokensPerWei.toNumber() ? 'Not Available' : Math.round(balance.div(new web3.BigNumber(('1'.padEnd(decimals.toNumber() + 1, '0')))).div(buyPriceTokensPerWei).toNumber() * 10 ** sigDigits) / 10 ** sigDigits; 118 | console.log(` ${address}: ${adjustForDecimals(balance, decimals)} ${cstSymbol} (${balanceEth} ETH)`); 119 | } 120 | } else { 121 | console.log(`Individual Account Info:`); 122 | let balance = await ledger.balanceOf(queryAddress); 123 | let balanceEth = !buyPriceTokensPerWei.toNumber() ? 'Not Available' : Math.round(balance.div(new web3.BigNumber(('1'.padEnd(decimals.toNumber() + 1, '0')))).div(buyPriceTokensPerWei).toNumber() * 10 ** sigDigits) / 10 ** sigDigits; 124 | console.log(` ${queryAddress}: ${adjustForDecimals(balance, decimals)} ${cstSymbol} (${balanceEth} ETH)`); 125 | } 126 | 127 | callback(); 128 | }; 129 | -------------------------------------------------------------------------------- /scripts/registry-add-storage.js: -------------------------------------------------------------------------------- 1 | const { CST_LEDGER_NAME, CST_STORAGE_NAME } = require("../lib/constants.js"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | const RegistryContract = artifacts.require("./Registry.sol"); 5 | const CstLedger = artifacts.require("./CstLedger.sol"); 6 | const ExternalStorage = artifacts.require("./ExternalStorage.sol"); 7 | 8 | const optionsDefs = [ 9 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 10 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 11 | { name: "ledger", type: String, description: "The address of the ledger contract (must be owner/superAdmin)" }, 12 | { name: "storage", type: String, description: "The address of the ledger storage contract (must be owner/superAdmin)" }, 13 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 14 | ]; 15 | 16 | const usage = [ 17 | { 18 | header: "registry-add-storage", 19 | content: "This script adds storage contracts to the registry" 20 | },{ 21 | header: "Options", 22 | optionList: optionsDefs 23 | } 24 | ]; 25 | 26 | module.exports = async function(callback) { 27 | const options = commandLineArgs(optionsDefs); 28 | let { network, ledger:ledgerAddress, storage:storageAddress, registry:registryAddress, help } = options; 29 | 30 | if (!network || !(ledgerAddress || storageAddress) || help || !registryAddress) { 31 | console.log(getUsage(usage)); 32 | callback(); 33 | return; 34 | } 35 | 36 | try { 37 | let registry = await RegistryContract.at(registryAddress); 38 | console.log(`Using registry at ${registry.address}`); 39 | 40 | let ledger = ledgerAddress ? await CstLedger.at(ledgerAddress) : null; 41 | let storage = storageAddress ? await ExternalStorage.at(storageAddress) : null; 42 | 43 | if (ledger) { 44 | console.log(`\nGranting registry permissions to manage ledger...`); 45 | await ledger.addSuperAdmin(registry.address); 46 | 47 | console.log(`Adding ledger storage to registry with name '${CST_LEDGER_NAME}'...`); 48 | await registry.addStorage(CST_LEDGER_NAME, ledger.address); 49 | } 50 | 51 | if (storage) { 52 | console.log(`\nGranting registry permissions to manage storage...`); 53 | await storage.addSuperAdmin(registry.address); 54 | 55 | console.log(`Adding storage to registry with name '${CST_STORAGE_NAME}'...`); 56 | await registry.addStorage(CST_STORAGE_NAME, storage.address); 57 | } 58 | 59 | console.log(`\nCompleted adding storage.`); 60 | 61 | } catch(err) { 62 | console.error("Error encountered adding storage", err); 63 | } 64 | 65 | callback(); 66 | }; 67 | -------------------------------------------------------------------------------- /scripts/remove-admin.js: -------------------------------------------------------------------------------- 1 | const { CST_NAME } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | 5 | let RegistryContract = artifacts.require("./Registry.sol"); 6 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 7 | let ExternalStorage = artifacts.require("./ExternalStorage.sol"); 8 | let CstLedger = artifacts.require("./CstLedger.sol"); 9 | 10 | const cstStorageName = 'cstStorage'; 11 | const cstLedgerName = 'cstLedger'; 12 | 13 | const optionsDefs = [ 14 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 15 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 16 | { name: "address", alias: "a", type: String, description: "The address to grant admin permissions"}, 17 | { name: "name", alias: "n", type: String, description: "The registered name of the contract/storage to grant admin permissions"}, 18 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 19 | { name: "data", alias: "d", type: Boolean, description: "Display the data necessary to invoke the transaction instead of actually invoking the transaction" } 20 | ]; 21 | 22 | const usage = [ 23 | { 24 | header: "remove-admin", 25 | content: "This script removes an admin from a contract" 26 | },{ 27 | header: "Options", 28 | optionList: optionsDefs 29 | } 30 | ]; 31 | 32 | module.exports = async function(callback) { 33 | const options = commandLineArgs(optionsDefs); 34 | 35 | if (!options.address || !options.name || !options.network || options.help || !options.registry) { 36 | console.log(getUsage(usage)); 37 | callback(); 38 | return; 39 | } 40 | 41 | let registryAddress = options.registry; 42 | 43 | let registry = registryAddress ? await RegistryContract.at(registryAddress) : await RegistryContract.deployed(); 44 | 45 | console.log(`Using registry at ${registry.address}`); 46 | 47 | let { address, name } = options; 48 | let cstAddress = await registry.contractForHash(web3.sha3(CST_NAME)); 49 | let cst = await CardstackToken.at(cstAddress); 50 | let storageAddress = await registry.storageForHash(web3.sha3(cstStorageName)); 51 | let ledgerAddress = await registry.storageForHash(web3.sha3(cstLedgerName)); 52 | let storage = await ExternalStorage.at(storageAddress); 53 | let ledger = await CstLedger.at(ledgerAddress); 54 | 55 | let contract; 56 | switch (name) { 57 | case 'cst': 58 | contract = cst; 59 | break; 60 | case 'cstLedger': 61 | contract = ledger; 62 | break; 63 | case 'cstStorage': 64 | contract = storage; 65 | break; 66 | } 67 | 68 | if (!contract) { 69 | console.log(`Could not find contract for ${name}`); 70 | callback(); 71 | return; 72 | } 73 | 74 | 75 | if (options.data) { 76 | let data = contract.contract.removeAdmin.getData(address); 77 | let estimatedGas = web3.eth.estimateGas({ 78 | to: contract.address, 79 | data 80 | }); 81 | console.log(`Data for removing "${address}" as admin for ${name} (${contract.address}):`); 82 | console.log(`\nAddress: ${contract.address}`); 83 | console.log(`Data: ${data}`); 84 | console.log(`Estimated gas: ${estimatedGas}`); 85 | callback(); 86 | return; 87 | } 88 | 89 | try { 90 | console.log(`Removing "${address}" as admin for ${name} (${contract.address})...`); 91 | await contract.removeAdmin(address); 92 | console.log('done'); 93 | } catch (err) { 94 | console.error(`Error encountered removing admin for ${name} (${contract.address}), ${err.message}`); 95 | } 96 | 97 | callback(); 98 | }; 99 | -------------------------------------------------------------------------------- /scripts/remove-super-admin.js: -------------------------------------------------------------------------------- 1 | const { CST_NAME } = require("../lib/constants"); 2 | const commandLineArgs = require('command-line-args'); 3 | const getUsage = require('command-line-usage'); 4 | let RegistryContract = artifacts.require("./Registry.sol"); 5 | let CardstackToken = artifacts.require("./CardstackToken.sol"); 6 | 7 | const optionsDefs = [ 8 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 9 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 10 | { name: "address", alias: "a", type: String, description: "The address to grant admin permissions"}, 11 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 12 | { name: "data", alias: "d", type: Boolean, description: "Display the data necessary to invoke the transaction instead of actually invoking the transaction" } 13 | ]; 14 | 15 | const usage = [ 16 | { 17 | header: "remove-super-admin", 18 | content: "This script removes a super admin from the CST and Registry." 19 | },{ 20 | header: "Options", 21 | optionList: optionsDefs 22 | } 23 | ]; 24 | 25 | module.exports = async function(callback) { 26 | const options = commandLineArgs(optionsDefs); 27 | 28 | if (!options.address || !options.network || options.help || !options.registry) { 29 | console.log(getUsage(usage)); 30 | callback(); 31 | return; 32 | } 33 | 34 | let registryAddress = options.registry; 35 | 36 | let registry = registryAddress ? await RegistryContract.at(registryAddress) : await RegistryContract.deployed(); 37 | 38 | console.log(`Using registry at ${registry.address}`); 39 | let cstAddress = await registry.contractForHash(web3.sha3(CST_NAME)); 40 | 41 | let cst = await CardstackToken.at(cstAddress); 42 | 43 | let address = options.address; 44 | 45 | if (options.data) { 46 | let data = cst.contract.removeSuperAdmin.getData(address); 47 | let estimatedGas = web3.eth.estimateGas({ 48 | to: cst.address, 49 | data 50 | }); 51 | console.log(`Data for removing "${address}" as super admin for CST ${cst.address}:`); 52 | console.log(`\nAddress: ${cst.address}`); 53 | console.log(`Data: ${data}`); 54 | console.log(`Estimated gas: ${estimatedGas}`); 55 | 56 | data = registry.contract.removeSuperAdmin.getData(address); 57 | estimatedGas = web3.eth.estimateGas({ 58 | to: registry.address, 59 | data 60 | }); 61 | 62 | console.log(`\nData for removing "${address}" as super admin for Registry ${registry.address}:`); 63 | console.log(`\nAddress: ${registry.address}`); 64 | console.log(`Data: ${data}`); 65 | console.log(`Estimated gas: ${estimatedGas}`); 66 | 67 | callback(); 68 | return; 69 | } 70 | 71 | try { 72 | console.log(`Removing "${address}" as super admin for CST ${cst.address}...`); 73 | await cst.removeSuperAdmin(address); 74 | console.log(`Removing "${address}" as super admin for Registry ${registry.address}...`); 75 | await registry.addSuperAdmin(address); 76 | console.log('done'); 77 | } catch (err) { 78 | console.error(`Error encountered removing super admin, ${err.message}`); 79 | } 80 | 81 | callback(); 82 | }; 83 | -------------------------------------------------------------------------------- /scripts/send-eth-list.js: -------------------------------------------------------------------------------- 1 | const commandLineArgs = require('command-line-args'); 2 | const getUsage = require('command-line-usage'); 3 | const fs = require("fs"); 4 | const _ = require("lodash"); 5 | const Parallel = require("async-parallel"); 6 | 7 | const optionsDefs = [ 8 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 9 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 10 | { name: "csv", type: String, description: "(optional) The CSV file to write the ledger report" }, 11 | { name: "gasPriceGwei", type: Number, description: "The gas price in units of gwei" }, 12 | { name: "concurrency", alias: "c", type: Number, description: "(Optional) The number of concurrent transactions to submit to the network at any one time. The default concurrency is 100 simultaneous transactions at a time." }, 13 | ]; 14 | 15 | const usage = [ 16 | { 17 | header: "send-eth-list", 18 | content: "This script send ETH to recipients from a CSV of addresses and amounts" 19 | },{ 20 | header: "Options", 21 | optionList: optionsDefs 22 | } 23 | ]; 24 | 25 | module.exports = async function(callback) { 26 | const options = commandLineArgs(optionsDefs); 27 | if (!process.env.WALLET) { 28 | console.log("WALLET environment variable is not set. Please set the WALLET env variable to the address that ETH will be sent from"); 29 | callback(); 30 | return; 31 | } 32 | 33 | if (!options.csv || !options.network || options.help) { 34 | console.log(getUsage(usage)); 35 | callback(); 36 | return; 37 | } 38 | let { csv, concurrency, gasPriceGwei } = options; 39 | 40 | concurrency = concurrency || 100; 41 | 42 | let fileStr = fs.readFileSync(csv); 43 | let rows = _.compact(fileStr.toString().split("\n")); 44 | 45 | console.log(`Scheduling ${rows.length} addresses to be sent ETH`); 46 | 47 | let counter = 0; 48 | await Parallel.each(rows, async row => { 49 | let count = ++counter; 50 | if (count % concurrency === 0) { 51 | console.log(`Processing ${count} of ${rows.length}, ${Math.round((count / rows.length) * 100)}% complete...`); 52 | } 53 | 54 | let [ address, amount ] = row.replace(/"/g, "").split(","); 55 | 56 | if (address && amount && amount.trim()) { 57 | let wei = web3.toWei(amount, "ether"); 58 | let options = { 59 | from: process.env.WALLET, 60 | to: address, 61 | value: wei 62 | }; 63 | if (gasPriceGwei) { 64 | options.gasPrice = web3.toWei(gasPriceGwei, 'gwei'); 65 | } 66 | console.log(`Sending ${address} wei ${wei}`); 67 | try { 68 | await web3.eth.sendTransaction(options); 69 | } catch (err) { 70 | console.error(`Error sending ETH ${address}, ${err.message}`); 71 | } 72 | } 73 | }, concurrency); 74 | 75 | console.log("done."); 76 | }; 77 | -------------------------------------------------------------------------------- /scripts/system-info.js: -------------------------------------------------------------------------------- 1 | const commandLineArgs = require('command-line-args'); 2 | const { getLedger } = require('../lib/ledger'); 3 | const getUsage = require('command-line-usage'); 4 | const moment = require('moment'); 5 | const { uniqBy } = require('lodash'); 6 | 7 | const dateFormat = "YYYY-MM-DD HH:mm Z"; 8 | 9 | const CardstackToken = artifacts.require("./CardstackToken.sol"); 10 | const RegistryContract = artifacts.require("./Registry.sol"); 11 | const ExternalStorage = artifacts.require("./ExternalStorage.sol"); 12 | const CstLedger = artifacts.require("./CstLedger.sol"); 13 | 14 | const { NULL_ADDRESS, CST_NAME, CST_LEDGER_NAME, CST_STORAGE_NAME } = require("../lib/constants"); 15 | 16 | function adjustForDecimals(value, decimals=(new web3.BigNumber(18))) { 17 | let decimalsFactor = new web3.BigNumber('1'.padEnd(decimals.toNumber() + 1, '0')); 18 | return (new web3.BigNumber(value)).div(decimalsFactor); 19 | } 20 | 21 | const optionsDefs = [ 22 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 23 | { name: "network", type: String, description: "The blockchain that you wish to use. Valid options are `testrpc`, `rinkeby`, `mainnet`." }, 24 | { name: "ledgerName", type: String, description: "(optional) The name of the ledger to use" }, 25 | { name: "storageName", type: String, description: "(optional) The name of the storage to use" }, 26 | { name: "registry", alias: "r", type: String, description: "The address of the registry." }, 27 | ]; 28 | 29 | const usage = [ 30 | { 31 | header: "system-info", 32 | content: "This script displays an overview of the Cardstack smart contracts." 33 | },{ 34 | header: "Options", 35 | optionList: optionsDefs 36 | } 37 | ]; 38 | 39 | module.exports = async function(callback) { 40 | const options = commandLineArgs(optionsDefs); 41 | 42 | if (!options.network || options.help || !options.registry) { 43 | console.log(getUsage(usage)); 44 | callback(); 45 | return; 46 | } 47 | 48 | let { ledgerName, storageName } = options; 49 | ledgerName = ledgerName || CST_LEDGER_NAME; 50 | storageName = storageName || CST_STORAGE_NAME; 51 | 52 | let registryAddress = options.registry; 53 | 54 | let registry = registryAddress ? await RegistryContract.at(registryAddress) : await RegistryContract.deployed(); 55 | 56 | 57 | console.log(`Using registry at ${registry.address}`); 58 | let cstAddress = await registry.contractForHash(web3.sha3(CST_NAME)); 59 | 60 | let cst, cstRegistry, cstFrozen, cstStorageName, cstLedgerName, cstName, 61 | cstSymbol = "", buyPriceTokensPerWei, circulationCap, foundation, balanceWei, totalSupply, cstFrozenCount, 62 | cstAdminCount, cstSuperAdminCount, cstHaltPurchase, cstBuyerCount, cstCustomBuyerCount, cstBalanceLimit, 63 | contributionMinimum, vestingCount, cstAvailable, cstTotalInCirculation, decimals, cstVersion, defaultLimitEth, 64 | registryVersion, registryOwner, ledgerVersion, storageOwner, ledgerOwner, cstOwner; 65 | 66 | registryOwner = await registry.owner(); 67 | 68 | try { 69 | registryVersion = await registry.version(); 70 | } catch (err) { 71 | registryVersion = "1"; // this property wasnt introduced in the initial contracts 72 | } 73 | 74 | if (cstAddress === NULL_ADDRESS) { 75 | console.log(`There is no CST contract resgistered with the Registry at ${registry.address}`); 76 | } else { 77 | cst = await CardstackToken.at(cstAddress); 78 | 79 | try { 80 | cstRegistry = await cst.registry(); 81 | cstFrozen = await cst.frozenToken(); 82 | 83 | cstStorageName = await cst.storageName(); 84 | cstLedgerName = await cst.ledgerName(); 85 | cstName = await cst.name(); 86 | cstSymbol = await cst.symbol(); 87 | buyPriceTokensPerWei = await cst.buyPrice(); 88 | circulationCap = await cst.circulationCap(); 89 | cstTotalInCirculation = await cst.totalInCirculation(); 90 | foundation = await cst.foundation(); 91 | balanceWei = await web3.eth.getBalance(cst.address); 92 | totalSupply = await cst.totalSupply(); 93 | cstFrozenCount = await cst.totalFrozenAccountsMapping(); 94 | cstAdminCount = await cst.totalAdminsMapping(); 95 | cstSuperAdminCount = await cst.totalSuperAdminsMapping(); 96 | cstBuyerCount = await cst.totalBuyersMapping(); 97 | cstHaltPurchase = await cst.haltPurchase(); 98 | cstCustomBuyerCount = await cst.totalCustomBuyersMapping(); 99 | cstBalanceLimit = await cst.cstBalanceLimit(); 100 | contributionMinimum = await cst.contributionMinimum(); 101 | vestingCount = await cst.vestingMappingSize(); 102 | cstAvailable = await cst.tokensAvailable(); 103 | cstOwner = await cst.owner(); 104 | 105 | decimals = await cst.decimals(); 106 | 107 | let sigDigits = 6; 108 | defaultLimitEth = !cstBalanceLimit.toNumber() ? 0 : Math.round(cstBalanceLimit.div(new web3.BigNumber(('1'.padEnd(decimals.toNumber() + 1, '0')))).div(buyPriceTokensPerWei).toNumber() * 10 ** sigDigits) / 10 ** sigDigits; 109 | } catch (err) { 110 | console.error("Encountered error getting token details", err); 111 | } 112 | 113 | try { 114 | cstVersion = await cst.version(); 115 | } catch (err) { 116 | cstVersion = "1"; // this property wasnt introduced in the initial contracts 117 | } 118 | } 119 | 120 | 121 | let registryAdminCount = await registry.totalAdminsMapping(); 122 | let registrySuperAdminCount = await registry.totalSuperAdminsMapping(); 123 | let registryCstNamehash = await registry.namehashForHash(web3.sha3(CST_NAME)); 124 | 125 | let storageAddress = await registry.storageForHash(web3.sha3(storageName)); 126 | let ledgerAddress = await registry.storageForHash(web3.sha3(ledgerName)); 127 | 128 | let storage, storageAdminCount, storageSuperAdminCount, ledger, totalTokens, totalInCirculation, 129 | numAccounts, ledgerAdminCount, ledgerSuperAdminCount; 130 | 131 | try { 132 | 133 | if (storageAddress === NULL_ADDRESS) { 134 | console.log(`There is no storage contract registered with the Registry at ${registry.address} with name of '${storageName}'`); 135 | } else { 136 | storage = await ExternalStorage.at(storageAddress); 137 | storageAdminCount = await storage.totalAdminsMapping(); 138 | storageSuperAdminCount = await storage.totalSuperAdminsMapping(); 139 | storageOwner = await storage.owner(); 140 | } 141 | 142 | if (ledgerAddress === NULL_ADDRESS) { 143 | console.log(`There is no ledger contract registered with the Registry at ${registryAddress} with name of '${ledgerName}'`); 144 | } else { 145 | ledger = await CstLedger.at(ledgerAddress); 146 | totalTokens = await ledger.totalTokens(); 147 | totalInCirculation = await ledger.totalInCirculation(); 148 | ledgerAdminCount = await ledger.totalAdminsMapping(); 149 | ledgerSuperAdminCount = await ledger.totalSuperAdminsMapping(); 150 | ledgerOwner = await ledger.owner(); 151 | let ledgerData = cst ? await getLedger(web3, cst) : {}; 152 | numAccounts = Object.keys(ledgerData).length; 153 | 154 | try { 155 | ledgerVersion = await ledger.version(); 156 | } catch (err) { 157 | ledgerVersion = "1"; // this property wasnt introduced in the initial contracts 158 | } 159 | } 160 | } catch (err) { 161 | console.error("Encountered error getting token details", err); 162 | } 163 | 164 | function prettyAddress(address) { 165 | if (address === registry.address) { 166 | return `${address} (registry)`; 167 | } else if (cst && address === cst.address) { 168 | return `${address} (cst)`; 169 | } else if (ledger && address === ledger.address) { 170 | return `${address} (ledger)`; 171 | } else if (storage && address === storage.address) { 172 | return `${address} (storage)`; 173 | } else if (foundation && address === foundation) { 174 | return `${address} (Cardstack Foundation)`; 175 | } 176 | 177 | return address; 178 | } 179 | 180 | try { 181 | 182 | console.log(` 183 | Contracts: 184 | Registry: ${prettyAddress(registry.address)}`); 185 | 186 | if (storage) { 187 | console.log(` Storage: ${prettyAddress(storage.address)}`); 188 | } 189 | 190 | if (ledger) { 191 | console.log(` Ledger: ${prettyAddress(ledger.address)}`); 192 | } 193 | 194 | if (cst) { 195 | console.log(` CST contract: ${prettyAddress(cst.address)}`); 196 | } 197 | 198 | console.log(` 199 | 200 | Registry (${registry.address}): 201 | version: ${registryVersion} 202 | owner: ${registryOwner} 203 | ${CST_NAME}: ${prettyAddress(cstAddress)} 204 | ${storageName}: ${prettyAddress(storageAddress)} 205 | ${ledgerName}: ${prettyAddress(ledgerAddress)} 206 | 207 | Namehash for CST Token contract: ${registryCstNamehash} 208 | 209 | Registry super admins:`); 210 | for (let i = 0; i < registrySuperAdminCount; i++) { 211 | let address = await registry.superAdminsForIndex(i); 212 | let isAdmin = await registry.superAdmins(address); 213 | if (isAdmin) { 214 | console.log(` ${prettyAddress(address)}`); 215 | } 216 | } 217 | console.log(` 218 | Registry admins:`); 219 | for (let i = 0; i < registryAdminCount; i++) { 220 | let address = await registry.adminsForIndex(i); 221 | let isAdmin = await registry.admins(address); 222 | if (isAdmin) { 223 | console.log(` ${prettyAddress(address)}`); 224 | } 225 | } 226 | 227 | if (ledger) { 228 | console.log(` 229 | 230 | Ledger - ${ledgerName} (${ledger.address}) 231 | version: ${ledgerVersion} 232 | owner: ${ledgerOwner} 233 | total tokens: ${adjustForDecimals(totalTokens, decimals)} 234 | number of accounts: ${numAccounts} 235 | total in circulation: ${adjustForDecimals(totalInCirculation, decimals)}* 236 | * not counting unvested & vested-unreleased tokens 237 | 238 | Ledger super admins:`); 239 | for (let i = 0; i < ledgerSuperAdminCount; i++) { 240 | let address = await ledger.superAdminsForIndex(i); 241 | let isAdmin = await ledger.superAdmins(address); 242 | if (isAdmin) { 243 | console.log(` ${prettyAddress(address)}`); 244 | } 245 | } 246 | console.log(` 247 | Ledger admins:`); 248 | for (let i = 0; i < ledgerAdminCount; i++) { 249 | let address = await ledger.adminsForIndex(i); 250 | let isAdmin = await ledger.admins(address); 251 | if (isAdmin) { 252 | console.log(` ${prettyAddress(address)}`); 253 | } 254 | } 255 | } 256 | 257 | if (storage) { 258 | console.log(` 259 | 260 | Storage - ${storageName} (${storage.address}) 261 | owner: ${storageOwner} 262 | Storage super admins:`); 263 | for (let i = 0; i < storageSuperAdminCount; i++) { 264 | let address = await storage.superAdminsForIndex(i); 265 | let isAdmin = await storage.superAdmins(address); 266 | if (isAdmin) { 267 | console.log(` ${prettyAddress(address)}`); 268 | } 269 | } 270 | console.log(` 271 | Storage admins:`); 272 | for (let i = 0; i < storageAdminCount; i++) { 273 | let address = await storage.adminsForIndex(i); 274 | let isAdmin = await storage.admins(address); 275 | if (isAdmin) { 276 | console.log(` ${prettyAddress(address)}`); 277 | } 278 | } 279 | } 280 | 281 | if (cst) { 282 | let vestingSchedules = ""; 283 | let totalUnvested = new web3.BigNumber(0); 284 | let totalVestedUnreleased = new web3.BigNumber(0); 285 | for (let i = 0; i < vestingCount; i++) { 286 | let beneficiary = await cst.vestingBeneficiaryForIndex(i); 287 | let releasableAmount = await cst.releasableAmount(beneficiary); 288 | totalVestedUnreleased = totalVestedUnreleased.add(releasableAmount); 289 | let [ startDate, 290 | cliffDate, 291 | durationSec, 292 | fullyVestedAmount, 293 | vestedAmount, 294 | vestedAvailableAmount, 295 | releasedAmount, 296 | revokeDate, 297 | isRevocable ] = await cst.vestingSchedule(beneficiary); 298 | if (!revokeDate.toNumber()) { 299 | totalUnvested = totalUnvested.add(fullyVestedAmount.sub(vestedAmount)); 300 | } 301 | vestingSchedules = `${vestingSchedules} 302 | beneficiary: ${beneficiary} ${revokeDate.toNumber() > 0 ? "Revoked on " + moment.unix(revokeDate.toNumber()).format(dateFormat) : ""} 303 | start date: ${moment.unix(startDate.toNumber()).format(dateFormat)} 304 | cliff date: ${moment.unix(cliffDate.toNumber()).format(dateFormat)} 305 | fully vested date: ${moment.unix(startDate.toNumber() + durationSec.toNumber()).format(dateFormat)} 306 | fully vested amount: ${adjustForDecimals(fullyVestedAmount, decimals)} ${cstSymbol} 307 | vested amount as of now (${moment().format(dateFormat)}): ${adjustForDecimals(vestedAmount, decimals).toFixed(0)} ${cstSymbol} 308 | vested amount available as of now (${moment().format(dateFormat)}): ${adjustForDecimals(vestedAvailableAmount, decimals)} ${cstSymbol} 309 | vested amount already released: ${adjustForDecimals(releasedAmount, decimals)} ${cstSymbol} 310 | vested amount not yet released ${adjustForDecimals(releasableAmount, decimals)} ${cstSymbol} 311 | is revocable: ${isRevocable}\n`; 312 | } 313 | 314 | console.log(` 315 | 316 | Cardstack Token (${cst.address}): 317 | version: ${cstVersion} 318 | owner: ${cstOwner} 319 | registry: ${prettyAddress(cstRegistry)} 320 | decimals: ${decimals} 321 | storage name: ${cstStorageName} 322 | ledger name: ${cstLedgerName} 323 | is frozen: ${cstFrozen} 324 | purchases halted: ${cstHaltPurchase} 325 | name: ${cstName} 326 | symbol: ${cstSymbol} 327 | buy price: ${cstSymbol} per ETH: ${buyPriceTokensPerWei} ${cstSymbol} 328 | total tokens available: ${adjustForDecimals(cstAvailable, decimals)} ${cstSymbol} 329 | circulation cap: ${adjustForDecimals(circulationCap, decimals)} ${cstSymbol} 330 | tokens in circulation (includes unvested & unreleased tokens): ${adjustForDecimals(cstTotalInCirculation, decimals)} ${cstSymbol} 331 | total tokens available to be air dropped: ${adjustForDecimals(circulationCap.sub(cstTotalInCirculation), decimals)} ${cstSymbol} 332 | total unvested tokens: ${adjustForDecimals(totalUnvested, decimals).toFixed(0)} ${cstSymbol} 333 | total vested and unreleased tokens: ${adjustForDecimals(totalVestedUnreleased, decimals)} ${cstSymbol} 334 | contribution minimum: ${adjustForDecimals(contributionMinimum, decimals)} ${cstSymbol} 335 | balance limit (purchase cap): ${defaultLimitEth} ETH (${adjustForDecimals(cstBalanceLimit, decimals)} ${cstSymbol}) 336 | total supply: ${adjustForDecimals(totalSupply, decimals)} ${cstSymbol} 337 | token contract balance: ${web3.fromWei(balanceWei, "ether")} ETH 338 | foundation address: ${prettyAddress(foundation)} 339 | 340 | CST super admins:`); 341 | for (let i = 0; i < cstSuperAdminCount; i++) { 342 | let address = await cst.superAdminsForIndex(i); 343 | let isAdmin = await cst.superAdmins(address); 344 | if (isAdmin) { 345 | console.log(` ${prettyAddress(address)}`); 346 | } 347 | } 348 | console.log(` 349 | CST admins:`); 350 | for (let i = 0; i < cstAdminCount; i++) { 351 | let address = await cst.adminsForIndex(i); 352 | let isAdmin = await cst.admins(address); 353 | if (isAdmin) { 354 | console.log(` ${prettyAddress(address)}`); 355 | } 356 | } 357 | console.log(` 358 | CST Vesting: ${vestingSchedules || '\n'} 359 | 360 | CST Buyers with custom balance limit: ~${cstCustomBuyerCount} addresses 361 | 362 | CST Buyers: ~${cstBuyerCount} addresses 363 | 364 | Frozen Accounts:`); 365 | for (let i = 0; i < cstFrozenCount; i++) { 366 | let address = await cst.frozenAccountForIndex(i); 367 | let isFrozen = await cst.frozenAccount(address); 368 | if (isFrozen) { 369 | console.log(` ${address}`); 370 | } 371 | } 372 | } 373 | 374 | } catch (err) { 375 | console.error("Error encountered displaying system info:", err); 376 | } 377 | 378 | callback(); 379 | }; 380 | -------------------------------------------------------------------------------- /scripts/testing/create-addresses.js: -------------------------------------------------------------------------------- 1 | const commandLineArgs = require('command-line-args'); 2 | const optionsDefs = [ 3 | { name: "network", type: String }, 4 | { name: "amount", alias: "a", type: Number}, 5 | ]; 6 | 7 | module.exports = async function(callback) { 8 | const options = commandLineArgs(optionsDefs); 9 | 10 | let { amount } = options; 11 | 12 | if (!amount) { 13 | callback(); 14 | return; 15 | } 16 | 17 | for (let i = 0; i < amount; i++) { 18 | let address = await web3.personal.newAccount(); 19 | console.log(address); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /scripts/testing/ledger-export-checksum.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const _ = require("lodash"); 3 | 4 | function adjustForDecimals(value, decimals=18) { 5 | let decimalsFactor = new web3.BigNumber('1'.padEnd(decimals + 1, '0')); 6 | return (new web3.BigNumber(value)).div(decimalsFactor); 7 | } 8 | module.exports = async function(callback) { 9 | const fileStr = fs.readFileSync('idex_card_ledger.csv'); 10 | const rows = _.compact(fileStr.toString().split("\n")); 11 | 12 | let sum = new web3.BigNumber(0); 13 | for (let row of rows) { 14 | let [ , amount ] = row.replace(/"/g, "").split(","); 15 | sum = sum.add(new web3.BigNumber(amount)); 16 | } 17 | 18 | console.log('raw ledger sum', sum.toPrecision(50).toString().split('.')[0]); 19 | console.log('raw ledger sum', adjustForDecimals(sum).toPrecision(50).toString()); 20 | callback(); 21 | }; 22 | -------------------------------------------------------------------------------- /scripts/testing/sign-message.js: -------------------------------------------------------------------------------- 1 | const commandLineArgs = require('command-line-args'); 2 | const getUsage = require('command-line-usage'); 3 | 4 | const optionsDefs = [ 5 | { name: "help", alias: "h", type: Boolean, description: "Print this usage guide." }, 6 | { name: "message", type: String, description: "The message to sign." }, 7 | { name: "address", type: String, description: "The address to sign with." }, 8 | ]; 9 | 10 | const usage = [ 11 | { 12 | header: "sign-message", 13 | content: "This script signs messages using your private key" 14 | },{ 15 | header: "Options", 16 | optionList: optionsDefs 17 | } 18 | ]; 19 | 20 | module.exports = async function(callback) { 21 | const { message, address, help } = commandLineArgs(optionsDefs); 22 | 23 | if (!message || help || !address) { 24 | console.log(getUsage(usage)); 25 | callback(); 26 | return; 27 | } 28 | 29 | let signedMessage = web3.eth.sign(address, web3.sha3(message)); 30 | 31 | console.log(`Signed message:\n${signedMessage}`); 32 | callback(); 33 | }; 34 | -------------------------------------------------------------------------------- /test/cardstack-token-allowance-test.js: -------------------------------------------------------------------------------- 1 | const TestingCardstackToken = artifacts.require("./TestingCardstackToken.sol"); 2 | const TestingCstLedger = artifacts.require("./TestingCstLedger.sol"); 3 | const Storage = artifacts.require("./ExternalStorage.sol"); 4 | const TestingRegistry = artifacts.require("./TestingRegistry.sol"); 5 | const { proxyContract } = require('./utils'); 6 | const { 7 | NULL_ADDRESS, 8 | CST_DEPLOY_GAS_LIMIT, 9 | CARDSTACK_NAMEHASH, 10 | assertRevert, 11 | asInt 12 | } = require("../lib/utils"); 13 | 14 | contract('CardstackToken', function(accounts) { 15 | let proxyAdmin = accounts[41]; 16 | 17 | describe("allowance", function() { 18 | let cst; 19 | let ledger; 20 | let storage; 21 | let registry; 22 | let grantor = accounts[3]; 23 | let spender = accounts[4]; 24 | let recipient = accounts[7]; 25 | 26 | beforeEach(async function() { 27 | ledger = (await proxyContract(TestingCstLedger, proxyAdmin)).contract; 28 | storage = await Storage.new(); 29 | registry = (await proxyContract(TestingRegistry, proxyAdmin)).contract; 30 | await registry.addStorage("cstStorage", storage.address); 31 | await registry.addStorage("cstLedger", ledger.address); 32 | await storage.addSuperAdmin(registry.address); 33 | await ledger.addSuperAdmin(registry.address); 34 | cst = (await proxyContract(TestingCardstackToken, proxyAdmin, registry.address, "cstStorage", "cstLedger", { 35 | gas: CST_DEPLOY_GAS_LIMIT 36 | })).contract; 37 | await registry.register("CST", cst.address, CARDSTACK_NAMEHASH); 38 | await cst.freezeToken(false); 39 | await ledger.mintTokens(100); 40 | await ledger.debitAccount(grantor, 50); 41 | await cst.configure(web3.toHex("CardStack Token"), web3.toHex("CST"), web3.toWei(0.1, "ether"), 100, 1000000, NULL_ADDRESS); 42 | }); 43 | 44 | it("allows account to increase the allowance for a spender", async function() { 45 | await cst.approve(spender, 10, { from: grantor }); 46 | let txn = await cst.increaseApproval(spender, 2, { from: grantor }); 47 | let allowance = await cst.allowance(grantor, spender); 48 | 49 | assert.equal(asInt(allowance), 12, "the allowance is correct"); 50 | 51 | let event = txn.logs[0]; 52 | 53 | assert.equal(event.event, "Approval", "The event type is correct"); 54 | assert.equal(event.args._value.toNumber(), 12, "The value is correct"); 55 | assert.equal(event.args._owner, grantor, "The grantor is correct"); 56 | assert.equal(event.args._spender, spender, "The spendor is correct"); 57 | }); 58 | 59 | it("does not allow an account to designate itself as a spender when increasing amount", async function() { 60 | await assertRevert(async () => await cst.increaseApproval(grantor, 10, { from: grantor })); 61 | let allowance = await cst.allowance(grantor, grantor); 62 | 63 | assert.equal(asInt(allowance), 0, "the allowance is correct"); 64 | }); 65 | 66 | it("allows account to decrease the allowance for a spender", async function() { 67 | await cst.approve(spender, 10, { from: grantor }); 68 | let txn = await cst.decreaseApproval(spender, 2, { from: grantor }); 69 | let allowance = await cst.allowance(grantor, spender); 70 | 71 | assert.equal(asInt(allowance), 8, "the allowance is correct"); 72 | 73 | let event = txn.logs[0]; 74 | 75 | assert.equal(event.event, "Approval", "The event type is correct"); 76 | assert.equal(event.args._value.toNumber(), 8, "The value is correct"); 77 | assert.equal(event.args._owner, grantor, "The grantor is correct"); 78 | assert.equal(event.args._spender, spender, "The spendor is correct"); 79 | }); 80 | 81 | it("does not allow an account to designate itself as a spender when decreasing amount", async function() { 82 | await assertRevert(async () => await cst.decreaseApproval(grantor, 10, { from: grantor })); 83 | let allowance = await cst.allowance(grantor, grantor); 84 | 85 | assert.equal(asInt(allowance), 0, "the allowance is correct"); 86 | }); 87 | 88 | it("asserts that the minimum allowance is not negative when decreasing the allowance", async function() { 89 | await cst.approve(spender, 2, { from: grantor }); 90 | let txn = await cst.decreaseApproval(spender, 3, { from: grantor }); 91 | let allowance = await cst.allowance(grantor, spender); 92 | 93 | assert.equal(asInt(allowance), 0, "the allowance is correct"); 94 | 95 | let event = txn.logs[0]; 96 | 97 | assert.equal(event.event, "Approval", "The event type is correct"); 98 | assert.equal(event.args._value.toNumber(), 0, "The value is correct"); 99 | assert.equal(event.args._owner, grantor, "The grantor is correct"); 100 | assert.equal(event.args._spender, spender, "The spendor is correct"); 101 | }); 102 | 103 | it("allows account to approve an allowance for a spender", async function() { 104 | let txn = await cst.approve(spender, 10, { from: grantor }); 105 | let allowance = await cst.allowance(grantor, spender); 106 | 107 | assert.equal(asInt(allowance), 10, "the allowance is correct"); 108 | 109 | let event = txn.logs[0]; 110 | 111 | assert.equal(event.event, "Approval", "The event type is correct"); 112 | assert.equal(event.args._value.toNumber(), 10, "The value is correct"); 113 | assert.equal(event.args._owner, grantor, "The grantor is correct"); 114 | assert.equal(event.args._spender, spender, "The spendor is correct"); 115 | }); 116 | 117 | it("does not allow an account to approve a non-0 allowance for a spender, when the current allowance for the spender is greater than 0", async function() { 118 | await cst.approve(spender, 10, { from: grantor }); 119 | await assertRevert(async () => await cst.approve(spender, 20, { from: grantor })); 120 | 121 | let allowance = await cst.allowance(grantor, spender); 122 | 123 | assert.equal(asInt(allowance), 10, "the allowance is correct"); 124 | }); 125 | 126 | it("does allow an account to set an allowance to 0 for a spender", async function() { 127 | await cst.approve(spender, 10, { from: grantor }); 128 | await cst.approve(spender, 0, { from: grantor }); 129 | 130 | let allowance = await cst.allowance(grantor, spender); 131 | 132 | assert.equal(asInt(allowance), 0, "the allowance is correct"); 133 | }); 134 | 135 | it("does not allow an account to approve itself as a spender", async function() { 136 | await assertRevert(async () => await cst.approve(grantor, 10, { from: grantor })); 137 | let allowance = await cst.allowance(grantor, grantor); 138 | 139 | assert.equal(asInt(allowance), 0, "the allowance is correct"); 140 | }); 141 | 142 | it("allows spender to transferFrom their approved account, and allowance is updated correctly", async function() { 143 | await cst.approve(spender, 10, { from: grantor }); 144 | 145 | let txn = await cst.transferFrom(grantor, recipient, 10, { from: spender }); 146 | 147 | let grantorBalance = await cst.balanceOf(grantor); 148 | let recipientBalance = await cst.balanceOf(recipient); 149 | let allowance = await cst.allowance(grantor, spender); 150 | 151 | assert.equal(asInt(allowance), 0, "the allowance is correct"); 152 | assert.equal(asInt(grantorBalance), 40, "the balance is correct"); 153 | assert.equal(asInt(recipientBalance), 10, "the balance is correct"); 154 | 155 | let event = txn.logs[0]; 156 | assert.equal(event.event, "Transfer", "The event type is correct"); 157 | assert.equal(event.args._value, 10, "The CST amount is correct"); 158 | assert.equal(event.args._from, grantor, "The sender is correct"); 159 | assert.equal(event.args._to, recipient, "The recipient is correct"); 160 | }); 161 | 162 | it("does not allow a spender to transferFrom an account that they have not been approved for", async function() { 163 | let unauthorizedAccount = accounts[9]; 164 | await cst.approve(spender, 10, { from: grantor }); 165 | await assertRevert(async () => await cst.transferFrom(unauthorizedAccount, recipient, 10, { from: spender })); 166 | 167 | let grantorBalance = await cst.balanceOf(grantor); 168 | let recipientBalance = await cst.balanceOf(recipient); 169 | let allowance = await cst.allowance(grantor, spender); 170 | 171 | assert.equal(asInt(allowance), 10, "the allowance is correct"); 172 | assert.equal(asInt(grantorBalance), 50, "the balance is correct"); 173 | assert.equal(asInt(recipientBalance), 0, "the balance is correct"); 174 | }); 175 | 176 | it("does allow a spender to transferFrom an account for 0 CST", async function() { 177 | let account = accounts[9]; 178 | await cst.approve(spender, 10, { from: grantor }); 179 | await cst.transferFrom(account, recipient, 0, { from: spender }); 180 | 181 | let grantorBalance = await cst.balanceOf(grantor); 182 | let recipientBalance = await cst.balanceOf(recipient); 183 | let allowance = await cst.allowance(grantor, spender); 184 | 185 | assert.equal(asInt(allowance), 10, "the allowance is correct"); 186 | assert.equal(asInt(grantorBalance), 50, "the balance is correct"); 187 | assert.equal(asInt(recipientBalance), 0, "the balance is correct"); 188 | }); 189 | 190 | it("does not allow a spender to transferFrom more than their allowance", async function() { 191 | await cst.approve(spender, 10, { from: grantor }); 192 | await assertRevert(async () => await cst.transferFrom(grantor, recipient, 11, { from: spender })); 193 | 194 | let grantorBalance = await cst.balanceOf(grantor); 195 | let recipientBalance = await cst.balanceOf(recipient); 196 | let allowance = await cst.allowance(grantor, spender); 197 | 198 | assert.equal(asInt(allowance), 10, "the allowance is correct"); 199 | assert.equal(asInt(grantorBalance), 50, "the balance is correct"); 200 | assert.equal(asInt(recipientBalance), 0, "the balance is correct"); 201 | }); 202 | 203 | it("does not allow a spender to transferFrom more than the balance in the approved account", async function() { 204 | await cst.approve(spender, 10, { from: grantor }); 205 | await assertRevert(async () => await cst.transferFrom(grantor, recipient, 51, { from: spender })); 206 | 207 | let grantorBalance = await cst.balanceOf(grantor); 208 | let recipientBalance = await cst.balanceOf(recipient); 209 | let allowance = await cst.allowance(grantor, spender); 210 | 211 | assert.equal(asInt(allowance), 10, "the allowance is correct"); 212 | assert.equal(asInt(grantorBalance), 50, "the balance is correct"); 213 | assert.equal(asInt(recipientBalance), 0, "the balance is correct"); 214 | }); 215 | 216 | }); 217 | }); 218 | -------------------------------------------------------------------------------- /test/cardstack-token-transfer-test.js: -------------------------------------------------------------------------------- 1 | const TestingCardstackToken = artifacts.require("./TestingCardstackToken.sol"); 2 | const TestingCstLedger = artifacts.require("./TestingCstLedger.sol"); 3 | const Storage = artifacts.require("./ExternalStorage.sol"); 4 | const TestingRegistry = artifacts.require("./TestingRegistry.sol"); 5 | const { proxyContract } = require('./utils'); 6 | const { 7 | GAS_PRICE, 8 | NULL_ADDRESS, 9 | CST_DEPLOY_GAS_LIMIT, 10 | CARDSTACK_NAMEHASH, 11 | assertRevert, 12 | checkBalance 13 | } = require("../lib/utils"); 14 | 15 | const MAX_TRANSFER_GAS = 50000; 16 | 17 | contract('CardstackToken', function(accounts) { 18 | let proxyAdmin = accounts[41]; 19 | 20 | describe("transfer()", function() { 21 | let cst; 22 | let ledger; 23 | let storage; 24 | let registry; 25 | let senderAccount = accounts[3]; 26 | let recipientAccount = accounts[4]; 27 | 28 | beforeEach(async function() { 29 | ledger = (await proxyContract(TestingCstLedger, proxyAdmin)).contract; 30 | storage = await Storage.new(); 31 | registry = (await proxyContract(TestingRegistry, proxyAdmin)).contract; 32 | await registry.addStorage("cstStorage", storage.address); 33 | await registry.addStorage("cstLedger", ledger.address); 34 | await storage.addSuperAdmin(registry.address); 35 | await ledger.addSuperAdmin(registry.address); 36 | cst = (await proxyContract(TestingCardstackToken, proxyAdmin, registry.address, "cstStorage", "cstLedger", { 37 | gas: CST_DEPLOY_GAS_LIMIT 38 | })).contract; 39 | await registry.register("CST", cst.address, CARDSTACK_NAMEHASH); 40 | await cst.freezeToken(false); 41 | await ledger.mintTokens(web3.toWei(100, 'ether')); 42 | await cst.configure(web3.toHex("CardStack Token"), web3.toHex("CST"), 10, web3.toWei(100, 'ether'), web3.toWei(1000000, 'ether'), NULL_ADDRESS); 43 | 44 | await checkBalance(senderAccount, 1); 45 | await cst.addBuyer(senderAccount); 46 | await cst.buy({ 47 | from: senderAccount, 48 | value: web3.toWei(1, "ether"), 49 | gasPrice: GAS_PRICE 50 | }); 51 | }); 52 | 53 | // be kind and return ethers to the root account 54 | afterEach(async function() { 55 | let cstEth = await web3.eth.getBalance(cst.address); 56 | 57 | await cst.freezeToken(true); 58 | await cst.configure(0x0, 0x0, 0, 0, 1000000, accounts[0]); 59 | await cst.foundationWithdraw(cstEth.toNumber()); 60 | }); 61 | 62 | it("should be able to transfer CST to another account", async function() { 63 | let transferAmount = web3.toWei(10, 'ether'); 64 | 65 | let txn = await cst.transfer(recipientAccount, transferAmount, { 66 | from: senderAccount 67 | }); 68 | 69 | assert.ok(txn.receipt); 70 | assert.ok(txn.logs); 71 | 72 | let senderBalance = await cst.balanceOf(senderAccount); 73 | let recipientBalance = await cst.balanceOf(recipientAccount); 74 | let totalInCirculation = await cst.totalInCirculation(); 75 | 76 | assert.equal(senderBalance.toNumber(), 0, "The CST balance is correct"); 77 | assert.equal(recipientBalance, web3.toWei(10, 'ether'), "The CST balance is correct"); 78 | assert.equal(totalInCirculation, web3.toWei(10, 'ether'), "The CST total in circulation has not changed"); 79 | 80 | assert.equal(txn.logs.length, 1, "The correct number of events were fired"); 81 | 82 | let event = txn.logs[0]; 83 | assert.equal(txn.receipt.gasUsed < MAX_TRANSFER_GAS, true, `The transfer txn uses less than ${MAX_TRANSFER_GAS} gas`); 84 | assert.equal(event.event, "Transfer", "The event type is correct"); 85 | assert.equal(event.args._value, web3.toWei(10, 'ether'), "The CST amount is correct"); 86 | assert.equal(event.args._from, senderAccount, "The sender is correct"); 87 | assert.equal(event.args._to, recipientAccount, "The recipient is correct"); 88 | }); 89 | 90 | it("should not be able to transfer more CST than is in the sender's account", async function() { 91 | let transferAmount = web3.toWei(11, 'ether'); 92 | 93 | await assertRevert(async () => await cst.transfer(recipientAccount, transferAmount, { 94 | from: senderAccount 95 | })); 96 | 97 | let senderBalance = await cst.balanceOf(senderAccount); 98 | let recipientBalance = await cst.balanceOf(recipientAccount); 99 | let totalInCirculation = await cst.totalInCirculation(); 100 | 101 | assert.equal(senderBalance, web3.toWei(10, 'ether'), "The CST balance is correct"); 102 | assert.equal(recipientBalance.toNumber(), 0, "The CST balance is correct"); 103 | assert.equal(totalInCirculation, web3.toWei(10, 'ether'), "The CST total in circulation has not changed"); 104 | }); 105 | 106 | it("should be able to transfer 0 CST ", async function() { 107 | let transferAmount = 0; 108 | 109 | await cst.transfer(recipientAccount, transferAmount, { 110 | from: senderAccount 111 | }); 112 | 113 | let senderBalance = await cst.balanceOf(senderAccount); 114 | let recipientBalance = await cst.balanceOf(recipientAccount); 115 | let totalInCirculation = await cst.totalInCirculation(); 116 | 117 | assert.equal(senderBalance, web3.toWei(10, 'ether'), "The CST balance is correct"); 118 | assert.equal(recipientBalance.toNumber(), 0, "The CST balance is correct"); 119 | assert.equal(totalInCirculation, web3.toWei(10, 'ether'), "The CST total in circulation has not changed"); 120 | }); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /test/cardstack-token-upgrade-test.js: -------------------------------------------------------------------------------- 1 | const { proxyContract } = require('./utils'); 2 | const { 3 | GAS_PRICE, 4 | CST_DEPLOY_GAS_LIMIT, 5 | CARDSTACK_NAMEHASH, 6 | assertRevert 7 | } = require("../lib/utils"); 8 | const CardstackToken = artifacts.require("./CardstackToken.sol"); 9 | const Registry = artifacts.require("./Registry.sol"); 10 | const CstLedger = artifacts.require("./CstLedger.sol"); 11 | const TestingRegistry = artifacts.require("./TestingRegistry.sol"); 12 | const Token_v1 = artifacts.require("./Token_v1.sol"); 13 | const Token_v2 = artifacts.require("./Token_v2.sol"); 14 | const TestingCstLedger = artifacts.require("./TestingCstLedger.sol"); 15 | const Storage = artifacts.require("./ExternalStorage.sol"); 16 | const AdminUpgradeabilityProxy = artifacts.require('AdminUpgradeabilityProxy'); 17 | 18 | contract('CardstackToken', function(accounts) { 19 | let proxyAdmin = accounts[41]; 20 | 21 | describe("upgrade contract", function() { 22 | let registry; 23 | let storage; 24 | let ledger; 25 | let cst1; 26 | let proxy; 27 | let superAdmin = accounts[19]; 28 | let foundation = accounts[31]; 29 | 30 | beforeEach(async function() { 31 | ledger = (await proxyContract(TestingCstLedger, proxyAdmin)).contract; 32 | storage = await Storage.new(); 33 | registry = (await proxyContract(TestingRegistry, proxyAdmin)).contract; 34 | await registry.addSuperAdmin(superAdmin); 35 | await registry.addStorage("cstStorage", storage.address); 36 | await registry.addStorage("cstLedger", ledger.address); 37 | await storage.addSuperAdmin(registry.address); 38 | await ledger.addSuperAdmin(registry.address); 39 | 40 | await storage.setBytes32Value("cstTokenName", web3.toHex("cst")); 41 | await storage.setBytes32Value("cstTokenSymbol", web3.toHex("CST")); 42 | await storage.setUIntValue("cstBuyPrice", 10); 43 | await storage.setUIntValue("cstCirculationCap", web3.toWei(100, 'ether')); 44 | await storage.setAddressValue("cstFoundation", foundation); 45 | 46 | let zosProxy = (await proxyContract(Token_v1, proxyAdmin, registry.address, "cstStorage", "cstLedger", { 47 | gas: CST_DEPLOY_GAS_LIMIT 48 | })); 49 | cst1 = zosProxy.contract; 50 | proxy = zosProxy.proxy; 51 | 52 | await ledger.mintTokens(web3.toWei(100, 'ether')); 53 | }); 54 | 55 | it('non-initializer cannot invoke CardstackToken initialize', async function() { 56 | let implementationContract = await CardstackToken.new({ 57 | gas: CST_DEPLOY_GAS_LIMIT 58 | }); 59 | let proxy = await AdminUpgradeabilityProxy.new(implementationContract.address); 60 | 61 | await proxy.changeAdmin(proxyAdmin); 62 | 63 | let contract = await CardstackToken.at(proxy.address); 64 | 65 | await assertRevert(async () => await contract.initialize(registry.address, "cstStorage", "cstLedger")); 66 | }); 67 | 68 | it('non-initializer cannot invoke CstLedger initialize', async function() { 69 | let implementationContract = await CstLedger.new({ 70 | gas: CST_DEPLOY_GAS_LIMIT 71 | }); 72 | let proxy = await AdminUpgradeabilityProxy.new(implementationContract.address); 73 | 74 | await proxy.changeAdmin(proxyAdmin); 75 | 76 | let contract = await CstLedger.at(proxy.address); 77 | 78 | await assertRevert(async () => await contract.initialize()); 79 | }); 80 | 81 | it('non-initializer cannot invoke Registry initialize', async function() { 82 | let implementationContract = await Registry.new({ 83 | gas: CST_DEPLOY_GAS_LIMIT 84 | }); 85 | let proxy = await AdminUpgradeabilityProxy.new(implementationContract.address); 86 | 87 | await proxy.changeAdmin(proxyAdmin); 88 | 89 | let contract = await Registry.at(proxy.address); 90 | 91 | await assertRevert(async () => await contract.initialize()); 92 | }); 93 | 94 | it('cannot initialize the CardstackToken proxy twice', async function() { 95 | let implementationContract = await Token_v1.new({ 96 | gas: CST_DEPLOY_GAS_LIMIT 97 | }); 98 | let proxy = await AdminUpgradeabilityProxy.new(implementationContract.address); 99 | 100 | await proxy.changeAdmin(proxyAdmin); 101 | 102 | let contract = await Token_v1.at(proxy.address); 103 | 104 | await contract.initialize(registry.address, "cstStorage", "cstLedger"); 105 | await assertRevert(async () => await contract.initialize(registry.address, "cstStorage", "cstLedger")); 106 | }); 107 | 108 | it('cannot initialize the Registry proxy twice', async function() { 109 | let implementationContract = await TestingRegistry.new(); 110 | let proxy = await AdminUpgradeabilityProxy.new(implementationContract.address); 111 | 112 | await proxy.changeAdmin(proxyAdmin); 113 | 114 | let contract = await TestingRegistry.at(proxy.address); 115 | 116 | await contract.initialize(); 117 | await assertRevert(async () => await contract.initialize()); 118 | }); 119 | 120 | it('cannot initialize the CstLedger proxy twice', async function() { 121 | let implementationContract = await TestingCstLedger.new(); 122 | let proxy = await AdminUpgradeabilityProxy.new(implementationContract.address); 123 | 124 | await proxy.changeAdmin(proxyAdmin); 125 | 126 | let contract = await TestingCstLedger.at(proxy.address); 127 | 128 | await contract.initialize(); 129 | await assertRevert(async () => await contract.initialize()); 130 | }); 131 | 132 | it("can preserve contract state through a contract upgrade", async function() { 133 | await registry.register("cst", cst1.address, CARDSTACK_NAMEHASH, { from: superAdmin }); 134 | await cst1.freezeToken(false); 135 | await cst1.configure(web3.toHex("cst"), 136 | web3.toHex("CST"), 137 | 10, 138 | web3.toWei(100, 'ether'), 139 | web3.toWei(1000000, 'ether'), 140 | foundation); 141 | 142 | let buyerAccount = accounts[8]; 143 | let recipientAccount = accounts[4]; 144 | let txnValue = web3.toWei(0.2, "ether"); 145 | 146 | await cst1.addBuyer(buyerAccount); 147 | await cst1.buy({ 148 | from: buyerAccount, 149 | value: txnValue, 150 | gasPrice: GAS_PRICE 151 | }); 152 | 153 | let cstBalance = await cst1.balanceOf(buyerAccount); 154 | let version = await cst1.getVersion(); 155 | let errMsg; 156 | 157 | try { 158 | await cst1.foo(); 159 | } catch (e) { 160 | errMsg = e.message; 161 | } 162 | 163 | assert.equal(errMsg, 'cst1.foo is not a function'); 164 | assert.equal(cstBalance, web3.toWei(2, 'ether'), "The CST balance is correct"); 165 | assert.equal(version, 'v1', 'the contract version is correct'); 166 | 167 | let cst2Impl = await Token_v2.new({ gas: CST_DEPLOY_GAS_LIMIT }); 168 | await proxy.upgradeTo(cst2Impl.address, { from: proxyAdmin }); 169 | let cst2 = await Token_v2.at(proxy.address); 170 | 171 | let totalInCirculation = await cst2.totalInCirculation(); 172 | cstBalance = await cst2.balanceOf(buyerAccount); 173 | 174 | assert.equal(cstBalance, web3.toWei(2, 'ether'), "The CST balance is correct"); 175 | assert.equal(totalInCirculation, web3.toWei(2, 'ether'), "The CST total in circulation was updated correctly"); 176 | 177 | let transferAmount = web3.toWei(2, 'ether'); 178 | 179 | await cst2.transfer(recipientAccount, transferAmount, { 180 | from: buyerAccount, 181 | gasPrice: GAS_PRICE 182 | }); 183 | 184 | let senderBalance = await cst2.balanceOf(buyerAccount); 185 | let recipientBalance = await cst2.balanceOf(recipientAccount); 186 | totalInCirculation = await cst2.totalInCirculation(); 187 | let name = await cst2.name(); 188 | let symbol = await cst2.symbol(); 189 | let buyPrice = await cst2.buyPrice(); 190 | let circulationCap = await cst2.circulationCap(); 191 | let foundationAddress = await cst2.foundation(); 192 | let foo = await cst2.foo(); 193 | version = await cst2.getVersion(); 194 | 195 | assert.equal(version, 'v2', 'the contract version is correct'); 196 | assert.equal(foo, 'bar', 'the upgraded contract has a new function'); 197 | assert.equal(senderBalance.toNumber(), 0, "The CST balance is correct"); 198 | assert.equal(recipientBalance, web3.toWei(2, 'ether'), "The CST balance is correct"); 199 | assert.equal(totalInCirculation, web3.toWei(2, 'ether'), "The CST total in circulation has not changed"); 200 | 201 | assert.equal(name, "cst", "the name is correct"); 202 | assert.equal(symbol, "CST", "the symbol is correct"); 203 | assert.equal(buyPrice.toNumber(), 10, "the buyPrice is correct"); 204 | assert.equal(circulationCap, web3.toWei(100, 'ether'), "the circulationCap is correct"); 205 | assert.equal(foundationAddress, foundation, "the foundation address is correct"); 206 | }); 207 | 208 | it("can preserve allowance state through a contract upgrade", async function() { 209 | let grantor = accounts[23]; 210 | let spender = accounts[31]; 211 | let recipient = accounts[37]; 212 | 213 | await ledger.debitAccount(grantor, web3.toWei(50, 'ether')); 214 | await registry.register("cst", cst1.address, CARDSTACK_NAMEHASH, { from: superAdmin }); 215 | await cst1.freezeToken(false); 216 | await cst1.approve(spender, web3.toWei(10, 'ether'), { from: grantor }); 217 | 218 | let allowance = await cst1.allowance(grantor, spender); 219 | let grantorBalance = await cst1.balanceOf(grantor); 220 | let spenderBalance = await cst1.balanceOf(spender); 221 | let recipientBalance = await cst1.balanceOf(recipient); 222 | let version = await cst1.getVersion(); 223 | 224 | assert.equal(allowance, web3.toWei(10, 'ether'), "the allowance is correct"); 225 | assert.equal(grantorBalance, web3.toWei(50, 'ether'), "the balance is correct"); 226 | assert.equal(spenderBalance.toNumber(), 0, "the balance is correct"); 227 | assert.equal(recipientBalance.toNumber(), 0, "the balance is correct"); 228 | assert.equal(version, 'v1', 'the contract version is correct'); 229 | 230 | let cst2Impl = await Token_v2.new({ gas: CST_DEPLOY_GAS_LIMIT }); 231 | await proxy.upgradeTo(cst2Impl.address, { from: proxyAdmin }); 232 | let cst2 = await Token_v2.at(proxy.address); 233 | 234 | allowance = await cst2.allowance(grantor, spender); 235 | grantorBalance = await cst2.balanceOf(grantor); 236 | spenderBalance = await cst2.balanceOf(spender); 237 | recipientBalance = await cst2.balanceOf(recipient); 238 | version = await cst2.getVersion(); 239 | 240 | assert.equal(version, 'v2', 'the contract version is correct'); 241 | assert.equal(allowance, web3.toWei(10, 'ether'), "the allowance is correct"); 242 | assert.equal(grantorBalance, web3.toWei(50, 'ether'), "the balance is correct"); 243 | assert.equal(spenderBalance, 0, "the balance is correct"); 244 | assert.equal(recipientBalance, 0, "the balance is correct"); 245 | 246 | await cst2.transferFrom(grantor, recipient, web3.toWei(10, 'ether'), { from: spender }); 247 | 248 | allowance = await cst2.allowance(grantor, spender); 249 | grantorBalance = await cst2.balanceOf(grantor); 250 | spenderBalance = await cst2.balanceOf(spender); 251 | recipientBalance = await cst2.balanceOf(recipient); 252 | 253 | assert.equal(allowance.toNumber(), 0, "the allowance is correct"); 254 | assert.equal(grantorBalance, web3.toWei(40, 'ether'), "the balance is correct"); 255 | assert.equal(spenderBalance, 0, "the balance is correct"); 256 | assert.equal(recipientBalance, web3.toWei(10, 'ether'), "the balance is correct"); 257 | }); 258 | }); 259 | }); 260 | 261 | -------------------------------------------------------------------------------- /test/contracts/TestingCardstackToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "../../contracts/CardstackToken.sol"; 4 | 5 | contract TestingCardstackToken is CardstackToken { 6 | address internal constant testingInitializer = 0xEa58Ed38E27dDD7Cf1c6e765B1d61CFC2AE3036E; 7 | 8 | function initialize(address _registry, string _storageName, string _ledgerName) public { 9 | owner = testingInitializer; 10 | _initialize(_registry, _storageName, _ledgerName); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/contracts/TestingCstLedger.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "../../contracts/CstLedger.sol"; 4 | 5 | contract TestingCstLedger is CstLedger { 6 | address internal constant testingInitializer = 0xEa58Ed38E27dDD7Cf1c6e765B1d61CFC2AE3036E; 7 | 8 | function initialize() public isInitializer { 9 | owner = testingInitializer; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/contracts/TestingRegistry.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "../../contracts/Registry.sol"; 4 | 5 | contract TestingRegistry is Registry { 6 | address internal constant testingInitializer = 0xEa58Ed38E27dDD7Cf1c6e765B1d61CFC2AE3036E; 7 | 8 | function initialize() public isInitializer { 9 | owner = testingInitializer; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/contracts/mocks/Token_v1.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "../TestingCardstackToken.sol"; 4 | 5 | contract Token_v1 is TestingCardstackToken { 6 | function getVersion() public pure returns (string) { 7 | return "v1"; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/contracts/mocks/Token_v2.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "../TestingCardstackToken.sol"; 4 | 5 | contract Token_v2 is TestingCardstackToken { 6 | function getVersion() public pure returns (string) { 7 | return "v2"; 8 | } 9 | 10 | function foo() public pure returns (string) { 11 | return 'bar'; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/ledger-test.js: -------------------------------------------------------------------------------- 1 | const TestingCstLedger = artifacts.require("./TestingCstLedger.sol"); 2 | const { proxyContract } = require('./utils'); 3 | const { assertRevert } = require("../lib/utils"); 4 | 5 | contract('CstLedger', function(accounts) { 6 | let ledger; 7 | let admin = accounts[3]; 8 | let proxyAdmin = accounts[41]; 9 | 10 | describe("ledger", function() { 11 | beforeEach(async function() { 12 | ledger = (await proxyContract(TestingCstLedger, proxyAdmin)).contract; 13 | }); 14 | 15 | it("allows owner to add an admin", async function() { 16 | await ledger.addAdmin(admin); 17 | let isAdmin = await ledger.admins(admin); 18 | let adminCount = await ledger.totalAdminsMapping(); 19 | let firstAdminAddress = await ledger.adminsForIndex(0); 20 | 21 | assert.ok(isAdmin, "admin was added"); 22 | assert.equal(adminCount, 1, 'the admin count is correct'); 23 | assert.equal(firstAdminAddress, admin, 'the admin address is correct'); 24 | }); 25 | 26 | it("allows owner to remove an admin", async function() { 27 | await ledger.addAdmin(admin); 28 | 29 | let isAdmin = await ledger.admins(admin); 30 | let adminCount = await ledger.totalAdminsMapping(); 31 | let firstAdminAddress = await ledger.adminsForIndex(0); 32 | 33 | assert.ok(isAdmin, "admin was added"); 34 | 35 | await ledger.removeAdmin(admin); 36 | isAdmin = await ledger.admins(admin); 37 | 38 | assert.notOk(isAdmin, "admin was removed"); 39 | assert.equal(adminCount, 1, 'the admin count is correct'); 40 | assert.equal(firstAdminAddress, admin, 'the admin address is correct'); 41 | }); 42 | 43 | it("non-owner cannot add admins", async function() { 44 | let nonOwner = accounts[7]; 45 | await assertRevert(async () => await ledger.addAdmin(admin, { from: nonOwner })); 46 | 47 | let isAdmin = await ledger.admins(admin); 48 | let adminCount = await ledger.totalAdminsMapping(); 49 | 50 | assert.notOk(isAdmin, "admin was not added"); 51 | assert.equal(adminCount, 0, 'the admin count is correct'); 52 | }); 53 | 54 | it("allows admin to mint tokens", async function() { 55 | await ledger.addAdmin(admin); 56 | 57 | let totalTokens = await ledger.totalTokens(); 58 | assert.equal(totalTokens, 0); 59 | 60 | await ledger.mintTokens(123, { from: admin }); 61 | 62 | totalTokens = await ledger.totalTokens(); 63 | assert.equal(totalTokens, 123); 64 | }); 65 | 66 | it("allows admin to transfer tokens", async function() { 67 | let senderAccount = accounts[5]; 68 | let recipientAccount = accounts[9]; 69 | 70 | await ledger.addAdmin(admin); 71 | await ledger.debitAccount(senderAccount, 100); 72 | await ledger.debitAccount(recipientAccount, 100); 73 | 74 | let senderBalance = await ledger.balanceOf(senderAccount); 75 | let recipientBalance = await ledger.balanceOf(recipientAccount); 76 | 77 | assert.equal(senderBalance, 100, "sender balance is 100"); 78 | assert.equal(recipientBalance, 100, "recipient balance is 100"); 79 | 80 | await ledger.transfer(senderAccount, recipientAccount, 37, { from: admin }); 81 | 82 | senderBalance = await ledger.balanceOf(senderAccount); 83 | recipientBalance = await ledger.balanceOf(recipientAccount); 84 | 85 | assert.equal(senderBalance, 63, "sender balance is 63"); 86 | assert.equal(recipientBalance, 137, "recipient balance is 137"); 87 | }); 88 | 89 | it("does not allow transfer of more tokens than in the sender's account", async function() { 90 | let senderAccount = accounts[5]; 91 | let recipientAccount = accounts[9]; 92 | 93 | await ledger.addAdmin(admin); 94 | await ledger.debitAccount(senderAccount, 100); 95 | await ledger.debitAccount(recipientAccount, 100); 96 | 97 | let senderBalance = await ledger.balanceOf(senderAccount); 98 | let recipientBalance = await ledger.balanceOf(recipientAccount); 99 | 100 | assert.equal(senderBalance, 100, "sender balance is 100"); 101 | assert.equal(recipientBalance, 100, "recipient balance is 100"); 102 | 103 | await assertRevert(async () => await ledger.transfer(senderAccount, recipientAccount, 137, { from: admin })); 104 | }); 105 | 106 | it("allows admin to debit accounts", async function() { 107 | let otherAccount = accounts[6]; 108 | await ledger.addAdmin(admin); 109 | 110 | let otherBalance = await ledger.balanceOf(otherAccount); 111 | assert.equal(otherBalance, 0, "account balance is 0"); 112 | 113 | await ledger.debitAccount(otherAccount, 100, { from: admin }); 114 | 115 | otherBalance = await ledger.balanceOf(otherAccount); 116 | assert.equal(otherBalance, 100, "account balance is 100"); 117 | }); 118 | 119 | it("allows admin to credit accounts", async function() { 120 | let otherAccount = accounts[6]; 121 | await ledger.addAdmin(admin); 122 | await ledger.debitAccount(otherAccount, 100, { from: admin }); // need to debit so that we can credit 123 | 124 | let otherBalance = await ledger.balanceOf(otherAccount); 125 | assert.equal(otherBalance, 100, "account balance is 100"); 126 | 127 | await ledger.creditAccount(otherAccount, 50, { from: admin }); 128 | 129 | otherBalance = await ledger.balanceOf(otherAccount); 130 | assert.equal(otherBalance, 50, "account balance is 50"); 131 | }); 132 | 133 | it("does not allow crediting more tokens than are in the account", async function() { 134 | let otherAccount = accounts[6]; 135 | await ledger.addAdmin(admin); 136 | await ledger.debitAccount(otherAccount, 100, { from: admin }); // need to debit so that we can credit 137 | 138 | await assertRevert(async () => await ledger.creditAccount(otherAccount, 150, { from: admin })); 139 | 140 | let otherBalance = await ledger.balanceOf(otherAccount); 141 | assert.equal(otherBalance, 100, "account balance is 100"); 142 | }); 143 | 144 | it("non-admins cannot credit accounts", async function() { 145 | let otherAccount = accounts[5]; 146 | let nonAdminAccount = accounts[13]; 147 | await ledger.addAdmin(admin); 148 | await ledger.debitAccount(otherAccount, 100, { from: admin }); 149 | 150 | 151 | await assertRevert(async () => await ledger.creditAccount(otherAccount, 50, { from: nonAdminAccount })); 152 | }); 153 | 154 | it("non-admins cannot debit accounts", async function() { 155 | let otherAccount = accounts[5]; 156 | let nonAdminAccount = accounts[13]; 157 | await assertRevert(async () => await ledger.debitAccount(otherAccount, 100, { from: nonAdminAccount })); 158 | }); 159 | 160 | it("non-admins cannot mint tokens", async function() { 161 | let nonAdminAccount = accounts[17]; 162 | 163 | await assertRevert(async () => await ledger.mintTokens(234, { from: nonAdminAccount })); 164 | }); 165 | 166 | it("non-admins cannot transfer tokens", async function() { 167 | let senderAccount = accounts[5]; 168 | let recipientAccount = accounts[9]; 169 | let nonAdminAccount = accounts[13]; 170 | 171 | await ledger.debitAccount(senderAccount, 100); 172 | await ledger.debitAccount(recipientAccount, 100); 173 | 174 | await assertRevert(async () => await ledger.transfer(senderAccount, recipientAccount, 37, { from: nonAdminAccount })); 175 | }); 176 | }); 177 | }); 178 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | const AdminUpgradeabilityProxy = artifacts.require('AdminUpgradeabilityProxy'); 2 | 3 | function isAddress(address) { 4 | return /^0x[a-fA-F0-9]{40}$/.test(address) ? parseInt(address, 16) : false; 5 | } 6 | 7 | async function proxyContract(artifact, proxyAdmin, ...args) { 8 | let opts; 9 | if (args && args.length && typeof args[args.length -1] === 'object') { 10 | opts = args.pop(); 11 | } 12 | 13 | let implementationContract = await artifact.new(opts); 14 | let proxy = await AdminUpgradeabilityProxy.new(implementationContract.address); 15 | 16 | await proxy.changeAdmin(proxyAdmin); 17 | 18 | let contract = await artifact.at(proxy.address); 19 | 20 | await contract.initialize.apply(this, args); 21 | 22 | return { contract, proxy }; 23 | } 24 | 25 | module.exports = { 26 | isAddress, 27 | proxyContract 28 | }; 29 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | solc: { 3 | optimizer: { 4 | enabled: true, 5 | runs: 200 6 | } 7 | }, 8 | networks: { 9 | // this is for auotmated testing and development 10 | development: { 11 | host: "localhost", 12 | port: 8545, 13 | network_id: "*" 14 | }, 15 | 16 | // this is for testing scripts, migration, and mist in testrpc 17 | testrpc: { 18 | host: "localhost", 19 | port: 8545, 20 | network_id: "*" 21 | }, 22 | 23 | rinkeby: { 24 | host: "localhost", 25 | port: 8545, 26 | from: process.env.WALLET, 27 | network_id: 4, 28 | gasPrice: 30000000000 29 | }, 30 | 31 | mainnet: { 32 | host: "localhost", 33 | port: 8545, 34 | from: process.env.WALLET, 35 | network_id: 1, 36 | gasPrice: 8000000000 // Using 8 GWEI, make sure to double check eth gas station 37 | } 38 | } 39 | }; 40 | --------------------------------------------------------------------------------