├── .gitattributes ├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── brownie-config.yaml ├── contracts ├── BookShare.sol ├── CertShare.sol ├── IDVerifierOrg.sol ├── IDVerifierRegistrar.sol ├── OrgCode.sol ├── README.md ├── bases │ ├── IDVerifier.sol │ ├── Modular.sol │ ├── MultiSig.sol │ ├── OrgShare.sol │ └── README.md ├── custodians │ └── OwnedCustodian.sol ├── interfaces │ ├── ICustodian.sol │ ├── IGovernance.sol │ ├── IIDVerifier.sol │ ├── IModules.sol │ ├── IOrgCode.sol │ └── IOrgShare.sol ├── modules │ ├── Dividend.sol │ ├── Governance.sol │ ├── MultiCheckpoint.sol │ ├── README.md │ ├── VestedOptions.sol │ └── bases │ │ ├── Checkpoint.sol │ │ └── Module.sol └── open-zeppelin │ └── SafeMath.sol ├── docs ├── Makefile ├── README.md ├── bookshare.rst ├── certshare.rst ├── conf.py ├── country-and-region-codes.csv ├── custodian-basics.rst ├── custodian.rst ├── data-standards.rst ├── flow1.png ├── flow2.png ├── getting-started.rst ├── glossary.rst ├── governance.rst ├── index.rst ├── kyc.rst ├── model-legal-forms │ └── Model-DAO-Charter-Unincorporated-Association-QDC.md ├── modules.rst ├── multisig.rst ├── orgcode.rst ├── orgshare.rst ├── owned-custodian.rst ├── papers-and-research │ ├── Audit-Report-for-SFT-aka-ZAP.pdf │ ├── SFT-Protocol-Yellowpaper-deprecated.pdf │ └── ZAP-Whitepaper.md ├── requirements.txt └── token-non-standard.rst ├── requirements-dev.txt ├── scripts ├── __init__.py └── deployment.py ├── tests ├── BookShare │ ├── OwnedCustodian │ │ ├── test_token_cust_balances.py │ │ ├── test_token_cust_counts.py │ │ ├── test_token_cust_restrictions.py │ │ ├── test_token_cust_reverts.py │ │ └── test_token_cust_transfer_from.py │ ├── mint_burn │ │ ├── test_balances_supply.py │ │ ├── test_investor_counts.py │ │ └── test_reverts.py │ ├── test_authorized_supply.py │ └── transfer │ │ ├── test_token_balances.py │ │ ├── test_token_country_investor_limits.py │ │ ├── test_token_investor_counts.py │ │ ├── test_token_investor_limits.py │ │ ├── test_token_locks.py │ │ ├── test_token_restrictions.py │ │ └── test_token_transfer_from.py ├── CertShare │ ├── OwnedCustodian │ │ ├── test_nft_cust_balances.py │ │ ├── test_nft_cust_investor_counts.py │ │ ├── test_nft_cust_restrictions.py │ │ ├── test_nft_cust_reverts.py │ │ └── test_nft_cust_transfer_from.py │ ├── mint_burn │ │ ├── test_nft_balances_supply.py │ │ ├── test_nft_investor_counts.py │ │ ├── test_nft_ranges.py │ │ ├── test_nft_reverts.py │ │ └── test_nft_tags.py │ ├── test_nft_authorized_supply.py │ ├── test_nft_modify_ranges.py │ ├── transfer │ │ ├── test_nft_country_investor_limits.py │ │ ├── test_nft_investor_limits.py │ │ ├── test_nft_locks.py │ │ ├── test_nft_restrictions.py │ │ ├── test_nft_transfer_balances.py │ │ ├── test_nft_transfer_from.py │ │ ├── test_nft_transfer_investor_counts.py │ │ └── test_nft_transfer_ranges.py │ └── transfer_range │ │ ├── conftest.py │ │ ├── test_merges.py │ │ ├── test_merges_into_custodian.py │ │ ├── test_merges_one_token.py │ │ ├── test_merges_reverts.py │ │ ├── test_merges_tags.py │ │ └── test_merges_upper_bound.py ├── IDVerifierOrg │ ├── conftest.py │ ├── test_kycissuer_addresses.py │ ├── test_kycissuer_investors.py │ └── test_kycissuer_multisig.py ├── IDVerifierRegistrar │ ├── conftest.py │ ├── test_kycregistrar_addresses.py │ ├── test_kycregistrar_authorities.py │ ├── test_kycregistrar_check_multisig.py │ └── test_kycregistrar_investors.py ├── OrgCode │ ├── Modular │ │ ├── test_attach_detach.py │ │ ├── test_hooks_nft.py │ │ ├── test_hooks_nft_tags.py │ │ ├── test_hooks_token.py │ │ └── test_permissions_issuer.py │ ├── MultiSig │ │ ├── conftest.py │ │ ├── test_add_authority.py │ │ ├── test_add_remove_addr.py │ │ ├── test_check_external.py │ │ ├── test_check_multisig.py │ │ └── test_setters.py │ ├── test_OwnedCustodian.py │ ├── test_add_token.py │ └── test_get_id.py ├── README.md ├── conftest.py ├── custodians │ └── OwnedCustodian │ │ └── Modular │ │ ├── conftest.py │ │ ├── test_hooks.py │ │ └── test_permissions_cust.py └── modules │ ├── GovernanceModule │ ├── conftest.py │ ├── test_close_proposal.py │ ├── test_custodial_vote.py │ ├── test_new_proposal.py │ ├── test_new_vote.py │ ├── test_permissions.py │ ├── test_proposal_results.py │ └── test_vote.py │ ├── MultiCheckpointModule │ ├── conftest.py │ ├── test_balance_at.py │ ├── test_custodian_balance_at.py │ ├── test_new_checkpoint.py │ └── test_totalsupply_at.py │ └── VestedOptions │ ├── conftest.py │ ├── test_accellerate_vesting.py │ ├── test_exercise_revert.py │ ├── test_exercise_success.py │ ├── test_get_in_money_options.py │ ├── test_get_options_at.py │ ├── test_hooks_permissions.py │ ├── test_issue_reverts.py │ ├── test_modify_peg.py │ ├── test_terminate_options.py │ ├── test_total_options.py │ └── test_vesting_expiration.py └── tox.ini /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .vscode 3 | .history 4 | docs/_build/ 5 | docs/_templates/ 6 | docs/_static/ 7 | build/ 8 | reports/ 9 | .idea/ 10 | .tox/ 11 | dist/ 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | matrix: 2 | include: 3 | - name: "Standard Tests, Doctests, Linting - Python 3.7.1 on Xenial Linux" 4 | language: python 5 | python: 3.7 6 | dist: xenial 7 | sudo: true 8 | install: 9 | - sudo add-apt-repository -y ppa:ethereum/ethereum 10 | - sudo add-apt-repository -y ppa:deadsnakes/ppa 11 | - sudo apt-get update 12 | - sudo apt-get install -y python3.7-dev npm solc 13 | - npm -g install ganache-cli 14 | - python -m pip install tox 15 | script: tox 16 | 17 | notifications: 18 | email: false 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # run with: 2 | # docker build -f Dockerfile -t brownie . 3 | # docker run -v $PWD:/usr/src brownie pytest tests 4 | 5 | FROM ubuntu:bionic 6 | WORKDIR /usr/src 7 | 8 | RUN apt-get update 9 | 10 | # Timezone required for tkinter 11 | ENV TZ=America/Vancouver 12 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 13 | 14 | RUN apt-get install -y python3.6 python3-pip python3-venv python3-tk wget curl git npm nodejs 15 | RUN pip3 install wheel pip setuptools virtualenv py-solc-x eth-brownie 16 | 17 | RUN npm install -g ganache-cli@6.5.1 18 | 19 | # Fix UnicodeEncodeError error when running tests 20 | ENV PYTHONIOENCODING=utf-8 21 | -------------------------------------------------------------------------------- /brownie-config.yaml: -------------------------------------------------------------------------------- 1 | # Brownie configuration file 2 | # https://eth-brownie.readthedocs.io/en/latest/config.html 3 | network: 4 | default: development # the default network that brownie connects to 5 | settings: 6 | gas_limit: false 7 | gas_price: false 8 | persist: true 9 | reverting_tx_gas_limit: false # if false, reverting tx's will raise without broadcasting 10 | networks: 11 | # any settings given here will replace the defaults 12 | development: 13 | host: http://127.0.0.1 14 | reverting_tx_gas_limit: 6721975 15 | persist: false 16 | test_rpc: 17 | cmd: ganache-cli 18 | port: 8545 19 | gas_limit: 6721975 20 | accounts: 20 21 | evm_version: petersburg 22 | mnemonic: brownie 23 | # set your Infura API token to the environment variable WEB3_INFURA_PROJECT_ID 24 | mainnet: 25 | host: https://mainnet.infura.io/v3/$WEB3_INFURA_PROJECT_ID 26 | goerli: 27 | host: https://goerli.infura.io/v3/$WEB3_INFURA_PROJECT_ID 28 | kovan: 29 | host: https://kovan.infura.io/v3/$WEB3_INFURA_PROJECT_ID 30 | rinkeby: 31 | host: https://rinkeby.infura.io/v3/$WEB3_INFURA_PROJECT_ID 32 | ropsten: 33 | host: https://ropsten.infura.io/v3/$WEB3_INFURA_PROJECT_ID 34 | pytest: 35 | # these settings replace the defaults when running pytest 36 | gas_limit: 6721975 37 | default_contract_owner: false 38 | reverting_tx_gas_limit: 6721975 39 | revert_traceback: true 40 | compiler: 41 | solc: 42 | version: 0.4.25 43 | evm_version: null 44 | optimize: true 45 | runs: 200 46 | minify_source: false 47 | colors: 48 | key: 49 | value: bright blue 50 | callable: bright cyan 51 | module: bright blue 52 | contract: bright magenta 53 | contract_method: bright magenta 54 | string: bright magenta 55 | dull: dark white 56 | error: bright red 57 | success: bright green 58 | pending: bright yellow 59 | -------------------------------------------------------------------------------- /contracts/README.md: -------------------------------------------------------------------------------- 1 | # ZAP-Tech/contracts 2 | 3 | All solidity contract sources are inside this folder or one of its subfolders. 4 | 5 | ## Subfolders 6 | 7 | * `bases`: Inherited base contracts used by the core contracts. 8 | * `custodians`: Custodian contracts. 9 | * `interfaces`: Contract interfaces. 10 | * `modules`: Optional modules that may be attached to core contracts as needed. 11 | * `modules/bases`: Inherited base contracts used by optional modules. 12 | * `open-zeppelin`: SafeMath. 13 | 14 | ## Contracts 15 | 16 | * `OrgCode.sol`: Central contract that ties together shares, verifiers, and custodians. 17 | * `BookShare.sol`: Share contract derived from ERC-20 standard. Represents fungible, book-entry style shares. 18 | * `CertShare.sol`: Share contract derived from ERC-20 standard. Represents non-fungible certificated shares. 19 | * `IDVerifierOrg.sol`: Streamlined verifier contract for use by a single org. 20 | * `IDVerifierRegistrar.sol`: Verifier contract that may be shared across many orgs. 21 | -------------------------------------------------------------------------------- /contracts/bases/README.md: -------------------------------------------------------------------------------- 1 | # ZAP-Tech/contracts/bases 2 | 3 | These are inherited base contracts used by the core contracts. 4 | 5 | ## Contracts 6 | 7 | * `KYC.sol`: Common base for `IDVerifierOrg` and `IDVerifierRegistrar`. 8 | * `Modular.sol`: Modular functionality used by `OrgCode`, custodians, and share contracts. 9 | * `MultiSig.sol`: Multi-signature, multi-owner functionality used by `OrgCode` and custodian contracts. 10 | * `OrgShare.sol`: Common base for `BookShare` and `CertShare`. 11 | -------------------------------------------------------------------------------- /contracts/interfaces/ICustodian.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.25; 2 | 3 | /** @title Minimal Custodian Interface 4 | @dev 5 | these are the minimum required methods that MUST be included for 6 | the module to attach to OrgCode 7 | */ 8 | interface ICustodian { 9 | 10 | function ownerID() external view returns (bytes32); 11 | 12 | function receiveTransfer( 13 | address _from, 14 | uint256 _value 15 | ) 16 | external 17 | returns (bool); 18 | } 19 | -------------------------------------------------------------------------------- /contracts/interfaces/IGovernance.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.25; 2 | 3 | /** @title Governance Module Interface 4 | @dev 5 | these are the minimum required methods that MUST be included for 6 | the module to attach to OrgCode 7 | */ 8 | interface IGovernance { 9 | 10 | function orgCode() external view returns (address); 11 | 12 | function modifyAuthorizedSupply(address, uint256) external returns (bool); 13 | function addOrgShare(address _share) external returns (bool); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /contracts/interfaces/IIDVerifier.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.25; 2 | 3 | /** @title IDVerifier Interface 4 | @dev 5 | this is a minimal interface that can be used to interact with both 6 | IDVerifierOrg and IDVerifierRegistrar contracts 7 | */ 8 | interface IIDVerifier { 9 | event MemberRestriction (bytes32 indexed id, bool restricted, bytes32 indexed authority); 10 | event NewMember (bytes32 indexed id, uint16 indexed country, bytes3 region, uint8 rating, uint40 expires, bytes32 indexed authority); 11 | event RegisteredAddresses (bytes32 indexed id, address[] addr, bytes32 indexed authority); 12 | event RestrictedAddresses (bytes32 indexed id, address[] addr, bytes32 indexed authority); 13 | event UpdatedMember (bytes32 indexed id, bytes3 region, uint8 rating, uint40 expires, bytes32 indexed authority); 14 | 15 | function addMember (bytes32, uint16, bytes3, uint8, uint40, address[]) external returns (bool); 16 | function registerAddresses (bytes32, address[]) external returns (bool); 17 | function restrictAddresses (bytes32, address[]) external returns (bool); 18 | function setMemberRestriction (bytes32, bool) external returns (bool); 19 | function updateMember (bytes32, bytes3, uint8, uint40) external returns (bool); 20 | function generateID (string _idString) external pure returns (bytes32); 21 | function getCountry (bytes32 _id) external view returns (uint16); 22 | function getExpires (bytes32 _id) external view returns (uint40); 23 | function getID (address _addr) external view returns (bytes32); 24 | function getMember (address _addr) external view returns (bytes32 _id, bool _permitted, uint8 _rating, uint16 _country); 25 | function getMembers (address _from, address _to) external view returns (bytes32[2] _id, bool[2] _permitted, uint8[2] _rating, uint16[2] _country); 26 | function getRating (bytes32 _id) external view returns (uint8); 27 | function getRegion (bytes32 _id) external view returns (bytes3); 28 | function isPermitted (address _addr) external view returns (bool); 29 | function isPermittedID (bytes32) external view returns (bool); 30 | function isRegistered (bytes32 _id) external view returns (bool); 31 | } 32 | -------------------------------------------------------------------------------- /contracts/interfaces/IOrgShare.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.25; 2 | 3 | /** @title OrgShareBase Interface 4 | @dev 5 | this is a minimal interface that can be used to interact with both 6 | BookShare and CertShare contracts 7 | */ 8 | contract IOrgShareBase { 9 | event Approval (address indexed shareOwner, address indexed spender, uint256 value); 10 | event AuthorizedSupplyChanged (uint256 oldAuthorized, uint256 newAuthorized); 11 | event ModuleAttached (address module, bytes4[] hooks, bytes4[] permissions); 12 | event ModuleDetached (address module); 13 | event ModuleHookSet (address module, bytes4 hook, bool active, bool always); 14 | event Transfer (address indexed from, address indexed to, uint256 value); 15 | 16 | function approve (address _spender, uint256 _value) external returns (bool); 17 | function attachModule (address _module) external returns (bool); 18 | function clearHookTags (bytes4 _sig, bytes1[] _tagBase) external returns (bool); 19 | function detachModule (address _module) external returns (bool); 20 | function modifyAuthorizedSupply (uint256 _value) external returns (bool); 21 | function setHook (bytes4 _sig, bool _active, bool _always) external returns (bool); 22 | function setHookTags (bytes4 _sig, bool _value, bytes1 _tagBase, bytes1[] _tags) external returns (bool); 23 | function transfer (address, uint256) external returns (bool); 24 | function transferCustodian (address[2], uint256) external returns (bool); 25 | function transferFrom (address, address, uint256) external returns (bool); 26 | function allowance (address _owner, address _spender) external view returns (uint256); 27 | function authorizedSupply () external view returns (uint256); 28 | function balanceOf (address) external view returns (uint256); 29 | function checkTransfer (address _from, address _to, uint256 _value) external view returns (bool); 30 | function checkTransferCustodian (address _cust, address _from, address _to, uint256 _value) external view returns (bool); 31 | function circulatingSupply () external view returns (uint256); 32 | function custodianBalanceOf (address _owner, address _cust) external view returns (uint256); 33 | function decimals () external view returns (uint8); 34 | function isActiveModule (address _module) external view returns (bool); 35 | function isPermittedModule (address _module, bytes4 _sig) external view returns (bool); 36 | function name () external view returns (string); 37 | function orgCode () external view returns (address); 38 | function ownerID () external view returns (bytes32); 39 | function symbol () external view returns (string); 40 | function totalSupply () external view returns (uint256); 41 | function treasurySupply () external view returns (uint256); 42 | } 43 | 44 | /** @title BookShare Interface 45 | @dev 46 | this is a minimal interface that can be used to interact BookShare contracts 47 | */ 48 | contract IBookShare is IOrgShareBase { 49 | function mint(address _owner, uint256 _value) external returns (bool); 50 | function burn(address _owner, uint256 _value) external returns (bool); 51 | } 52 | 53 | /** @title CertShare Interface 54 | @dev 55 | this is a minimal interface that can be used to interact CertShare contracts 56 | */ 57 | contract ICertShare is IOrgShareBase { 58 | function burn(uint48 _start, uint48 _stop) external returns (bool); 59 | function mint(address _owner, uint48 _value, uint32 _time, bytes2 _tag) external returns (bool); 60 | function modifyRange(uint48 _pointer, uint32 _time, bytes2 _tag) public returns (bool); 61 | function modifyRanges(uint48 _start, uint48 _stop, uint32 _time, bytes2 _tag) public returns (bool); 62 | function transferRange(address _to, uint48 _start, uint48 _stop) external returns (bool); 63 | function custodianRangesOf(address _owner, address _custodian) external view returns (uint48[2][]); 64 | function getRange(uint256 _idx) external view returns (address _owner, uint48 _start, uint48 _stop, uint32 _time, bytes2 _tag, address _custodian); 65 | function rangesOf(address _owner) external view returns (uint48[2][]); 66 | } 67 | -------------------------------------------------------------------------------- /contracts/modules/README.md: -------------------------------------------------------------------------------- 1 | # ZAP-Tech/contracts/modules 2 | 3 | Optional modules that may be attached to core contracts as needed. 4 | 5 | ## Subfolders 6 | 7 | * `bases`: Inherited base contracts used by modules. 8 | 9 | ## Contracts 10 | 11 | * `bases/Modular.sol`: Contains module base contracts. All modules **must** inherit one of these base contracts, or implement their functionality. 12 | * `bases/Checkpoint.sol`: Base module for creating a single balance checkpoint for an `OrgShare`. 13 | * `Dividend.sol`: Share module for paying out divedends denominated in ETH. 14 | * `MultiCheckpoint.sol`: Org module for creating many checkpoints across multiple `Orgshare`s. 15 | * `VestedOptions.sol`: `BookShare` module for issuing vested stock options. 16 | -------------------------------------------------------------------------------- /contracts/open-zeppelin/SafeMath.sol: -------------------------------------------------------------------------------- 1 | 2 | pragma solidity 0.4.25; 3 | 4 | 5 | /** 6 | * @title SafeMath 7 | * @dev Math operations with safety checks that revert on error 8 | */ 9 | library SafeMath { 10 | 11 | /** 12 | * @dev Multiplies two numbers, reverts on overflow. 13 | */ 14 | function mul(uint256 _a, uint256 _b) internal pure returns (uint256) { 15 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the 16 | // benefit is lost if 'b' is also tested. 17 | // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 18 | if (_a == 0) { 19 | return 0; 20 | } 21 | 22 | uint256 c = _a * _b; 23 | require(c / _a == _b); 24 | 25 | return c; 26 | } 27 | 28 | /** 29 | * @dev Integer division of two numbers truncating the quotient, reverts on division by zero. 30 | */ 31 | function div(uint256 _a, uint256 _b) internal pure returns (uint256) { 32 | require(_b > 0); // Solidity only automatically asserts when dividing by 0 33 | uint256 c = _a / _b; 34 | // assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold 35 | 36 | return c; 37 | } 38 | 39 | /** 40 | * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend). 41 | */ 42 | function sub(uint256 _a, uint256 _b) internal pure returns (uint256) { 43 | require(_b <= _a); 44 | uint256 c = _a - _b; 45 | 46 | return c; 47 | } 48 | 49 | /** 50 | * @dev Adds two numbers, reverts on overflow. 51 | */ 52 | function add(uint256 _a, uint256 _b) internal pure returns (uint256) { 53 | uint256 c = _a + _b; 54 | require(c >= _a); 55 | 56 | return c; 57 | } 58 | 59 | /** 60 | * @dev Divides two numbers and returns the remainder (unsigned integer modulo), 61 | * reverts when dividing by zero. 62 | */ 63 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 64 | require(b != 0); 65 | return a % b; 66 | } 67 | } 68 | 69 | library SafeMath32 { 70 | 71 | function mul(uint32 _a, uint32 _b) internal pure returns (uint32) { 72 | if (_a == 0) { 73 | return 0; 74 | } 75 | 76 | uint32 c = _a * _b; 77 | require(c / _a == _b); 78 | 79 | return c; 80 | } 81 | 82 | function div(uint32 _a, uint32 _b) internal pure returns (uint32) { 83 | require(_b > 0); // Solidity only automatically asserts when dividing by 0 84 | uint32 c = _a / _b; 85 | // assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold 86 | 87 | return c; 88 | } 89 | 90 | function sub(uint32 _a, uint32 _b) internal pure returns (uint32) { 91 | require(_b <= _a); 92 | uint32 c = _a - _b; 93 | 94 | return c; 95 | } 96 | 97 | function add(uint32 _a, uint32 _b) internal pure returns (uint32) { 98 | uint32 c = _a + _b; 99 | require(c >= _a); 100 | 101 | return c; 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = . 8 | BUILDDIR = _build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # ZAP Protocol Documentation 2 | 3 | Available on [ReadTheDocs](https://sft-protocol.readthedocs.io/en/latest/) 4 | -------------------------------------------------------------------------------- /docs/custodian.rst: -------------------------------------------------------------------------------- 1 | .. _custodian: 2 | 3 | ########## 4 | Custodians 5 | ########## 6 | 7 | Custodian contracts are approved to hold shares on behalf of multiple members. Each custodian must be individually approved by an org before they can receive tokens. 8 | 9 | There are two broad categories of custodians: 10 | 11 | * **Owned** custodians are contracts that are controlled and maintained by a known legal entity. Examples of owned custodians include broker/dealers or centralized exchanges. 12 | * **Autonomous** custodians are contracts without an owner. Once deployed there is no authority capable of exercising control over the contract. Examples of autonomous custodians include escrow services, privacy protocols and decentralized exchanges. 13 | 14 | It may be useful to view source code for the following contracts while reading this section: 15 | 16 | * `OwnedCustodian.sol `__: Standard owned custodian contract with ``Multisig`` and ``Modular`` functionality. 17 | * `ICustodian.sol `__: The minimum contract interface required for a custodian to interact with an ``OrgCode`` contract. 18 | 19 | .. warning:: An org should not approve a Custodian if the contract source code cannot be verified, or it is using a non-standard implementation that has not undergone a thorough audit. ZAP includes a standard owned Custodian contract that allows for customization through modules. 20 | 21 | .. toctree:: :maxdepth: 2 22 | 23 | custodian-basics.rst 24 | owned-custodian.rst 25 | -------------------------------------------------------------------------------- /docs/data-standards.rst: -------------------------------------------------------------------------------- 1 | .. _data-standards: 2 | 3 | ####################### 4 | Member Data Standards 5 | ####################### 6 | 7 | The following generation and format standards should be followed across 8 | ZAP to ensure interoperability between network 9 | participants. 10 | 11 | Member IDs 12 | ------------ 13 | 14 | Member IDs are stored as a bytes32 keccak256 hash of the member's 15 | personally identifiable information. 16 | 17 | For legal entities, the hash is generated from their `Global Legal 18 | Entity Identifier (LEI) `__: 19 | 20 | *The International Organization for Standardization (ISO) 1744 standard defines a set of attributes or legal entity reference data that are the most essential elements of identification. The Legal Entity Identifier (LEI) code itself is neutral, with no embedded intelligence or country codes that could create unnecessary complexity for users.* 21 | 22 | For natural persons, a hash is produced from a concatenation of the 23 | following: 24 | 25 | * Full legal name in all capital letters without spaces 26 | * Date of Birth as ``DDMMYYYY`` 27 | * Unique tax ID from current jurisdiction of residence 28 | 29 | If any of the malleable fields are changed (via a legal name change or a change of home jurisdictions), the member will be required to pass identity checks again and a new member ID will be generated. Once identification is verified, the shares held in previous addresses must be transferred to addresses associated to the new member ID. It is impossible to remove or change the ID association of an address. 30 | 31 | Country Codes 32 | ------------- 33 | 34 | Based on the `ISO-3166-1 numeric `__ standard. Country codes are stored as a uint16 and follow the standard exactly. 35 | 36 | A CSV of country and region codes is available `here `__. 37 | 38 | Region Codes 39 | ------------ 40 | 41 | Based on the `ISO 3166-2 `__ standard. Region codes are stored as a bytes3 and are generated in the following way: 42 | 43 | 1. Convert each character of the ISO 3166-2 code to a hexadecimal ASCII code point 44 | 2. Concatenate the hex values 45 | 3. Pad right where necessary 46 | 47 | A quick example to generate region codes using python: 48 | 49 | .. code-block:: python 50 | 51 | iso3166 = "US-AL"[3:] 52 | iso3166 = [hex(ord(i)).replace('0x','') for i in iso3166] 53 | print("0x"+"".join(iso3166)).ljust(6, '0')) 54 | 55 | * Original code: ``US-AL`` 56 | * Resulting bytes3: ``0x414c00`` 57 | 58 | A CSV of country and region codes is available `here `__. 59 | -------------------------------------------------------------------------------- /docs/flow1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zerolawtech/ZAP-Tech/2af399913dc4ead5fa59b38bb928834dea3f1b8d/docs/flow1.png -------------------------------------------------------------------------------- /docs/flow2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zerolawtech/ZAP-Tech/2af399913dc4ead5fa59b38bb928834dea3f1b8d/docs/flow2.png -------------------------------------------------------------------------------- /docs/glossary.rst: -------------------------------------------------------------------------------- 1 | .. _glossary: 2 | 3 | ######## 4 | Glossary 5 | ######## 6 | 7 | * **Authority**: A collection of one or more addresses permitted to call specific admin-level functionality in a multisig contract. 8 | * **Custodian**: An entity that holds shares on behalf of members, without taking beneficial ownership. May be a natural person, legal entity, or autonomous smart contract. Examples of custodians include broker/dealers, escrow agreements and secondary markets. 9 | * **Entity**: A participant in ZAP. Entity may refer to natural persons or legal entities. 10 | * **Hook**: The point at which a module attaches to a method in a parent contract. 11 | * **Org**: An entity that creates and sells shares. 12 | * **Member**: An entity that has been verified and is able to hold and transfer shares. 13 | * **Module**: A non-essential smart contract associated with a share or custodian contract, used to add extra transfer permissioning or handle on-chain governance events. 14 | * **Owner**: The highest authority of a contract, set durin deployment. Only the owner is capable of creating or restricting other authorities on that contract. 15 | * **Rating**: A number assigned to each member that corresponds to their accreditation status. 16 | * **Region**: Refers to the state, province, or other principal subdivision that a member resides in. 17 | * **OrgShare**: An ERC-20 compliant token, created by an org, who's transferrability is restricted through on-chain logic. 18 | * **Threshold**: The number of required calls from an authority to an admin-level function before it executes. This value cannot be greater the number of addresses associated with the authority. 19 | * **Verifier**: A whitelist contract that associates Ethereum addresses to specific members. 20 | -------------------------------------------------------------------------------- /docs/governance.rst: -------------------------------------------------------------------------------- 1 | .. _governance: 2 | 3 | ########## 4 | Governance 5 | ########## 6 | 7 | The ``Goveranance`` contract is a special type of module that may optionally be attached to an :ref:`org-code`. It is used to add on-chain voting functionality for share holders. When attached, it adds a permissioning check before increasing authorized token supplies or adding new tokens. 8 | 9 | ZAP includes a very minimal proof of concept as a starting point for developing a governance contract. It can be combined with a checkpoint module to build whatever specific setup is required by an org. 10 | 11 | It may be useful to view source code for the following contracts while reading this document: 12 | 13 | * `Governance.sol `__: A minimal implementation of ``Goverance``, intended for testing purposes or as a base for building a functional contract. 14 | * `IGovernance.sol `__: The minimum contract interface required for a governance module to interact with an ``OrgCode`` contract. 15 | 16 | Public Constants 17 | ================ 18 | 19 | .. method:: Governance.orgCode() 20 | 21 | The address of the associated ``OrgCode`` contract. 22 | 23 | .. code-block:: python 24 | 25 | >>> governance.orgCode() 26 | 0x40b49Ad1B8D6A8Df6cEdB56081D51b69e6569e06 27 | 28 | Checking Permissions 29 | ==================== 30 | 31 | The following methods must return ``true`` in order for the calling methods to execute. 32 | 33 | .. method:: Governance.addShare(address _share) 34 | 35 | Called by ``OrgCode.addShare`` before associating a new share contract. 36 | 37 | .. method:: Governance.modifyAuthorizedSupply(address _share, uint256 _value) 38 | 39 | Called by ``OrgShare.modifyAuthorizedSupply`` before modifying the authorized supply. 40 | -------------------------------------------------------------------------------- /docs/orgshare.rst: -------------------------------------------------------------------------------- 1 | .. _orgshare: 2 | 3 | ######## 4 | OrgShare 5 | ######## 6 | 7 | Each token contract represents a single class of securities from an org. Share contracts are based on the `ERC20 Token 8 | Standard `__. Depending on the use case, there are two token implementations: 9 | 10 | * `BookShare.sol `__ is used for the issuance of non-certificated (book entry) securities. These tokens are fungible. 11 | * `CertShare.sol `__ is used for the issuance of certificated securities. These tokens are non-fungible. 12 | 13 | Both contracts are derived from a common base `OrgShare.sol `__. 14 | 15 | Share contracts include :ref:`multisig` and :ref:`modules` via the associated :ref:`org-code` contract. See the respective documents for more detailed information. 16 | 17 | This documentation only explains contract methods that are meant to be accessed directly. External methods that will revert unless called through another contract, such as ``OrgCode`` or modules, are not included. 18 | 19 | Because of significant differences in the contracts, ``BookShare`` and ``CertShare`` are documented seperately. 20 | 21 | .. toctree:: :maxdepth: 2 22 | 23 | bookshare.rst 24 | certshare.rst 25 | token-non-standard.rst 26 | -------------------------------------------------------------------------------- /docs/papers-and-research/Audit-Report-for-SFT-aka-ZAP.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zerolawtech/ZAP-Tech/2af399913dc4ead5fa59b38bb928834dea3f1b8d/docs/papers-and-research/Audit-Report-for-SFT-aka-ZAP.pdf -------------------------------------------------------------------------------- /docs/papers-and-research/SFT-Protocol-Yellowpaper-deprecated.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zerolawtech/ZAP-Tech/2af399913dc4ead5fa59b38bb928834dea3f1b8d/docs/papers-and-research/SFT-Protocol-Yellowpaper-deprecated.pdf -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx_rtd_theme>=0.3.1 2 | pygments-lexer-solidity>=0.3.1 3 | -------------------------------------------------------------------------------- /docs/token-non-standard.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _share-non-standard: 3 | 4 | ======================= 5 | Non Standard Behaviours 6 | ======================= 7 | 8 | ``BookShare`` and ``CertShare`` are based upon the `ERC20 Token 9 | Standard `__, however they deviate in several areas. 10 | 11 | Org Balances 12 | ============ 13 | 14 | Shares held by the org will always be at the address of the OrgCode contract. ``OrgShare.treasurySupply()`` returns the same result as ``OrgShare.balanceOf(OrgShare.orgCode())``. 15 | 16 | As a result, the following non-standard behaviours exist: 17 | 18 | * Any address associated with the org can transfer shares from the OrgCode contract using ``OrgShare.transfer``. 19 | * Attempting to send shares to any address associated with the org will result in the tokens being sent to the OrgCode contract. 20 | 21 | Share Transfers 22 | =============== 23 | 24 | The following behaviours deviate from ERC20 relating to share transfers: 25 | 26 | * Transfers of 0 shares will revert with an error string "Cannot send 0 tokens". 27 | * If the caller and sender addresses are both associated to the same ID, ``OrgShare.transferFrom`` may be called without giving prior approval. In this way a member can easily recover shares when a private key is lost or compromised. 28 | * The org may call ``OrgShare.transferFrom`` to move shares between any addresses without prior approval. Transfers of this type must still pass the normal checks, with the exception that the sending address may be restricted. In this way the org can aid members with token recovery in the event of a lost or compromised private key, or force a transfer in the event of a court order. 29 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | black==19.3b0 2 | eth-brownie==1.2.1 3 | flake8==3.7.7 4 | pytest>=5.0.0 5 | sphinx==2.0.1 6 | tox==3.14.0 7 | -------------------------------------------------------------------------------- /scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zerolawtech/ZAP-Tech/2af399913dc4ead5fa59b38bb928834dea3f1b8d/scripts/__init__.py -------------------------------------------------------------------------------- /scripts/deployment.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import itertools 4 | 5 | from brownie import * 6 | 7 | 8 | def main(share_contract=BookShare, countries=(1, 2, 3), ratings=(1, 2)): 9 | share, issuer, kyc = deploy_contracts(share_contract) 10 | add_investors(countries, ratings) 11 | return share, issuer, kyc 12 | 13 | 14 | def deploy_contracts(share_contract=BookShare): 15 | kyc = accounts[0].deploy(IDVerifierRegistrar, [accounts[0]], 1) 16 | issuer = accounts[0].deploy(OrgCode, [accounts[0]], 1) 17 | share = accounts[0].deploy(share_contract, issuer, "Test Token", "TST", 1000000) 18 | issuer.addOrgShare(share, {"from": accounts[0]}) 19 | issuer.setVerifier(kyc, False, {"from": accounts[0]}) 20 | return share, issuer, kyc 21 | 22 | 23 | def deploy_custodian(): 24 | accounts[0].deploy(OwnedCustodian, [a[0]], 1) 25 | OrgCode[0].addCustodian(OwnedCustodian[0], {"from": a[0]}) 26 | return OwnedCustodian[0] 27 | 28 | 29 | def add_investors(countries=(1, 2, 3), ratings=(1, 2)): 30 | # Approves accounts[1:7] in IDVerifierRegistrar[0], investor ratings 1-2 and country codes 1-3 31 | product = itertools.product(countries, ratings) 32 | for count, country, rating in [ 33 | (c, i[0], i[1]) for c, i in enumerate(product, start=1) 34 | ]: 35 | IDVerifierRegistrar[0].addInvestor( 36 | ("investor" + str(count)).encode(), 37 | country, 38 | "0x000001", 39 | rating, 40 | 9999999999, 41 | [accounts[count]], 42 | {"from": accounts[0]}, 43 | ) 44 | # Approves investors from country codes 1-3 in OrgCode[0] 45 | OrgCode[0].setCountries( 46 | countries, [1] * len(countries), [0] * len(countries), {"from": accounts[0]} 47 | ) 48 | -------------------------------------------------------------------------------- /tests/BookShare/OwnedCustodian/test_token_cust_balances.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import functools 4 | import pytest 5 | 6 | from brownie import accounts 7 | 8 | 9 | @pytest.fixture(scope="module", autouse=True) 10 | def setup(approve_many, org, share): 11 | share.mint(org, 100000, {"from": accounts[0]}) 12 | 13 | 14 | @pytest.fixture(scope="module") 15 | def balance(share, cust): 16 | yield functools.partial(_check_balance, share, cust) 17 | 18 | 19 | def _check_balance(share, cust, account, bal, custbal): 20 | assert share.balanceOf(account) == bal 21 | assert share.custodianBalanceOf(account, cust) == custbal 22 | 23 | 24 | def test_into_custodian(balance, share, cust): 25 | """Transfer into custodian - member""" 26 | share.transfer(accounts[1], 10000, {"from": accounts[0]}) 27 | share.transfer(accounts[2], 10000, {"from": accounts[0]}) 28 | share.transfer(cust, 4000, {"from": accounts[1]}) 29 | share.transfer(cust, 10000, {"from": accounts[2]}) 30 | balance(accounts[1], 6000, 4000) 31 | balance(accounts[2], 0, 10000) 32 | assert share.balanceOf(cust) == 14000 33 | 34 | 35 | def test_cust_internal(balance, share, cust): 36 | """Custodian transfer internal - member to member""" 37 | share.transfer(accounts[2], 10000, {"from": accounts[0]}) 38 | share.transfer(cust, 5000, {"from": accounts[2]}) 39 | cust.transferInternal(share, accounts[2], accounts[3], 5000, {"from": accounts[0]}) 40 | balance(accounts[2], 5000, 0) 41 | balance(accounts[3], 0, 5000) 42 | assert share.balanceOf(cust) == 5000 43 | 44 | 45 | def test_cust_out(balance, share, cust): 46 | """Transfer out of custodian - member""" 47 | share.transfer(accounts[1], 10000, {"from": accounts[0]}) 48 | share.transfer(cust, 10000, {"from": accounts[1]}) 49 | cust.transferInternal(share, accounts[1], accounts[2], 10000, {"from": accounts[0]}) 50 | cust.transfer(share, accounts[2], 10000, {"from": accounts[0]}) 51 | balance(accounts[1], 0, 0) 52 | balance(accounts[2], 10000, 0) 53 | assert share.balanceOf(cust) == 0 54 | 55 | 56 | def test_org_cust_in(balance, org, share, cust): 57 | """Transfers into custodian - org""" 58 | share.transfer(cust, 10000, {"from": accounts[0]}) 59 | balance(accounts[0], 0, 0) 60 | balance(org, 90000, 10000) 61 | assert share.balanceOf(cust) == 10000 62 | share.transfer(cust, 90000, {"from": accounts[0]}) 63 | balance(accounts[0], 0, 0) 64 | balance(org, 0, 100000) 65 | assert share.balanceOf(cust) == 100000 66 | 67 | 68 | def test_org_cust_internal(balance, org, share, cust): 69 | """Custodian internal transfers - org / member""" 70 | share.transfer(cust, 10000, {"from": accounts[0]}) 71 | cust.transferInternal(share, org, accounts[1], 10000, {"from": accounts[0]}) 72 | balance(accounts[0], 0, 0) 73 | balance(org, 90000, 0) 74 | balance(accounts[1], 0, 10000) 75 | assert share.balanceOf(cust) == 10000 76 | cust.transferInternal(share, accounts[1], org, 5000, {"from": accounts[0]}) 77 | balance(accounts[0], 0, 0) 78 | balance(org, 90000, 5000) 79 | balance(accounts[1], 0, 5000) 80 | assert share.balanceOf(cust) == 10000 81 | cust.transferInternal(share, accounts[1], accounts[0], 5000, {"from": accounts[0]}) 82 | balance(accounts[0], 0, 0) 83 | balance(org, 90000, 10000) 84 | balance(accounts[1], 0, 0) 85 | assert share.balanceOf(cust) == 10000 86 | 87 | 88 | def test_org_cust_out(balance, org, share, cust): 89 | """Transfers out of custodian - org""" 90 | share.transfer(cust, 10000, {"from": accounts[0]}) 91 | balance(accounts[0], 0, 0) 92 | balance(org, 90000, 10000) 93 | assert share.balanceOf(cust) == 10000 94 | cust.transfer(share, org, 3000, {"from": accounts[0]}) 95 | balance(accounts[0], 0, 0) 96 | balance(org, 93000, 7000) 97 | assert share.balanceOf(cust) == 7000 98 | cust.transfer(share, accounts[0], 7000, {"from": accounts[0]}) 99 | balance(accounts[0], 0, 0) 100 | balance(org, 100000, 0) 101 | assert share.balanceOf(cust) == 0 102 | -------------------------------------------------------------------------------- /tests/BookShare/OwnedCustodian/test_token_cust_counts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(approve_many, org, share): 10 | share.mint(org, 100000, {"from": accounts[0]}) 11 | 12 | 13 | def test_into_custodian(check_counts, share, cust): 14 | """Transfer into custodian - member""" 15 | share.transfer(accounts[1], 10000, {"from": accounts[0]}) 16 | share.transfer(accounts[2], 10000, {"from": accounts[0]}) 17 | check_counts(one=(2, 1, 1)) 18 | share.transfer(cust, 5000, {"from": accounts[1]}) 19 | check_counts(one=(2, 1, 1)) 20 | share.transfer(cust, 10000, {"from": accounts[2]}) 21 | check_counts(one=(2, 1, 1)) 22 | 23 | 24 | def test_cust_internal(check_counts, share, cust): 25 | """Custodian transfer internal - member to member""" 26 | share.transfer(accounts[2], 10000, {"from": accounts[0]}) 27 | share.transfer(cust, 5000, {"from": accounts[2]}) 28 | cust.transferInternal(share, accounts[2], accounts[3], 5000, {"from": accounts[0]}) 29 | check_counts(one=(1, 0, 1), two=(1, 1, 0)) 30 | share.transfer(accounts[3], 5000, {"from": accounts[0]}) 31 | check_counts(one=(1, 0, 1), two=(1, 1, 0)) 32 | 33 | 34 | def test_cust_out(check_counts, org, share, cust): 35 | """Transfer out of custodian - member""" 36 | share.transfer(accounts[1], 10000, {"from": accounts[0]}) 37 | share.transfer(cust, 10000, {"from": accounts[1]}) 38 | cust.transferInternal(share, accounts[1], accounts[2], 10000, {"from": accounts[0]}) 39 | check_counts(one=(1, 0, 1)) 40 | cust.transfer(share, accounts[2], 10000, {"from": accounts[0]}) 41 | check_counts(one=(1, 0, 1)) 42 | share.transfer(org, 10000, {"from": accounts[2]}) 43 | check_counts() 44 | 45 | 46 | def test_org_cust_in(check_counts, share, cust): 47 | """Transfers into custodian - org""" 48 | share.transfer(cust, 10000, {"from": accounts[0]}) 49 | check_counts() 50 | share.transfer(cust, 90000, {"from": accounts[0]}) 51 | check_counts() 52 | 53 | 54 | def test_org_cust_internal(check_counts, org, share, cust): 55 | """Custodian internal transfers - org / member""" 56 | share.transfer(cust, 10000, {"from": accounts[0]}) 57 | cust.transferInternal(share, org, accounts[1], 10000, {"from": accounts[0]}) 58 | check_counts(one=(1, 1, 0)) 59 | cust.transferInternal(share, accounts[1], org, 5000, {"from": accounts[0]}) 60 | check_counts(one=(1, 1, 0)) 61 | cust.transferInternal(share, accounts[1], accounts[0], 5000, {"from": accounts[0]}) 62 | check_counts() 63 | 64 | 65 | def test_org_cust_out(check_counts, org, share, cust): 66 | """Transfers out of custodian - org""" 67 | share.transfer(cust, 10000, {"from": accounts[0]}) 68 | check_counts() 69 | cust.transfer(share, org, 3000, {"from": accounts[0]}) 70 | check_counts() 71 | cust.transfer(share, accounts[0], 7000, {"from": accounts[0]}) 72 | check_counts() 73 | -------------------------------------------------------------------------------- /tests/BookShare/OwnedCustodian/test_token_cust_restrictions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(approve_many, org, share, cust): 10 | share.mint(org, 100000, {"from": accounts[0]}) 11 | share.transfer(accounts[2], 1000, {"from": accounts[0]}) 12 | share.transfer(cust, 500, {"from": accounts[0]}) 13 | share.transfer(cust, 500, {"from": accounts[2]}) 14 | org.setEntityRestriction(cust.ownerID(), True, {"from": accounts[0]}) 15 | 16 | 17 | def test_from_org(share, cust): 18 | """restricted custodian - org to custodian""" 19 | with pytest.reverts("Receiver restricted: Org"): 20 | share.transfer(cust, 1000, {"from": accounts[0]}) 21 | 22 | 23 | def test_from_member(share, cust): 24 | """restricted custodian - member to custodian""" 25 | with pytest.reverts("Receiver restricted: Org"): 26 | share.transfer(cust, 1000, {"from": accounts[2]}) 27 | 28 | 29 | def test_transferInternal(share, cust): 30 | """restricted custodian - internal transfer""" 31 | with pytest.reverts("Authority restricted"): 32 | cust.transferInternal( 33 | share, accounts[2], accounts[3], 500, {"from": accounts[0]} 34 | ) 35 | 36 | 37 | def test_to_org(share, cust): 38 | """restricted custodian - to org""" 39 | with pytest.reverts("Sender restricted: Org"): 40 | cust.transfer(share, accounts[0], 500, {"from": accounts[0]}) 41 | 42 | 43 | def test_to_member(share, cust): 44 | """restricted custodian - to member""" 45 | with pytest.reverts("Sender restricted: Org"): 46 | cust.transfer(share, accounts[2], 500, {"from": accounts[0]}) 47 | 48 | 49 | def test_org_transferFrom(share, cust): 50 | """restricted custodian - org transfer out with transferFrom""" 51 | share.transferFrom(cust, accounts[2], 500, {"from": accounts[0]}) 52 | -------------------------------------------------------------------------------- /tests/BookShare/OwnedCustodian/test_token_cust_reverts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(id1, id2, org, share): 10 | share.mint(org, 100000, {"from": accounts[0]}) 11 | 12 | 13 | def test_zero(share, cust): 14 | """Custodian transfer internal - zero value""" 15 | share.transfer(accounts[1], 10000, {"from": accounts[0]}) 16 | share.transfer(cust, 5000, {"from": accounts[1]}) 17 | with pytest.reverts("Cannot send 0 shares"): 18 | cust.transferInternal(share, accounts[1], accounts[2], 0, {"from": accounts[0]}) 19 | 20 | 21 | def test_exceed(share, cust): 22 | """Custodian transfer internal - exceed balance""" 23 | share.transfer(accounts[1], 10000, {"from": accounts[0]}) 24 | share.transfer(cust, 5000, {"from": accounts[1]}) 25 | with pytest.reverts("Insufficient Custodial Balance"): 26 | cust.transferInternal( 27 | share, accounts[1], accounts[2], 6000, {"from": accounts[0]} 28 | ) 29 | 30 | 31 | def test_cust_to_cust(OwnedCustodian, org, share, cust): 32 | """custodian to custodian""" 33 | cust2 = accounts[0].deploy(OwnedCustodian, [accounts[0]], 1) 34 | org.addCustodian(cust2, {"from": accounts[0]}) 35 | share.transfer(accounts[1], 10000, {"from": accounts[0]}) 36 | share.transfer(cust, 5000, {"from": accounts[1]}) 37 | with pytest.reverts("Custodian to Custodian"): 38 | cust.transferInternal(share, accounts[1], cust2, 500, {"from": accounts[0]}) 39 | 40 | 41 | def test_mint(share, cust): 42 | """mint to custodian""" 43 | with pytest.reverts(): 44 | share.mint(cust, 1000, {"from": accounts[0]}) 45 | -------------------------------------------------------------------------------- /tests/BookShare/OwnedCustodian/test_token_cust_transfer_from.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(id1, id2, org, share): 10 | share.mint(org, 100000, {"from": accounts[0]}) 11 | 12 | 13 | def test_org_txfrom(share, cust): 14 | """Org transferFrom custodian""" 15 | share.transfer(accounts[1], 10000, {"from": accounts[0]}) 16 | share.transfer(cust, 10000, {"from": accounts[1]}) 17 | share.transferFrom(cust, accounts[1], 5000, {"from": accounts[0]}) 18 | assert share.balanceOf(accounts[1]) == 5000 19 | assert share.balanceOf(cust) == 5000 20 | assert share.custodianBalanceOf(accounts[1], cust) == 5000 21 | 22 | 23 | def test_member_txfrom(share, cust): 24 | """Member transferFrom custodian""" 25 | share.transfer(accounts[1], 10000, {"from": accounts[0]}) 26 | share.transfer(cust, 10000, {"from": accounts[1]}) 27 | with pytest.reverts("Insufficient allowance"): 28 | share.transferFrom(cust, accounts[1], 5000, {"from": accounts[1]}) 29 | with pytest.reverts("Insufficient allowance"): 30 | share.transferFrom(cust, accounts[1], 5000, {"from": accounts[2]}) 31 | -------------------------------------------------------------------------------- /tests/BookShare/mint_burn/test_balances_supply.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from brownie import accounts 4 | 5 | 6 | def test_mint_to_org(org, share): 7 | """mint to org""" 8 | share.mint(org, 1000, {"from": accounts[0]}) 9 | assert share.totalSupply() == 1000 10 | assert share.balanceOf(org) == 1000 11 | share.mint(org, 2000, {"from": accounts[0]}) 12 | assert share.totalSupply() == 3000 13 | assert share.balanceOf(org) == 3000 14 | 15 | 16 | def test_mint_to_members(id1, id2, share): 17 | """mint to members""" 18 | share.mint(accounts[1], 1000, {"from": accounts[0]}) 19 | assert share.totalSupply() == 1000 20 | assert share.balanceOf(accounts[1]) == 1000 21 | share.mint(accounts[2], 2000, {"from": accounts[0]}) 22 | assert share.totalSupply() == 3000 23 | assert share.balanceOf(accounts[1]) == 1000 24 | assert share.balanceOf(accounts[2]) == 2000 25 | share.mint(accounts[1], 3000, {"from": accounts[0]}) 26 | assert share.totalSupply() == 6000 27 | assert share.balanceOf(accounts[1]) == 4000 28 | assert share.balanceOf(accounts[2]) == 2000 29 | share.mint(accounts[2], 4000, {"from": accounts[0]}) 30 | assert share.totalSupply() == 10000 31 | assert share.balanceOf(accounts[1]) == 4000 32 | assert share.balanceOf(accounts[2]) == 6000 33 | 34 | 35 | def test_burn_from_org(id1, id2, org, share): 36 | """burn from org""" 37 | share.mint(org, 10000, {"from": accounts[0]}) 38 | share.burn(org, 1000, {"from": accounts[0]}) 39 | assert share.totalSupply() == 9000 40 | assert share.balanceOf(org) == 9000 41 | share.burn(org, 4000, {"from": accounts[0]}) 42 | assert share.totalSupply() == 5000 43 | assert share.balanceOf(org) == 5000 44 | share.burn(org, 5000, {"from": accounts[0]}) 45 | assert share.totalSupply() == 0 46 | assert share.balanceOf(org) == 0 47 | 48 | 49 | def test_burn_from_members(share): 50 | """burn from members""" 51 | share.mint(accounts[1], 5000, {"from": accounts[0]}) 52 | share.mint(accounts[2], 10000, {"from": accounts[0]}) 53 | share.burn(accounts[1], 2000, {"from": accounts[0]}) 54 | assert share.totalSupply() == 13000 55 | assert share.balanceOf(accounts[1]) == 3000 56 | assert share.balanceOf(accounts[2]) == 10000 57 | share.burn(accounts[2], 3000, {"from": accounts[0]}) 58 | assert share.totalSupply() == 10000 59 | assert share.balanceOf(accounts[1]) == 3000 60 | assert share.balanceOf(accounts[2]) == 7000 61 | share.burn(accounts[1], 3000, {"from": accounts[0]}) 62 | assert share.totalSupply() == 7000 63 | assert share.balanceOf(accounts[1]) == 0 64 | assert share.balanceOf(accounts[2]) == 7000 65 | share.burn(accounts[2], 7000, {"from": accounts[0]}) 66 | assert share.totalSupply() == 0 67 | assert share.balanceOf(accounts[1]) == 0 68 | assert share.balanceOf(accounts[2]) == 0 69 | -------------------------------------------------------------------------------- /tests/BookShare/mint_burn/test_investor_counts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from brownie import accounts 4 | 5 | 6 | def test_mint_org(check_counts, org, share): 7 | """mint to org""" 8 | share.mint(org, 1000, {"from": accounts[0]}) 9 | check_counts() 10 | share.mint(org, 9000, {"from": accounts[0]}) 11 | check_counts() 12 | 13 | 14 | def test_burn_org(check_counts, org, share): 15 | """burn from org""" 16 | share.mint(org, 10000, {"from": accounts[0]}) 17 | share.burn(org, 2000, {"from": accounts[0]}) 18 | check_counts() 19 | share.burn(org, 8000, {"from": accounts[0]}) 20 | check_counts() 21 | 22 | 23 | def test_mint_members(check_counts, share): 24 | """mint to members""" 25 | check_counts() 26 | share.mint(accounts[1], 1000, {"from": accounts[0]}) 27 | check_counts(one=(1, 1, 0)) 28 | share.mint(accounts[1], 1000, {"from": accounts[0]}) 29 | share.mint(accounts[2], 1000, {"from": accounts[0]}) 30 | share.mint(accounts[3], 1000, {"from": accounts[0]}) 31 | check_counts(one=(2, 1, 1), two=(1, 1, 0)) 32 | share.mint(accounts[1], 996000, {"from": accounts[0]}) 33 | check_counts(one=(2, 1, 1), two=(1, 1, 0)) 34 | 35 | 36 | def test_burn_members(check_counts, share): 37 | """burn from members""" 38 | share.mint(accounts[1], 5000, {"from": accounts[0]}) 39 | share.mint(accounts[2], 3000, {"from": accounts[0]}) 40 | share.mint(accounts[3], 2000, {"from": accounts[0]}) 41 | share.burn(accounts[1], 1000, {"from": accounts[0]}) 42 | check_counts(one=(2, 1, 1), two=(1, 1, 0)) 43 | share.burn(accounts[1], 4000, {"from": accounts[0]}) 44 | check_counts(one=(1, 0, 1), two=(1, 1, 0)) 45 | share.burn(accounts[2], 3000, {"from": accounts[0]}) 46 | share.burn(accounts[3], 2000, {"from": accounts[0]}) 47 | check_counts() 48 | -------------------------------------------------------------------------------- /tests/BookShare/mint_burn/test_reverts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | def test_mint_zero(org, share): 9 | """mint 0 shares""" 10 | with pytest.reverts("dev: mint 0"): 11 | share.mint(org, 0, {"from": accounts[0]}) 12 | share.mint(org, 10000, {"from": accounts[0]}) 13 | with pytest.reverts("dev: mint 0"): 14 | share.mint(org, 0, {"from": accounts[0]}) 15 | 16 | 17 | def test_burn_zero(org, share): 18 | """burn 0 shares""" 19 | with pytest.reverts("dev: burn 0"): 20 | share.burn(org, 0, {"from": accounts[0]}) 21 | share.mint(org, 10000, {"from": accounts[0]}) 22 | with pytest.reverts("dev: burn 0"): 23 | share.burn(org, 0, {"from": accounts[0]}) 24 | 25 | 26 | def test_authorized_below_total(org, share): 27 | """authorized supply below total supply""" 28 | share.mint(org, 100000, {"from": accounts[0]}) 29 | with pytest.reverts("dev: auth below total"): 30 | share.modifyAuthorizedSupply(10000, {"from": accounts[0]}) 31 | 32 | 33 | def test_total_above_authorized(org, share): 34 | """total supply above authorized""" 35 | share.modifyAuthorizedSupply(10000, {"from": accounts[0]}) 36 | with pytest.reverts("dev: exceed auth"): 37 | share.mint(org, 20000, {"from": accounts[0]}) 38 | share.mint(org, 6000, {"from": accounts[0]}) 39 | with pytest.reverts("dev: exceed auth"): 40 | share.mint(org, 6000, {"from": accounts[0]}) 41 | share.mint(org, 4000, {"from": accounts[0]}) 42 | with pytest.reverts("dev: exceed auth"): 43 | share.mint(org, 1, {"from": accounts[0]}) 44 | with pytest.reverts("dev: mint 0"): 45 | share.mint(org, 0, {"from": accounts[0]}) 46 | 47 | 48 | def test_burn_exceeds_balance(org, share): 49 | """burn exceeds balance""" 50 | with pytest.reverts(): 51 | share.burn(org, 100, {"from": accounts[0]}) 52 | share.mint(org, 4000, {"from": accounts[0]}) 53 | with pytest.reverts(): 54 | share.burn(org, 5000, {"from": accounts[0]}) 55 | share.burn(org, 3000, {"from": accounts[0]}) 56 | with pytest.reverts(): 57 | share.burn(org, 1001, {"from": accounts[0]}) 58 | share.burn(org, 1000, {"from": accounts[0]}) 59 | with pytest.reverts(): 60 | share.burn(org, 100, {"from": accounts[0]}) 61 | 62 | 63 | def test_mint_to_custodian(org, share, cust): 64 | """mint to custodian""" 65 | with pytest.reverts("dev: custodian"): 66 | share.mint(cust, 6000, {"from": accounts[0]}) 67 | 68 | 69 | def test_burn_from_custodian(org, share, cust): 70 | """burn from custodian""" 71 | share.mint(org, 10000, {"from": accounts[0]}) 72 | share.transfer(cust, 10000, {"from": accounts[0]}) 73 | with pytest.reverts("dev: custodian"): 74 | share.burn(cust, 5000, {"from": accounts[0]}) 75 | 76 | 77 | def test_global_lock(org, share, id1): 78 | """mint - share lock""" 79 | org.setOrgShareRestriction(share, True, {"from": accounts[0]}) 80 | with pytest.reverts("dev: share locked"): 81 | share.mint(accounts[1], 1, {"from": accounts[0]}) 82 | -------------------------------------------------------------------------------- /tests/BookShare/test_authorized_supply.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts, compile_source 6 | 7 | module_source = """pragma solidity 0.4.25; 8 | contract TestGovernance { 9 | address public orgCode; 10 | bool result; 11 | constructor(address _org) public { orgCode = _org; } 12 | function setResult(bool _result) external { result = _result; } 13 | function modifyAuthorizedSupply(address, uint256) external returns (bool) { return result; } 14 | }""" 15 | 16 | 17 | @pytest.fixture(scope="module") 18 | def gov(org): 19 | project = compile_source(module_source) 20 | g = project.TestGovernance.deploy(org, {"from": accounts[0]}) 21 | org.setGovernance(g, {"from": accounts[0]}) 22 | yield g 23 | 24 | 25 | def test_authorized_supply(share): 26 | """modify authorized supply""" 27 | share.modifyAuthorizedSupply(10000, {"from": accounts[0]}) 28 | assert share.authorizedSupply() == 10000 29 | assert share.totalSupply() == 0 30 | share.modifyAuthorizedSupply(0, {"from": accounts[0]}) 31 | assert share.authorizedSupply() == 0 32 | assert share.totalSupply() == 0 33 | share.modifyAuthorizedSupply(1234567, {"from": accounts[0]}) 34 | assert share.authorizedSupply(), 1234567 35 | assert share.totalSupply() == 0 36 | share.modifyAuthorizedSupply(2400000000, {"from": accounts[0]}) 37 | assert share.authorizedSupply(), 2400000000 38 | assert share.totalSupply() == 0 39 | 40 | 41 | def test_authorized_supply_governance_false(share, gov): 42 | """modify authorized supply - blocked by governance module""" 43 | gov.setResult(False, {"from": accounts[0]}) 44 | with pytest.reverts("Action has not been approved"): 45 | share.modifyAuthorizedSupply(10000, {"from": accounts[0]}) 46 | 47 | 48 | def test_authorized_supply_governance_true(share, gov): 49 | """modify authorized supply - allowed by governance module""" 50 | gov.setResult(True, {"from": accounts[0]}) 51 | share.modifyAuthorizedSupply(10000, {"from": accounts[0]}) 52 | 53 | 54 | def test_authorized_supply_governance_removed(org, share, gov): 55 | """modify authorized supply - removed governance module""" 56 | gov.setResult(False, {"from": accounts[0]}) 57 | with pytest.reverts("Action has not been approved"): 58 | share.modifyAuthorizedSupply(10000, {"from": accounts[0]}) 59 | org.setGovernance("0" * 40, {"from": accounts[0]}) 60 | share.modifyAuthorizedSupply(10000, {"from": accounts[0]}) 61 | -------------------------------------------------------------------------------- /tests/BookShare/transfer/test_token_balances.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(approve_many, org, share): 10 | share.mint(org, 100000, {"from": accounts[0]}) 11 | 12 | 13 | def test_zero_shares(share): 14 | """cannot send 0 shares""" 15 | with pytest.reverts("Cannot send 0 shares"): 16 | share.transfer(accounts[1], 0, {"from": accounts[0]}) 17 | 18 | 19 | def test_insufficient_balance_member(share): 20 | """insufficient balance - member to member""" 21 | share.transfer(accounts[1], 1000, {"from": accounts[0]}) 22 | with pytest.reverts("Insufficient Balance"): 23 | share.transfer(accounts[2], 2000, {"from": accounts[1]}) 24 | 25 | 26 | def test_insufficient_balance_org(share): 27 | """insufficient balance - org to member""" 28 | with pytest.reverts("Insufficient Balance"): 29 | share.transfer(accounts[1], 20000000000, {"from": accounts[0]}) 30 | 31 | 32 | def test_balance(share): 33 | """successful transfer""" 34 | share.transfer(accounts[1], 1000, {"from": accounts[0]}) 35 | assert share.balanceOf(accounts[1]) == 1000 36 | share.transfer(accounts[2], 400, {"from": accounts[1]}) 37 | assert share.balanceOf(accounts[1]) == 600 38 | assert share.balanceOf(accounts[2]) == 400 39 | 40 | 41 | def test_balance_org(org, share): 42 | """org balances""" 43 | assert share.balanceOf(accounts[0]) == 0 44 | assert share.balanceOf(org) == 100000 45 | share.transfer(accounts[1], 1000, {"from": accounts[0]}) 46 | assert share.balanceOf(accounts[0]) == 0 47 | assert share.balanceOf(org) == 99000 48 | share.transfer(accounts[0], 1000, {"from": accounts[1]}) 49 | assert share.balanceOf(accounts[0]) == 0 50 | assert share.balanceOf(org) == 100000 51 | share.transfer(accounts[1], 1000, {"from": accounts[0]}) 52 | share.transfer(org, 1000, {"from": accounts[1]}) 53 | assert share.balanceOf(accounts[0]) == 0 54 | assert share.balanceOf(org) == 100000 55 | 56 | 57 | def test_authority_permission(org, share): 58 | """org subauthority balances""" 59 | org.addAuthority( 60 | [accounts[-1]], ["0xa9059cbb"], 2000000000, 1, {"from": accounts[0]} 61 | ) 62 | share.transfer(accounts[1], 1000, {"from": accounts[-1]}) 63 | assert share.balanceOf(accounts[0]) == 0 64 | assert share.balanceOf(accounts[-1]) == 0 65 | assert share.balanceOf(org) == 99000 66 | share.transfer(accounts[-1], 1000, {"from": accounts[1]}) 67 | assert share.balanceOf(accounts[0]) == 0 68 | assert share.balanceOf(accounts[-1]) == 0 69 | assert share.balanceOf(org) == 100000 70 | -------------------------------------------------------------------------------- /tests/BookShare/transfer/test_token_country_investor_limits.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(approve_many, org, share): 10 | share.mint(org, 100000, {"from": accounts[0]}) 11 | org.setCountry(1, True, 1, (1, 0, 0, 0, 0, 0, 0, 0), {"from": accounts[0]}) 12 | share.transfer(accounts[1], 1000, {"from": accounts[0]}) 13 | 14 | 15 | def test_country_member_limit_blocked_org_member(share): 16 | """country member limit - blocked, org to member""" 17 | with pytest.reverts("Country Member Limit"): 18 | share.transfer(accounts[2], 1000, {"from": accounts[0]}) 19 | 20 | 21 | def test_country_member_limit_blocked_member_member(share): 22 | """country member limit - blocked, member to member""" 23 | with pytest.reverts("Country Member Limit"): 24 | share.transfer(accounts[2], 500, {"from": accounts[1]}) 25 | 26 | 27 | def test_country_member_limit_org_member(share): 28 | """country member limit - org to existing member""" 29 | share.transfer(accounts[1], 1000, {"from": accounts[0]}) 30 | 31 | 32 | def test_country_member_limit_member_member(share): 33 | """country member limit - member to member, full balance""" 34 | share.transfer(accounts[2], 1000, {"from": accounts[1]}) 35 | 36 | 37 | def test_country_member_limit_member_member_different_country(share): 38 | """country member limit, member to member, different country""" 39 | share.transfer(accounts[3], 500, {"from": accounts[1]}) 40 | 41 | 42 | def test_country_member_limit_rating_blocked_org_member(kyc, org, share): 43 | """country member limit, rating - blocked, org to member""" 44 | org.setCountry(1, True, 1, (0, 1, 0, 0, 0, 0, 0, 0), {"from": accounts[0]}) 45 | kyc.updateMember(kyc.getID(accounts[2]), 1, 1, 2000000000, {"from": accounts[0]}) 46 | with pytest.reverts("Country Member Limit: Rating"): 47 | share.transfer(accounts[2], 1000, {"from": accounts[0]}) 48 | 49 | 50 | def test_country_member_limit_rating_blocked_member_member(kyc, org, share): 51 | """country member limit, rating - blocked, member to member""" 52 | org.setCountry(1, True, 1, (0, 1, 0, 0, 0, 0, 0, 0), {"from": accounts[0]}) 53 | kyc.updateMember(kyc.getID(accounts[2]), 1, 1, 2000000000, {"from": accounts[0]}) 54 | with pytest.reverts("Country Member Limit: Rating"): 55 | share.transfer(accounts[2], 500, {"from": accounts[1]}) 56 | 57 | 58 | def test_country_member_limit_rating_org_member(org, share): 59 | """country member limit, rating - org to existing member""" 60 | org.setCountry(1, True, 1, (0, 1, 0, 0, 0, 0, 0, 0), {"from": accounts[0]}) 61 | share.transfer(accounts[1], 1000, {"from": accounts[0]}) 62 | 63 | 64 | def test_country_member_limit_rating_member_member(org, share): 65 | """country member limit, rating - member to member, full balance""" 66 | org.setCountry(1, True, 1, (0, 1, 0, 0, 0, 0, 0, 0), {"from": accounts[0]}) 67 | share.transfer(accounts[2], 1000, {"from": accounts[1]}) 68 | 69 | 70 | def test_country_member_limit_rating_member_member_different_country(org, share): 71 | """country member limit, rating - member to member, different rating""" 72 | org.setCountry(1, True, 1, (0, 1, 0, 0, 0, 0, 0, 0), {"from": accounts[0]}) 73 | share.transfer(accounts[2], 500, {"from": accounts[1]}) 74 | -------------------------------------------------------------------------------- /tests/BookShare/transfer/test_token_investor_counts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(approve_many, org, share): 10 | share.mint(org, 1000000, {"from": accounts[0]}) 11 | 12 | 13 | def test_org_to_member(check_counts, share): 14 | """member counts - org/member transfers""" 15 | check_counts() 16 | share.transfer(accounts[1], 1000, {"from": accounts[0]}) 17 | check_counts(one=(1, 1, 0)) 18 | share.transfer(accounts[1], 1000, {"from": accounts[0]}) 19 | share.transfer(accounts[2], 1000, {"from": accounts[0]}) 20 | share.transfer(accounts[3], 1000, {"from": accounts[0]}) 21 | check_counts(one=(2, 1, 1), two=(1, 1, 0)) 22 | share.transfer(accounts[1], 996000, {"from": accounts[0]}) 23 | check_counts(one=(2, 1, 1), two=(1, 1, 0)) 24 | share.transfer(accounts[0], 1000, {"from": accounts[1]}) 25 | check_counts(one=(2, 1, 1), two=(1, 1, 0)) 26 | share.transfer(accounts[0], 997000, {"from": accounts[1]}) 27 | check_counts(one=(1, 0, 1), two=(1, 1, 0)) 28 | share.transfer(accounts[0], 1000, {"from": accounts[2]}) 29 | share.transfer(accounts[0], 1000, {"from": accounts[3]}) 30 | check_counts() 31 | 32 | 33 | def test_member_to_member(check_counts, share): 34 | """member counts - member/member transfers""" 35 | share.transfer(accounts[1], 1000, {"from": accounts[0]}) 36 | share.transfer(accounts[2], 1000, {"from": accounts[0]}) 37 | share.transfer(accounts[3], 1000, {"from": accounts[0]}) 38 | share.transfer(accounts[4], 1000, {"from": accounts[0]}) 39 | share.transfer(accounts[5], 1000, {"from": accounts[0]}) 40 | share.transfer(accounts[6], 1000, {"from": accounts[0]}) 41 | check_counts(one=(2, 1, 1), two=(2, 1, 1), three=(2, 1, 1)) 42 | share.transfer(accounts[2], 500, {"from": accounts[1]}) 43 | check_counts(one=(2, 1, 1), two=(2, 1, 1), three=(2, 1, 1)) 44 | share.transfer(accounts[2], 500, {"from": accounts[1]}) 45 | check_counts(one=(1, 0, 1), two=(2, 1, 1), three=(2, 1, 1)) 46 | share.transfer(accounts[3], 2000, {"from": accounts[2]}) 47 | check_counts(two=(2, 1, 1), three=(2, 1, 1)) 48 | share.transfer(accounts[3], 1000, {"from": accounts[4]}) 49 | check_counts(two=(1, 1, 0), three=(2, 1, 1)) 50 | share.transfer(accounts[4], 500, {"from": accounts[3]}) 51 | check_counts(two=(2, 1, 1), three=(2, 1, 1)) 52 | -------------------------------------------------------------------------------- /tests/BookShare/transfer/test_token_investor_limits.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(approve_many, org, share): 10 | share.mint(org, 100000, {"from": accounts[0]}) 11 | org.setMemberLimits((1, 0, 0, 0, 0, 0, 0, 0), {"from": accounts[0]}) 12 | share.transfer(accounts[1], 1000, {"from": accounts[0]}) 13 | 14 | 15 | @pytest.fixture(scope="module") 16 | def adjust_limits(org): 17 | org.setMemberLimits((0, 1, 0, 0, 0, 0, 0, 0), {"from": accounts[0]}) 18 | 19 | 20 | def test_total_member_limit_blocked_org_member(share): 21 | """total member limit - blocked, org to member""" 22 | with pytest.reverts("Total Member Limit"): 23 | share.transfer(accounts[2], 1000, {"from": accounts[0]}) 24 | 25 | 26 | def test_total_member_limit_blocked_member_member(share): 27 | """total member limit - blocked, member to member""" 28 | with pytest.reverts("Total Member Limit"): 29 | share.transfer(accounts[2], 500, {"from": accounts[1]}) 30 | 31 | 32 | def test_total_member_limit_org_member(share): 33 | """total member limit - org to existing member""" 34 | share.transfer(accounts[1], 1000, {"from": accounts[0]}) 35 | 36 | 37 | def test_total_member_limit_member_member(share): 38 | """total member limit - member to member, full balance""" 39 | share.transfer(accounts[2], 1000, {"from": accounts[1]}) 40 | 41 | 42 | def test_total_member_limit_rating_blocked_org_member(adjust_limits, share): 43 | """total member limit, rating - blocked, org to member""" 44 | with pytest.reverts("Total Member Limit: Rating"): 45 | share.transfer(accounts[3], 1000, {"from": accounts[0]}) 46 | 47 | 48 | def test_total_member_limit_rating_blocked_member_member(share): 49 | """total member limit, rating - blocked, member to member""" 50 | with pytest.reverts("Total Member Limit: Rating"): 51 | share.transfer(accounts[3], 500, {"from": accounts[1]}) 52 | 53 | 54 | def test_total_member_limit_rating_org_member(org, share): 55 | """total member limit, rating - org to existing member""" 56 | share.transfer(accounts[1], 1000, {"from": accounts[0]}) 57 | 58 | 59 | def test_total_member_limit_rating_member_member(org, share): 60 | """total member limit, rating - member to member, full balance""" 61 | share.transfer(accounts[2], 1000, {"from": accounts[1]}) 62 | 63 | 64 | def test_total_member_limit_rating_member_member_different_country(share): 65 | """total member limit, rating - member to member, different rating""" 66 | share.transfer(accounts[2], 500, {"from": accounts[1]}) 67 | -------------------------------------------------------------------------------- /tests/BookShare/transfer/test_token_locks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(id1, id2, org, share): 10 | share.mint(org, 100000, {"from": accounts[0]}) 11 | 12 | 13 | def test_global_lock(org, share): 14 | """global lock - member / member""" 15 | share.transfer(accounts[1], 1000, {"from": accounts[0]}) 16 | org.setGlobalRestriction(True, {"from": accounts[0]}) 17 | with pytest.reverts("Transfers locked: Org"): 18 | share.transfer(accounts[2], 1000, {"from": accounts[1]}) 19 | org.setGlobalRestriction(False, {"from": accounts[0]}) 20 | share.transfer(accounts[2], 1000, {"from": accounts[1]}) 21 | 22 | 23 | def test_global_lock_org(org, share): 24 | """global lock - org / member""" 25 | org.setGlobalRestriction(True, {"from": accounts[0]}) 26 | share.transfer(accounts[1], 1000, {"from": accounts[0]}) 27 | with pytest.reverts("Transfers locked: Org"): 28 | share.transfer(accounts[0], 1000, {"from": accounts[1]}) 29 | org.setGlobalRestriction(False, {"from": accounts[0]}) 30 | share.transfer(accounts[0], 1000, {"from": accounts[1]}) 31 | 32 | 33 | def test_share_lock(org, share): 34 | """share lock - member / member""" 35 | share.transfer(accounts[1], 1000, {"from": accounts[0]}) 36 | org.setOrgShareRestriction(share, True, {"from": accounts[0]}) 37 | with pytest.reverts("Transfers locked: Share"): 38 | share.transfer(accounts[2], 1000, {"from": accounts[1]}) 39 | org.setOrgShareRestriction(share, False, {"from": accounts[0]}) 40 | share.transfer(accounts[2], 1000, {"from": accounts[1]}) 41 | 42 | 43 | def test_share_lock_org(org, share): 44 | """share lock - org / member""" 45 | org.setOrgShareRestriction(share, True, {"from": accounts[0]}) 46 | share.transfer(accounts[1], 1000, {"from": accounts[0]}) 47 | with pytest.reverts("Transfers locked: Share"): 48 | share.transfer(accounts[0], 1000, {"from": accounts[1]}) 49 | org.setOrgShareRestriction(share, False, {"from": accounts[0]}) 50 | share.transfer(accounts[0], 1000, {"from": accounts[1]}) 51 | -------------------------------------------------------------------------------- /tests/BookShare/transfer/test_token_transfer_from.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(approve_many, org, share): 10 | share.mint(org, 100000, {"from": accounts[0]}) 11 | share.transfer(accounts[1], 1000, {"from": accounts[0]}) 12 | 13 | 14 | def test_transfer_from(share): 15 | """member transferFrom - approved""" 16 | share.approve(accounts[3], 500, {"from": accounts[1]}) 17 | assert share.allowance(accounts[1], accounts[3]) == 500 18 | share.transferFrom(accounts[1], accounts[2], 400, {"from": accounts[3]}) 19 | assert share.allowance(accounts[1], accounts[3]) == 100 20 | share.transferFrom(accounts[1], accounts[2], 100, {"from": accounts[3]}) 21 | assert share.allowance(accounts[1], accounts[3]) == 0 22 | 23 | 24 | def test_transfer_from_member_no_approval(share): 25 | """transferFrom - no approval""" 26 | with pytest.reverts("Insufficient allowance"): 27 | share.transferFrom(accounts[1], accounts[2], 1000, {"from": accounts[3]}) 28 | 29 | 30 | def test_transfer_from_member_insufficient_approval(share): 31 | """transferFrom - insufficient approval""" 32 | share.approve(accounts[3], 500, {"from": accounts[1]}) 33 | with pytest.reverts("Insufficient allowance"): 34 | share.transferFrom(accounts[1], accounts[2], 1000, {"from": accounts[3]}) 35 | 36 | 37 | def test_transfer_from_same_id(kyc, share): 38 | """transferFrom - same member ID""" 39 | kyc.registerAddresses(kyc.getID(accounts[1]), [accounts[-1]], {"from": accounts[0]}) 40 | share.transferFrom(accounts[1], accounts[2], 500, {"from": accounts[-1]}) 41 | 42 | 43 | def test_transfer_from_org(share): 44 | """org transferFrom""" 45 | share.transferFrom(accounts[1], accounts[2], 1000, {"from": accounts[0]}) 46 | 47 | 48 | def test_authority_permission(org, share): 49 | """authority transferFrom permission""" 50 | org.addAuthority( 51 | [accounts[-1]], ["0x23b872dd"], 2000000000, 1, {"from": accounts[0]} 52 | ) 53 | share.transferFrom(accounts[1], accounts[2], 500, {"from": accounts[-1]}) 54 | org.setAuthoritySignatures( 55 | org.getID(accounts[-1]), ["0x23b872dd"], False, {"from": accounts[0]} 56 | ) 57 | with pytest.reverts("Authority not permitted"): 58 | share.transferFrom(accounts[1], accounts[2], 500, {"from": accounts[-1]}) 59 | -------------------------------------------------------------------------------- /tests/CertShare/OwnedCustodian/test_nft_cust_investor_counts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(approve_many, org, nft): 10 | nft.mint(org, 100000, 0, "0x00", {"from": accounts[0]}) 11 | 12 | 13 | def test_into_custodian(check_counts, nft, cust): 14 | """Transfer into custodian - member""" 15 | nft.transfer(accounts[1], 10000, {"from": accounts[0]}) 16 | nft.transfer(accounts[2], 10000, {"from": accounts[0]}) 17 | check_counts(one=(2, 1, 1)) 18 | nft.transfer(cust, 5000, {"from": accounts[1]}) 19 | check_counts(one=(2, 1, 1)) 20 | nft.transfer(cust, 10000, {"from": accounts[2]}) 21 | check_counts(one=(2, 1, 1)) 22 | 23 | 24 | def test_cust_internal(check_counts, nft, cust): 25 | """Custodian transfer internal - member to member""" 26 | nft.transfer(accounts[2], 10000, {"from": accounts[0]}) 27 | nft.transfer(cust, 5000, {"from": accounts[2]}) 28 | cust.transferInternal(nft, accounts[2], accounts[3], 5000, {"from": accounts[0]}) 29 | check_counts(one=(1, 0, 1), two=(1, 1, 0)) 30 | nft.transfer(accounts[3], 5000, {"from": accounts[0]}) 31 | check_counts(one=(1, 0, 1), two=(1, 1, 0)) 32 | 33 | 34 | def test_cust_out(check_counts, org, nft, cust): 35 | """Transfer out of custodian - member""" 36 | nft.transfer(accounts[1], 10000, {"from": accounts[0]}) 37 | nft.transfer(cust, 10000, {"from": accounts[1]}) 38 | cust.transferInternal(nft, accounts[1], accounts[2], 10000, {"from": accounts[0]}) 39 | check_counts(one=(1, 0, 1)) 40 | cust.transfer(nft, accounts[2], 10000, {"from": accounts[0]}) 41 | check_counts(one=(1, 0, 1)) 42 | nft.transfer(org, 10000, {"from": accounts[2]}) 43 | check_counts() 44 | 45 | 46 | def test_org_cust_in(check_counts, nft, cust): 47 | """Transfers into custodian - org""" 48 | nft.transfer(cust, 10000, {"from": accounts[0]}) 49 | check_counts() 50 | nft.transfer(cust, 90000, {"from": accounts[0]}) 51 | check_counts() 52 | 53 | 54 | def test_org_cust_internal(check_counts, org, nft, cust): 55 | """Custodian internal transfers - org / member""" 56 | nft.transfer(cust, 10000, {"from": accounts[0]}) 57 | cust.transferInternal(nft, org, accounts[1], 10000, {"from": accounts[0]}) 58 | check_counts(one=(1, 1, 0)) 59 | cust.transferInternal(nft, accounts[1], org, 5000, {"from": accounts[0]}) 60 | check_counts(one=(1, 1, 0)) 61 | cust.transferInternal(nft, accounts[1], accounts[0], 5000, {"from": accounts[0]}) 62 | check_counts() 63 | 64 | 65 | def test_org_cust_out(check_counts, org, nft, cust): 66 | """Transfers out of custodian - org""" 67 | nft.transfer(cust, 10000, {"from": accounts[0]}) 68 | check_counts() 69 | cust.transfer(nft, org, 3000, {"from": accounts[0]}) 70 | check_counts() 71 | cust.transfer(nft, accounts[0], 7000, {"from": accounts[0]}) 72 | check_counts() 73 | -------------------------------------------------------------------------------- /tests/CertShare/OwnedCustodian/test_nft_cust_restrictions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(approve_many, org, nft, cust): 10 | nft.mint(org, 100000, 0, "0x00", {"from": accounts[0]}) 11 | nft.transfer(accounts[2], 1000, {"from": accounts[0]}) 12 | nft.transfer(cust, 500, {"from": accounts[0]}) 13 | nft.transfer(cust, 500, {"from": accounts[2]}) 14 | org.setEntityRestriction(cust.ownerID(), True, {"from": accounts[0]}) 15 | 16 | 17 | def test_from_org(nft, cust): 18 | """restricted custodian - org to custodian""" 19 | with pytest.reverts("Receiver restricted: Org"): 20 | nft.transfer(cust, 1000, {"from": accounts[0]}) 21 | 22 | 23 | def test_from_member(nft, cust): 24 | """restricted custodian - member to custodian""" 25 | with pytest.reverts("Receiver restricted: Org"): 26 | nft.transfer(cust, 1000, {"from": accounts[2]}) 27 | 28 | 29 | def test_transferInternal(nft, cust): 30 | """restricted custodian - internal transfer""" 31 | with pytest.reverts("Authority restricted"): 32 | cust.transferInternal(nft, accounts[2], accounts[3], 500, {"from": accounts[0]}) 33 | 34 | 35 | def test_to_org(nft, cust): 36 | """restricted custodian - to org""" 37 | with pytest.reverts("Sender restricted: Org"): 38 | cust.transfer(nft, accounts[0], 500, {"from": accounts[0]}) 39 | 40 | 41 | def test_to_member(nft, cust): 42 | """restricted custodian - to member""" 43 | with pytest.reverts("Sender restricted: Org"): 44 | cust.transfer(nft, accounts[2], 500, {"from": accounts[0]}) 45 | 46 | 47 | def test_org_transferFrom(nft, cust): 48 | """restricted custodian - org transfer out with transferFrom""" 49 | nft.transferFrom(cust, accounts[2], 500, {"from": accounts[0]}) 50 | -------------------------------------------------------------------------------- /tests/CertShare/OwnedCustodian/test_nft_cust_reverts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(approve_many, org, nft): 10 | nft.mint(org, 100000, 0, "0x00", {"from": accounts[0]}) 11 | 12 | 13 | def test_zero(nft, cust): 14 | """Custodian transfer internal - zero value""" 15 | nft.transfer(accounts[2], 10000, {"from": accounts[0]}) 16 | nft.transfer(cust, 5000, {"from": accounts[2]}) 17 | with pytest.reverts("Cannot send 0 shares"): 18 | cust.transferInternal(nft, accounts[2], accounts[3], 0, {"from": accounts[0]}) 19 | 20 | 21 | def test_exceed(nft, cust): 22 | """Custodian transfer internal - exceed balance""" 23 | nft.transfer(accounts[2], 10000, {"from": accounts[0]}) 24 | nft.transfer(cust, 5000, {"from": accounts[2]}) 25 | with pytest.reverts("Insufficient Custodial Balance"): 26 | cust.transferInternal( 27 | nft, accounts[2], accounts[3], 6000, {"from": accounts[0]} 28 | ) 29 | 30 | 31 | def test_cust_to_cust(OwnedCustodian, org, nft, cust): 32 | """custodian to custodian""" 33 | cust2 = accounts[0].deploy(OwnedCustodian, [accounts[0]], 1) 34 | org.addCustodian(cust2, {"from": accounts[0]}) 35 | nft.transfer(accounts[2], 10000, {"from": accounts[0]}) 36 | nft.transfer(cust, 5000, {"from": accounts[2]}) 37 | with pytest.reverts("Custodian to Custodian"): 38 | cust.transferInternal(nft, accounts[2], cust2, 500, {"from": accounts[0]}) 39 | 40 | 41 | def test_mint(nft, cust): 42 | """mint to custodian""" 43 | with pytest.reverts(): 44 | nft.mint(cust, 1000, 0, "0x00", {"from": accounts[0]}) 45 | 46 | 47 | def test_transfer_range(nft, cust): 48 | """transfer range - custodian""" 49 | nft.transferRange(cust, 100, 1000, {"from": accounts[0]}) 50 | with pytest.reverts("dev: custodian"): 51 | nft.transferRange(accounts[0], 100, 1000, {"from": accounts[0]}) 52 | -------------------------------------------------------------------------------- /tests/CertShare/OwnedCustodian/test_nft_cust_transfer_from.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(approve_many, org, nft): 10 | nft.mint(org, 100000, 0, "0x00", {"from": accounts[0]}) 11 | 12 | 13 | def test_org_txfrom(nft, cust): 14 | """Org transferFrom custodian""" 15 | nft.transfer(accounts[1], 10000, {"from": accounts[0]}) 16 | nft.transfer(cust, 10000, {"from": accounts[1]}) 17 | nft.transferFrom(cust, accounts[1], 5000, {"from": accounts[0]}) 18 | assert nft.balanceOf(accounts[1]) == 5000 19 | assert nft.balanceOf(cust) == 5000 20 | assert nft.custodianBalanceOf(accounts[1], cust) == 5000 21 | 22 | 23 | def test_member_txfrom(nft, cust): 24 | """Member transferFrom custodian""" 25 | nft.transfer(accounts[1], 10000, {"from": accounts[0]}) 26 | nft.transfer(cust, 10000, {"from": accounts[1]}) 27 | with pytest.reverts("Insufficient allowance"): 28 | nft.transferFrom(cust, accounts[1], 5000, {"from": accounts[1]}) 29 | with pytest.reverts("Insufficient allowance"): 30 | nft.transferFrom(cust, accounts[1], 5000, {"from": accounts[2]}) 31 | -------------------------------------------------------------------------------- /tests/CertShare/mint_burn/test_nft_balances_supply.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from brownie import accounts 4 | 5 | 6 | def test_mint_to_org(approve_many, org, nft): 7 | """mint to org""" 8 | nft.mint(org, 1000, 0, "0x00", {"from": accounts[0]}) 9 | assert nft.totalSupply() == 1000 10 | assert nft.balanceOf(org), 1000 11 | nft.mint(org, 2000, 0, "0x00", {"from": accounts[0]}) 12 | assert nft.totalSupply() == 3000 13 | assert nft.balanceOf(org) == 3000 14 | 15 | 16 | def test_mint_to_members(nft): 17 | """mint to members""" 18 | nft.mint(accounts[1], 1000, 0, "0x00", {"from": accounts[0]}) 19 | assert nft.totalSupply() == 1000 20 | assert nft.balanceOf(accounts[1]) == 1000 21 | nft.mint(accounts[2], 2000, 0, "0x00", {"from": accounts[0]}) 22 | assert nft.totalSupply() == 3000 23 | assert nft.balanceOf(accounts[1]) == 1000 24 | assert nft.balanceOf(accounts[2]) == 2000 25 | nft.mint(accounts[1], 3000, 0, "0x00", {"from": accounts[0]}) 26 | assert nft.totalSupply() == 6000 27 | assert nft.balanceOf(accounts[1]) == 4000 28 | assert nft.balanceOf(accounts[2]) == 2000 29 | nft.mint(accounts[2], 4000, 0, "0x00", {"from": accounts[0]}) 30 | assert nft.totalSupply() == 10000 31 | assert nft.balanceOf(accounts[1]) == 4000 32 | assert nft.balanceOf(accounts[2]) == 6000 33 | 34 | 35 | def test_burn_from_org(org, nft): 36 | """burn from org""" 37 | nft.mint(org, 10000, 0, "0x00", {"from": accounts[0]}) 38 | nft.burn(1, 1001, {"from": accounts[0]}) 39 | assert nft.totalSupply() == 9000 40 | assert nft.balanceOf(org) == 9000 41 | nft.burn(1001, 5001, {"from": accounts[0]}) 42 | assert nft.totalSupply() == 5000 43 | assert nft.balanceOf(org) == 5000 44 | nft.burn(5001, 10001, {"from": accounts[0]}) 45 | assert nft.totalSupply() == 0 46 | assert nft.balanceOf(org) == 0 47 | 48 | 49 | def test_burn_from_members(nft): 50 | """burn from members""" 51 | nft.mint(accounts[1], 5000, 0, "0x00", {"from": accounts[0]}) 52 | nft.mint(accounts[2], 10000, 0, "0x00", {"from": accounts[0]}) 53 | nft.burn(3001, 5001, {"from": accounts[0]}) 54 | assert nft.totalSupply() == 13000 55 | assert nft.balanceOf(accounts[1]) == 3000 56 | assert nft.balanceOf(accounts[2]) == 10000 57 | nft.burn(5001, 8001, {"from": accounts[0]}) 58 | assert nft.totalSupply() == 10000 59 | assert nft.balanceOf(accounts[1]) == 3000 60 | assert nft.balanceOf(accounts[2]) == 7000 61 | nft.burn(1, 3001, {"from": accounts[0]}) 62 | assert nft.totalSupply() == 7000 63 | assert nft.balanceOf(accounts[1]) == 0 64 | assert nft.balanceOf(accounts[2]) == 7000 65 | nft.burn(8001, 15001, {"from": accounts[0]}) 66 | assert nft.totalSupply() == 0 67 | assert nft.balanceOf(accounts[1]) == 0 68 | assert nft.balanceOf(accounts[2]) == 0 69 | 70 | 71 | def test_authorized_supply(nft): 72 | """modify authorized supply""" 73 | nft.modifyAuthorizedSupply(10000, {"from": accounts[0]}) 74 | assert nft.authorizedSupply() == 10000 75 | assert nft.totalSupply() == 0 76 | nft.modifyAuthorizedSupply(0, {"from": accounts[0]}) 77 | assert nft.authorizedSupply() == 0 78 | assert nft.totalSupply() == 0 79 | nft.modifyAuthorizedSupply(1234567, {"from": accounts[0]}) 80 | assert nft.authorizedSupply() == 1234567 81 | assert nft.totalSupply() == 0 82 | nft.modifyAuthorizedSupply(2400000000, {"from": accounts[0]}) 83 | assert nft.authorizedSupply() == 2400000000 84 | assert nft.totalSupply() == 0 85 | 86 | 87 | def test_mint_zero(org, nft): 88 | """mint, burn, mint""" 89 | nft.mint(org, 10000, 0, "0x00", {"from": accounts[0]}) 90 | assert nft.totalSupply() == 10000 91 | nft.burn(1, 10001, {"from": accounts[0]}) 92 | assert nft.totalSupply() == 0 93 | nft.mint(org, 10000, 0, "0x00", {"from": accounts[0]}) 94 | assert nft.totalSupply() == 10000 95 | assert nft.rangesOf(org) == ((10001, 20001),) 96 | assert nft.getRange(1)[0] == "0x0000000000000000000000000000000000000000" 97 | -------------------------------------------------------------------------------- /tests/CertShare/mint_burn/test_nft_investor_counts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from brownie import accounts 4 | 5 | 6 | def test_mint_org(check_counts, approve_many, org, nft): 7 | """mint to org""" 8 | nft.mint(org, 1000, 0, "0x00", {"from": accounts[0]}) 9 | check_counts() 10 | nft.mint(org, 9000, 0, "0x00", {"from": accounts[0]}) 11 | check_counts() 12 | 13 | 14 | def test_burn_org(check_counts, org, nft): 15 | """burn from org""" 16 | nft.mint(org, 10000, 0, "0x00", {"from": accounts[0]}) 17 | nft.burn(1, 2001, {"from": accounts[0]}) 18 | check_counts() 19 | nft.burn(2001, 10001, {"from": accounts[0]}) 20 | check_counts() 21 | 22 | 23 | def test_mint_members(check_counts, nft): 24 | """mint to members""" 25 | check_counts() 26 | nft.mint(accounts[1], 1000, 0, "0x00", {"from": accounts[0]}) 27 | check_counts(one=(1, 1, 0)) 28 | nft.mint(accounts[1], 1000, 0, "0x00", {"from": accounts[0]}) 29 | nft.mint(accounts[2], 1000, 0, "0x00", {"from": accounts[0]}) 30 | nft.mint(accounts[3], 1000, 0, "0x00", {"from": accounts[0]}) 31 | check_counts(one=(2, 1, 1), two=(1, 1, 0)) 32 | nft.mint(accounts[1], 996000, 0, "0x00", {"from": accounts[0]}) 33 | check_counts(one=(2, 1, 1), two=(1, 1, 0)) 34 | 35 | 36 | def test_burn_members(check_counts, nft): 37 | """burn from members""" 38 | nft.mint(accounts[1], 5000, 0, "0x00", {"from": accounts[0]}) 39 | nft.mint(accounts[2], 3000, 0, "0x00", {"from": accounts[0]}) 40 | nft.mint(accounts[3], 2000, 0, "0x00", {"from": accounts[0]}) 41 | nft.burn(1, 1001, {"from": accounts[0]}) 42 | check_counts(one=(2, 1, 1), two=(1, 1, 0)) 43 | nft.burn(1001, 5001, {"from": accounts[0]}) 44 | check_counts(one=(1, 0, 1), two=(1, 1, 0)) 45 | nft.burn(5001, 8001, {"from": accounts[0]}) 46 | nft.burn(8001, 10001, {"from": accounts[0]}) 47 | check_counts() 48 | -------------------------------------------------------------------------------- /tests/CertShare/mint_burn/test_nft_ranges.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from brownie import accounts 4 | 5 | 6 | def test_mint_no_merge_owner(nft, approve_many): 7 | """Mint and do not merge - different owners""" 8 | nft.mint(accounts[1], 10000, 0, "0x00", {"from": accounts[0]}) 9 | nft.mint(accounts[2], 5000, 0, "0x00", {"from": accounts[0]}) 10 | assert nft.totalSupply() == 15000 11 | assert nft.balanceOf(accounts[1]) == 10000 12 | assert nft.balanceOf(accounts[2]) == 5000 13 | assert nft.rangesOf(accounts[1]) == ((1, 10001),) 14 | assert nft.rangesOf(accounts[2]) == ((10001, 15001),) 15 | 16 | 17 | def test_mint_no_merge_tag(nft): 18 | """Mint and do not merge - different takgs""" 19 | nft.mint(accounts[1], 10000, 0, "0x00", {"from": accounts[0]}) 20 | nft.mint(accounts[1], 5000, 0, "0x01", {"from": accounts[0]}) 21 | assert nft.totalSupply() == 15000 22 | assert nft.rangesOf(accounts[1]) == ((1, 10001), (10001, 15001)) 23 | assert nft.balanceOf(accounts[1]) == 15000 24 | 25 | 26 | def test_mint_merge(nft): 27 | """Mint and merge range""" 28 | nft.mint(accounts[1], 10000, 0, "0x00", {"from": accounts[0]}) 29 | nft.mint(accounts[1], 5000, 0, "0x00", {"from": accounts[0]}) 30 | assert nft.totalSupply() == 15000 31 | assert nft.rangesOf(accounts[1]) == ((1, 15001),) 32 | assert nft.balanceOf(accounts[1]) == 15000 33 | 34 | 35 | def test_burn_range(nft): 36 | """Burn range""" 37 | nft.mint(accounts[1], 10000, 0, "0x00", {"from": accounts[0]}) 38 | nft.mint(accounts[2], 5000, 0, "0x00", {"from": accounts[0]}) 39 | nft.mint(accounts[1], 5000, 0, "0x00", {"from": accounts[0]}) 40 | assert nft.totalSupply() == 20000 41 | assert nft.rangesOf(accounts[1]) == ((1, 10001), (15001, 20001)) 42 | assert nft.rangesOf(accounts[2]) == ((10001, 15001),) 43 | nft.burn(10001, 15001, {"from": accounts[0]}) 44 | assert nft.totalSupply() == 15000 45 | assert nft.rangesOf(accounts[1]) == ((1, 10001), (15001, 20001)) 46 | assert nft.rangesOf(accounts[2]) == () 47 | assert nft.balanceOf(accounts[2]) == 0 48 | 49 | 50 | def test_burn_all(nft): 51 | """Burn total supply""" 52 | nft.mint(accounts[1], 10000, 0, "0x00", {"from": accounts[0]}) 53 | nft.burn(1, 10001, {"from": accounts[0]}) 54 | assert nft.totalSupply() == 0 55 | assert nft.balanceOf(accounts[1]) == 0 56 | assert nft.rangesOf(accounts[1]) == () 57 | 58 | 59 | def test_burn_inside(nft): 60 | """Burn inside""" 61 | nft.mint(accounts[1], 10000, 0, "0x00", {"from": accounts[0]}) 62 | nft.burn(2000, 4000, {"from": accounts[0]}) 63 | assert nft.totalSupply() == 8000 64 | assert nft.balanceOf(accounts[1]) == 8000 65 | assert nft.rangesOf(accounts[1]) == ((1, 2000), (4000, 10001)) 66 | 67 | 68 | def test_burn_left(nft): 69 | """Burn left""" 70 | nft.mint(accounts[2], 1000, 0, "0x00", {"from": accounts[0]}) 71 | nft.mint(accounts[1], 9000, 0, "0x00", {"from": accounts[0]}) 72 | nft.burn(1001, 5001, {"from": accounts[0]}) 73 | assert nft.totalSupply() == 6000 74 | assert nft.rangesOf(accounts[1]) == ((5001, 10001),) 75 | 76 | 77 | def test_burn_right(nft): 78 | """Burn right""" 79 | nft.mint(accounts[1], 9000, 0, "0x00", {"from": accounts[0]}) 80 | nft.mint(accounts[2], 1000, 0, "0x00", {"from": accounts[0]}) 81 | nft.burn(5001, 9001) 82 | assert nft.totalSupply() == 6000 83 | assert nft.rangesOf(accounts[1]) == ((1, 5001),) 84 | 85 | 86 | def test_burn_abs_left(nft): 87 | """Burn absolute left""" 88 | nft.mint(accounts[1], 10000, 0, "0x00", {"from": accounts[0]}) 89 | nft.burn(1, 5001, {"from": accounts[0]}) 90 | assert nft.totalSupply() == 5000 91 | assert nft.rangesOf(accounts[1]) == ((5001, 10001),) 92 | 93 | 94 | def test_burn_abs_right(nft): 95 | """Burn absolute right""" 96 | nft.mint(accounts[1], 10000, 0, "0x00", {"from": accounts[0]}) 97 | nft.burn(5001, 10001, {"from": accounts[0]}) 98 | assert nft.totalSupply() == 5000 99 | assert nft.rangesOf(accounts[1]) == ((1, 5001),) 100 | -------------------------------------------------------------------------------- /tests/CertShare/mint_burn/test_nft_reverts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | def test_mint_zero(org, nft): 9 | """mint 0 shares""" 10 | with pytest.reverts("dev: mint 0"): 11 | nft.mint(org, 0, 0, "0x00", {"from": accounts[0]}) 12 | nft.mint(org, 10000, 0, "0x00", {"from": accounts[0]}) 13 | with pytest.reverts("dev: mint 0"): 14 | nft.mint(org, 0, 0, "0x00", {"from": accounts[0]}) 15 | 16 | 17 | def test_mint_time(org, nft): 18 | """mint - lock time < now""" 19 | with pytest.reverts("dev: time"): 20 | nft.mint(org, 1000, 1, "0x00", {"from": accounts[0]}) 21 | 22 | 23 | def test_mint_overflow(org, nft): 24 | """mint - overflows""" 25 | nft.modifyAuthorizedSupply(2 ** 49, {"from": accounts[0]}) 26 | nft.mint(org, (2 ** 48) - 10, 0, "0x00", {"from": accounts[0]}) 27 | with pytest.reverts("dev: overflow"): 28 | nft.mint(org, 1000, 1, "0x00", {"from": accounts[0]}) 29 | with pytest.reverts("dev: upper bound"): 30 | nft.mint(org, 9, 1, "0x00", {"from": accounts[0]}) 31 | 32 | 33 | def test_burn_zero(org, nft): 34 | """burn 0 nfts""" 35 | with pytest.reverts("dev: burn 0"): 36 | nft.burn(1, 1, {"from": accounts[0]}) 37 | nft.mint(org, 10000, 0, "0x00", {"from": accounts[0]}) 38 | with pytest.reverts("dev: burn 0"): 39 | nft.burn(1, 1, {"from": accounts[0]}) 40 | 41 | 42 | def test_burn_exceeds_balance(org, nft): 43 | """burn exceeds balance""" 44 | with pytest.reverts("dev: exceeds upper bound"): 45 | nft.burn(1, 101, {"from": accounts[0]}) 46 | nft.mint(org, 4000, 0, "0x00", {"from": accounts[0]}) 47 | with pytest.reverts("dev: exceeds upper bound"): 48 | nft.burn(1, 5001, {"from": accounts[0]}) 49 | nft.burn(1, 3001, {"from": accounts[0]}) 50 | with pytest.reverts("dev: exceeds upper bound"): 51 | nft.burn(3001, 4002, {"from": accounts[0]}) 52 | nft.burn(3001, 4001, {"from": accounts[0]}) 53 | with pytest.reverts("dev: exceeds upper bound"): 54 | nft.burn(4001, 4101, {"from": accounts[0]}) 55 | 56 | 57 | def test_burn_multiple_ranges(org, nft): 58 | """burn multiple ranges""" 59 | nft.mint(org, 1000, 0, "0x00", {"from": accounts[0]}) 60 | nft.mint(org, 1000, 0, "0x01", {"from": accounts[0]}) 61 | with pytest.reverts("dev: multiple ranges"): 62 | nft.burn(500, 1500, {"from": accounts[0]}) 63 | 64 | 65 | def test_reburn(org, nft): 66 | """burn already burnt nfts""" 67 | nft.mint(org, 1000, "0x00", 0, {"from": accounts[0]}) 68 | nft.burn(100, 200, {"from": accounts[0]}) 69 | with pytest.reverts("dev: already burnt"): 70 | nft.burn(100, 200, {"from": accounts[0]}) 71 | 72 | 73 | def test_authorized_below_total(org, nft): 74 | """authorized supply below total supply""" 75 | nft.mint(org, 100000, "0x00", 0, {"from": accounts[0]}) 76 | with pytest.reverts("dev: auth below total"): 77 | nft.modifyAuthorizedSupply(10000, {"from": accounts[0]}) 78 | 79 | 80 | def test_total_above_authorized(org, nft): 81 | """total supply above authorized""" 82 | nft.modifyAuthorizedSupply(10000, {"from": accounts[0]}) 83 | with pytest.reverts("dev: exceed auth"): 84 | nft.mint(org, 20000, 0, "0x00", {"from": accounts[0]}) 85 | nft.mint(org, 6000, 0, "0x00", {"from": accounts[0]}) 86 | with pytest.reverts("dev: exceed auth"): 87 | nft.mint(org, 6000, 0, "0x00", {"from": accounts[0]}) 88 | nft.mint(org, 4000, 0, "0x00", {"from": accounts[0]}) 89 | with pytest.reverts("dev: exceed auth"): 90 | nft.mint(org, 1, 0, "0x00", {"from": accounts[0]}) 91 | with pytest.reverts("dev: mint 0"): 92 | nft.mint(org, 0, 0, "0x00", {"from": accounts[0]}) 93 | 94 | 95 | def test_mint_to_custodian(nft, cust): 96 | """mint to custodian""" 97 | with pytest.reverts("dev: custodian"): 98 | nft.mint(cust, 6000, 0, "0x00", {"from": accounts[0]}) 99 | 100 | 101 | def test_burn_from_custodian(org, nft, cust): 102 | """burn from custodian""" 103 | nft.mint(org, 10000, 0, "0x00", {"from": accounts[0]}) 104 | nft.transfer(cust, 10000, {"from": accounts[0]}) 105 | with pytest.reverts("dev: custodian"): 106 | nft.burn(1, 5000, {"from": accounts[0]}) 107 | -------------------------------------------------------------------------------- /tests/CertShare/mint_burn/test_nft_tags.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from brownie import accounts 4 | 5 | zero = "0x0000000000000000000000000000000000000000" 6 | 7 | 8 | def test_add_tags_via_mint(approve_many, nft): 9 | """Add tags through minting""" 10 | nft.mint(accounts[1], 1000, 0, "0x0100", {"from": accounts[0]}) 11 | nft.mint(accounts[2], 1000, 0, "0x0002", {"from": accounts[0]}) 12 | nft.mint(accounts[3], 1000, 0, "0xff33", {"from": accounts[0]}) 13 | nft.mint(accounts[4], 1000, 0, "0x0123", {"from": accounts[0]}) 14 | assert nft.getRange(1) == (accounts[1], 1, 1001, 0, "0x0100", zero) 15 | assert nft.getRange(1001) == (accounts[2], 1001, 2001, 0, "0x0002", zero) 16 | assert nft.getRange(2001) == (accounts[3], 2001, 3001, 0, "0xff33", zero) 17 | assert nft.getRange(3001) == (accounts[4], 3001, 4001, 0, "0x0123", zero) 18 | -------------------------------------------------------------------------------- /tests/CertShare/test_nft_authorized_supply.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts, compile_source 6 | 7 | module_source = """pragma solidity 0.4.25; 8 | contract TestGovernance { 9 | address public orgCode; 10 | bool result; 11 | constructor(address _org) public { orgCode = _org; } 12 | function setResult(bool _result) external { result = _result; } 13 | function modifyAuthorizedSupply(address, uint256) external returns (bool) { return result; } 14 | }""" 15 | 16 | 17 | @pytest.fixture(scope="module") 18 | def gov(org): 19 | project = compile_source(module_source) 20 | g = project.TestGovernance.deploy(org, {"from": accounts[0]}) 21 | org.setGovernance(g, {"from": accounts[0]}) 22 | yield g 23 | 24 | 25 | def test_authorized_supply(share): 26 | """modify authorized supply""" 27 | share.modifyAuthorizedSupply(10000, {"from": accounts[0]}) 28 | assert share.authorizedSupply() == 10000 29 | assert share.totalSupply() == 0 30 | share.modifyAuthorizedSupply(0, {"from": accounts[0]}) 31 | assert share.authorizedSupply() == 0 32 | assert share.totalSupply() == 0 33 | share.modifyAuthorizedSupply(1234567, {"from": accounts[0]}) 34 | assert share.authorizedSupply() == 1234567 35 | assert share.totalSupply() == 0 36 | share.modifyAuthorizedSupply(2400000000, {"from": accounts[0]}) 37 | assert share.authorizedSupply() == 2400000000 38 | assert share.totalSupply() == 0 39 | 40 | 41 | def test_authorized_supply_governance_false(org, share, gov): 42 | """modify authorized supply - blocked by governance module""" 43 | gov.setResult(False, {"from": accounts[0]}) 44 | with pytest.reverts("Action has not been approved"): 45 | share.modifyAuthorizedSupply(10000, {"from": accounts[0]}) 46 | 47 | 48 | def test_authorized_supply_governance_true(org, share, gov): 49 | """modify authorized supply - allowed by governance module""" 50 | gov.setResult(True, {"from": accounts[0]}) 51 | share.modifyAuthorizedSupply(10000, {"from": accounts[0]}) 52 | 53 | 54 | def test_authorized_supply_governance_removed(org, share, gov): 55 | """modify authorized supply - removed governance module""" 56 | gov.setResult(False, {"from": accounts[0]}) 57 | with pytest.reverts("Action has not been approved"): 58 | share.modifyAuthorizedSupply(10000, {"from": accounts[0]}) 59 | org.setGovernance("0" * 40, {"from": accounts[0]}) 60 | share.modifyAuthorizedSupply(10000, {"from": accounts[0]}) 61 | -------------------------------------------------------------------------------- /tests/CertShare/test_nft_modify_ranges.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | zero = "0x0000000000000000000000000000000000000000" 8 | 9 | 10 | @pytest.fixture(scope="module", autouse=True) 11 | def setup(approve_many, nft): 12 | nft.mint(accounts[1], 10000, 0, "0x01", {"from": accounts[0]}) 13 | nft.mint(accounts[2], 10000, 0, "0x01", {"from": accounts[0]}) 14 | nft.mint(accounts[3], 10000, 0, "0x01", {"from": accounts[0]}) 15 | 16 | 17 | def test_modify_range(nft): 18 | """Modify range""" 19 | nft.modifyRange(10001, 0, "0x1234") 20 | assert nft.getRange(1) == (accounts[1], 1, 10001, 0, "0x0001", zero) 21 | assert nft.getRange(10001) == (accounts[2], 10001, 20001, 0, "0x1234", zero) 22 | assert nft.getRange(20001) == (accounts[3], 20001, 30001, 0, "0x0001", zero) 23 | 24 | 25 | def test_modify_ranges(nft): 26 | """Modify ranges""" 27 | nft.modifyRanges(5000, 25000, 0, "0x1234", {"from": accounts[0]}) 28 | assert nft.rangesOf(accounts[1]) == ((1, 5000), (5000, 10001)) 29 | assert nft.rangesOf(accounts[2]) == ((10001, 20001),) 30 | assert nft.rangesOf(accounts[3]) == ((20001, 25000), (25000, 30001)) 31 | 32 | 33 | def test_modify_many(nft): 34 | """Modify many rangels""" 35 | nft.modifyRanges(5000, 25000, 0, "0x1234") 36 | nft.modifyRanges(7000, 15000, 0, "0x1111") 37 | nft.modifyRanges(14800, 22000, 0, "0x9999") 38 | assert nft.getRange(5001) == (accounts[1], 5000, 7000, 0, "0x1234", zero) 39 | assert nft.getRange(7001) == (accounts[1], 7000, 10001, 0, "0x1111", zero) 40 | assert nft.getRange(10002) == (accounts[2], 10001, 14800, 0, "0x1111", zero) 41 | assert nft.getRange(14810) == (accounts[2], 14800, 20001, 0, "0x9999", zero) 42 | assert nft.getRange(20002) == (accounts[3], 20001, 22000, 0, "0x9999", zero) 43 | 44 | 45 | def test_modify_join(nft): 46 | """Split and join ranges with modifyRange""" 47 | nft.modifyRanges(2000, 4000, 0, "0x1234") 48 | assert nft.rangesOf(accounts[1]) == ((1, 2000), (4000, 10001), (2000, 4000)) 49 | nft.modifyRange(2000, 0, "0x01") 50 | assert nft.rangesOf(accounts[1]) == ((1, 10001),) 51 | 52 | 53 | def test_modify_join_many(nft): 54 | """Split and join with modifyRanges""" 55 | nft.modifyRanges(2000, 4000, 0, "0x1234") 56 | nft.modifyRanges(1000, 6000, 0, "0x01") 57 | assert nft.rangesOf(accounts[1]) == ((1, 10001),) 58 | -------------------------------------------------------------------------------- /tests/CertShare/transfer/test_nft_country_investor_limits.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(approve_many, org, nft): 10 | nft.mint(org, 100000, 0, "0x00", {"from": accounts[0]}) 11 | org.setCountry(1, True, 1, (1, 0, 0, 0, 0, 0, 0, 0), {"from": accounts[0]}) 12 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 13 | 14 | 15 | @pytest.fixture(scope="module") 16 | def setcountry(org): 17 | org.setCountry(1, True, 1, (0, 1, 0, 0, 0, 0, 0, 0), {"from": accounts[0]}) 18 | 19 | 20 | @pytest.fixture(scope="module") 21 | def updatemember(setcountry, kyc): 22 | kyc.updateMember(kyc.getID(accounts[2]), 1, 1, 2000000000, {"from": accounts[0]}) 23 | 24 | 25 | def test_country_member_limit_blocked_org_member(nft): 26 | """country member limit - blocked, org to member""" 27 | with pytest.reverts("Country Member Limit"): 28 | nft.transfer(accounts[2], 1000, {"from": accounts[0]}) 29 | 30 | 31 | def test_country_member_limit_blocked_member_member(nft): 32 | """country member limit - blocked, member to member""" 33 | with pytest.reverts("Country Member Limit"): 34 | nft.transfer(accounts[2], 500, {"from": accounts[1]}) 35 | 36 | 37 | def test_country_member_limit_org_member(nft): 38 | """country member limit - org to existing member""" 39 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 40 | 41 | 42 | def test_country_member_limit_member_member(nft): 43 | """country member limit - member to member, full balance""" 44 | nft.transfer(accounts[2], 1000, {"from": accounts[1]}) 45 | 46 | 47 | def test_country_member_limit_member_member_different_country(nft): 48 | """country member limit, member to member, different country""" 49 | nft.transfer(accounts[3], 500, {"from": accounts[1]}) 50 | 51 | 52 | def test_country_member_limit_rating_org_member(setcountry, nft): 53 | """country member limit, rating - org to existing member""" 54 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 55 | 56 | 57 | def test_country_member_limit_rating_member_member(setcountry, nft): 58 | """country member limit, rating - member to member, full balance""" 59 | nft.transfer(accounts[2], 1000, {"from": accounts[1]}) 60 | 61 | 62 | def test_country_member_limit_rating_member_member_different_country(setcountry, nft): 63 | """country member limit, rating - member to member, different rating""" 64 | nft.transfer(accounts[2], 500, {"from": accounts[1]}) 65 | 66 | 67 | def test_country_member_limit_rating_blocked_org_member(updatemember, nft): 68 | """country member limit, rating - blocked, org to member""" 69 | with pytest.reverts("Country Member Limit: Rating"): 70 | nft.transfer(accounts[2], 1000, {"from": accounts[0]}) 71 | 72 | 73 | def test_country_member_limit_rating_blocked_member_member(updatemember, nft): 74 | """country member limit, rating - blocked, member to member""" 75 | with pytest.reverts("Country Member Limit: Rating"): 76 | nft.transfer(accounts[2], 500, {"from": accounts[1]}) 77 | -------------------------------------------------------------------------------- /tests/CertShare/transfer/test_nft_investor_limits.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(approve_many, org, nft): 10 | nft.mint(org, 100000, 0, "0x00", {"from": accounts[0]}) 11 | org.setMemberLimits((1, 0, 0, 0, 0, 0, 0, 0), {"from": accounts[0]}) 12 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 13 | 14 | 15 | @pytest.fixture(scope="module") 16 | def setlimit(org): 17 | org.setMemberLimits((0, 1, 0, 0, 0, 0, 0, 0), {"from": accounts[0]}) 18 | 19 | 20 | def test_total_member_limit_blocked_org_member(nft): 21 | """total member limit - blocked, org to member""" 22 | with pytest.reverts("Total Member Limit"): 23 | nft.transfer(accounts[2], 1000, {"from": accounts[0]}) 24 | 25 | 26 | def test_total_member_limit_blocked_member_member(nft): 27 | """total member limit - blocked, member to member""" 28 | with pytest.reverts("Total Member Limit"): 29 | nft.transfer(accounts[2], 500, {"from": accounts[1]}) 30 | 31 | 32 | def test_total_member_limit_org_member(nft): 33 | """total member limit - org to existing member""" 34 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 35 | 36 | 37 | def test_total_member_limit_member_member(nft): 38 | """total member limit - member to member, full balance""" 39 | nft.transfer(accounts[2], 1000, {"from": accounts[1]}) 40 | 41 | 42 | def test_total_member_limit_rating_blocked_org_member(setlimit, nft): 43 | """total member limit, rating - blocked, org to member""" 44 | with pytest.reverts("Total Member Limit: Rating"): 45 | nft.transfer(accounts[3], 1000, {"from": accounts[0]}) 46 | 47 | 48 | def test_total_member_limit_rating_blocked_member_member(setlimit, nft): 49 | """total member limit, rating - blocked, member to member""" 50 | with pytest.reverts("Total Member Limit: Rating"): 51 | nft.transfer(accounts[3], 500, {"from": accounts[1]}) 52 | 53 | 54 | def test_total_member_limit_rating_org_member(setlimit, nft): 55 | """total member limit, rating - org to existing member""" 56 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 57 | 58 | 59 | def test_total_member_limit_rating_member_member(setlimit, nft): 60 | """total member limit, rating - member to member, full balance""" 61 | nft.transfer(accounts[2], 1000, {"from": accounts[1]}) 62 | 63 | 64 | def test_total_member_limit_rating_member_member_different_country(setlimit, nft): 65 | """total member limit, rating - member to member, different rating""" 66 | nft.transfer(accounts[2], 500, {"from": accounts[1]}) 67 | -------------------------------------------------------------------------------- /tests/CertShare/transfer/test_nft_locks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts, rpc 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(approve_many, org, nft): 10 | nft.mint(org, 100000, 0, "0x00", {"from": accounts[0]}) 11 | 12 | 13 | def test_global_lock(org, nft): 14 | """global lock - member / member""" 15 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 16 | org.setGlobalRestriction(True, {"from": accounts[0]}) 17 | with pytest.reverts("Transfers locked: Org"): 18 | nft.transfer(accounts[2], 1000, {"from": accounts[1]}) 19 | org.setGlobalRestriction(False, {"from": accounts[0]}) 20 | nft.transfer(accounts[2], 1000, {"from": accounts[1]}) 21 | 22 | 23 | def test_global_lock_org(org, nft): 24 | """global lock - org / member""" 25 | org.setGlobalRestriction(True, {"from": accounts[0]}) 26 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 27 | with pytest.reverts("Transfers locked: Org"): 28 | nft.transfer(accounts[0], 1000, {"from": accounts[1]}) 29 | org.setGlobalRestriction(False, {"from": accounts[0]}) 30 | nft.transfer(accounts[0], 1000, {"from": accounts[1]}) 31 | 32 | 33 | def test_nft_lock(org, nft): 34 | """nft lock - member / member""" 35 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 36 | org.setOrgShareRestriction(nft, True, {"from": accounts[0]}) 37 | with pytest.reverts("Transfers locked: Share"): 38 | nft.transfer(accounts[2], 1000, {"from": accounts[1]}) 39 | org.setOrgShareRestriction(nft, False, {"from": accounts[0]}) 40 | nft.transfer(accounts[2], 1000, {"from": accounts[1]}) 41 | 42 | 43 | def test_nft_lock_org(org, nft): 44 | """nft lock - org / member""" 45 | org.setOrgShareRestriction(nft, True, {"from": accounts[0]}) 46 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 47 | with pytest.reverts("Transfers locked: Share"): 48 | nft.transfer(accounts[0], 1000, {"from": accounts[1]}) 49 | org.setOrgShareRestriction(nft, False, {"from": accounts[0]}) 50 | nft.transfer(accounts[0], 1000, {"from": accounts[1]}) 51 | 52 | 53 | def test_time(nft): 54 | """Block transfers with range time lock""" 55 | nft.mint(accounts[1], 10000, rpc.time() + 20, "0x00", {"from": accounts[0]}) 56 | with pytest.reverts(): 57 | nft.transfer(accounts[2], 1000, {"from": accounts[1]}) 58 | rpc.sleep(21) 59 | nft.transfer(accounts[2], 1000, {"from": accounts[1]}) 60 | 61 | 62 | def test_time_partial(nft): 63 | """Partially block a transfer with range time lock""" 64 | nft.mint(accounts[1], 10000, 0, "0x00", {"from": accounts[0]}) 65 | nft.modifyRanges(102001, 106001, rpc.time() + 20, "0x00", {"from": accounts[0]}) 66 | assert nft.getRange(102001)["_stop"] == 106001 67 | nft.transfer(accounts[2], 4000, {"from": accounts[1]}) 68 | assert nft.rangesOf(accounts[1]) == ((102001, 106001), (108001, 110001)) 69 | rpc.sleep(25) 70 | nft.transfer(accounts[2], 6000, {"from": accounts[1]}) 71 | assert nft.rangesOf(accounts[2]) == ((100001, 110001),) 72 | -------------------------------------------------------------------------------- /tests/CertShare/transfer/test_nft_restrictions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(approve_many, org, nft): 10 | nft.mint(org, 100000, 0, "0x00", {"from": accounts[0]}) 11 | 12 | 13 | def test_sender_restricted(kyc, org, nft): 14 | """sender restricted - member / member""" 15 | id_ = kyc.getID(accounts[1]) 16 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 17 | org.setEntityRestriction(id_, True, {"from": accounts[0]}) 18 | with pytest.reverts("Sender restricted: Org"): 19 | nft.transfer(accounts[2], 1000, {"from": accounts[1]}) 20 | org.setEntityRestriction(id_, False, {"from": accounts[0]}) 21 | nft.transfer(accounts[2], 1000, {"from": accounts[1]}) 22 | 23 | 24 | def test_sender_restricted_org(org, nft): 25 | """sender restricted - org / member""" 26 | with pytest.reverts("dev: authority"): 27 | org.setEntityRestriction(org.ownerID(), True, {"from": accounts[0]}) 28 | org.addAuthorityAddresses(org.ownerID(), [accounts[-1]], {"from": accounts[0]}) 29 | nft.transfer(accounts[1], 1000, {"from": accounts[-1]}) 30 | org.removeAuthorityAddresses(org.ownerID(), [accounts[-1]], {"from": accounts[0]}) 31 | with pytest.reverts("Restricted Authority Address"): 32 | nft.transfer(accounts[1], 1000, {"from": accounts[-1]}) 33 | org.addAuthorityAddresses(org.ownerID(), [accounts[-1]], {"from": accounts[0]}) 34 | nft.transfer(accounts[1], 1000, {"from": accounts[-1]}) 35 | 36 | 37 | def test_sender_restricted_kyc_id(kyc, nft): 38 | """sender ID restricted at kyc""" 39 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 40 | kyc.setMemberRestriction(kyc.getID(accounts[1]), True, {"from": accounts[0]}) 41 | with pytest.reverts("Sender restricted: Verifier"): 42 | nft.transfer(accounts[2], 1000, {"from": accounts[1]}) 43 | 44 | 45 | def test_sender_restricted_kyc_addr(kyc, nft): 46 | """sender address restricted at kyc""" 47 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 48 | kyc.restrictAddresses(kyc.getID(accounts[1]), [accounts[1]], {"from": accounts[0]}) 49 | with pytest.reverts("Sender restricted: Verifier"): 50 | nft.transfer(accounts[2], 1000, {"from": accounts[1]}) 51 | 52 | 53 | def test_receiver_restricted_org(org, nft): 54 | """receiver restricted""" 55 | org.setEntityRestriction(org.getID(accounts[1]), True, {"from": accounts[0]}) 56 | with pytest.reverts("Receiver restricted: Org"): 57 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 58 | 59 | 60 | def test_receiver_restricted_kyc_id(kyc, nft): 61 | """receiver ID restricted at kyc""" 62 | kyc.setMemberRestriction(kyc.getID(accounts[1]), True, {"from": accounts[0]}) 63 | with pytest.reverts("Receiver restricted: Verifier"): 64 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 65 | 66 | 67 | def test_receiver_restricted_kyc_addr(kyc, nft): 68 | """receiver address restricted at kyc""" 69 | kyc.restrictAddresses(kyc.getID(accounts[1]), [accounts[1]], {"from": accounts[0]}) 70 | with pytest.reverts("Receiver restricted: Verifier"): 71 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 72 | 73 | 74 | def test_authority_permission(org, nft): 75 | """authority transfer permission""" 76 | org.addAuthority( 77 | [accounts[-1]], ["0xa9059cbb"], 2000000000, 1, {"from": accounts[0]} 78 | ) 79 | nft.transfer(accounts[1], 1000, {"from": accounts[-1]}) 80 | org.setAuthoritySignatures( 81 | org.getID(accounts[-1]), ["0xa9059cbb"], False, {"from": accounts[0]} 82 | ) 83 | with pytest.reverts("Authority not permitted"): 84 | nft.transfer(accounts[1], 1000, {"from": accounts[-1]}) 85 | nft.transfer(accounts[-1], 100, {"from": accounts[1]}) 86 | 87 | 88 | def test_receiver_blocked_rating(org, nft): 89 | """receiver blocked - rating""" 90 | org.setCountry(1, True, 3, (0, 0, 0, 0, 0, 0, 0, 0), {"from": accounts[0]}) 91 | with pytest.reverts("Receiver blocked: Rating"): 92 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 93 | 94 | 95 | def test_receiver_blocked_country(org, nft): 96 | """receiver blocked - country""" 97 | org.setCountry(1, False, 1, (0, 0, 0, 0, 0, 0, 0, 0), {"from": accounts[0]}) 98 | with pytest.reverts("Receiver blocked: Country"): 99 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 100 | -------------------------------------------------------------------------------- /tests/CertShare/transfer/test_nft_transfer_balances.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(approve_many, org, nft): 10 | nft.mint(org, 100000, 0, "0x00", {"from": accounts[0]}) 11 | 12 | 13 | def test_zero_nfts(nft): 14 | """cannot send 0 nfts""" 15 | with pytest.reverts("Cannot send 0 shares"): 16 | nft.transfer(accounts[1], 0, {"from": accounts[0]}) 17 | 18 | 19 | def test_overflow(nft): 20 | """cannot send >2**48 nfts""" 21 | with pytest.reverts("Value too large"): 22 | nft.transfer(accounts[1], 2 ** 49, {"from": accounts[0]}) 23 | 24 | 25 | def test_to_self(nft): 26 | """cannot send to self""" 27 | with pytest.reverts("Cannot send to self"): 28 | nft.transfer(accounts[0], 100, {"from": accounts[0]}) 29 | 30 | 31 | def test_insufficient_balance_member(nft): 32 | """insufficient balance - member to member""" 33 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 34 | with pytest.reverts("Insufficient Balance"): 35 | nft.transfer(accounts[2], 2000, {"from": accounts[1]}) 36 | 37 | 38 | def test_insufficient_balance_org(nft): 39 | """insufficient balance - org to member""" 40 | with pytest.reverts("Insufficient Balance"): 41 | nft.transfer(accounts[1], 20000000000, {"from": accounts[0]}) 42 | 43 | 44 | def test_balance(nft): 45 | """successful transfer""" 46 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 47 | assert nft.balanceOf(accounts[1]) == 1000 48 | nft.transfer(accounts[2], 400, {"from": accounts[1]}) 49 | assert nft.balanceOf(accounts[1]) == 600 50 | assert nft.balanceOf(accounts[2]) == 400 51 | 52 | 53 | def test_balance_org(org, nft): 54 | """org balances""" 55 | assert nft.balanceOf(accounts[0]) == 0 56 | assert nft.balanceOf(org) == 100000 57 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 58 | assert nft.balanceOf(accounts[0]) == 0 59 | assert nft.balanceOf(org) == 99000 60 | nft.transfer(accounts[0], 1000, {"from": accounts[1]}) 61 | assert nft.balanceOf(accounts[0]) == 0 62 | assert nft.balanceOf(org) == 100000 63 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 64 | nft.transfer(org, 1000, {"from": accounts[1]}) 65 | assert nft.balanceOf(accounts[0]) == 0 66 | assert nft.balanceOf(org) == 100000 67 | 68 | 69 | def test_authority_permission(org, nft): 70 | """org subauthority balances""" 71 | org.addAuthority( 72 | [accounts[-1]], ["0xa9059cbb"], 2000000000, 1, {"from": accounts[0]} 73 | ) 74 | nft.transfer(accounts[1], 1000, {"from": accounts[-1]}) 75 | assert nft.balanceOf(accounts[0]) == 0 76 | assert nft.balanceOf(accounts[-1]) == 0 77 | assert nft.balanceOf(org) == 99000 78 | nft.transfer(accounts[-1], 1000, {"from": accounts[1]}) 79 | assert nft.balanceOf(accounts[0]) == 0 80 | assert nft.balanceOf(accounts[-1]) == 0 81 | assert nft.balanceOf(org) == 100000 82 | -------------------------------------------------------------------------------- /tests/CertShare/transfer/test_nft_transfer_from.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(approve_many, org, nft): 10 | nft.mint(org, 100000, 0, "0x00", {"from": accounts[0]}) 11 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 12 | 13 | 14 | def test_transfer_from(nft): 15 | """member transferFrom - approved""" 16 | nft.approve(accounts[3], 500, {"from": accounts[1]}) 17 | assert nft.allowance(accounts[1], accounts[3]) == 500 18 | nft.transferFrom(accounts[1], accounts[2], 400, {"from": accounts[3]}) 19 | assert nft.allowance(accounts[1], accounts[3]) == 100 20 | nft.transferFrom(accounts[1], accounts[2], 100, {"from": accounts[3]}) 21 | assert nft.allowance(accounts[1], accounts[3]) == 0 22 | 23 | 24 | def test_transfer_from_member_no_approval(nft): 25 | """transferFrom - no approval""" 26 | with pytest.reverts("Insufficient allowance"): 27 | nft.transferFrom(accounts[1], accounts[2], 1000, {"from": accounts[3]}) 28 | 29 | 30 | def test_transfer_from_member_insufficient_approval(nft): 31 | """transferFrom - insufficient approval""" 32 | nft.approve(accounts[3], 500, {"from": accounts[1]}) 33 | with pytest.reverts("Insufficient allowance"): 34 | nft.transferFrom(accounts[1], accounts[2], 1000, {"from": accounts[3]}) 35 | 36 | 37 | def test_transfer_from_same_id(kyc, nft): 38 | """transferFrom - same member ID""" 39 | kyc.registerAddresses(kyc.getID(accounts[1]), [accounts[-1]], {"from": accounts[0]}) 40 | nft.transferFrom(accounts[1], accounts[2], 500, {"from": accounts[-1]}) 41 | 42 | 43 | def test_transfer_from_org(nft): 44 | """org transferFrom""" 45 | nft.transferFrom(accounts[1], accounts[2], 1000, {"from": accounts[0]}) 46 | 47 | 48 | def test_authority_permission(org, nft): 49 | """authority transferFrom permission""" 50 | org.addAuthority( 51 | [accounts[-1]], ["0x23b872dd"], 2000000000, 1, {"from": accounts[0]} 52 | ) 53 | nft.transferFrom(accounts[1], accounts[2], 500, {"from": accounts[-1]}) 54 | org.setAuthoritySignatures( 55 | org.getID(accounts[-1]), ["0x23b872dd"], False, {"from": accounts[0]} 56 | ) 57 | with pytest.reverts("Authority not permitted"): 58 | nft.transferFrom(accounts[1], accounts[2], 500, {"from": accounts[-1]}) 59 | -------------------------------------------------------------------------------- /tests/CertShare/transfer/test_nft_transfer_investor_counts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(org, nft): 10 | nft.mint(org, 100000, 0, "0x00", {"from": accounts[0]}) 11 | 12 | 13 | def test_org_to_member(check_counts, nft): 14 | """member counts - org/member transfers""" 15 | check_counts() 16 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 17 | check_counts(one=(1, 1, 0)) 18 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 19 | nft.transfer(accounts[2], 1000, {"from": accounts[0]}) 20 | nft.transfer(accounts[3], 1000, {"from": accounts[0]}) 21 | check_counts(one=(2, 1, 1), two=(1, 1, 0)) 22 | nft.transfer(accounts[1], 96000, {"from": accounts[0]}) 23 | check_counts(one=(2, 1, 1), two=(1, 1, 0)) 24 | nft.transfer(accounts[0], 1000, {"from": accounts[1]}) 25 | check_counts(one=(2, 1, 1), two=(1, 1, 0)) 26 | nft.transfer(accounts[0], 97000, {"from": accounts[1]}) 27 | check_counts(one=(1, 0, 1), two=(1, 1, 0)) 28 | nft.transfer(accounts[0], 1000, {"from": accounts[2]}) 29 | nft.transfer(accounts[0], 1000, {"from": accounts[3]}) 30 | check_counts() 31 | 32 | 33 | def test_member_to_member(check_counts, nft): 34 | """member counts - member/member transfers""" 35 | nft.transfer(accounts[1], 1000, {"from": accounts[0]}) 36 | nft.transfer(accounts[2], 1000, {"from": accounts[0]}) 37 | nft.transfer(accounts[3], 1000, {"from": accounts[0]}) 38 | nft.transfer(accounts[4], 1000, {"from": accounts[0]}) 39 | nft.transfer(accounts[5], 1000, {"from": accounts[0]}) 40 | nft.transfer(accounts[6], 1000, {"from": accounts[0]}) 41 | check_counts(one=(2, 1, 1), two=(2, 1, 1), three=(2, 1, 1)) 42 | nft.transfer(accounts[2], 500, {"from": accounts[1]}) 43 | check_counts(one=(2, 1, 1), two=(2, 1, 1), three=(2, 1, 1)) 44 | nft.transfer(accounts[2], 500, {"from": accounts[1]}) 45 | check_counts(one=(1, 0, 1), two=(2, 1, 1), three=(2, 1, 1)) 46 | nft.transfer(accounts[3], 2000, {"from": accounts[2]}) 47 | check_counts(two=(2, 1, 1), three=(2, 1, 1)) 48 | nft.transfer(accounts[3], 1000, {"from": accounts[4]}) 49 | check_counts(two=(1, 1, 0), three=(2, 1, 1)) 50 | nft.transfer(accounts[4], 500, {"from": accounts[3]}) 51 | check_counts(two=(2, 1, 1), three=(2, 1, 1)) 52 | -------------------------------------------------------------------------------- /tests/CertShare/transfer/test_nft_transfer_ranges.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import functools 4 | import pytest 5 | 6 | from brownie import accounts 7 | 8 | 9 | @pytest.fixture(scope="module", autouse=True) 10 | def setup(approve_many, org, nft): 11 | nft.mint(org, 100000, 0, "0x00", {"from": accounts[0]}) 12 | 13 | 14 | @pytest.fixture 15 | def transfer(no_call_coverage, org, nft): 16 | yield functools.partial(_transfer, org, nft) 17 | 18 | 19 | @pytest.fixture 20 | def ts(org, nft): 21 | yield functools.partial(_totalSupply, org, nft) 22 | 23 | 24 | def test_simple(transfer): 25 | """Simple transfer""" 26 | transfer(0, 1, 12345) 27 | 28 | 29 | def test_no_intersect(transfer, ts): 30 | """No intersection""" 31 | transfer(0, 1, 10) 32 | transfer(0, 2, 1000) 33 | transfer(0, 3, 10) 34 | transfer(2, 4, 50) 35 | transfer(2, 4, 900) 36 | transfer(2, 4, 49) 37 | transfer(2, 4, 1) 38 | transfer(4, 2, 1000) 39 | ts(5) 40 | 41 | 42 | def test_middle(transfer, ts): 43 | """Intersect on both sides""" 44 | transfer(0, 1, 100) 45 | transfer(0, 2, 120) 46 | transfer(0, 1, 3) 47 | transfer(2, 1, 120) 48 | ts(3) 49 | 50 | 51 | def test_start(transfer, ts): 52 | """Intersect at start""" 53 | transfer(0, 1, 3040) 54 | transfer(0, 2, 33) 55 | transfer(1, 2, 41) 56 | transfer(1, 2, 2999) 57 | ts(3) 58 | 59 | 60 | def test_stop(transfer, ts): 61 | """Intersect at end""" 62 | transfer(0, 1, 100) 63 | transfer(0, 2, 100) 64 | transfer(0, 3, 42) 65 | transfer(3, 2, 19) 66 | transfer(3, 2, 22) 67 | transfer(3, 2, 1) 68 | ts(4) 69 | 70 | 71 | def test_one(transfer, ts): 72 | """One nft""" 73 | transfer(0, 1, 1) 74 | transfer(0, 2, 1) 75 | transfer(0, 3, 1) 76 | transfer(0, 4, 1) 77 | transfer(0, 5, 1) 78 | transfer(1, 4, 1) 79 | transfer(2, 3, 1) 80 | transfer(5, 4, 1) 81 | transfer(3, 4, 2) 82 | ts(6) 83 | 84 | 85 | def test_split(transfer, nft, org, skip_coverage): 86 | """many ranges""" 87 | nft.modifyAuthorizedSupply("1000 gwei", {"from": accounts[0]}) 88 | nft.mint(org, "100 gwei", 0, "0x00", {"from": accounts[0]}) 89 | for i in range(2, 7): 90 | transfer(0, 1, 12345678) 91 | transfer(0, i, 12345678) 92 | transfer(0, 1, 12345678) 93 | transfer(0, i, 12345678) 94 | for i in range(6, 1): 95 | transfer(1, i, nft.balanceOf(accounts[1]) // 2) 96 | for i in range(1, 5): 97 | transfer(i, 6, nft.balanceOf(accounts[i])) 98 | 99 | 100 | def _totalSupply(org, nft, limit): 101 | b = nft.balanceOf(org) 102 | for i in range(limit): 103 | c = nft.balanceOf(accounts[i]) 104 | b += c 105 | assert nft.totalSupply() == b 106 | 107 | 108 | def _transfer(org, nft, from_, to, amount): 109 | if from_ == 0: 110 | from_bal = nft.balanceOf(org) 111 | else: 112 | from_bal = nft.balanceOf(accounts[from_]) 113 | if to == 0: 114 | to_bal = nft.balanceOf(org) 115 | else: 116 | to_bal = nft.balanceOf(accounts[to]) 117 | nft.transfer(accounts[to], amount, {"from": accounts[from_]}) 118 | if from_ == 0 or to == 0: 119 | return 120 | assert nft.balanceOf(accounts[from_]) == from_bal - amount 121 | assert nft.balanceOf(accounts[to]) == to_bal + amount 122 | assert nft.balanceOf(accounts[from_]) == ( 123 | sum((i[1] - i[0]) for i in nft.rangesOf(accounts[from_])) 124 | ) 125 | assert nft.balanceOf(accounts[to]) == ( 126 | sum((i[1] - i[0]) for i in nft.rangesOf(accounts[to])) 127 | ) 128 | -------------------------------------------------------------------------------- /tests/CertShare/transfer_range/conftest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import functools 4 | import pytest 5 | 6 | from brownie import accounts 7 | 8 | 9 | @pytest.fixture(scope="module") 10 | def check_ranges(nft): 11 | upper = nft.totalSupply() + 1 12 | yield functools.partial(_check_ranges, nft, upper) 13 | 14 | 15 | @pytest.fixture(autouse=True) 16 | def package_setup(no_call_coverage): 17 | pass 18 | 19 | 20 | def _check_ranges(nft, upper, *expected_ranges, tags=False): 21 | for num, expected in enumerate(expected_ranges, start=1): 22 | account = accounts[num] 23 | ranges = nft.rangesOf(account) 24 | assert set(ranges) == set(expected) 25 | assert nft.balanceOf(account) == sum((i[1] - i[0]) for i in ranges) 26 | for start, stop in ranges: 27 | if stop - start == 1: 28 | assert nft.getRange(start)[:3] == (account, start, stop) 29 | continue 30 | for i in range(max(1, start - 1), start + 2): 31 | try: 32 | data = nft.getRange(i) 33 | except Exception: 34 | raise AssertionError( 35 | f"Could not get range pointer {i} for account {num}" 36 | ) 37 | if i < start: 38 | if not tags: 39 | assert data[0] != account 40 | assert data[2] == start 41 | else: 42 | if not tags: 43 | assert data[0] == account 44 | assert data[2] == stop 45 | for i in range(stop - 1, min(stop + 1, upper)): 46 | data = nft.getRange(i) 47 | if i < stop: 48 | if not tags: 49 | assert data[0] == account 50 | assert data[1] == start 51 | else: 52 | if not tags: 53 | assert data[0] != account 54 | assert data[1] == stop 55 | -------------------------------------------------------------------------------- /tests/CertShare/transfer_range/test_merges_one_token.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(approve_many, nft): 10 | nft.mint(accounts[1], 10000, 0, "0x00", {"from": accounts[0]}) 11 | nft.mint(accounts[2], 10000, 0, "0x00", {"from": accounts[0]}) 12 | nft.mint(accounts[3], 10000, 0, "0x00", {"from": accounts[0]}) 13 | 14 | 15 | def test_inside_one(check_ranges, nft): 16 | """inside, one nft""" 17 | nft.transferRange(accounts[4], 12000, 12001, {"from": accounts[2]}) 18 | check_ranges( 19 | [(1, 10001)], 20 | [(10001, 12000), (12001, 20001)], 21 | [(20001, 30001)], 22 | [(12000, 12001)], 23 | ) 24 | 25 | 26 | def test_one_left(check_ranges, nft): 27 | """one nft, touch left""" 28 | nft.transferRange(accounts[4], 10001, 10002, {"from": accounts[2]}) 29 | check_ranges([(1, 10001)], [(10002, 20001)], [(20001, 30001)], [(10001, 10002)]) 30 | 31 | 32 | def test_one_left_abs(check_ranges, nft): 33 | """one nft, touch left, absolute""" 34 | nft.transferRange(accounts[4], 1, 2, {"from": accounts[1]}) 35 | check_ranges([(2, 10001)], [(10001, 20001)], [(20001, 30001)], [(1, 2)]) 36 | 37 | 38 | def test_one_left_merge(check_ranges, nft): 39 | """one nft, touch left, merge""" 40 | nft.transferRange(accounts[4], 1, 5000, {"from": accounts[1]}) 41 | nft.transferRange(accounts[1], 10001, 10002, {"from": accounts[2]}) 42 | check_ranges([(5000, 10002)], [(10002, 20001)], [(20001, 30001)], [(1, 5000)]) 43 | 44 | 45 | def test_one_left_merge_abs(check_ranges, nft): 46 | """one nft, touch left, merge, absolute""" 47 | nft.transferRange(accounts[1], 10001, 10002, {"from": accounts[2]}) 48 | check_ranges([(1, 10002)], [(10002, 20001)], [(20001, 30001)], []) 49 | 50 | 51 | def test_one_right(check_ranges, nft): 52 | """one nft, touch right""" 53 | nft.transferRange(accounts[4], 20000, 20001, {"from": accounts[2]}) 54 | check_ranges([(1, 10001)], [(10001, 20000)], [(20001, 30001)], [(20000, 20001)]) 55 | 56 | 57 | def test_one_right_abs(check_ranges, nft): 58 | """one nft, touch right, absolute""" 59 | nft.transferRange(accounts[4], 30000, 30001, {"from": accounts[3]}) 60 | check_ranges([(1, 10001)], [(10001, 20001)], [(20001, 30000)], [(30000, 30001)]) 61 | 62 | 63 | def test_one_right_merge(check_ranges, nft): 64 | """one nft touch right, merge""" 65 | nft.transferRange(accounts[4], 25000, 30001, {"from": accounts[3]}) 66 | nft.transferRange(accounts[3], 20000, 20001, {"from": accounts[2]}) 67 | check_ranges([(1, 10001)], [(10001, 20000)], [(20000, 25000)], [(25000, 30001)]) 68 | 69 | 70 | def test_one_right_merge_abs(check_ranges, nft): 71 | """one nft, touch right, merge absolute""" 72 | nft.transferRange(accounts[3], 20000, 20001, {"from": accounts[2]}) 73 | check_ranges([(1, 10001)], [(10001, 20000)], [(20000, 30001)], []) 74 | 75 | 76 | def test_create_one_start(check_ranges, nft): 77 | """create one nft range at start""" 78 | nft.transferRange(accounts[4], 10002, 12001, {"from": accounts[2]}) 79 | check_ranges( 80 | [(1, 10001)], 81 | [(10001, 10002), (12001, 20001)], 82 | [(20001, 30001)], 83 | [(10002, 12001)], 84 | ) 85 | 86 | 87 | def test_create_one_start_abs(check_ranges, nft): 88 | """create one nft range at start, absolute""" 89 | nft.transferRange(accounts[4], 2, 1000, {"from": accounts[1]}) 90 | check_ranges( 91 | [(1, 2), (1000, 10001)], [(10001, 20001)], [(20001, 30001)], [(2, 1000)] 92 | ) 93 | 94 | 95 | def test_create_one_end(check_ranges, nft): 96 | """create one nft range at end""" 97 | nft.transferRange(accounts[4], 19000, 20000, {"from": accounts[2]}) 98 | check_ranges( 99 | [(1, 10001)], 100 | [(10001, 19000), (20000, 20001)], 101 | [(20001, 30001)], 102 | [(19000, 20000)], 103 | ) 104 | 105 | 106 | def test_create_one_end_abs(check_ranges, nft): 107 | """create one nft range at end, absolute""" 108 | nft.transferRange(accounts[4], 29000, 30000, {"from": accounts[3]}) 109 | check_ranges( 110 | [(1, 10001)], 111 | [(10001, 20001)], 112 | [(20001, 29000), (30000, 30001)], 113 | [(29000, 30000)], 114 | ) 115 | -------------------------------------------------------------------------------- /tests/CertShare/transfer_range/test_merges_reverts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(approve_many, nft): 10 | nft.mint(accounts[1], 10000, 0, "0x00", {"from": accounts[0]}) 11 | nft.mint(accounts[2], 10000, 0, "0x00", {"from": accounts[0]}) 12 | nft.mint(accounts[3], 10000, 0, "0x00", {"from": accounts[0]}) 13 | 14 | 15 | def test_check_bounds(nft): 16 | """check bounds""" 17 | with pytest.reverts("Invalid index"): 18 | nft.transferRange(accounts[2], 0, 1000, {"from": accounts[0]}) 19 | with pytest.reverts("Invalid index"): 20 | nft.transferRange(accounts[2], 1000000, 1000, {"from": accounts[0]}) 21 | with pytest.reverts("Invalid index"): 22 | nft.transferRange(accounts[2], 1, 0, {"from": accounts[0]}) 23 | with pytest.reverts("Invalid index"): 24 | nft.transferRange(accounts[2], 1, 1000000, {"from": accounts[0]}) 25 | 26 | 27 | def test_stop_start(nft): 28 | """stop below start""" 29 | with pytest.reverts("dev: stop < start"): 30 | nft.transferRange(accounts[2], 2000, 1000, {"from": accounts[1]}) 31 | 32 | 33 | # TODO send out of custodian 34 | 35 | 36 | def test_multiple_ranges(nft): 37 | """multiple ranges""" 38 | with pytest.reverts("dev: multiple ranges"): 39 | nft.transferRange(accounts[2], 1000, 15000, {"from": accounts[1]}) 40 | with pytest.reverts("dev: multiple ranges"): 41 | nft.transferRange(accounts[2], 10000, 10002, {"from": accounts[1]}) 42 | 43 | 44 | def test_time_lock(nft): 45 | """time lock""" 46 | nft.modifyRange(10001, 2000000000, "0x00", {"from": accounts[0]}) 47 | with pytest.reverts("dev: time"): 48 | nft.transferRange(accounts[1], 11000, 12000, {"from": accounts[2]}) 49 | 50 | 51 | # TODO prevent send from custodian? 52 | 53 | 54 | def test_not_owner(nft): 55 | """sender does not own range""" 56 | with pytest.reverts("Sender does not own range"): 57 | nft.transferRange(accounts[3], 11000, 12000, {"from": accounts[1]}) 58 | 59 | 60 | def test_same_addr(nft): 61 | """cannot send to self""" 62 | with pytest.reverts("Cannot send to self"): 63 | nft.transferRange(accounts[2], 11000, 12000, {"from": accounts[2]}) 64 | 65 | 66 | # TODO org send / receive ? 67 | -------------------------------------------------------------------------------- /tests/CertShare/transfer_range/test_merges_upper_bound.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(approve_many, nft): 10 | nft.modifyAuthorizedSupply(2 ** 48, {"from": accounts[0]}) 11 | nft.mint(accounts[1], 2 ** 48 - 20002, 0, "0x00", {"from": accounts[0]}) 12 | nft.mint(accounts[2], 10000, 0, "0x00", {"from": accounts[0]}) 13 | nft.mint(accounts[3], 10000, 0, "0x00", {"from": accounts[0]}) 14 | 15 | 16 | def test_initial(check_ranges): 17 | check_ranges( 18 | [(1, 2 ** 48 - 20001)], 19 | [(2 ** 48 - 20001, 2 ** 48 - 10001)], 20 | [(2 ** 48 - 10001, 2 ** 48 - 1)], 21 | [], 22 | ) 23 | 24 | 25 | def test_whole_range_right_abs(check_ranges, nft): 26 | """whole range, merge right, absolute""" 27 | nft.transferRange( 28 | accounts[3], 2 ** 48 - 20001, 2 ** 48 - 10001, {"from": accounts[2]} 29 | ) 30 | check_ranges([(1, 2 ** 48 - 20001)], [], [(2 ** 48 - 20001, 2 ** 48 - 1)], []) 31 | 32 | 33 | def test_whole_range_same_both(check_ranges, nft): 34 | """whole range, merge both sides, absolute both""" 35 | nft.transferRange(accounts[3], 1, 2 ** 48 - 20001, {"from": accounts[1]}) 36 | nft.transferRange( 37 | accounts[3], 2 ** 48 - 20001, 2 ** 48 - 10001, {"from": accounts[2]} 38 | ) 39 | check_ranges([], [], [(1, 2 ** 48 - 1)], []) 40 | 41 | 42 | def test_whole_range_same_right(check_ranges, nft): 43 | """whole range, merge both sides, absolute right""" 44 | nft.transferRange(accounts[3], 5000, 2 ** 48 - 20001, {"from": accounts[1]}) 45 | nft.transferRange( 46 | accounts[3], 2 ** 48 - 20001, 2 ** 48 - 10001, {"from": accounts[2]} 47 | ) 48 | check_ranges([(1, 5000)], [], [(5000, 2 ** 48 - 1)], []) 49 | 50 | 51 | def test_stop_absolute(check_ranges, nft): 52 | """partial, touch stop, absolute""" 53 | nft.transferRange(accounts[4], 2 ** 48 - 5000, 2 ** 48 - 1, {"from": accounts[3]}) 54 | check_ranges( 55 | [(1, 2 ** 48 - 20001)], 56 | [(2 ** 48 - 20001, 2 ** 48 - 10001)], 57 | [(2 ** 48 - 10001, 2 ** 48 - 5000)], 58 | [(2 ** 48 - 5000, 2 ** 48 - 1)], 59 | ) 60 | 61 | 62 | def test_stop_partial_same_abs(check_ranges, nft): 63 | """partial, touch stop, merge, absolute""" 64 | nft.transferRange( 65 | accounts[3], 2 ** 48 - 15000, 2 ** 48 - 10001, {"from": accounts[2]} 66 | ) 67 | check_ranges( 68 | [(1, 2 ** 48 - 20001)], 69 | [(2 ** 48 - 20001, 2 ** 48 - 15000)], 70 | [(2 ** 48 - 15000, 2 ** 48 - 1)], 71 | [], 72 | ) 73 | -------------------------------------------------------------------------------- /tests/IDVerifierOrg/conftest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | 6 | @pytest.fixture(scope="module") 7 | def ikyc(IDVerifierOrg, org, accounts): 8 | kyc = accounts[0].deploy(IDVerifierOrg, org) 9 | org.setVerifier(kyc, False, {"from": accounts[0]}) 10 | kyc.addMember( 11 | "member1".encode(), 12 | 1, 13 | "0x000001", 14 | 1, 15 | 9999999999, 16 | (accounts[1],), 17 | {"from": accounts[0]}, 18 | ) 19 | yield kyc 20 | -------------------------------------------------------------------------------- /tests/IDVerifierOrg/test_kycissuer_addresses.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | id_ = "member1".encode() 8 | 9 | 10 | def test_add_addresses_known_address(ikyc): 11 | """cannot add known addresses""" 12 | ikyc.addMember( 13 | "0x123456", 1, 1, 1, 9999999999, accounts[5:7], {"from": accounts[0]} 14 | ) 15 | with pytest.reverts("dev: known address"): 16 | ikyc.registerAddresses(id_, (accounts[5],), {"from": accounts[0]}) 17 | with pytest.reverts("dev: known address"): 18 | ikyc.registerAddresses(id_, (accounts[1],), {"from": accounts[0]}) 19 | with pytest.reverts("dev: auth address"): 20 | ikyc.registerAddresses(id_, (accounts[0],), {"from": accounts[0]}) 21 | 22 | 23 | def test_add_address_repeat(ikyc): 24 | """cannot add - repeat addresses""" 25 | with pytest.reverts("dev: known address"): 26 | ikyc.registerAddresses( 27 | id_, (accounts[5], accounts[6], accounts[5]), {"from": accounts[0]} 28 | ) 29 | 30 | 31 | def test_restrict_already_restricted(ikyc): 32 | """cannot restrict - already restricted""" 33 | ikyc.restrictAddresses(id_, (accounts[1],), {"from": accounts[0]}) 34 | with pytest.reverts("dev: already restricted"): 35 | ikyc.restrictAddresses(id_, (accounts[1],), {"from": accounts[0]}) 36 | 37 | 38 | def test_restrict_wrong_ID(ikyc): 39 | """cannot restrict - wrong ID""" 40 | ikyc.addMember( 41 | "0x123456", 1, 1, 1, 9999999999, (accounts[6],), {"from": accounts[0]} 42 | ) 43 | with pytest.reverts("dev: wrong ID"): 44 | ikyc.restrictAddresses(id_, (accounts[6],), {"from": accounts[0]}) 45 | with pytest.reverts("dev: wrong ID"): 46 | ikyc.restrictAddresses("0x123456", (accounts[2],), {"from": accounts[0]}) 47 | 48 | 49 | def test_owner_add_member_addresses(ikyc): 50 | """owner - add member addresses""" 51 | ikyc.addMember( 52 | "0x123456", 1, 1, 1, 9999999999, (accounts[5],), {"from": accounts[0]} 53 | ) 54 | ikyc.registerAddresses( 55 | "0x123456", (accounts[6], accounts[7]), {"from": accounts[0]} 56 | ) 57 | assert ikyc.getID(accounts[6]) == "0x123456" 58 | assert ikyc.getID(accounts[7]) == "0x123456" 59 | -------------------------------------------------------------------------------- /tests/IDVerifierOrg/test_kycissuer_investors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | id_ = "member1".encode() 8 | 9 | 10 | def test_add_member(ikyc): 11 | """add member""" 12 | assert not ikyc.isRegistered("0x1234") 13 | ikyc.addMember("0x1234", 1, 1, 1, 9999999999, (accounts[3],), {"from": accounts[0]}) 14 | assert ikyc.isRegistered("0x1234") 15 | assert ikyc.getMember(accounts[3])[1:] == (True, 1, 1) 16 | assert ikyc.getExpires("0x1234") == 9999999999 17 | 18 | 19 | def test_add_member_country_zero(ikyc): 20 | """add member - country 0""" 21 | with pytest.reverts("dev: country 0"): 22 | ikyc.addMember( 23 | "0x1234", 24 | 0, 25 | 1, 26 | 1, 27 | 9999999999, 28 | (accounts[1], accounts[2]), 29 | {"from": accounts[0]}, 30 | ) 31 | 32 | 33 | def test_add_member_rating_zero(ikyc): 34 | """add member - rating 0""" 35 | with pytest.reverts("dev: rating 0"): 36 | ikyc.addMember( 37 | "0x1234", 38 | 1, 39 | 1, 40 | 0, 41 | 9999999999, 42 | (accounts[1], accounts[2]), 43 | {"from": accounts[0]}, 44 | ) 45 | 46 | 47 | def test_add_member_authority_id(ikyc, org): 48 | """add member - known authority ID""" 49 | oid = org.ownerID() 50 | with pytest.reverts("dev: authority ID"): 51 | ikyc.addMember(oid, 1, 1, 1, 9999999999, (accounts[2],), {"from": accounts[0]}) 52 | 53 | 54 | def test_add_member_member_id(ikyc): 55 | """add member - known member ID""" 56 | with pytest.reverts("dev: member ID"): 57 | ikyc.addMember(id_, 1, 1, 1, 9999999999, (accounts[3],), {"from": accounts[0]}) 58 | 59 | 60 | def test_update_member(ikyc, share): 61 | """update member""" 62 | assert ikyc.isRegistered(id_) 63 | ikyc.updateMember(id_, 2, 4, 1234567890, {"from": accounts[0]}) 64 | assert ikyc.isRegistered(id_) 65 | assert ikyc.getMember(accounts[1])[1:] == (False, 4, 1) 66 | assert ikyc.getExpires(id_) == 1234567890 67 | assert ikyc.getRegion(id_) == "0x000002" 68 | 69 | 70 | def test_update_member_unknown_id(ikyc, org): 71 | """update member - unknown ID""" 72 | oid = org.ownerID() 73 | with pytest.reverts("dev: unknown ID"): 74 | ikyc.updateMember("0x1234", 1, 1, 9999999999, {"from": accounts[0]}) 75 | with pytest.reverts("dev: unknown ID"): 76 | ikyc.updateMember(oid, 1, 1, 9999999999, {"from": accounts[0]}) 77 | 78 | 79 | def test_update_member_rating_zero(ikyc): 80 | """update member - rating zero""" 81 | with pytest.reverts("dev: rating 0"): 82 | ikyc.updateMember(id_, 1, 0, 9999999999, {"from": accounts[0]}) 83 | 84 | 85 | def test_set_restriction(ikyc): 86 | """set member restriction""" 87 | assert ikyc.isPermittedID(id_) 88 | ikyc.setMemberRestriction(id_, True, {"from": accounts[0]}) 89 | assert not ikyc.isPermittedID(id_) 90 | ikyc.setMemberRestriction(id_, False, {"from": accounts[0]}) 91 | assert ikyc.isPermittedID(id_) 92 | -------------------------------------------------------------------------------- /tests/IDVerifierOrg/test_kycissuer_multisig.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import functools 4 | import pytest 5 | 6 | from brownie import accounts, rpc 7 | 8 | id_ = "member1".encode() 9 | 10 | 11 | @pytest.fixture(scope="module", autouse=True) 12 | def setup(org, ikyc): 13 | org.addAuthorityAddresses(org.ownerID(), accounts[1:3], {"from": accounts[0]}) 14 | org.addAuthority(accounts[3:6], [], 2000000000, 1, {"from": accounts[0]}) 15 | 16 | 17 | @pytest.fixture(scope="module") 18 | def multisig(org): 19 | yield functools.partial(_multisig, org) 20 | 21 | 22 | def test_addMember(multisig, ikyc): 23 | multisig(ikyc.addMember, "0x1234", 1, 1, 1, 9999999999, (accounts[7],)) 24 | 25 | 26 | def test_updateMember(multisig, ikyc): 27 | multisig(ikyc.updateMember, id_, 2, 4, 1234567890) 28 | 29 | 30 | def test_setMemberRestriction(multisig, ikyc): 31 | multisig(ikyc.setMemberRestriction, id_, False) 32 | 33 | 34 | def test_registerAddresses(multisig, ikyc): 35 | multisig(ikyc.registerAddresses, id_, (accounts[7],)) 36 | 37 | 38 | def test_restrictAddresses(multisig, ikyc): 39 | multisig(ikyc.restrictAddresses, id_, (accounts[1],)) 40 | 41 | 42 | def _multisig(org, fn, *args): 43 | auth_id = org.getID(accounts[3]) 44 | args = list(args) + [{"from": accounts[3]}] 45 | # check for failed call, no permission 46 | with pytest.reverts("dev: not permitted"): 47 | fn(*args) 48 | # give permission and check for successful call 49 | org.setAuthoritySignatures(auth_id, [fn.signature], True, {"from": accounts[0]}) 50 | assert "MultiSigCallApproved" in fn(*args).events 51 | rpc.revert() 52 | # give permission, threhold to 3, check for success and fails 53 | org.setAuthoritySignatures(auth_id, [fn.signature], True, {"from": accounts[0]}) 54 | org.setAuthorityThreshold(auth_id, 3, {"from": accounts[0]}) 55 | args[-1]["from"] = accounts[3] 56 | assert "MultiSigCallApproved" not in fn(*args).events 57 | with pytest.reverts("dev: repeat caller"): 58 | fn(*args) 59 | args[-1]["from"] = accounts[4] 60 | assert "MultiSigCallApproved" not in fn(*args).events 61 | with pytest.reverts("dev: repeat caller"): 62 | fn(*args) 63 | args[-1]["from"] = accounts[5] 64 | assert "MultiSigCallApproved" in fn(*args).events 65 | -------------------------------------------------------------------------------- /tests/IDVerifierRegistrar/conftest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(kyc): 10 | kyc.addAuthority((accounts[-1], accounts[-2]), [1], 1, {"from": accounts[0]}) 11 | 12 | 13 | @pytest.fixture(scope="module") 14 | def owner_id(kyc): 15 | yield kyc.getAuthorityID(accounts[0]) 16 | 17 | 18 | @pytest.fixture(scope="module") 19 | def auth_id(kyc): 20 | yield kyc.getAuthorityID(accounts[-1]) 21 | -------------------------------------------------------------------------------- /tests/IDVerifierRegistrar/test_kycregistrar_authorities.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | def test_add_threshold_zero(kyc): 9 | """add - zero threshold""" 10 | with pytest.reverts("dev: zero threshold"): 11 | kyc.addAuthority((accounts[1],), (1, 2, 3), 0, {"from": accounts[0]}) 12 | 13 | 14 | def test_add_exists_as_member(kyc, rpc): 15 | """add - ID already assigned to member""" 16 | kyc.addAuthority((accounts[1],), (1, 2, 3), 1, {"from": accounts[0]}) 17 | id_ = kyc.getAuthorityID(accounts[1]) 18 | rpc.revert() 19 | kyc.addMember( 20 | id_, 1, 1, 1, 9999999999, (accounts[1], accounts[2]), {"from": accounts[0]} 21 | ) 22 | with pytest.reverts("dev: member ID"): 23 | kyc.addAuthority((accounts[1],), (1, 2, 3), 1, {"from": accounts[0]}) 24 | 25 | 26 | def test_authority_exists(kyc): 27 | """add - authority already exists""" 28 | kyc.addAuthority((accounts[1],), (1, 2, 3), 1, {"from": accounts[0]}) 29 | with pytest.reverts("dev: authority exists"): 30 | kyc.addAuthority((accounts[1],), (1, 2, 3), 1, {"from": accounts[0]}) 31 | 32 | 33 | def test_add_threshold_high(kyc): 34 | """add - threshold exceed address count""" 35 | with pytest.reverts("dev: threshold too high"): 36 | kyc.addAuthority((accounts[1],), (1, 2, 3), 2, {"from": accounts[0]}) 37 | 38 | 39 | def test_add_repeat_address(kyc): 40 | """add - repeat address""" 41 | with pytest.reverts("dev: known address"): 42 | kyc.addAuthority( 43 | (accounts[1], accounts[1]), (1, 2, 3), 2, {"from": accounts[0]} 44 | ) 45 | 46 | 47 | def test_threshold(kyc, auth_id): 48 | """set threshold""" 49 | kyc.setAuthorityThreshold(auth_id, 2, {"from": accounts[0]}) 50 | kyc.setAuthorityThreshold(auth_id, 1, {"from": accounts[0]}) 51 | 52 | 53 | def test_threshold_zero(kyc, auth_id): 54 | """set threshold - zero""" 55 | with pytest.reverts("dev: zero threshold"): 56 | kyc.setAuthorityThreshold(auth_id, 0, {"from": accounts[0]}) 57 | 58 | 59 | def test_threshold_not_auth(kyc): 60 | """set threshold - not an authority""" 61 | with pytest.reverts("dev: not authority"): 62 | kyc.setAuthorityThreshold("0x1234", 1, {"from": accounts[0]}) 63 | 64 | 65 | def test_threshold_too_high(kyc, auth_id): 66 | """set threshold - too high""" 67 | with pytest.reverts("dev: threshold too high"): 68 | kyc.setAuthorityThreshold(auth_id, 3, {"from": accounts[0]}) 69 | 70 | 71 | def test_country(kyc, auth_id): 72 | """set countries""" 73 | countries = (10, 300, 510, 512, 515, 600, 700) 74 | kyc.setAuthorityCountries(auth_id, countries, True, {"from": accounts[0]}) 75 | for c in countries: 76 | assert not kyc.isApprovedAuthority(accounts[-1], c - 1) 77 | assert kyc.isApprovedAuthority(accounts[-1], c) 78 | assert not kyc.isApprovedAuthority(accounts[-1], c + 1) 79 | for c in countries: 80 | kyc.setAuthorityCountries(auth_id, [c], False, {"from": accounts[0]}) 81 | assert not kyc.isApprovedAuthority(accounts[-1], c) 82 | 83 | 84 | def test_country_not_authority(kyc): 85 | """set countries - not an authority""" 86 | with pytest.reverts("dev: not authority"): 87 | kyc.setAuthorityCountries("0x1234", (10, 20), True, {"from": accounts[0]}) 88 | 89 | 90 | def test_restricted(kyc, auth_id): 91 | """restrict authority""" 92 | kyc.setAuthorityCountries(auth_id, (1,), True, {"from": accounts[0]}) 93 | assert kyc.isApprovedAuthority(accounts[-1], 1) 94 | kyc.setAuthorityRestriction(auth_id, True, {"from": accounts[0]}) 95 | assert not kyc.isApprovedAuthority(accounts[-1], 1) 96 | kyc.setAuthorityRestriction(auth_id, False, {"from": accounts[0]}) 97 | assert kyc.isApprovedAuthority(accounts[-1], 1) 98 | 99 | 100 | def test_restricted_not_authority(kyc): 101 | """restrict - not authority""" 102 | with pytest.reverts("dev: not authority"): 103 | kyc.setAuthorityRestriction("0x1234", False, {"from": accounts[0]}) 104 | 105 | 106 | def test_restricted_owner(kyc): 107 | """restrict - owner""" 108 | with pytest.reverts("dev: owner"): 109 | kyc.setAuthorityRestriction( 110 | kyc.getAuthorityID(accounts[0]), False, {"from": accounts[0]} 111 | ) 112 | -------------------------------------------------------------------------------- /tests/IDVerifierRegistrar/test_kycregistrar_check_multisig.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import functools 4 | import pytest 5 | 6 | from brownie import accounts, rpc 7 | 8 | 9 | @pytest.fixture(scope="module", autouse=True) 10 | def local_setup(kyc, owner_id, auth_id): 11 | kyc.setAuthorityCountries(auth_id, (1,), False, {"from": accounts[0]}) 12 | kyc.registerAddresses(owner_id, accounts[1:5], {"from": accounts[0]}) 13 | kyc.registerAddresses(auth_id, accounts[-5:-2], {"from": accounts[0]}) 14 | kyc.addMember("0x1111", 1, 1, 1, 9999999999, (accounts[5],), {"from": accounts[0]}) 15 | 16 | 17 | @pytest.fixture(scope="module") 18 | def msowner(kyc, owner_id): 19 | yield functools.partial(_owner_multisig, kyc, owner_id) 20 | 21 | 22 | @pytest.fixture(scope="module") 23 | def msauth(kyc, auth_id): 24 | yield functools.partial(_auth_multisig, kyc, auth_id) 25 | 26 | 27 | def test_addAuthority(msowner, kyc): 28 | msowner(kyc.addAuthority, (accounts[6], accounts[7]), [], 1) 29 | 30 | 31 | def test_setAuthorityThreshold(msowner, kyc, auth_id): 32 | msowner(kyc.setAuthorityThreshold, auth_id, 2) 33 | 34 | 35 | def test_setAuthorityCountries(msowner, kyc, auth_id): 36 | msowner(kyc.setAuthorityCountries, auth_id, (1, 5, 7), True) 37 | 38 | 39 | def test_setAuthorityRestriction(msowner, kyc, auth_id): 40 | msowner(kyc.setAuthorityRestriction, auth_id, True) 41 | 42 | 43 | def test_setMemberAuthority(msowner, kyc, auth_id): 44 | msowner(kyc.setMemberAuthority, auth_id, ("0x1111",)) 45 | 46 | 47 | def test_addMember(msauth, kyc): 48 | msauth(kyc.addMember, "0x1234", 1, 1, 1, 9999999999, (accounts[6],)) 49 | 50 | 51 | def test_updateMember(msauth, kyc): 52 | msauth(kyc.updateMember, "0x1111", 2, 4, 1234567890) 53 | 54 | 55 | def test_setMemberRestriction(msauth, kyc): 56 | msauth(kyc.setMemberRestriction, "0x1111", True) 57 | 58 | 59 | def test_registerAddresses(msauth, kyc): 60 | msauth(kyc.registerAddresses, "0x1111", (accounts[6],)) 61 | 62 | 63 | def test_restrictAddresses(msauth, kyc): 64 | msauth(kyc.restrictAddresses, "0x1111", (accounts[5],)) 65 | 66 | 67 | def _auth_multisig(kyc, auth_id, fn, *args): 68 | args = list(args) + [{"from": accounts[-1]}] 69 | with pytest.reverts("dev: country"): 70 | fn(*args) 71 | kyc.setAuthorityCountries(auth_id, (1,), True, {"from": accounts[0]}) 72 | kyc.setAuthorityThreshold(auth_id, 3, {"from": accounts[0]}) 73 | assert "MultiSigCallApproved" not in fn(*args).events 74 | with pytest.reverts("dev: repeat caller"): 75 | fn(*args) 76 | args[-1]["from"] = accounts[-2] 77 | assert "MultiSigCallApproved" not in fn(*args).events 78 | with pytest.reverts("dev: repeat caller"): 79 | fn(*args) 80 | args[-1]["from"] = accounts[-3] 81 | assert "MultiSigCallApproved" in fn(*args).events 82 | 83 | 84 | def _owner_multisig(kyc, owner_id, fn, *args): 85 | args = list(args) + [{"from": accounts[0]}] 86 | with pytest.reverts("dev: only owner"): 87 | fn(*args[:-1] + [{"from": accounts[-1]}]) 88 | assert "MultiSigCallApproved" in fn(*args).events 89 | rpc.revert() 90 | kyc.setAuthorityThreshold(owner_id, 3, {"from": accounts[0]}) 91 | assert "MultiSigCallApproved" not in fn(*args).events 92 | with pytest.reverts("dev: repeat caller"): 93 | fn(*args) 94 | args[-1]["from"] = accounts[1] 95 | assert "MultiSigCallApproved" not in fn(*args).events 96 | with pytest.reverts("dev: repeat caller"): 97 | fn(*args) 98 | args[-1]["from"] = accounts[2] 99 | assert "MultiSigCallApproved" in fn(*args).events 100 | -------------------------------------------------------------------------------- /tests/OrgCode/Modular/test_attach_detach.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts, compile_source 6 | 7 | module_source = """ 8 | pragma solidity 0.4.25; 9 | 10 | contract TestModule { 11 | 12 | address owner; 13 | 14 | constructor(address _owner) public { owner = _owner; } 15 | function getOwner() external view returns (address) { return owner; } 16 | 17 | function getPermissions() 18 | external 19 | pure 20 | returns 21 | ( 22 | bytes4[] permissions, 23 | bytes4[] hooks, 24 | uint256 hookBools 25 | ) 26 | { 27 | return (permissions, hooks, 0); 28 | } 29 | 30 | }""" 31 | 32 | 33 | @pytest.fixture(scope="module") 34 | def TestModule(): 35 | yield compile_source(module_source).TestModule 36 | 37 | 38 | @pytest.fixture(scope="module") 39 | def module_share(TestModule, share): 40 | module = accounts[0].deploy(TestModule, share) 41 | yield module 42 | 43 | 44 | @pytest.fixture(scope="module") 45 | def module_org(TestModule, org): 46 | module = accounts[0].deploy(TestModule, org) 47 | yield module 48 | 49 | 50 | def test_attach_share(org, share, module_share): 51 | """attach a share module""" 52 | assert share.isActiveModule(module_share) is False 53 | org.attachModule(share, module_share, {"from": accounts[0]}) 54 | assert share.isActiveModule(module_share) is True 55 | 56 | 57 | def test_detach_share(org, share, module_share): 58 | """detach a share module""" 59 | org.attachModule(share, module_share, {"from": accounts[0]}) 60 | org.detachModule(share, module_share, {"from": accounts[0]}) 61 | assert share.isActiveModule(module_share) is False 62 | 63 | 64 | def test_attach_via_share(share, module_share): 65 | """cannot attach directly via share""" 66 | with pytest.reverts("dev: only orgCode"): 67 | share.attachModule(module_share, {"from": accounts[0]}) 68 | 69 | 70 | def test_detach_via_share(org, share, module_share): 71 | """cannot detach directly via share""" 72 | org.attachModule(share, module_share, {"from": accounts[0]}) 73 | with pytest.reverts("dev: only orgCode"): 74 | share.detachModule(module_share, {"from": accounts[0]}) 75 | 76 | 77 | def test_attach_org(org, share, module_org): 78 | """attach an org module""" 79 | assert share.isActiveModule(module_org) is False 80 | org.attachModule(share, module_org, {"from": accounts[0]}) 81 | assert share.isActiveModule(module_org) is True 82 | 83 | 84 | def test_detach_org(org, share, module_org): 85 | """detach an org module""" 86 | org.attachModule(share, module_org, {"from": accounts[0]}) 87 | org.detachModule(share, module_org, {"from": accounts[0]}) 88 | assert share.isActiveModule(module_org) is False 89 | 90 | 91 | def test_already_active(org, share, module_org, module_share): 92 | """attach already active module""" 93 | org.attachModule(share, module_org, {"from": accounts[0]}) 94 | with pytest.reverts("dev: already active"): 95 | org.attachModule(share, module_org, {"from": accounts[0]}) 96 | org.attachModule(share, module_share, {"from": accounts[0]}) 97 | with pytest.reverts("dev: already active"): 98 | org.attachModule(share, module_share, {"from": accounts[0]}) 99 | 100 | 101 | def test_share_locked(org, share, module_share): 102 | """attach and detach - locked share""" 103 | org.setOrgShareRestriction(share, True, {"from": accounts[0]}) 104 | org.attachModule(share, module_share, {"from": accounts[0]}) 105 | org.detachModule(share, module_share, {"from": accounts[0]}) 106 | 107 | 108 | def test_attach_unknown_target(org, module_share): 109 | """attach and detach - unknown target""" 110 | with pytest.reverts("dev: unknown target"): 111 | org.attachModule(accounts[0], module_share, {"from": accounts[0]}) 112 | with pytest.reverts("dev: unknown target"): 113 | org.detachModule(accounts[0], module_share, {"from": accounts[0]}) 114 | -------------------------------------------------------------------------------- /tests/OrgCode/Modular/test_hooks_token.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import functools 4 | import pytest 5 | 6 | from brownie import accounts, compile_source 7 | 8 | module_source = """ 9 | pragma solidity 0.4.25; 10 | 11 | contract TestModule {{ 12 | 13 | address owner; 14 | bool hookReturn = true; 15 | 16 | constructor(address _owner) public {{ owner = _owner; }} 17 | function getOwner() external view returns (address) {{ return owner; }} 18 | 19 | function getPermissions() 20 | external 21 | pure 22 | returns 23 | ( 24 | bytes4[] permissions, 25 | bytes4[] hooks, 26 | uint256 hookBools 27 | ) 28 | {{ 29 | bytes4[] memory _hooks = new bytes4[](1); 30 | _hooks[0] = {}; 31 | return (permissions, _hooks, uint256(0)-1); 32 | }} 33 | 34 | function setReturn(bool _return) external {{ 35 | hookReturn = _return; 36 | }} 37 | 38 | function {}) external returns (bool) {{ 39 | if (!hookReturn) {{ 40 | revert(); 41 | }} 42 | return true; 43 | }} 44 | 45 | }}""" 46 | 47 | 48 | @pytest.fixture(scope="module", autouse=True) 49 | def testhook(approve_many, org, share): 50 | share.mint(org, 100000, {"from": accounts[0]}) 51 | hook = functools.partial(_hook, org, share) 52 | yield hook 53 | 54 | 55 | def test_checkTransfer(testhook, share): 56 | source = """checkTransfer( 57 | address[2] _addr, 58 | bytes32 _authID, 59 | bytes32[2] _id, 60 | uint8[2] _rating, 61 | uint16[2] _country, 62 | uint256 _value""" 63 | testhook( 64 | share.checkTransfer, (accounts[0], accounts[1], 1000), source, "0x70aaf928" 65 | ) 66 | 67 | 68 | def test_transferShares(testhook, share): 69 | source = """transferShares( 70 | address[2] _addr, 71 | bytes32[2] _id, 72 | uint8[2] _rating, 73 | uint16[2] _country, 74 | uint256 _value""" 75 | testhook(share.transfer, (accounts[1], 1000), source, "0x0675a5e0") 76 | 77 | 78 | def test_transferSharesCustodian(testhook, share, cust): 79 | source = """transferSharesCustodian( 80 | address _custodian, 81 | address[2] _addr, 82 | bytes32[2] _id, 83 | uint8[2] _rating, 84 | uint16[2] _country, 85 | uint256 _value""" 86 | share.transfer(accounts[2], 10000, {"from": accounts[0]}) 87 | share.transfer(cust, 5000, {"from": accounts[2]}) 88 | testhook( 89 | cust.transferInternal, 90 | (share, accounts[2], accounts[3], 100), 91 | source, 92 | "0xdc9d1da1", 93 | ) 94 | 95 | 96 | def test_totalSupplyChanged(testhook, org, share, cust): 97 | source = """totalSupplyChanged( 98 | address _addr, 99 | bytes32 _id, 100 | uint8 _rating, 101 | uint16 _country, 102 | uint256 _old, 103 | uint256 _new""" 104 | testhook(share.burn, (org, 1000), source, "0x741b5078") 105 | testhook(share.mint, (accounts[2], 1000), source, "0x741b5078") 106 | 107 | 108 | def _hook(org, share, fn, args, source, sig): 109 | args = list(args) + [{"from": accounts[0]}] 110 | source = module_source.format(sig, source) 111 | project = compile_source(source) 112 | module = project.TestModule.deploy(share, {"from": accounts[0]}) 113 | fn(*args) 114 | org.attachModule(share, module, {"from": accounts[0]}) 115 | fn(*args) 116 | module.setReturn(False, {"from": accounts[0]}) 117 | with pytest.reverts(): 118 | fn(*args) 119 | org.detachModule(share, module, {"from": accounts[0]}) 120 | fn(*args) 121 | -------------------------------------------------------------------------------- /tests/OrgCode/MultiSig/conftest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import functools 4 | import pytest 5 | 6 | from brownie import accounts, rpc 7 | 8 | 9 | @pytest.fixture(scope="module") 10 | def multisig(org): 11 | fn = functools.partial(_multisig, org) 12 | yield fn 13 | 14 | 15 | def _multisig(org, fn, *args): 16 | id1 = org.getID(accounts[-6]) 17 | args = list(args) + [{"from": accounts[-6]}] 18 | # check for failed call, no permission 19 | with pytest.reverts("dev: not permitted"): 20 | fn(*args) 21 | # give permission and check for successful call 22 | org.setAuthoritySignatures(id1, [fn.signature], True, {"from": accounts[0]}) 23 | assert "MultiSigCallApproved" in fn(*args).events 24 | rpc.revert() 25 | # give permission, threhold to 3, check for success and fails 26 | org.setAuthoritySignatures(id1, [fn.signature], True, {"from": accounts[0]}) 27 | org.setAuthorityThreshold(id1, 3, {"from": accounts[0]}) 28 | args[-1]["from"] = accounts[-6] 29 | assert "MultiSigCallApproved" not in fn(*args).events 30 | with pytest.reverts("dev: repeat caller"): 31 | fn(*args) 32 | args[-1]["from"] = accounts[-5] 33 | assert "MultiSigCallApproved" not in fn(*args).events 34 | with pytest.reverts("dev: repeat caller"): 35 | fn(*args) 36 | args[-1]["from"] = accounts[-4] 37 | assert "MultiSigCallApproved" in fn(*args).events 38 | -------------------------------------------------------------------------------- /tests/OrgCode/MultiSig/test_add_authority.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | def test_add_authority(org): 9 | """add an authority""" 10 | org.addAuthority([accounts[-1]], [], 2000000000, 1, {"from": accounts[0]}) 11 | assert org.getAuthority(org.getID(accounts[-1])) == (1, 1, 2000000000) 12 | 13 | 14 | def test_zero_threshold(org): 15 | """threshold zero""" 16 | with pytest.reverts("dev: threshold zero"): 17 | org.addAuthority([accounts[-1]], [], 2000000000, 0, {"from": accounts[0]}) 18 | 19 | 20 | def test_high_threshold(org): 21 | """threshold too low""" 22 | with pytest.reverts("dev: treshold > count"): 23 | org.addAuthority( 24 | [accounts[-1], accounts[-2]], [], 2000000000, 3, {"from": accounts[0]} 25 | ) 26 | with pytest.reverts("dev: treshold > count"): 27 | org.addAuthority([], [], 2000000000, 1, {"from": accounts[0]}) 28 | 29 | 30 | def test_repeat_addr(org): 31 | """repeat address in addAuthority array""" 32 | with pytest.reverts("dev: known address"): 33 | org.addAuthority( 34 | [accounts[-1], accounts[-1]], [], 2000000000, 1, {"from": accounts[0]} 35 | ) 36 | 37 | 38 | def test_known_address(org, share, id1): 39 | """known address""" 40 | with pytest.reverts("dev: known address"): 41 | org.addAuthority([accounts[0]], [], 2000000000, 1, {"from": accounts[0]}) 42 | share.mint(accounts[1], 100, {"from": accounts[0]}) 43 | with pytest.reverts("dev: known address"): 44 | org.addAuthority([accounts[1]], [], 2000000000, 1, {"from": accounts[0]}) 45 | 46 | 47 | def test_known_auth(org): 48 | """known authority""" 49 | org.addAuthority([accounts[-1]], [], 2000000000, 1, {"from": accounts[0]}) 50 | with pytest.reverts("dev: known authority"): 51 | org.addAuthority([accounts[-1]], [], 2000000000, 1, {"from": accounts[0]}) 52 | -------------------------------------------------------------------------------- /tests/OrgCode/MultiSig/test_check_external.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(id1, id2, org, nft, share): 10 | for i in range(6): 11 | accounts.add() 12 | accounts[0].transfer(accounts[-1], "1 ether") 13 | org.addAuthority(accounts[-6:-3], [], 2000000000, 1, {"from": accounts[0]}) 14 | share.mint(accounts[2], 1000, {"from": accounts[0]}) 15 | nft.mint(accounts[2], 1000, 0, "0x00", {"from": accounts[0]}) 16 | 17 | 18 | def test_share_modifyAuthorizedSupply(multisig, share): 19 | multisig(share.modifyAuthorizedSupply, 10000) 20 | 21 | 22 | def test_share_mint(multisig, share): 23 | multisig(share.mint, accounts[2], 1000) 24 | 25 | 26 | def test_share_burn(multisig, share): 27 | multisig(share.burn, accounts[2], 1000) 28 | 29 | 30 | def test_nft_modifyAuthorizedSupply(multisig, nft): 31 | multisig(nft.modifyAuthorizedSupply, 10000) 32 | 33 | 34 | def test_nft_mint(multisig, nft): 35 | multisig(nft.mint, accounts[2], 1000, 0, "0x00") 36 | 37 | 38 | def test_nft_burn(multisig, nft): 39 | multisig(nft.burn, 1, 500) 40 | 41 | 42 | def test_nft_modifyRange(multisig, nft): 43 | multisig(nft.modifyRange, 1, 0, "0xff") 44 | 45 | 46 | def test_nft_modifyRanges(multisig, nft): 47 | multisig(nft.modifyRanges, 30, 800, 0, "0xff") 48 | -------------------------------------------------------------------------------- /tests/OrgCode/MultiSig/test_check_multisig.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(org): 10 | for i in range(6): 11 | accounts.add() 12 | accounts[0].transfer(accounts[-1], "1 ether") 13 | org.addAuthority(accounts[-6:-3], [], 2000000000, 1, {"from": accounts[0]}) 14 | 15 | 16 | @pytest.fixture(scope="module") 17 | def cust(OwnedCustodian): 18 | yield accounts[0].deploy(OwnedCustodian, [accounts[0]], 1) 19 | 20 | 21 | @pytest.fixture(scope="module") 22 | def share2(BookShare, org): 23 | yield accounts[0].deploy(BookShare, org, "Test Share2", "TS2", 1000000) 24 | 25 | 26 | def test_setCountry(multisig, org): 27 | multisig(org.setCountry, 1, True, 1, [0] * 8) 28 | 29 | 30 | def test_setCountries(multisig, org): 31 | multisig(org.setCountries, [1, 2], [1, 1], [0, 0]) 32 | 33 | 34 | def test_setMemberLimits(multisig, org): 35 | multisig(org.setMemberLimits, [0] * 8) 36 | 37 | 38 | def test_setDocumentHash(multisig, org): 39 | multisig(org.setDocumentHash, "blah blah", "0x1234") 40 | 41 | 42 | def test_setVerifier(multisig, org): 43 | multisig(org.setVerifier, accounts[9], False) 44 | 45 | 46 | def test_addCustodian(multisig, org, cust): 47 | multisig(org.addCustodian, cust) 48 | 49 | 50 | def test_addOrgShare(multisig, org, share2): 51 | multisig(org.addOrgShare, share2) 52 | 53 | 54 | def test_setEntityRestriction(multisig, org): 55 | multisig(org.setEntityRestriction, "0x11", True) 56 | 57 | 58 | def test_setOrgShareRestriction(multisig, org, share): 59 | multisig(org.setOrgShareRestriction, share, False) 60 | 61 | 62 | def test_setGlobalRestriction(multisig, org): 63 | multisig(org.setGlobalRestriction, True) 64 | -------------------------------------------------------------------------------- /tests/OrgCode/test_OwnedCustodian.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts, compile_source 6 | 7 | source = """pragma solidity 0.4.25; 8 | 9 | contract TestCustodian { 10 | 11 | bytes32 public ownerID = 0x1234; 12 | 13 | function setID (bytes32 _id) public { ownerID = _id; } 14 | 15 | }""" 16 | 17 | 18 | @pytest.fixture(scope="module") 19 | def TestCustodian(): 20 | yield compile_source(source).TestCustodian 21 | 22 | 23 | @pytest.fixture(scope="module") 24 | def testcust(TestCustodian): 25 | cust = accounts[0].deploy(TestCustodian) 26 | yield cust 27 | 28 | 29 | def test_add(id1, id2, org, testcust): 30 | """add custodian""" 31 | org.addCustodian(testcust, {"from": accounts[0]}) 32 | assert org.getID(testcust) == testcust.ownerID() 33 | 34 | 35 | def test_add_twice(org, testcust, TestCustodian): 36 | """add custodian - already added""" 37 | org.addCustodian(testcust, {"from": accounts[0]}) 38 | with pytest.reverts("dev: known address"): 39 | org.addCustodian(testcust, {"from": accounts[0]}) 40 | c = accounts[1].deploy(TestCustodian) 41 | with pytest.reverts("dev: known ID"): 42 | org.addCustodian(c, {"from": accounts[0]}) 43 | 44 | 45 | def test_add_zero_id(org, testcust): 46 | """add custodian - zero id""" 47 | testcust.setID(0, {"from": accounts[0]}) 48 | with pytest.reverts("dev: zero ID"): 49 | org.addCustodian(testcust, {"from": accounts[0]}) 50 | 51 | 52 | def test_add_member_id(share, org, testcust): 53 | """custodian / member collision - member seen first""" 54 | share.mint(accounts[2], 100, {"from": accounts[0]}) 55 | id_ = org.getID.call(accounts[2]) 56 | testcust.setID(id_, {"from": accounts[0]}) 57 | with pytest.reverts("dev: known ID"): 58 | org.addCustodian(testcust, {"from": accounts[0]}) 59 | 60 | 61 | def test_add_member_id2(share, org, testcust): 62 | """custodian / member collision - custodian seen first""" 63 | share.mint(org, 100, {"from": accounts[0]}) 64 | id_ = org.getID.call(accounts[2]) 65 | testcust.setID(id_, {"from": accounts[0]}) 66 | org.addCustodian(testcust, {"from": accounts[0]}) 67 | with pytest.reverts(): 68 | share.transfer(accounts[2], 100, {"from": accounts[0]}) 69 | 70 | 71 | def test_cust_auth_id(org, share, testcust, rpc): 72 | """custodian / authority collisions""" 73 | org.addAuthority([accounts[-1]], [], 2000000000, 1, {"from": accounts[0]}) 74 | id_ = org.getID(accounts[-1]) 75 | testcust.setID(id_, {"from": accounts[0]}) 76 | with pytest.reverts("dev: authority ID"): 77 | org.addCustodian(testcust, {"from": accounts[0]}) 78 | rpc.revert() 79 | testcust.setID(id_, {"from": accounts[0]}) 80 | org.addCustodian(testcust, {"from": accounts[0]}) 81 | with pytest.reverts("dev: known ID"): 82 | org.addAuthority([accounts[-1]], [], 2000000000, 1, {"from": accounts[0]}) 83 | -------------------------------------------------------------------------------- /tests/OrgCode/test_add_token.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts, compile_source 6 | 7 | module_source = """pragma solidity 0.4.25; 8 | contract TestGovernance { 9 | address public orgCode; 10 | bool result; 11 | constructor(address _org) public { orgCode = _org; } 12 | function setResult(bool _result) external { result = _result; } 13 | function addOrgShare(address) external returns (bool) { return result; } 14 | }""" 15 | 16 | 17 | @pytest.fixture(scope="module") 18 | def gov(org): 19 | project = compile_source(module_source) 20 | gov = project.TestGovernance.deploy(org, {"from": accounts[0]}) 21 | org.setGovernance(gov, {"from": accounts[0]}) 22 | yield gov 23 | 24 | 25 | @pytest.fixture(scope="module") 26 | def share2(BookShare, org, accounts, share): 27 | t = accounts[0].deploy(BookShare, org, "Test Share2", "TS2", 1000000) 28 | yield t 29 | 30 | 31 | def test_add_share(org, share2): 32 | """add share""" 33 | org.addOrgShare(share2, {"from": accounts[0]}) 34 | 35 | 36 | def test_add_share_twice(org, share): 37 | """add share - already added""" 38 | with pytest.reverts("dev: already set"): 39 | org.addOrgShare(share, {"from": accounts[0]}) 40 | 41 | 42 | def test_add_share_wrong_org(org, OrgCode, BookShare): 43 | """add share - wrong org""" 44 | org2 = accounts[0].deploy(OrgCode, [accounts[0]], 1) 45 | share = accounts[0].deploy(BookShare, org2, "ABC", "ABC Share", 18) 46 | with pytest.reverts("dev: wrong owner"): 47 | org.addOrgShare(share, {"from": accounts[0]}) 48 | 49 | 50 | def test_add_share_governance_true(org, gov, share2): 51 | """add share - governance allows""" 52 | org.setGovernance(gov, {"from": accounts[0]}) 53 | gov.setResult(True, {"from": accounts[0]}) 54 | org.addOrgShare(share2, {"from": accounts[0]}) 55 | 56 | 57 | def test_add_share_governance_false(org, gov, share2): 58 | """add share - governance allows""" 59 | gov.setResult(False, {"from": accounts[0]}) 60 | with pytest.reverts("Action has not been approved"): 61 | org.addOrgShare(share2, {"from": accounts[0]}) 62 | 63 | 64 | def test_add_share_governance_removed(org, gov, share2): 65 | """add share - governance allows""" 66 | gov.setResult(False, {"from": accounts[0]}) 67 | with pytest.reverts("Action has not been approved"): 68 | org.addOrgShare(share2, {"from": accounts[0]}) 69 | org.setGovernance("0" * 40, {"from": accounts[0]}) 70 | org.addOrgShare(share2, {"from": accounts[0]}) 71 | -------------------------------------------------------------------------------- /tests/OrgCode/test_get_id.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(org, share, kyc2): 10 | org.setVerifier(kyc2, False, {"from": accounts[0]}) 11 | share.mint(org, 1000000, {"from": accounts[0]}) 12 | 13 | 14 | @pytest.fixture(scope="module") 15 | def kyc2(IDVerifierRegistrar, org): 16 | kyc2 = accounts[0].deploy(IDVerifierRegistrar, [accounts[0]], 1) 17 | org.setVerifier(kyc2, False, {"from": accounts[0]}) 18 | yield kyc2 19 | 20 | 21 | def test_unknown_address(org): 22 | """unknown address""" 23 | org.getID(accounts[0]) 24 | with pytest.reverts("Address not registered"): 25 | org.getID(accounts[1]) 26 | 27 | 28 | def test_registrar_restricted(org, kyc): 29 | """registrar restricted""" 30 | kyc.addMember("0x1234", 1, 1, 1, 9999999999, (accounts[1],), {"from": accounts[0]}) 31 | org.getID.transact(accounts[1]) 32 | org.setVerifier(kyc, True, {"from": accounts[0]}) 33 | with pytest.reverts("Verifier restricted"): 34 | org.getID(accounts[1]) 35 | 36 | 37 | def test_different_registrar(org, kyc, kyc2): 38 | """multiple registrars, different addresses""" 39 | kyc.addMember( 40 | "0x1234", 1, 1, 1, 9999999999, (accounts[1], accounts[3]), {"from": accounts[0]} 41 | ) 42 | kyc2.addMember( 43 | "0x1234", 1, 1, 1, 9999999999, (accounts[1], accounts[2]), {"from": accounts[0]} 44 | ) 45 | org.setVerifier(kyc2, True, {"from": accounts[0]}) 46 | org.getID.transact(accounts[1]) 47 | org.setVerifier(kyc2, False, {"from": accounts[0]}) 48 | with pytest.reverts("Address not registered"): 49 | org.getID(accounts[2]) 50 | org.getID(accounts[3]) 51 | 52 | 53 | def test_restrict_registrar(org, kyc, kyc2): 54 | """change registrar""" 55 | kyc.addMember( 56 | "0x1234", 1, 1, 1, 9999999999, (accounts[1], accounts[3]), {"from": accounts[0]} 57 | ) 58 | kyc2.addMember( 59 | "0x1234", 1, 1, 1, 9999999999, (accounts[1], accounts[2]), {"from": accounts[0]} 60 | ) 61 | org.getID(accounts[1]) 62 | org.setVerifier(kyc, True, {"from": accounts[0]}) 63 | org.getID(accounts[1]) 64 | org.getID(accounts[2]) 65 | with pytest.reverts("Address not registered"): 66 | org.getID(accounts[3]) 67 | 68 | 69 | def test_cust_auth_id(org, kyc, rpc): 70 | """member / authority collisions""" 71 | org.addAuthority([accounts[-1]], [], 2000000000, 1, {"from": accounts[0]}) 72 | id_ = org.getID(accounts[-1]) 73 | kyc.addMember( 74 | id_, 1, 1, 1, 9999999999, (accounts[1], accounts[3]), {"from": accounts[0]} 75 | ) 76 | with pytest.reverts("Address not registered"): 77 | org.getID(accounts[1]) 78 | rpc.revert() 79 | kyc.addMember( 80 | id_, 1, 1, 1, 9999999999, (accounts[1], accounts[3]), {"from": accounts[0]} 81 | ) 82 | org.getID.transact(accounts[1]) 83 | with pytest.reverts("dev: known ID"): 84 | org.addAuthority([accounts[-1]], [], 2000000000, 1, {"from": accounts[0]}) 85 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # ZAP-Tech/tests 2 | 3 | Unit testing of this project is performed with [Brownie](https://github.com/iamdefinitelyahuman/brownie). 4 | 5 | To run the tests: 6 | 7 | ```bash 8 | $ pytest tests/ 9 | ``` 10 | 11 | A [dockerfile](Dockerfile) is available if you are experiencing issues. 12 | 13 | ## Organization 14 | 15 | Tests for ZAP are sorted by the main contract being tested, then optionally by the main contract being interacted with and the methods being called. 16 | 17 | ## Subfolders 18 | 19 | * `custodians`: Test folders for custodian contracts. 20 | * `modules`: Test folders for module contracts. 21 | 22 | ## Test Folders 23 | 24 | * `OrgCode`: Tests that target [OrgCode](../contracts/OrgCode.sol). 25 | * `IDVerifierOrg`: Tests that target [IDVerifierOrg](../contracts/IDVerifierOrg.sol). 26 | * `IDVerifierRegistrar`: Tests that target [IDVerifierRegistrar](../contracts/IDVerifierRegistrar.sol). 27 | * `CertShare`: Tests that target [CertShare](../contracts/CertShare.sol). 28 | * `BookShare`: Tests that target [BookShare](../contracts/BookShare.sol). 29 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import functools 4 | import itertools 5 | import pytest 6 | 7 | from brownie import accounts 8 | from brownie.convert import to_bytes 9 | 10 | 11 | # test isolation, always use! 12 | 13 | 14 | @pytest.fixture(autouse=True) 15 | def isolation(fn_isolation): 16 | pass 17 | 18 | 19 | # share deployments / linking 20 | 21 | 22 | @pytest.fixture(scope="module") 23 | def share(BookShare, org, accounts): 24 | t = accounts[0].deploy(BookShare, org, "Test Share", "TST", 1000000) 25 | org.addOrgShare(t, {"from": accounts[0]}) 26 | yield t 27 | 28 | 29 | @pytest.fixture(scope="module") 30 | def share2(BookShare, org, accounts, share): 31 | t = accounts[0].deploy(BookShare, org, "Test Share2", "TS2", 1000000) 32 | org.addOrgShare(t, {"from": accounts[0]}) 33 | yield t 34 | 35 | 36 | @pytest.fixture(scope="module") 37 | def nft(CertShare, org, accounts): 38 | share = accounts[0].deploy(CertShare, org, "Test NFT", "NFT", 1000000) 39 | org.addOrgShare(share, {"from": accounts[0]}) 40 | yield share 41 | 42 | 43 | @pytest.fixture(scope="module") 44 | def org(OrgCode, accounts): 45 | org = accounts[0].deploy(OrgCode, [accounts[0]], 1) 46 | yield org 47 | 48 | 49 | @pytest.fixture(scope="module") 50 | def kyc(IDVerifierRegistrar, org, accounts): 51 | kyc = accounts[0].deploy(IDVerifierRegistrar, [accounts[0]], 1) 52 | org.setVerifier(kyc, False, {"from": accounts[0]}) 53 | yield kyc 54 | 55 | 56 | @pytest.fixture(scope="module") 57 | def cust(OwnedCustodian, accounts, org): 58 | accounts[0].deploy(OwnedCustodian, [accounts[0]], 1) 59 | org.addCustodian(OwnedCustodian[0], {"from": accounts[0]}) 60 | yield OwnedCustodian[0] 61 | 62 | 63 | # member approval 64 | 65 | 66 | @pytest.fixture(scope="module") 67 | def ownerid(org): 68 | yield org.ownerID() 69 | 70 | 71 | @pytest.fixture(scope="module") 72 | def set_countries(org): 73 | org.setCountries((1, 2, 3), (1, 1, 1), (0, 0, 0), {"from": accounts[0]}) 74 | 75 | 76 | @pytest.fixture(scope="module") 77 | def id1(set_countries, kyc): 78 | yield _add_member(kyc, 1, 1, 1) 79 | 80 | 81 | @pytest.fixture(scope="module") 82 | def id2(set_countries, kyc): 83 | yield _add_member(kyc, 2, 1, 2) 84 | 85 | 86 | @pytest.fixture(scope="module") 87 | def approve_many(id1, id2, kyc): 88 | product = list(itertools.product((2, 3), (1, 2))) 89 | for count, country, rating in [ 90 | (c, i[0], i[1]) for c, i in enumerate(product, start=3) 91 | ]: 92 | _add_member(kyc, count, country, rating) 93 | 94 | 95 | def _add_member(kyc, i, country, rating): 96 | id_ = to_bytes(f"member{i}".encode()).hex() 97 | kyc.addMember( 98 | id_, 99 | country, 100 | "0x000001", 101 | rating, 102 | 9999999999, 103 | (accounts[i],), 104 | {"from": accounts[0]}, 105 | ) 106 | return id_ 107 | 108 | 109 | @pytest.fixture 110 | def check_counts(org, approve_many, no_call_coverage): 111 | yield functools.partial(_check_countries, org) 112 | 113 | 114 | def _check_countries(org, one=(0, 0, 0), two=(0, 0, 0), three=(0, 0, 0)): 115 | assert org.getMemberCounts()[0][:3] == ( 116 | one[0] + two[0] + three[0], 117 | one[1] + two[1] + three[1], 118 | one[2] + two[2] + three[2], 119 | ) 120 | assert org.getCountry(1)[1][:3] == one 121 | assert org.getCountry(2)[1][:3] == two 122 | assert org.getCountry(3)[1][:3] == three 123 | -------------------------------------------------------------------------------- /tests/custodians/OwnedCustodian/Modular/conftest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | 6 | @pytest.fixture(scope="module", autouse=True) 7 | def modular_setup(approve_many, share, org, accounts): 8 | share.mint(org, 100000, {"from": accounts[0]}) 9 | share.transfer(accounts[2], 10000, {"from": accounts[0]}) 10 | -------------------------------------------------------------------------------- /tests/custodians/OwnedCustodian/Modular/test_hooks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import functools 4 | import pytest 5 | 6 | from brownie import accounts, compile_source 7 | 8 | module_source = """ 9 | pragma solidity 0.4.25; 10 | 11 | contract TestModule {{ 12 | 13 | address owner; 14 | bool hookReturn = true; 15 | 16 | constructor(address _owner) public {{ owner = _owner; }} 17 | function getOwner() external view returns (address) {{ return owner; }} 18 | 19 | function getPermissions() 20 | external 21 | pure 22 | returns 23 | ( 24 | bytes4[] permissions, 25 | bytes4[] hooks, 26 | uint256 hookBools 27 | ) 28 | {{ 29 | bytes4[] memory _hooks = new bytes4[](1); 30 | _hooks[0] = {}; 31 | return (permissions, _hooks, uint256(0)-1); 32 | }} 33 | 34 | function setReturn(bool _return) external {{ 35 | hookReturn = _return; 36 | }} 37 | 38 | function {}) external returns (bool) {{ 39 | if (!hookReturn) {{ 40 | revert(); 41 | }} 42 | return true; 43 | }} 44 | 45 | }}""" 46 | 47 | 48 | @pytest.fixture(scope="module") 49 | def check_hooks(cust): 50 | yield functools.partial(_hook, cust) 51 | 52 | 53 | def test_custodian_sentShares(check_hooks, share, cust): 54 | source = """sentShares( 55 | address _share, 56 | address _to, 57 | uint256 _value""" 58 | share.transfer(cust, 10000, {"from": accounts[0]}) 59 | check_hooks(cust.transfer, (share, accounts[0], 100), source, "0xa110724f") 60 | 61 | 62 | def test_custodian_receivedShares(check_hooks, share, cust): 63 | source = """receivedShares( 64 | address _share, 65 | address _from, 66 | uint256 _value""" 67 | check_hooks(share.transfer, (cust, 1000), source, "0xa000ff88") 68 | 69 | 70 | def test_custodian_internalTransfer(check_hooks, share, cust): 71 | source = """internalTransfer( 72 | address _share, 73 | address _from, 74 | address _to, 75 | uint256 _value""" 76 | share.transfer(cust, 10000, {"from": accounts[0]}) 77 | check_hooks( 78 | cust.transferInternal, 79 | (share, accounts[0], accounts[2], 100), 80 | source, 81 | "0x44a29e2a", 82 | ) 83 | 84 | 85 | def _hook(cust, fn, args, source, sig): 86 | args = list(args) + [{"from": accounts[0]}] 87 | source = module_source.format(sig, source) 88 | project = compile_source(source) 89 | module = project.TestModule.deploy(cust, {"from": accounts[0]}) 90 | fn(*args) 91 | cust.attachModule(module, {"from": accounts[0]}) 92 | fn(*args) 93 | module.setReturn(False, {"from": accounts[0]}) 94 | with pytest.reverts(): 95 | fn(*args) 96 | cust.detachModule(module, {"from": accounts[0]}) 97 | fn(*args) 98 | -------------------------------------------------------------------------------- /tests/custodians/OwnedCustodian/Modular/test_permissions_cust.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import functools 4 | import pytest 5 | 6 | from brownie import accounts, compile_source 7 | 8 | module_source = """ 9 | pragma solidity 0.4.25; 10 | 11 | contract TestModule {{ 12 | 13 | address owner; 14 | 15 | constructor(address _owner) public {{ owner = _owner; }} 16 | function getOwner() external view returns (address) {{ return owner; }} 17 | 18 | function getPermissions() 19 | external 20 | pure 21 | returns 22 | ( 23 | bytes4[] permissions, 24 | bytes4[] hooks, 25 | uint256 hookBools 26 | ) 27 | {{ 28 | bytes4[] memory _permissions = new bytes4[](1); 29 | _permissions[0] = {}; 30 | return (_permissions, hooks, 0); 31 | }} 32 | 33 | function test(bytes _data) external {{ 34 | require(owner.call(_data)); 35 | }} 36 | 37 | }}""" 38 | 39 | 40 | @pytest.fixture(scope="module", autouse=True) 41 | def permissions_setup(share, cust): 42 | share.transfer(cust, 10000, {"from": accounts[2]}) 43 | 44 | 45 | @pytest.fixture(scope="module") 46 | def check_permission(cust): 47 | yield functools.partial(_check_permission, cust) 48 | 49 | 50 | def test_is_permitted(cust): 51 | """check permitted""" 52 | source = module_source.format("0xbb2a8522") 53 | project = compile_source(source) 54 | module = project.TestModule.deploy(cust, {"from": accounts[0]}) 55 | assert not cust.isPermittedModule(module, "0xbb2a8522") 56 | assert not cust.isPermittedModule(module, "0xbeabacc8") 57 | cust.attachModule(module, {"from": accounts[0]}) 58 | assert cust.isPermittedModule(module, "0xbb2a8522") 59 | assert not cust.isPermittedModule(module, "0xbeabacc8") 60 | cust.detachModule(module, {"from": accounts[0]}) 61 | assert not cust.isPermittedModule(module, "0xbb2a8522") 62 | assert not cust.isPermittedModule(module, "0xbeabacc8") 63 | 64 | 65 | def test_share_detachModule(cust): 66 | """detach module""" 67 | source = module_source.format("0xbb2a8522") 68 | project = compile_source(source) 69 | module = project.TestModule.deploy(cust, {"from": accounts[0]}) 70 | with pytest.reverts(): 71 | module.test(cust.detachModule.encode_input(module), {"from": accounts[0]}) 72 | cust.attachModule(module, {"from": accounts[0]}) 73 | module.test(cust.detachModule.encode_input(module), {"from": accounts[0]}) 74 | with pytest.reverts(): 75 | module.test(cust.detachModule.encode_input(module), {"from": accounts[0]}) 76 | 77 | 78 | def test_custodian_transfer(check_permission, share, cust): 79 | """custodian transfer""" 80 | check_permission("0xbeabacc8", cust.transfer.encode_input(share, accounts[2], 4000)) 81 | assert share.balanceOf(accounts[2]) == 4000 82 | assert share.custodianBalanceOf(accounts[2], cust) == 6000 83 | 84 | 85 | def test_custodian_transferInternal(check_permission, share, cust): 86 | check_permission( 87 | "0x2f98a4c3", 88 | cust.transferInternal.encode_input(share, accounts[2], accounts[3], 4000), 89 | ) 90 | assert share.custodianBalanceOf(accounts[2], cust) == 6000 91 | assert share.custodianBalanceOf(accounts[3], cust) == 4000 92 | 93 | 94 | def _check_permission(cust, sig, calldata): 95 | 96 | # deploy the module 97 | source = module_source.format(sig) 98 | project = compile_source(source) 99 | module = project.TestModule.deploy(cust, {"from": accounts[0]}) 100 | 101 | # check that call fails prior to attaching module 102 | with pytest.reverts(): 103 | module.test(calldata, {"from": accounts[0]}) 104 | 105 | # attach the module and check that the call now succeeds 106 | cust.attachModule(module, {"from": accounts[0]}) 107 | module.test(calldata, {"from": accounts[0]}) 108 | 109 | # detach the module and check that the call fails again 110 | cust.detachModule(module, {"from": accounts[0]}) 111 | with pytest.reverts(): 112 | module.test(calldata, {"from": accounts[0]}) 113 | -------------------------------------------------------------------------------- /tests/modules/GovernanceModule/conftest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | import time 5 | 6 | from brownie import accounts 7 | 8 | 9 | @pytest.fixture(scope="module") 10 | def cp(MultiCheckpointModule, org, share): 11 | cp = accounts[0].deploy(MultiCheckpointModule, org) 12 | org.attachModule(share, cp, {"from": accounts[0]}) 13 | yield cp 14 | 15 | 16 | @pytest.fixture(scope="module") 17 | def gov(GovernanceModule, org, cp): 18 | gov = accounts[0].deploy(GovernanceModule, org, cp) 19 | org.setGovernance(gov, {"from": accounts[0]}) 20 | yield gov 21 | 22 | 23 | @pytest.fixture(scope="module") 24 | def share2(BookShare, org, cp): 25 | t = accounts[0].deploy(BookShare, org, "", "", 1000000) 26 | org.addOrgShare(t, {"from": accounts[0]}) 27 | org.attachModule(t, cp, {"from": accounts[0]}) 28 | yield t 29 | 30 | 31 | @pytest.fixture(scope="module") 32 | def share3(BookShare, org, cp): 33 | t = accounts[0].deploy(BookShare, org, "", "", 1000000) 34 | org.addOrgShare(t, {"from": accounts[0]}) 35 | org.attachModule(t, cp, {"from": accounts[0]}) 36 | yield t 37 | 38 | 39 | @pytest.fixture(scope="module") 40 | def cptime(): 41 | yield int(time.time() + 100) 42 | 43 | 44 | @pytest.fixture(scope="module") 45 | def proposal(approve_many, cp, share, share2, share3, gov, cptime): 46 | for i in range(1, 4): 47 | share.mint(accounts[i], 1000, {"from": accounts[0]}) 48 | for i in range(3, 6): 49 | share2.mint(accounts[i], 1000, {"from": accounts[0]}) 50 | share3.mint(accounts[5], 1000, {"from": accounts[0]}) 51 | cp.newCheckpoint(share, cptime, {"from": accounts[0]}) 52 | cp.newCheckpoint(share2, cptime, {"from": accounts[0]}) 53 | cp.newCheckpoint(share3, cptime, {"from": accounts[0]}) 54 | gov.newProposal( 55 | "0x1234", 56 | cptime, 57 | cptime + 100, 58 | cptime + 200, 59 | "test proposal", 60 | "0" * 40, 61 | "0x", 62 | {"from": accounts[0]}, 63 | ) 64 | -------------------------------------------------------------------------------- /tests/modules/GovernanceModule/test_close_proposal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts, rpc 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(proposal, gov, share, share2): 10 | gov.newVote("0x1234", 5000, 0, [share], [1], {"from": accounts[0]}) 11 | gov.newVote("0x1234", 5000, 0, [share, share2], [1, 2], {"from": accounts[0]}) 12 | 13 | 14 | def test_close_proposal(gov): 15 | rpc.sleep(210) 16 | gov.voteOnProposal("0x1234", 1, {"from": accounts[1]}) 17 | rpc.sleep(110) 18 | gov.closeProposal("0x1234", {"from": accounts[0]}) 19 | 20 | 21 | def test_close_proposal_no_votes(gov): 22 | rpc.sleep(310) 23 | gov.closeProposal("0x1234", {"from": accounts[0]}) 24 | 25 | 26 | def test_close_proposal_not_ended(gov): 27 | with pytest.reverts("dev: voting has not finished"): 28 | gov.closeProposal("0x1234", {"from": accounts[0]}) 29 | 30 | 31 | def test_close_proposal_invalid_id(gov): 32 | with pytest.reverts("dev: proposal not active"): 33 | gov.closeProposal("0x1111", {"from": accounts[0]}) 34 | 35 | 36 | def test_close_proposal_already_closed(gov): 37 | rpc.sleep(310) 38 | gov.closeProposal("0x1234", {"from": accounts[0]}) 39 | with pytest.reverts("dev: proposal not active"): 40 | gov.closeProposal("0x1234", {"from": accounts[0]}) 41 | 42 | 43 | def test_close_proposal_no_end_not_passing(gov, cptime, share3): 44 | gov.newProposal( 45 | "0xffff", 46 | cptime, 47 | cptime + 100, 48 | 0, 49 | "test proposal", 50 | "0" * 40, 51 | "0x", 52 | {"from": accounts[0]}, 53 | ) 54 | gov.newVote("0xffff", 5000, 0, [share3], [1], {"from": accounts[0]}) 55 | rpc.sleep(210) 56 | with pytest.reverts("dev: proposal has not passed"): 57 | gov.closeProposal("0xffff", {"from": accounts[0]}) 58 | 59 | 60 | def test_close_proposal_no_end_passing(gov, share3, cptime): 61 | gov.newProposal( 62 | "0xffff", 63 | cptime, 64 | cptime + 100, 65 | 0, 66 | "test proposal", 67 | "0" * 40, 68 | "0x", 69 | {"from": accounts[0]}, 70 | ) 71 | gov.newVote("0xffff", 5000, 0, [share3], [1], {"from": accounts[0]}) 72 | rpc.sleep(210) 73 | gov.voteOnProposal("0xffff", 1, {"from": accounts[5]}) 74 | gov.closeProposal("0xffff", {"from": accounts[0]}) 75 | -------------------------------------------------------------------------------- /tests/modules/GovernanceModule/test_custodial_vote.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts, rpc 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(proposal, gov, share, share2, cust): 10 | gov.newVote("0x1234", 5000, 0, [share], [1], {"from": accounts[0]}) 11 | gov.newVote("0x1234", 5000, 0, [share, share2], [1, 2], {"from": accounts[0]}) 12 | for i in range(1, 4): 13 | share.transfer(cust, 500, {"from": accounts[i]}) 14 | 15 | 16 | def test_vote(gov, cust): 17 | rpc.sleep(210) 18 | gov.voteOnProposal("0x1234", 1, {"from": accounts[1]}) 19 | gov.custodialVoteOnProposal("0x1234", cust, {"from": accounts[1]}) 20 | gov.voteOnProposal("0x1234", 1, {"from": accounts[4]}) 21 | gov.custodialVoteOnProposal("0x1234", cust, {"from": accounts[4]}) 22 | gov.voteOnProposal("0x1234", 1, {"from": accounts[8]}) 23 | gov.custodialVoteOnProposal("0x1234", cust, {"from": accounts[8]}) 24 | 25 | 26 | def test_vote_invalid(gov, cust): 27 | with pytest.reverts("dev: proposal not active"): 28 | gov.custodialVoteOnProposal("0x1111", cust, {"from": accounts[1]}) 29 | 30 | 31 | def test_vote_ended(gov, cust): 32 | rpc.sleep(210) 33 | gov.voteOnProposal("0x1234", 1, {"from": accounts[1]}) 34 | rpc.sleep(110) 35 | with pytest.reverts("dev: voting has finished"): 36 | gov.custodialVoteOnProposal("0x1234", cust, {"from": accounts[1]}) 37 | 38 | 39 | def test_custodial_has_not_voted(gov, cust): 40 | rpc.sleep(210) 41 | gov.voteOnProposal("0x1234", 1, {"from": accounts[2]}) 42 | with pytest.reverts("dev: has not voted"): 43 | gov.custodialVoteOnProposal("0x1234", cust, {"from": accounts[1]}) 44 | 45 | 46 | def test_custodial_already_voted(gov, cust): 47 | rpc.sleep(210) 48 | gov.voteOnProposal("0x1234", 1, {"from": accounts[1]}) 49 | gov.custodialVoteOnProposal("0x1234", cust, {"from": accounts[1]}) 50 | with pytest.reverts("dev: has voted with custodian"): 51 | gov.custodialVoteOnProposal("0x1234", cust, {"from": accounts[1]}) 52 | -------------------------------------------------------------------------------- /tests/modules/GovernanceModule/test_new_proposal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | proposal_inputs = [ 8 | "0x1234", 9 | 1500000000, 10 | 2000000000, 11 | 2100000000, 12 | "test proposal", 13 | "0" * 40, 14 | "0x", 15 | ] 16 | 17 | 18 | @pytest.fixture() 19 | def pinputs(): 20 | yield proposal_inputs.copy() + [{"from": accounts[0]}] 21 | 22 | 23 | def test_new_proposal(gov, pinputs): 24 | gov.newProposal(*pinputs) 25 | 26 | 27 | def test_new_proposal_no_end(gov, pinputs): 28 | pinputs[3] = 0 29 | gov.newProposal(*pinputs) 30 | 31 | 32 | def test_new_proposal_exists(gov, pinputs): 33 | gov.newProposal(*pinputs) 34 | with pytest.reverts("dev: proposal already exists"): 35 | gov.newProposal(*pinputs) 36 | 37 | 38 | def test_new_proposal_start_before_now(gov, pinputs): 39 | pinputs[2] = 151000000 40 | with pytest.reverts("dev: start < now"): 41 | gov.newProposal(*pinputs) 42 | 43 | 44 | def test_new_proposal_start_before_cp(gov, pinputs): 45 | pinputs[1] = 2100000000 46 | with pytest.reverts("dev: start < checkpoint"): 47 | gov.newProposal(*pinputs) 48 | 49 | 50 | def test_new_proposal_end_before_start(gov, pinputs): 51 | pinputs[3] = 190000000 52 | with pytest.reverts("dev: end < start"): 53 | gov.newProposal(*pinputs) 54 | -------------------------------------------------------------------------------- /tests/modules/GovernanceModule/test_new_vote.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(cp, share, share2, share3, gov, cptime): 10 | cp.newCheckpoint(share, cptime, {"from": accounts[0]}) 11 | cp.newCheckpoint(share2, cptime, {"from": accounts[0]}) 12 | cp.newCheckpoint(share3, cptime + 50, {"from": accounts[0]}) 13 | gov.newProposal( 14 | "0x1234", 15 | cptime, 16 | cptime + 100, 17 | cptime + 200, 18 | "test proposal", 19 | "0" * 40, 20 | "0x", 21 | {"from": accounts[0]}, 22 | ) 23 | 24 | 25 | def test_new_vote(gov, share, share2): 26 | gov.newVote("0x1234", 5000, 0, [share, share2], [1, 2], {"from": accounts[0]}) 27 | 28 | 29 | def test_new_vote_wrong_state(gov, share, share2): 30 | with pytest.reverts("dev: wrong state"): 31 | gov.newVote("0x1111", 5000, 0, [share, share2], [1, 2], {"from": accounts[0]}) 32 | 33 | 34 | def test_new_vote_no_shares(gov): 35 | with pytest.reverts("dev: empty share array"): 36 | gov.newVote("0x1234", 5000, 0, [], [], {"from": accounts[0]}) 37 | 38 | 39 | def test_new_vote_mismatch(gov, share, share2): 40 | with pytest.reverts("dev: array length mismatch"): 41 | gov.newVote( 42 | "0x1234", 5000, 0, [share, share2], [1, 2, 3], {"from": accounts[0]} 43 | ) 44 | 45 | 46 | def test_new_vote_required_pct(gov, share, share2): 47 | with pytest.reverts("dev: required pct too high"): 48 | gov.newVote("0x1234", 11000, 0, [share, share2], [1, 2], {"from": accounts[0]}) 49 | 50 | 51 | def test_new_vote_quorum_pct(gov, share, share2): 52 | with pytest.reverts("dev: quorum pct too high"): 53 | gov.newVote( 54 | "0x1234", 5000, 11000, [share, share2], [1, 2], {"from": accounts[0]} 55 | ) 56 | 57 | 58 | def test_new_vote_repeat_share(gov, share): 59 | with pytest.reverts("dev: share repeat"): 60 | gov.newVote("0x1234", 5000, 0, [share, share], [1, 2], {"from": accounts[0]}) 61 | 62 | 63 | def test_new_vote_no_checkpoint(gov, share, share3): 64 | with pytest.reverts("dev: no checkpoint"): 65 | gov.newVote("0x1234", 5000, 0, [share, share3], [1, 2], {"from": accounts[0]}) 66 | -------------------------------------------------------------------------------- /tests/modules/GovernanceModule/test_permissions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import functools 4 | import pytest 5 | 6 | from brownie import accounts, rpc 7 | 8 | 9 | @pytest.fixture(scope="module", autouse=True) 10 | def setup(share, share2, gov, cp, id1, cptime): 11 | share.mint(accounts[1], 10000, {"from": accounts[0]}) 12 | cp.newCheckpoint(share, cptime, {"from": accounts[0]}) 13 | 14 | 15 | @pytest.fixture(scope="module") 16 | def vote(gov, org, share, cptime): 17 | yield functools.partial(_vote, gov, org, share, cptime) 18 | 19 | 20 | def _vote(gov, org, share, cptime, approval_abi): 21 | gov.newProposal( 22 | "0xffff", 23 | cptime, 24 | cptime + 100, 25 | 0, 26 | "test proposal", 27 | org, 28 | approval_abi, 29 | {"from": accounts[0]}, 30 | ) 31 | gov.newVote("0xffff", 5000, 0, [share], [1], {"from": accounts[0]}) 32 | rpc.sleep(210) 33 | gov.voteOnProposal("0xffff", 1, {"from": accounts[1]}) 34 | gov.closeProposal("0xffff", {"from": accounts[0]}) 35 | 36 | 37 | def test_modify_authorized_supply_not_approved(share): 38 | with pytest.reverts(): 39 | share.modifyAuthorizedSupply(200000000, {"from": accounts[0]}) 40 | 41 | 42 | def test_modify_authorized_supply_approved(vote, share, share2, gov): 43 | vote(gov.modifyAuthorizedSupply.encode_input(share, 200000000)) 44 | # wrong share 45 | with pytest.reverts(): 46 | share2.modifyAuthorizedSupply(200000000, {"from": accounts[0]}) 47 | # wrong amount 48 | with pytest.reverts(): 49 | share.modifyAuthorizedSupply(190000000, {"from": accounts[0]}) 50 | share.modifyAuthorizedSupply(200000000, {"from": accounts[0]}) 51 | # call is only approved once 52 | with pytest.reverts(): 53 | share.modifyAuthorizedSupply(200000000, {"from": accounts[0]}) 54 | 55 | 56 | def test_add_share_not_approved(BookShare, org): 57 | t = accounts[0].deploy(BookShare, org, "", "", 1000000) 58 | with pytest.reverts(): 59 | org.addOrgShare(t, {"from": accounts[0]}) 60 | 61 | 62 | def test_add_share_approved(BookShare, vote, org, gov): 63 | share3 = accounts[0].deploy(BookShare, org, "", "", 1000000) 64 | vote(gov.addOrgShare.encode_input(share3)) 65 | share4 = accounts[0].deploy(BookShare, org, "", "", 1000000) 66 | # wrong share 67 | with pytest.reverts(): 68 | org.addOrgShare(share4, {"from": accounts[0]}) 69 | org.addOrgShare(share3, {"from": accounts[0]}) 70 | -------------------------------------------------------------------------------- /tests/modules/GovernanceModule/test_vote.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts, rpc 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def setup(proposal, gov, share, share2): 10 | gov.newVote("0x1234", 5000, 0, [share], [1], {"from": accounts[0]}) 11 | gov.newVote("0x1234", 5000, 0, [share, share2], [1, 2], {"from": accounts[0]}) 12 | 13 | 14 | def test_vote(gov): 15 | rpc.sleep(210) 16 | gov.voteOnProposal("0x1234", 1, {"from": accounts[1]}) 17 | gov.voteOnProposal("0x1234", 0, {"from": accounts[3]}) 18 | gov.voteOnProposal("0x1234", 1, {"from": accounts[5]}) 19 | gov.voteOnProposal("0x1234", 1, {"from": accounts[8]}) 20 | 21 | 22 | def test_vote_invalid(gov): 23 | with pytest.reverts("dev: invalid vote"): 24 | gov.voteOnProposal("0x1234", 2, {"from": accounts[1]}) 25 | 26 | 27 | def test_vote_invalid_id(gov): 28 | rpc.sleep(210) 29 | with pytest.reverts("dev: invalid id"): 30 | gov.voteOnProposal("0x1111", 1, {"from": accounts[1]}) 31 | 32 | 33 | def test_vote_proposal_closed(gov): 34 | rpc.sleep(310) 35 | gov.closeProposal("0x1234", {"from": accounts[0]}) 36 | with pytest.reverts("dev: proposal has closed"): 37 | gov.voteOnProposal("0x1234", 1, {"from": accounts[1]}) 38 | 39 | 40 | def test_vote_ended(gov): 41 | rpc.sleep(310) 42 | with pytest.reverts("dev: voting has finished"): 43 | gov.voteOnProposal("0x1234", 1, {"from": accounts[1]}) 44 | 45 | 46 | def test_vote_already_voted(gov): 47 | rpc.sleep(210) 48 | gov.voteOnProposal("0x1234", 1, {"from": accounts[1]}) 49 | with pytest.reverts("dev: already voted"): 50 | gov.voteOnProposal("0x1234", 1, {"from": accounts[1]}) 51 | 52 | 53 | def test_vote_not_started(gov): 54 | with pytest.reverts("dev: vote has not started"): 55 | gov.voteOnProposal("0x1234", 1, {"from": accounts[1]}) 56 | -------------------------------------------------------------------------------- /tests/modules/MultiCheckpointModule/conftest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts, rpc 6 | 7 | 8 | @pytest.fixture(scope="module") 9 | def cp(MultiCheckpointModule, org, share): 10 | cp = accounts[0].deploy(MultiCheckpointModule, org) 11 | org.attachModule(share, cp, {"from": accounts[0]}) 12 | yield cp 13 | 14 | 15 | @pytest.fixture 16 | def cptime(): 17 | yield rpc.time() + 100 18 | -------------------------------------------------------------------------------- /tests/modules/MultiCheckpointModule/test_new_checkpoint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts, rpc 6 | 7 | 8 | def test_set_checkpoint(cp, share, cptime): 9 | """set a checkpoint""" 10 | cp.newCheckpoint(share, cptime, {"from": accounts[0]}) 11 | 12 | 13 | def test_set_checkpoint_time(cp, share): 14 | """set a checkpoint - time already passed""" 15 | with pytest.reverts("dev: time"): 16 | cp.newCheckpoint(share, rpc.time() - 100, {"from": accounts[0]}) 17 | with pytest.reverts("dev: time"): 18 | cp.newCheckpoint(share, rpc.time(), {"from": accounts[0]}) 19 | 20 | 21 | def test_set_checkpoint_restricted_share(cp, org, share, cptime): 22 | """set a checkpoint - restricted share""" 23 | org.setOrgShareRestriction(share, True, {"from": accounts[0]}) 24 | with pytest.reverts("dev: share"): 25 | cp.newCheckpoint(share, cptime, {"from": accounts[0]}) 26 | 27 | 28 | def test_set_checkpoint_not_share(cp, org, cptime): 29 | """set a checkpoint - not a share""" 30 | with pytest.reverts("dev: share"): 31 | cp.newCheckpoint(org, cptime, {"from": accounts[0]}) 32 | with pytest.reverts("dev: share"): 33 | cp.newCheckpoint(accounts[3], cptime, {"from": accounts[0]}) 34 | 35 | 36 | def test_set_checkpoint_already_set(cp, share, cptime): 37 | """set a checkpoint - already exists""" 38 | cp.newCheckpoint(share, cptime, {"from": accounts[0]}) 39 | with pytest.reverts("dev: already set"): 40 | cp.newCheckpoint(share, cptime, {"from": accounts[0]}) 41 | 42 | 43 | def test_set_checkpoint_second_share(cp, org, share, share2, cptime): 44 | """set a checkpoint - second share""" 45 | # share2 = accounts[0].deploy(BookShare, org, "Test Share", "TST", 1000000) 46 | # org.addOrgShare(share2, {'from': accounts[0]}) 47 | org.attachModule(share2, cp, {"from": accounts[0]}) 48 | cp.newCheckpoint(share, cptime, {"from": accounts[0]}) 49 | cp.newCheckpoint(share2, cptime, {"from": accounts[0]}) 50 | -------------------------------------------------------------------------------- /tests/modules/VestedOptions/conftest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import functools 4 | import pytest 5 | 6 | from brownie import accounts, rpc 7 | 8 | 9 | @pytest.fixture(scope="module") 10 | def options(VestedOptions, org, share): 11 | o = accounts[0].deploy(VestedOptions, share, org, 1, 100, 6, accounts[0]) 12 | org.attachModule(share, o, {"from": accounts[0]}) 13 | yield o 14 | 15 | 16 | @pytest.fixture(scope="module") 17 | def issueoptions(options): 18 | yield functools.partial(_issue, options) 19 | 20 | 21 | @pytest.fixture 22 | def sleep(): 23 | yield _sleep 24 | 25 | 26 | def _issue(options, id_, price): 27 | options.issueOptions( 28 | id_, 29 | price, 30 | False, 31 | [100, 100, 100, 100, 100], 32 | [1, 2, 3, 4, 5], 33 | {"from": accounts[0]}, 34 | ) 35 | 36 | 37 | def _sleep(months): 38 | now = int(rpc.time() // 2592000 + 1) * 2592000 39 | rpc.sleep(now - rpc.time() + 1 + 2592000 * (months - 1)) 40 | rpc.mine() 41 | -------------------------------------------------------------------------------- /tests/modules/VestedOptions/test_accellerate_vesting.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from brownie import accounts 4 | 5 | 6 | def test_accellerate_fully_unvested(options, id1, issueoptions, sleep, share): 7 | """fully unvested""" 8 | issueoptions(id1, 10) 9 | issueoptions(id1, 20) 10 | options.accellerateVesting(id1, {"from": accounts[0]}) 11 | assert options.getOptions(id1) == (1000, 0, [(10, 1), (20, 1)]) 12 | sleep(100) 13 | assert options.getOptions(id1) == (1000, 0, [(10, 1), (20, 1)]) 14 | sleep(1) 15 | assert options.getOptions(id1) == (0, 0, []) 16 | 17 | 18 | def test_accellerate_partially_unvested(options, id1, issueoptions, sleep): 19 | """partially vested""" 20 | issueoptions(id1, 10) 21 | issueoptions(id1, 20) 22 | sleep(2) 23 | options.accellerateVesting(id1, {"from": accounts[0]}) 24 | assert options.getOptions(id1) == (1000, 0, [(10, 1), (20, 1)]) 25 | assert options.getTotalOptionsAtPrice(10) == (500, 0) 26 | assert options.getTotalOptionsAtPrice(20) == (500, 0) 27 | assert options.totalOptions() == 1000 28 | sleep(98) 29 | assert options.getOptions(id1) == (1000, 0, [(10, 1), (20, 1)]) 30 | sleep(1) 31 | assert options.getOptions(id1) == (0, 0, []) 32 | 33 | 34 | def test_accellerate_already_vested(options, id1, issueoptions, sleep): 35 | """already vested""" 36 | issueoptions(id1, 10) 37 | issueoptions(id1, 20) 38 | sleep(7) 39 | options.accellerateVesting(id1, {"from": accounts[0]}) 40 | assert options.getOptions(id1) == (1000, 0, [(10, 1), (20, 1)]) 41 | assert options.getTotalOptionsAtPrice(10) == (500, 0) 42 | assert options.getTotalOptionsAtPrice(20) == (500, 0) 43 | assert options.totalOptions() == 1000 44 | sleep(93) 45 | assert options.getOptions(id1) == (1000, 0, [(10, 1), (20, 1)]) 46 | sleep(1) 47 | assert options.getOptions(id1) == (0, 0, []) 48 | 49 | 50 | def test_accellerate_multiple_expirations(options, id1, issueoptions, sleep): 51 | """accellerate multiple, different expirations""" 52 | issueoptions(id1, 10) 53 | sleep(2) 54 | issueoptions(id1, 10) 55 | sleep(2) 56 | issueoptions(id1, 10) 57 | sleep(1) 58 | options.accellerateVesting(id1, {"from": accounts[0]}) 59 | assert options.getOptions(id1) == (1500, 0, [(10, 3)]) 60 | assert options.getTotalOptionsAtPrice(10) == (1500, 0) 61 | assert options.totalOptions() == 1500 62 | 63 | 64 | def test_accellerate_multiple_prices(options, id1, issueoptions, sleep): 65 | """accellerate multiple, different prices and expirations""" 66 | issueoptions(id1, 10) 67 | sleep(2) 68 | issueoptions(id1, 20) 69 | sleep(2) 70 | issueoptions(id1, 10) 71 | issueoptions(id1, 20) 72 | sleep(1) 73 | options.accellerateVesting(id1, {"from": accounts[0]}) 74 | assert options.getOptions(id1) == (2000, 0, [(10, 2), (20, 2)]) 75 | assert options.getTotalOptionsAtPrice(10) == (1000, 0) 76 | assert options.getTotalOptionsAtPrice(20) == (1000, 0) 77 | assert options.totalOptions() == 2000 78 | -------------------------------------------------------------------------------- /tests/modules/VestedOptions/test_exercise_revert.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts 6 | 7 | 8 | # def test_setup(): 9 | # global org, share, options, id1 10 | # share, org, _ = main(BookShare, (1,), (1,)) 11 | # options = accounts[0].deploy(VestedOptions, share, org, 1, 10, 6, accounts[0]) 12 | # org.attachModule(share, options, {'from': accounts[0]}) 13 | # id1 = org.getID(accounts[1]) 14 | 15 | 16 | def test_wrong_price(options, id1, issueoptions, sleep): 17 | """incorrect payment amount""" 18 | issueoptions(id1, 10) 19 | sleep(7) 20 | options.exerciseOptions(10, 100, {"from": accounts[1], "amount": 1000}) 21 | with pytest.reverts("Incorrect payment"): 22 | options.exerciseOptions(10, 100, {"from": accounts[1], "amount": 100}) 23 | with pytest.reverts("Incorrect payment"): 24 | options.exerciseOptions(10, 100, {"from": accounts[1]}) 25 | with pytest.reverts("Incorrect payment"): 26 | options.exerciseOptions(10, 100, {"from": accounts[1], "amount": 100000}) 27 | 28 | 29 | def test_unknown_id(options): 30 | """unknown member ID""" 31 | with pytest.reverts("Address not registered"): 32 | options.exerciseOptions(10, 100, {"from": accounts[9], "amount": 1000}) 33 | 34 | 35 | def test_no_options_at_price(options, id1, issueoptions, sleep): 36 | """no options at this price""" 37 | issueoptions(id1, 10) 38 | sleep(7) 39 | with pytest.reverts("No options at this price"): 40 | options.exerciseOptions(20, 500, {"from": accounts[1], "amount": 10000}) 41 | options.exerciseOptions(10, 500, {"from": accounts[1], "amount": 5000}) 42 | with pytest.reverts("No options at this price"): 43 | options.exerciseOptions(10, 500, {"from": accounts[1], "amount": 5000}) 44 | 45 | 46 | def test_not_enough_options(options, id1, issueoptions, sleep): 47 | """insufficient vested options""" 48 | issueoptions(id1, 10) 49 | sleep(3) 50 | with pytest.reverts("Insufficient vested options"): 51 | options.exerciseOptions(10, 400, {"from": accounts[1], "amount": 4000}) 52 | options.exerciseOptions(10, 200, {"from": accounts[1], "amount": 2000}) 53 | with pytest.reverts("Insufficient vested options"): 54 | options.exerciseOptions(10, 100, {"from": accounts[1], "amount": 1000}) 55 | 56 | 57 | def test_zero_options(options, id1, issueoptions, sleep): 58 | """cannot exercise zero options""" 59 | issueoptions(id1, 10) 60 | sleep(3) 61 | with pytest.reverts("dev: mint 0"): 62 | options.exerciseOptions(10, 0, {"from": accounts[1], "amount": 0}) 63 | -------------------------------------------------------------------------------- /tests/modules/VestedOptions/test_get_in_money_options.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | 4 | def test_in_money(options, id1, issueoptions, sleep): 5 | """get in money options""" 6 | issueoptions(id1, 10) 7 | issueoptions(id1, 20) 8 | issueoptions(id1, 30) 9 | assert options.getInMoneyOptions(id1, 40) == (0, 0) 10 | sleep(2) 11 | assert options.getInMoneyOptions(id1, 5) == (0, 0) 12 | assert options.getInMoneyOptions(id1, 10) == (0, 0) 13 | assert options.getInMoneyOptions(id1, 11) == (100, 1000) 14 | assert options.getInMoneyOptions(id1, 20) == (100, 1000) 15 | assert options.getInMoneyOptions(id1, 21) == (200, 3000) 16 | assert options.getInMoneyOptions(id1, 30) == (200, 3000) 17 | assert options.getInMoneyOptions(id1, 31) == (300, 6000) 18 | sleep(1) 19 | assert options.getInMoneyOptions(id1, 31) == (600, 12000) 20 | 21 | 22 | def test_reverts(options, id1, issueoptions, sleep): 23 | """no options, expired options""" 24 | assert options.getInMoneyOptions(id1, 11) == (0, 0) 25 | issueoptions(id1, 10) 26 | assert options.getInMoneyOptions(id1, 11) == (0, 0) 27 | sleep(2) 28 | assert options.getInMoneyOptions(id1, 11) == (100, 1000) 29 | sleep(100) 30 | assert options.getInMoneyOptions(id1, 11) == (0, 0) 31 | 32 | 33 | def test_multiple_expirations(options, id1, issueoptions, sleep): 34 | """multiple expiration dates""" 35 | issueoptions(id1, 10) 36 | sleep(3) 37 | issueoptions(id1, 10) 38 | assert options.getInMoneyOptions(id1, 11) == (200, 2000) 39 | sleep(3) 40 | assert options.getInMoneyOptions(id1, 11) == (700, 7000) 41 | sleep(97) 42 | assert options.getInMoneyOptions(id1, 11) == (500, 5000) 43 | -------------------------------------------------------------------------------- /tests/modules/VestedOptions/test_get_options_at.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts, rpc 6 | 7 | optmonths = [0, 100, 100, 100, 100, 100, 0, 0, 0, 0, 0] 8 | 9 | 10 | @pytest.fixture(scope="module") 11 | def options(VestedOptions, org, share): 12 | options = accounts[0].deploy(VestedOptions, share, org, 1, 10, 6, accounts[0]) 13 | org.attachModule(share, options, {"from": accounts[0]}) 14 | yield options 15 | 16 | 17 | def test_get_options_at(options, id1, issueoptions, sleep): 18 | issueoptions(id1, 10) 19 | assert options.getOptionsAt(id1, 10, 0) == (0, 500, False, _months(11), optmonths) 20 | sleep(2) 21 | assert options.getOptionsAt(id1, 10, 0) == ( 22 | 100, 23 | 400, 24 | False, 25 | _months(9), 26 | optmonths[2:], 27 | ) 28 | sleep(8) 29 | assert options.getOptionsAt(id1, 10, 0) == (500, 0, False, _months(1), [0]) 30 | sleep(1) 31 | with pytest.reverts(): 32 | options.getOptionsAt(id1, 10, 0) 33 | 34 | 35 | def test_expire_and_recreate(options, id1, issueoptions, sleep): 36 | issueoptions(id1, 10) 37 | sleep(12) 38 | with pytest.reverts(): 39 | options.getOptionsAt(id1, 10, 0) 40 | issueoptions(id1, 10) 41 | options.getOptionsAt(id1, 10, 0) 42 | with pytest.reverts(): 43 | options.getOptionsAt(id1, 10, 1) 44 | issueoptions(id1, 10) 45 | options.getOptionsAt(id1, 10, 0) 46 | with pytest.reverts(): 47 | options.getOptionsAt(id1, 10, 1) 48 | sleep(12) 49 | with pytest.reverts(): 50 | options.getOptionsAt(id1, 10, 0) 51 | 52 | 53 | def test_multiple(options, id1, issueoptions, sleep): 54 | issueoptions(id1, 10) 55 | sleep(6) 56 | issueoptions(id1, 10) 57 | assert options.getOptionsAt(id1, 10, 0) == ( 58 | 500, 59 | 0, 60 | False, 61 | _months(5), 62 | [0, 0, 0, 0, 0], 63 | ) 64 | assert options.getOptionsAt(id1, 10, 1) == (0, 500, False, _months(11), optmonths) 65 | sleep(5) 66 | assert options.getOptionsAt(id1, 10, 0) == ( 67 | 400, 68 | 100, 69 | False, 70 | _months(6), 71 | optmonths[5:], 72 | ) 73 | with pytest.reverts(): 74 | options.getOptionsAt(id1, 10, 1) 75 | 76 | 77 | def _months(months): 78 | return int(rpc.time() // 2592000 + 1) * 2592000 + months * 2592000 79 | -------------------------------------------------------------------------------- /tests/modules/VestedOptions/test_hooks_permissions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | 4 | def test_permissions(share, options): 5 | assert share.isPermittedModule(options, share.mint.signature) 6 | 7 | 8 | def hooks(options): 9 | result = options.getPermissions() 10 | assert result["hooks"] == ("0x741b5078",) 11 | assert result["hookBools"] == 2 ** 256 - 1 12 | -------------------------------------------------------------------------------- /tests/modules/VestedOptions/test_issue_reverts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | from brownie import accounts, rpc 6 | 7 | 8 | def test_exercise_price_zero(options, id1): 9 | """exercise price cannot be zero""" 10 | with pytest.reverts("dev: exercise price == 0"): 11 | options.issueOptions(id1, 0, False, [1, 2], [1, 2], {"from": accounts[0]}) 12 | 13 | 14 | def test_mismatch(options, id1): 15 | """arrays must be of same length""" 16 | with pytest.reverts("dev: length mismatch"): 17 | options.issueOptions(id1, 10, False, [1, 2], [1, 2, 3], {"from": accounts[0]}) 18 | 19 | 20 | def test_vest_exceeds_expiration(options, id1): 21 | """vesting months cannot exceed expiration""" 22 | with pytest.reverts("dev: vest > expiration"): 23 | options.issueOptions(id1, 10, False, [1, 2], [1, 100], {"from": accounts[0]}) 24 | options.issueOptions(id1, 10, False, [1, 2], [1, 99], {"from": accounts[0]}) 25 | 26 | 27 | def test_exceeds_authorized_supply(share, options, id1): 28 | """options + shares cannot exceed authorized supply""" 29 | options.issueOptions( 30 | id1, 10, False, [250000, 250000], [1, 2], {"from": accounts[0]} 31 | ) 32 | share.mint(accounts[1], 250000, {"from": accounts[0]}) 33 | with pytest.reverts("dev: exceeds authorized"): 34 | options.issueOptions( 35 | id1, 20, False, [250000, 250000], [1, 2], {"from": accounts[0]} 36 | ) 37 | options.issueOptions(id1, 20, False, [250000], [4], {"from": accounts[0]}) 38 | with pytest.reverts("dev: exceeds authorized"): 39 | options.issueOptions(id1, 10, False, [1], [4], {"from": accounts[0]}) 40 | 41 | 42 | def test_iso_mismatch(options, id1): 43 | """cannot change iso in same expiration period""" 44 | options.issueOptions(id1, 10, False, [100, 100], [1, 2], {"from": accounts[0]}) 45 | with pytest.reverts("dev: iso mismatch"): 46 | options.issueOptions(id1, 10, True, [100, 100], [1, 2], {"from": accounts[0]}) 47 | options.issueOptions(id1, 10, False, [100, 100], [1, 2], {"from": accounts[0]}) 48 | rpc.sleep(2592000) 49 | options.issueOptions(id1, 10, True, [100, 100], [1, 2], {"from": accounts[0]}) 50 | -------------------------------------------------------------------------------- /tests/modules/VestedOptions/test_modify_peg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from brownie import accounts 4 | 5 | 6 | def test_modify_peg(options, id1, issueoptions, sleep): 7 | issueoptions(id1, 10) 8 | sleep(7) 9 | options.modifyPeg(2, {"from": accounts[0]}) 10 | balance = accounts[0].balance() 11 | options.exerciseOptions(10, 200, {"from": accounts[1], "amount": 4000}) 12 | assert accounts[0].balance() == balance + 4000 13 | options.modifyPeg(5, {"from": accounts[0]}) 14 | balance = accounts[0].balance() 15 | options.exerciseOptions(10, 100, {"from": accounts[1], "amount": 5000}) 16 | assert accounts[0].balance() == balance + 5000 17 | options.modifyPeg(1, {"from": accounts[0]}) 18 | balance = accounts[0].balance() 19 | options.exerciseOptions(10, 200, {"from": accounts[1], "amount": 2000}) 20 | assert accounts[0].balance() == balance + 2000 21 | -------------------------------------------------------------------------------- /tests/modules/VestedOptions/test_terminate_options.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from brownie import accounts 4 | 5 | 6 | def test_terminate_unvested(options, id1, issueoptions): 7 | """unvested""" 8 | issueoptions(id1, 10) 9 | issueoptions(id1, 20) 10 | options.terminateOptions(id1, {"from": accounts[0]}) 11 | assert options.getOptions(id1) == (0, 0, []) 12 | assert options.getTotalOptionsAtPrice(10) == (0, 0) 13 | assert options.totalOptions() == 0 14 | 15 | 16 | def test_terminate_partial(options, id1, issueoptions, sleep): 17 | """partially vested""" 18 | issueoptions(id1, 10) 19 | issueoptions(id1, 20) 20 | sleep(3) 21 | options.terminateOptions(id1, {"from": accounts[0]}) 22 | assert options.getOptions(id1), (400, 0, [(10, 1) == (20, 1)]) 23 | assert options.getTotalOptionsAtPrice(10) == (200, 0) 24 | assert options.totalOptions() == 400 25 | sleep(5) 26 | assert options.getOptions(id1), (400, 0, [(10, 1) == (20, 1)]) 27 | assert options.getTotalOptionsAtPrice(10) == (200, 0) 28 | assert options.totalOptions() == 400 29 | sleep(2) 30 | assert options.getOptions(id1) == (0, 0, []) 31 | assert options.getTotalOptionsAtPrice(10) == (0, 0) 32 | assert options.totalOptions() == 0 33 | 34 | 35 | def test_terminate_vested(options, id1, issueoptions, sleep): 36 | """already vested""" 37 | issueoptions(id1, 10) 38 | issueoptions(id1, 20) 39 | sleep(10) 40 | options.terminateOptions(id1, {"from": accounts[0]}) 41 | assert options.getOptions(id1) == (1000, 0, [(10, 1), (20, 1)]) 42 | assert options.getTotalOptionsAtPrice(10) == (500, 0) 43 | assert options.totalOptions() == 1000 44 | sleep(5) 45 | assert options.getOptions(id1) == (1000, 0, [(10, 1), (20, 1)]) 46 | assert options.getTotalOptionsAtPrice(10) == (500, 0) 47 | assert options.totalOptions() == 1000 48 | sleep(1) 49 | assert options.getOptions(id1) == (0, 0, []) 50 | assert options.getTotalOptionsAtPrice(10) == (0, 0) 51 | assert options.totalOptions() == 0 52 | 53 | 54 | def test_terminate_do_not_extend_grace(options, id1, issueoptions, sleep): 55 | """grace period > expiration date""" 56 | issueoptions(id1, 10) 57 | issueoptions(id1, 20) 58 | sleep(16) 59 | options.terminateOptions(id1, {"from": accounts[0]}) 60 | assert options.getOptions(id1) == (1000, 0, [(10, 1), (20, 1)]) 61 | assert options.getTotalOptionsAtPrice(10) == (500, 0) 62 | assert options.totalOptions() == 1000 63 | sleep(4) 64 | assert options.getOptions(id1) == (1000, 0, [(10, 1), (20, 1)]) 65 | assert options.getTotalOptionsAtPrice(10) == (500, 0) 66 | assert options.totalOptions() == 1000 67 | sleep(80) 68 | assert options.getOptions(id1) == (0, 0, []) 69 | assert options.getTotalOptionsAtPrice(10) == (0, 0) 70 | assert options.totalOptions() == 0 71 | 72 | 73 | def test_grace_equals_expiration(options, id1, issueoptions, sleep): 74 | """grace period == expiration date""" 75 | issueoptions(id1, 10) 76 | issueoptions(id1, 20) 77 | sleep(15) 78 | options.terminateOptions(id1, {"from": accounts[0]}) 79 | assert options.getOptions(id1), (1000, 0, [(10, 1) == (20, 1)]) 80 | assert options.getTotalOptionsAtPrice(10) == (500, 0) 81 | assert options.totalOptions() == 1000 82 | sleep(5) 83 | assert options.getOptions(id1) == (1000, 0, [(10, 1), (20, 1)]) 84 | assert options.getTotalOptionsAtPrice(10) == (500, 0) 85 | assert options.totalOptions() == 1000 86 | sleep(1) 87 | assert options.getOptions(id1) == (0, 0, []) 88 | assert options.getTotalOptionsAtPrice(10) == (0, 0) 89 | assert options.totalOptions() == 0 90 | -------------------------------------------------------------------------------- /tests/modules/VestedOptions/test_total_options.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from brownie import accounts 4 | 5 | 6 | def test_vest_expire(options, id1, id2, issueoptions, sleep): 7 | """total options - vesting and expiring""" 8 | issueoptions(id1, 10) 9 | assert options.totalOptions() == 500 10 | issueoptions(id1, 20) 11 | assert options.totalOptions() == 1000 12 | sleep(10) 13 | assert options.totalOptions() == 1000 14 | issueoptions(id2, 10) 15 | assert options.totalOptions() == 1500 16 | sleep(95) 17 | assert options.totalOptions() == 500 18 | sleep(10) 19 | assert options.totalOptions() == 0 20 | 21 | 22 | def test_issue_exercise(options, id1, issueoptions, sleep): 23 | """total options - issuing and exercising""" 24 | issueoptions(id1, 10) 25 | assert options.totalOptions() == 500 26 | sleep(7) 27 | assert options.totalOptions() == 500 28 | options.exerciseOptions(10, 400, {"from": accounts[1], "amount": 4000}) 29 | assert options.totalOptions() == 100 30 | options.exerciseOptions(10, 100, {"from": accounts[1], "amount": 1000}) 31 | assert options.totalOptions() == 0 32 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | lint 4 | doctest 5 | py37 6 | skipsdist=True 7 | 8 | [flake8] 9 | max-line-length = 100 10 | ignore = E203,W503,F403,F405 11 | 12 | [testenv] 13 | passenv = 14 | GITHUB_TOKEN 15 | WEB3_INFURA_PROJECT_ID 16 | 17 | [testenv:lint] 18 | deps = 19 | black==19.3b0 20 | flake8==3.7.7 21 | basepython=python3 22 | extras=linter 23 | commands = 24 | black --check {toxinidir}/scripts {toxinidir}/tests 25 | flake8 {toxinidir}/scripts {toxinidir}/tests 26 | 27 | [testenv:doctest] 28 | deps = 29 | sphinx 30 | sphinx_rtd_theme 31 | pygments_lexer_solidity 32 | commands = 33 | sphinx-build {posargs:-E} -b html docs dist/docs 34 | sphinx-build -b linkcheck docs dist/docs 35 | 36 | [testenv:py37] 37 | deps = 38 | pytest 39 | eth-brownie==1.2.1 40 | commands=python -m pytest tests/ 41 | --------------------------------------------------------------------------------