├── .env.example ├── .gitattributes ├── .gitignore ├── .openzeppelin ├── base.json ├── goerli.json ├── mainnet.json ├── polygon-mumbai.json ├── polygon.json ├── rinkeby.json ├── ropsten.json ├── unknown-11155111.json └── unknown-84532.json ├── README.md ├── artifacts-external └── contracts │ ├── ENSRegistry.sol │ ├── ENSRegistry.dbg.json │ └── ENSRegistry.json │ ├── FIFSRegistrar.sol │ ├── FIFSRegistrar.dbg.json │ └── FIFSRegistrar.json │ ├── GnosisSafe.sol │ └── GnosisSafe.json │ ├── GnosisSafeProxyFactory.sol │ ├── GnosisSafeProxyFactory.dbg.json │ └── GnosisSafeProxyFactory.json │ ├── LaunchCurveExponential.sol │ └── LaunchCurveExponential.json │ ├── LaunchPool.sol │ └── LaunchPool.json │ └── PublicResolver.sol │ ├── PublicResolver.dbg.json │ └── PublicResolver.json ├── audit ├── v1 │ ├── Coinspect - Smart Contract Audit - OtoCo - v220524.pdf │ ├── Coinspect - Smart Contract Audit - OtoCo - v220527.pdf │ └── OtoCo - Correction - v220525.md └── v2 │ └── OtoCoV2_Internal_Audit.md ├── contracts ├── OtoCoJurisdiction.sol ├── OtoCoJurisdictionV2.sol ├── OtoCoMaster.sol ├── OtoCoMasterV2.sol ├── OtoCoPlugin.sol ├── OtoCoPluginV2.sol ├── governor │ ├── GovernorNoEIP712NoName.sol │ ├── InitializableEIP712.sol │ ├── InitializableGovernorCountingSimple.sol │ ├── InitializableGovernorQuorumFraction.sol │ ├── InitializableGovernorSettings.sol │ └── InitializableGovernorVotes.sol ├── initializers │ └── GovernorInitializer.sol ├── jurisdictions │ ├── Delaware.sol │ ├── Unincorporated.sol │ └── Wyoming.sol ├── jurisdictionsV2 │ ├── DelawareV2.sol │ ├── MarshallIslandsV2.sol │ ├── SwissAssociationV2.sol │ ├── UnaDunaV2.sol │ ├── UnincorporatedV2.sol │ └── WyomingV2.sol ├── plugins │ ├── ENS.sol │ ├── Launchpool.sol │ ├── Multisig.sol │ ├── Timestamp.sol │ ├── Token.sol │ └── Tokenization.sol ├── pluginsV2 │ ├── ENSV2.sol │ ├── LaunchpoolV2.sol │ ├── MultisigV2.sol │ ├── TimestampV2.sol │ ├── TokenV2.sol │ └── TokenizationV2.sol └── utils │ ├── BadgeVerifier.sol │ ├── IOtoCoJurisdiction.sol │ ├── IOtoCoMaster.sol │ ├── IOtoCoMasterV2.sol │ ├── IOtoCoPlugin.sol │ ├── IOtoCoPluginV2.sol │ ├── IOtoCoURI.sol │ ├── MockAggregatorV3.sol │ ├── OtoCoGovernor.sol │ ├── OtoCoToken.sol │ ├── OtoCoTokenMintable.sol │ ├── OtoCoTokenNonTransferable.sol │ └── OtoCoURI.sol ├── deploys ├── v1 │ ├── base.json │ ├── basesepolia.json │ ├── goerli.json │ ├── localhost.json │ ├── main.json │ ├── mainnet.json │ ├── mumbai.json │ ├── polygon.json │ └── sepolia.json └── v2 │ ├── base.json │ ├── basesepolia.json │ ├── goerli.json │ ├── hardhat.json │ ├── localhost.json │ ├── mainnet.json │ ├── mumbai.json │ ├── polygon.json │ ├── previous.base.json │ ├── previous.basesepolia.json │ ├── previous.main.json │ ├── previous.sepolia.json │ └── sepolia.json ├── hardhat.config.js ├── mochaOutput.json ├── package.json ├── scripts ├── 1-deploy-master.js ├── 10-deploy-plugin-timestamp.js ├── 11-deploy-plugin-tokenization.js ├── 12-deploy-plugin-launchpool.js ├── 2-deploy-jurisdictions.js ├── 3-deploy-uri.js ├── 4-post-configs.js ├── 5-deploy-initializers.js ├── 6-deploy-badge-verifier.js ├── 7-deploy-plugin-token.js ├── 8-deploy-plugin-multisig.js ├── 9-deploy-plugin-ens.js ├── jurisdiction-settings.json └── sendEmpty.js ├── tasks ├── initializers.js ├── jurisdictions.js ├── master.js ├── plugins │ ├── ens.js │ ├── launchpool.js │ ├── multisig.js │ ├── timestamp.js │ ├── token.js │ └── tokenization.js ├── postsetup.js ├── setup.js ├── uri.js ├── utils │ ├── accounts.js │ ├── gas.js │ └── verification.js └── verifier.js ├── test ├── 01-otoco-jurisdiction.test.js ├── 02-otoco-master.test.js ├── 03-otoco-launchpool-token.test.js ├── 04-otoco-timestamp.test.js ├── 05-otoco-token-nofee.test.js ├── 06-otoco-multisig.test.js ├── 07-otoco-ens.test.js ├── 08-otoco-governor.test.js ├── 09-migration.test.js ├── 10-migrate-masterV2.test.js ├── 11-initializers-masterV2.test.js ├── companies.json ├── halmos │ └── mocks │ │ └── mockify.sh ├── tarantula │ ├── output.json │ ├── suspiciousness.json │ └── tarantula.js └── utils.js ├── testMatrix.json └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | ## ACCOUNTS CONFIG 2 | # Comment the seed phrase out if using the default hardhat accounts is intended. 3 | MNEMONIC_PHRASE= "{INSERT_TWELVE_WORD_MNEMONIC}" 4 | 5 | RPC_MAINNET_API="{INSERT_API_RPC_URL}" 6 | RPC_GOERLI_API="{INSERT_API_RPC_URL}" 7 | RPC_POLYGON_API="{INSERT_API_RPC_URL}" 8 | RPC_MUMBAI_API="{INSERT_API_RPC_URL}" 9 | 10 | # Block explorer API keys 11 | ETHERSCAN_API_KEY="{INSERT_API_KEY}" 12 | POLYGONSCAN_API_KEY="{INSERT_API_KEY}" 13 | 14 | ## HARDHAT NODE & SCRIPTS CONFIG 15 | # For Ethereum Mainnet forking, set the following variable to true and add your Alchemy/Infura API URL. 16 | FORK_ENABLED="{SET_STATE}" 17 | 18 | ## SETUP TASK CONFIG 19 | # For Ethereum Mainnet forking, set the network variable to "mainnet". 20 | FORKED_NETWORK="{INSERT_NETWORK}" 21 | 22 | # Insert price arrays (for `renewPrice` and `deployPrice`) as strings. 23 | UNINCORPORATED_PRICES="{INSERT_PRICE_ARRAY}" 24 | DELAWARE_PRICES="{INSERT_PRICE_ARRAY}" 25 | WYOMING_PRICES="{INSERT_PRICE_ARRAY}" 26 | 27 | # Insert base fee to be charged for Initializers and Plugins I.E.: 2000000000 28 | BASE_FEE="{BASE_FEE}" -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | contracts/.placeholder 3 | test/.placeholder 4 | node_modules 5 | .secret.main 6 | .secret.test 7 | .api* 8 | flattened.sol 9 | migrations_data 10 | .solcx* 11 | .env 12 | 13 | # hardhat 14 | artifacts 15 | cache 16 | deployments 17 | node_modules 18 | coverage* 19 | 20 | # local tests 21 | .openzeppelin/unknown-31337.json 22 | deploys/localhost.json 23 | 24 | 25 | -------------------------------------------------------------------------------- /artifacts-external/contracts/ENSRegistry.sol/ENSRegistry.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../../build-info/45b090f55a270be8c512bf6657c3dd1d.json" 4 | } 5 | -------------------------------------------------------------------------------- /artifacts-external/contracts/FIFSRegistrar.sol/FIFSRegistrar.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../../build-info/45b090f55a270be8c512bf6657c3dd1d.json" 4 | } 5 | -------------------------------------------------------------------------------- /artifacts-external/contracts/FIFSRegistrar.sol/FIFSRegistrar.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "FIFSRegistrar", 4 | "sourceName": "contracts/registry/FIFSRegistrar.sol", 5 | "abi": [ 6 | { 7 | "inputs": [ 8 | { 9 | "internalType": "contract ENS", 10 | "name": "ensAddr", 11 | "type": "address" 12 | }, 13 | { 14 | "internalType": "bytes32", 15 | "name": "node", 16 | "type": "bytes32" 17 | } 18 | ], 19 | "stateMutability": "nonpayable", 20 | "type": "constructor" 21 | }, 22 | { 23 | "inputs": [ 24 | { 25 | "internalType": "bytes32", 26 | "name": "label", 27 | "type": "bytes32" 28 | }, 29 | { 30 | "internalType": "address", 31 | "name": "owner", 32 | "type": "address" 33 | } 34 | ], 35 | "name": "register", 36 | "outputs": [], 37 | "stateMutability": "nonpayable", 38 | "type": "function" 39 | } 40 | ], 41 | "bytecode": "0x608060405234801561001057600080fd5b5060405161036038038061036083398101604081905261002f91610058565b600080546001600160a01b0319166001600160a01b039390931692909217909155600155610092565b6000806040838503121561006b57600080fd5b82516001600160a01b038116811461008257600080fd5b6020939093015192949293505050565b6102bf806100a16000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063d22057a914610030575b600080fd5b61004361003e36600461021c565b610045565b005b6000805460015460408051602080820193909352808201879052815180820383018152606082019283905280519301929092207f02571be300000000000000000000000000000000000000000000000000000000909152606482015284929173ffffffffffffffffffffffffffffffffffffffff16906302571be390608401602060405180830381865afa1580156100e1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610105919061024c565b905073ffffffffffffffffffffffffffffffffffffffff8116158061013f575073ffffffffffffffffffffffffffffffffffffffff811633145b61014857600080fd5b6000546001546040517f06ab592300000000000000000000000000000000000000000000000000000000815260048101919091526024810186905273ffffffffffffffffffffffffffffffffffffffff8581166044830152909116906306ab5923906064016020604051808303816000875af11580156101cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101f09190610270565b5050505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461021957600080fd5b50565b6000806040838503121561022f57600080fd5b823591506020830135610241816101f7565b809150509250929050565b60006020828403121561025e57600080fd5b8151610269816101f7565b9392505050565b60006020828403121561028257600080fd5b505191905056fea2646970667358221220ebc2ef866ffc60ba3123696843656f33936969e397b1433177d8284216be8ead64736f6c634300080d0033", 42 | "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063d22057a914610030575b600080fd5b61004361003e36600461021c565b610045565b005b6000805460015460408051602080820193909352808201879052815180820383018152606082019283905280519301929092207f02571be300000000000000000000000000000000000000000000000000000000909152606482015284929173ffffffffffffffffffffffffffffffffffffffff16906302571be390608401602060405180830381865afa1580156100e1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610105919061024c565b905073ffffffffffffffffffffffffffffffffffffffff8116158061013f575073ffffffffffffffffffffffffffffffffffffffff811633145b61014857600080fd5b6000546001546040517f06ab592300000000000000000000000000000000000000000000000000000000815260048101919091526024810186905273ffffffffffffffffffffffffffffffffffffffff8581166044830152909116906306ab5923906064016020604051808303816000875af11580156101cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101f09190610270565b5050505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461021957600080fd5b50565b6000806040838503121561022f57600080fd5b823591506020830135610241816101f7565b809150509250929050565b60006020828403121561025e57600080fd5b8151610269816101f7565b9392505050565b60006020828403121561028257600080fd5b505191905056fea2646970667358221220ebc2ef866ffc60ba3123696843656f33936969e397b1433177d8284216be8ead64736f6c634300080d0033", 43 | "linkReferences": {}, 44 | "deployedLinkReferences": {} 45 | } 46 | -------------------------------------------------------------------------------- /artifacts-external/contracts/GnosisSafeProxyFactory.sol/GnosisSafeProxyFactory.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../../build-info/9fdd36fc2ffb3e3a0f2cd4d555b03cd7.json" 4 | } 5 | -------------------------------------------------------------------------------- /artifacts-external/contracts/PublicResolver.sol/PublicResolver.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../../build-info/45b090f55a270be8c512bf6657c3dd1d.json" 4 | } 5 | -------------------------------------------------------------------------------- /audit/v1/Coinspect - Smart Contract Audit - OtoCo - v220524.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otoco-io/SmartContract/eb697e4b406061c816e6ac6fe01f0ffcc2edf24e/audit/v1/Coinspect - Smart Contract Audit - OtoCo - v220524.pdf -------------------------------------------------------------------------------- /audit/v1/Coinspect - Smart Contract Audit - OtoCo - v220527.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otoco-io/SmartContract/eb697e4b406061c816e6ac6fe01f0ffcc2edf24e/audit/v1/Coinspect - Smart Contract Audit - OtoCo - v220527.pdf -------------------------------------------------------------------------------- /audit/v1/OtoCo - Correction - v220525.md: -------------------------------------------------------------------------------- 1 | ## CORRECTIONS RELATED TO Otoco - Smart Contract Audit v220524 2 | 3 | ### COMMIT 4 | 5 | The commit related to the corrections is: `98f3589b012b78f74b047f22d51595479790b5b7` 6 | 7 | We recommend run tests using command: ```npx hardhat coverage``` 8 | 9 | ### ASSESSMENT 10 | 11 | Coinspect found that if the number of jurisdictions surpasses 255, the OtoCoMaster contract can no longer mint series in batch mode and always causes a revert of the transaction (**OGO-9**). 12 | 13 | The OtoCoToken contract can be taken over by an attacker by front-running the initialization process (**OGO-10**). The impact of this issue was diminished as medium risk, because the OtoCoToken contract is only created via the Token plugin contract that always initializes the contract after cloning it. 14 | 15 | Regarding low risk issues, it was found that some variables declared in the OtoCoToken contract shadow variables in the parent ERC20 contract. This causes different variables to be used in the parent and inherited contract (**OGO-11**). 16 | 17 | Another low risk issue was found where the contract owner can batch mint series with a timestamp in the past or the future (**OGO-12**). 18 | 19 | Also, the function `createBatchSeries()` in the `OtoCoMaster` contract iterates over all the jurisdictions, and if the number of jurisdictions is high the cost of transactions can be rendered prohibitive (**OGO-13**). 20 | 21 | Some informative issues are also reported, concerning documentation and validations, that do not represent security problems (**OGO-14**, **OGO-15**). 22 | 23 | Finally, we found the included tests to have a reasonable coverage of the contracts code and functionality. 24 | 25 | ### ISSUES 26 | 27 | **OGO-9** - Integer overflow in function createBatchSeries 28 | 29 | Resolved as recommended removing changing `i` variable type to `uint16` 30 | 31 | **OGO-10** - Improper initialization of token contract 32 | 33 | Resolved as recommended inheriting OpenZeppeling `Initializable` contract and using `Initializable` modifier on `constructor` (as recommended on `Initializable.sol`). Also added a modifier `initializer` at `initialize` function and `removed` the previous modifier `NotInitialized`. Removed the Event `Initialize` that wasn't used by the system anymore. 34 | We had to use the `contracts-upgradeable` version of the Initializeable due to the conflict of Address.sol 35 | 36 | **OGO-11** - Shadowing of state variables 37 | 38 | Removed as recommended removing mappings that aren't used on token implementation. Modified `_name` to `__name` and `_symbol` to `__symbol` to not match inherited contract. 39 | 40 | **OGO-12** - Lack of parameter validation at createBatchSeries 41 | 42 | Limit amount of series migrated to 255 in a single batch transaction. 43 | Additional verification was added to prevent wrong timestamps at the migration process. Take a look at `3-migrate-series.js:63`. 44 | As the migration will only be used by the OtoCo team, only for migrate entities that are already deployed previously and using the current jurisdiction rules. We will not have any concern related to the names since they already correspond with the rules used on current contracts. Also, the volume of entities migrated at once could reach 255, so we thought it could be prohibitive to use this kind of name validation at smart-contract level. Is the responsibility of the OtoCo team at the migration phase to arrange companies correctly and check possible wrong names. 45 | 46 | **OGO-13** - Jurisdiction number causes high gas usage 47 | To prevent rewriting values that will add 0 amount to it, we added a condition inside the loop checking if the `seriesPerJurisdictionTemp[i]` is bigger than `0` and skip the update in the affirmative case. 48 | We added an alert at the documentation of the function to alert not to try to migrate in case more than 500 jurisdictions exist. And recommend upgrading the function in those cases. 49 | We couldn't remove any Jurisdiction cause entities are linked to them. To remove jurisdictions we would be required to close all entities related to it. 50 | We also couldn't add any constraints to jurisdictions during migration cause series will be migrated in the order of creation, so, it couldn't be prevented for one series to use a jurisdiction 1 and other uses 500 for example. 51 | Anyway, currently OtoCo has only 3 Jurisdictions and 530 series at the moment, it's difficult to conceive a future where it supports more than 400 jurisdictions, but who knows. We also don't have plans to migrate any more series that we currently have on the system. So any other change on the function to reduce the high gas spending when a higher amount of jurisdictions would cause an increase in gas spending for the current low amount of jurisdiction and higher amount of series. 52 | 53 | **OGO-14** - Inconsistent Documentation 54 | 55 | Resolved according to recommendations. 56 | 57 | **OGO-15** - Lack of mapping length validations in function createBatchSeries 58 | 59 | Resolved according to recommendations. 60 | -------------------------------------------------------------------------------- /contracts/OtoCoJurisdiction.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | abstract contract OtoCoJurisdiction { 5 | 6 | string private name; 7 | string private defaultBadge; 8 | string private goldBadge; 9 | 10 | constructor ( 11 | string memory _name, 12 | string memory _defaultBadge, 13 | string memory _goldBadge 14 | ) { 15 | name = _name; 16 | defaultBadge = _defaultBadge; 17 | goldBadge = _goldBadge; 18 | } 19 | 20 | /** 21 | * Get formatted name according to the jurisdiction requirement. 22 | * To use when create new series, before series creation. 23 | * Returns the string name formatted accordingly. 24 | * 25 | * @param count current number of series deployed at the jurisdiction. 26 | * @return nameToFormat name of the series to format accordingly. 27 | */ 28 | function getSeriesNameFormatted (uint256 count, string calldata nameToFormat) public pure virtual returns(string memory); 29 | 30 | /** 31 | * Return the name of the jurisdiction. 32 | * 33 | * @return name the name of the jurisdiction. 34 | */ 35 | function getJurisdictionName () external view returns(string memory){ 36 | return name; 37 | } 38 | 39 | /** 40 | * Return the NFT URI link of the jurisdiction. 41 | * 42 | * @return defaultBadge the badge URI. 43 | */ 44 | function getJurisdictionBadge () external view returns(string memory) { 45 | return defaultBadge; 46 | } 47 | 48 | /** 49 | * Return the Gold NFT URI link of the jurisdiction. 50 | * 51 | * @return goldBadge the gold badge URI. 52 | */ 53 | function getJurisdictionGoldBadge () external view returns(string memory){ 54 | return goldBadge; 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /contracts/OtoCoJurisdictionV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | abstract contract OtoCoJurisdictionV2 { 5 | 6 | string private name; 7 | string private defaultBadge; 8 | string private goldBadge; 9 | uint256 private renewCost; 10 | uint256 private deployCost; 11 | uint256 private closeCost; 12 | bool private standalone; 13 | 14 | constructor ( 15 | uint256 _renewCost, 16 | uint256 _deployCost, 17 | uint256 _closeCost, 18 | string memory _name, 19 | string memory _defaultBadge, 20 | string memory _goldBadge, 21 | bool _standalone 22 | ) { 23 | renewCost = _renewCost; 24 | deployCost = _deployCost; 25 | closeCost = _closeCost; 26 | name = _name; 27 | defaultBadge = _defaultBadge; 28 | goldBadge = _goldBadge; 29 | 30 | assembly { 31 | // we avoid initializing default values 32 | if iszero(iszero(_standalone)) { 33 | sstore(standalone.slot, _standalone) 34 | } 35 | } 36 | } 37 | 38 | /** 39 | * Get formatted name according to the jurisdiction requirement. 40 | * To use when create new series, before series creation. 41 | * Returns the string name formatted accordingly. 42 | * 43 | * @param count current number of series deployed at the jurisdiction. 44 | * @return nameToFormat name of the series to format accordingly. 45 | */ 46 | function getSeriesNameFormatted(uint256 count, string calldata nameToFormat) public pure virtual returns(string memory); 47 | 48 | /** 49 | * Return the name of the jurisdiction. 50 | * 51 | * @return name the name of the jurisdiction. 52 | */ 53 | function getJurisdictionName() external view returns(string memory) { 54 | return name; 55 | } 56 | 57 | /** 58 | * Return the NFT URI link of the jurisdiction. 59 | * 60 | * @return defaultBadge the badge URI. 61 | */ 62 | function getJurisdictionBadge() external view returns(string memory) { 63 | return defaultBadge; 64 | } 65 | 66 | /** 67 | * Return the Gold NFT URI link of the jurisdiction. 68 | * 69 | * @return goldBadge the gold badge URI. 70 | */ 71 | function getJurisdictionGoldBadge() external view returns(string memory) { 72 | return goldBadge; 73 | } 74 | 75 | 76 | /** 77 | * Return the renewal price in USD. 78 | * 79 | * @return renewCost the cost to renew a entity of this jurisdiction for 1 year. 80 | */ 81 | function getJurisdictionRenewalPrice() external view returns(uint256) { 82 | return renewCost; 83 | } 84 | 85 | /** 86 | * Return the renewal price in USD. 87 | * 88 | * @return deployCost the cost to renew a entity of this jurisdiction for 1 year. 89 | */ 90 | function getJurisdictionDeployPrice() external view returns(uint256) { 91 | return deployCost; 92 | } 93 | 94 | /** 95 | * Return the close price in USD. 96 | * 97 | * @return closeCost the cost to close the entity. 98 | */ 99 | function getJurisdictionClosePrice() external view returns(uint256) { 100 | return closeCost; 101 | } 102 | 103 | function isStandalone() external view returns(bool) { 104 | return standalone; 105 | } 106 | } -------------------------------------------------------------------------------- /contracts/OtoCoPlugin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "./utils/IOtoCoMaster.sol"; 5 | import "./utils/IOtoCoPlugin.sol"; 6 | import "@openzeppelin/contracts/access/Ownable.sol"; 7 | 8 | abstract contract OtoCoPlugin is IOtoCoPlugin, Ownable { 9 | 10 | // Reference to the OtoCo Master to transfer plugin cost 11 | IOtoCoMaster public otocoMaster; 12 | 13 | /** 14 | * Modifier to allow only series owners to change content. 15 | * @param tokenId The plugin index to update. 16 | */ 17 | modifier onlySeriesOwner(uint256 tokenId) { 18 | require(otocoMaster.ownerOf(tokenId) == msg.sender, "OtoCoPlugin: Not the entity owner."); 19 | _; 20 | } 21 | 22 | /** 23 | * Modifier to check if the function set the correct amount of ETH value and transfer it to master. 24 | * If baseFee is 0 or sender is OtoCoMaster this step is jumped. 25 | * @dev in the future add/attach/remove could be called from OtoCo Master. In those cases no transfer should be called. 26 | */ 27 | modifier transferFees() { 28 | if (otocoMaster.baseFee() > 0 && msg.sender != address(otocoMaster)) payable(otocoMaster).transfer(msg.value); 29 | _; 30 | } 31 | 32 | constructor(address payable _otocoMaster) Ownable() { 33 | otocoMaster = IOtoCoMaster(_otocoMaster); 34 | } 35 | 36 | /** 37 | * Plugin initializer with a fuinction template to be used. 38 | * @dev To decode initialization data use i.e.: (string memory name) = abi.decode(pluginData, (string)); 39 | * @dev Override this function to implement your elements. 40 | * @param pluginData The parameters to create a new instance of plugin. 41 | */ 42 | function addPlugin(uint256 seriesId, bytes calldata pluginData) external payable virtual override; 43 | 44 | /** 45 | * Allow attach a previously deployed plugin if possible 46 | * @dev This function should run enumerous amounts of verifications before allow the attachment. 47 | * @dev To decode initialization data use i.e.: (string memory name) = abi.decode(pluginData, (string)); 48 | * @dev Override this function to implement your elements. 49 | * @param pluginData The parameters to remove a instance of the plugin. 50 | */ 51 | function attachPlugin(uint256 seriesId, bytes calldata pluginData) external payable virtual override { 52 | revert("OtoCoPlugin: Attach elements are not possible on this plugin."); 53 | } 54 | 55 | /** 56 | * Plugin initializer with a fuinction template to be used. 57 | * @dev To decode initialization data use i.e.: (string memory name) = abi.decode(pluginData, (string)); 58 | * @dev Override this function to implement your elements. 59 | * @param pluginData The parameters to remove a instance of the plugin. 60 | */ 61 | function removePlugin(uint256 seriesId, bytes calldata pluginData) external payable virtual override { 62 | revert("OtoCoPlugin: Remove elements are not possible on this plugin."); 63 | } 64 | } -------------------------------------------------------------------------------- /contracts/OtoCoPluginV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "./utils/IOtoCoMasterV2.sol"; 5 | import "./utils/IOtoCoPluginV2.sol"; 6 | import "@openzeppelin/contracts/access/Ownable.sol"; 7 | 8 | 9 | abstract contract OtoCoPluginV2 is IOtoCoPluginV2, Ownable { 10 | 11 | // Custom errors 12 | /// @dev 0x82b42900 13 | error Unauthorized(); 14 | /// @dev 0xdd4f4ace 15 | error AttachNotAllowed(); 16 | /// @dev 0x713e7aee 17 | error RemoveNotAllowed(); 18 | 19 | // Reference to the OtoCo Master to transfer plugin cost 20 | IOtoCoMasterV2 public otocoMaster; 21 | 22 | /** 23 | * Modifier to allow only series owners to change content or OtoCo Master itself. 24 | * @param tokenId The plugin index to update. 25 | */ 26 | modifier onlySeriesOwner(uint256 tokenId) { 27 | if( 28 | msg.sender != address(otocoMaster) && 29 | otocoMaster.ownerOf(tokenId) != msg.sender 30 | ) revert Unauthorized(); 31 | _; 32 | } 33 | 34 | /** 35 | * Modifier to check if the function set the correct amount of ETH value and transfer it to master. 36 | * If baseFee is 0 or sender is OtoCoMaster this step is jumped. 37 | * @dev in the future add/attach/remove could be called from OtoCo Master. In those cases no transfer should be called. 38 | */ 39 | modifier transferFees() { 40 | if ( 41 | otocoMaster.baseFee() > 0 && 42 | msg.sender != address(otocoMaster) 43 | ) payable(otocoMaster).transfer(msg.value); 44 | _; 45 | } 46 | 47 | constructor(address payable _otocoMaster) Ownable() { 48 | otocoMaster = IOtoCoMasterV2(_otocoMaster); 49 | } 50 | 51 | /** 52 | * Plugin initializer with a fuinction template to be used. 53 | * @dev To decode initialization data use i.e.: (string memory name) = abi.decode(pluginData, (string)); 54 | * @dev Override this function to implement your elements. 55 | * @param pluginData The parameters to create a new instance of plugin. 56 | */ 57 | function addPlugin(uint256 seriesId, bytes calldata pluginData) external payable virtual override; 58 | 59 | /** 60 | * Allow attach a previously deployed plugin if possible 61 | * @dev This function should run enumerous amounts of verifications before allow the attachment. 62 | * @dev To decode initialization data use i.e.: (string memory name) = abi.decode(pluginData, (string)); 63 | * @dev Override this function to implement your elements. 64 | * @param pluginData The parameters to remove a instance of the plugin. 65 | */ 66 | function attachPlugin(uint256 seriesId, bytes calldata pluginData) external payable virtual override { 67 | revert AttachNotAllowed(); 68 | } 69 | 70 | /** 71 | * Plugin initializer with a fuinction template to be used. 72 | * @dev To decode initialization data use i.e.: (string memory name) = abi.decode(pluginData, (string)); 73 | * @dev Override this function to implement your elements. 74 | * @param pluginData The parameters to remove a instance of the plugin. 75 | */ 76 | function removePlugin(uint256 seriesId, bytes calldata pluginData) external payable virtual override { 77 | revert RemoveNotAllowed(); 78 | } 79 | } -------------------------------------------------------------------------------- /contracts/governor/InitializableGovernorCountingSimple.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts v4.4.1 (governance/extensions/GovernorCountingSimple.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import "./GovernorNoEIP712NoName.sol"; 7 | 8 | /** 9 | * @dev Extension of {Governor} for simple, 3 options, vote counting. 10 | * 11 | * _Available since v4.3._ 12 | */ 13 | abstract contract InitializableGovernorCountingSimple is GovernorNoEIP712NoName{ 14 | /** 15 | * @dev Supported vote types. Matches Governor Bravo ordering. 16 | */ 17 | enum VoteType { 18 | Against, 19 | For, 20 | Abstain 21 | } 22 | 23 | struct ProposalVote { 24 | uint256 againstVotes; 25 | uint256 forVotes; 26 | uint256 abstainVotes; 27 | mapping(address => bool) hasVoted; 28 | } 29 | 30 | mapping(uint256 => ProposalVote) private _proposalVotes; 31 | 32 | /** 33 | * @dev See {IGovernor-COUNTING_MODE}. 34 | */ 35 | // solhint-disable-next-line func-name-mixedcase 36 | function COUNTING_MODE() public pure virtual override returns (string memory) { 37 | return "support=bravo&quorum=for,abstain"; 38 | } 39 | 40 | /** 41 | * @dev See {IGovernor-hasVoted}. 42 | */ 43 | function hasVoted(uint256 proposalId, address account) public view virtual override returns (bool) { 44 | return _proposalVotes[proposalId].hasVoted[account]; 45 | } 46 | 47 | /** 48 | * @dev Accessor to the internal vote counts. 49 | */ 50 | function proposalVotes(uint256 proposalId) 51 | public 52 | view 53 | virtual 54 | returns ( 55 | uint256 againstVotes, 56 | uint256 forVotes, 57 | uint256 abstainVotes 58 | ) 59 | { 60 | ProposalVote storage proposalvote = _proposalVotes[proposalId]; 61 | return (proposalvote.againstVotes, proposalvote.forVotes, proposalvote.abstainVotes); 62 | } 63 | 64 | /** 65 | * @dev See {Governor-_quorumReached}. 66 | */ 67 | function _quorumReached(uint256 proposalId) internal view virtual override returns (bool) { 68 | ProposalVote storage proposalvote = _proposalVotes[proposalId]; 69 | 70 | return quorum(proposalSnapshot(proposalId)) <= proposalvote.forVotes + proposalvote.abstainVotes; 71 | } 72 | 73 | /** 74 | * @dev See {Governor-_voteSucceeded}. In this module, the forVotes must be strictly over the againstVotes. 75 | */ 76 | function _voteSucceeded(uint256 proposalId) internal view virtual override returns (bool) { 77 | ProposalVote storage proposalvote = _proposalVotes[proposalId]; 78 | return proposalvote.forVotes > proposalvote.againstVotes; 79 | } 80 | 81 | /** 82 | * @dev See {Governor-_countVote}. In this module, the support follows the `VoteType` enum (from Governor Bravo). 83 | */ 84 | function _countVote( 85 | uint256 proposalId, 86 | address account, 87 | uint8 support, 88 | uint256 weight 89 | ) internal virtual override { 90 | ProposalVote storage proposalvote = _proposalVotes[proposalId]; 91 | 92 | require(!proposalvote.hasVoted[account], "GovernorVotingSimple: vote already cast"); 93 | proposalvote.hasVoted[account] = true; 94 | 95 | if (support == uint8(VoteType.Against)) { 96 | proposalvote.againstVotes += weight; 97 | } else if (support == uint8(VoteType.For)) { 98 | proposalvote.forVotes += weight; 99 | } else if (support == uint8(VoteType.Abstain)) { 100 | proposalvote.abstainVotes += weight; 101 | } else { 102 | revert("GovernorVotingSimple: invalid value for enum VoteType"); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /contracts/governor/InitializableGovernorQuorumFraction.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v4.5.0) (governance/extensions/GovernorVotesQuorumFraction.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import "./InitializableGovernorVotes.sol"; 7 | 8 | /** 9 | * @dev Extension of {Governor} for voting weight extraction from an {ERC20Votes} token and a quorum expressed as a 10 | * fraction of the total supply. 11 | * 12 | * _Available since v4.3._ 13 | */ 14 | abstract contract InitializableGovernorQuorumFraction is InitializableGovernorVotes { 15 | uint256 private _quorumNumerator; 16 | 17 | event QuorumNumeratorUpdated(uint256 oldQuorumNumerator, uint256 newQuorumNumerator); 18 | 19 | /** 20 | * @dev Returns the current quorum numerator. See {quorumDenominator}. 21 | */ 22 | function quorumNumerator() public view virtual returns (uint256) { 23 | return _quorumNumerator; 24 | } 25 | 26 | /** 27 | * @dev Returns the quorum denominator. Defaults to 100, but may be overridden. 28 | */ 29 | function quorumDenominator() public view virtual returns (uint256) { 30 | return 100; 31 | } 32 | 33 | /** 34 | * @dev Returns the quorum for a block number, in terms of number of votes: `supply * numerator / denominator`. 35 | */ 36 | function quorum(uint256 blockNumber) public view virtual override returns (uint256) { 37 | return (token.getPastTotalSupply(blockNumber) * quorumNumerator()) / quorumDenominator(); 38 | } 39 | 40 | /** 41 | * @dev Changes the quorum numerator. 42 | * 43 | * Emits a {QuorumNumeratorUpdated} event. 44 | * 45 | * Requirements: 46 | * 47 | * - Must be called through a governance proposal. 48 | * - New numerator must be smaller or equal to the denominator. 49 | */ 50 | function updateQuorumNumerator(uint256 newQuorumNumerator) external virtual onlyGovernance { 51 | _updateQuorumNumerator(newQuorumNumerator); 52 | } 53 | 54 | /** 55 | * @dev Changes the quorum numerator. 56 | * 57 | * Emits a {QuorumNumeratorUpdated} event. 58 | * 59 | * Requirements: 60 | * 61 | * - New numerator must be smaller or equal to the denominator. 62 | */ 63 | function _updateQuorumNumerator(uint256 newQuorumNumerator) internal virtual { 64 | require( 65 | newQuorumNumerator <= quorumDenominator(), 66 | "GovernorVotesQuorumFraction: quorumNumerator over quorumDenominator" 67 | ); 68 | 69 | uint256 oldQuorumNumerator = _quorumNumerator; 70 | _quorumNumerator = newQuorumNumerator; 71 | 72 | emit QuorumNumeratorUpdated(oldQuorumNumerator, newQuorumNumerator); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /contracts/governor/InitializableGovernorSettings.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts v4.4.1 (governance/extensions/GovernorSettings.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import "./GovernorNoEIP712NoName.sol"; 7 | 8 | /** 9 | * @dev Extension of {Governor} for settings updatable through governance. 10 | * 11 | * _Available since v4.4._ 12 | */ 13 | abstract contract InitializableGovernorSettings is GovernorNoEIP712NoName { 14 | uint256 private _votingDelay; 15 | uint256 private _votingPeriod; 16 | uint256 private _proposalThreshold; 17 | 18 | event VotingDelaySet(uint256 oldVotingDelay, uint256 newVotingDelay); 19 | event VotingPeriodSet(uint256 oldVotingPeriod, uint256 newVotingPeriod); 20 | event ProposalThresholdSet(uint256 oldProposalThreshold, uint256 newProposalThreshold); 21 | 22 | /** 23 | * @dev See {IGovernor-votingDelay}. 24 | */ 25 | function votingDelay() public view virtual override returns (uint256) { 26 | return _votingDelay; 27 | } 28 | 29 | /** 30 | * @dev See {IGovernor-votingPeriod}. 31 | */ 32 | function votingPeriod() public view virtual override returns (uint256) { 33 | return _votingPeriod; 34 | } 35 | 36 | /** 37 | * @dev See {Governor-proposalThreshold}. 38 | */ 39 | function proposalThreshold() public view virtual override returns (uint256) { 40 | return _proposalThreshold; 41 | } 42 | 43 | /** 44 | * @dev Update the voting delay. This operation can only be performed through a governance proposal. 45 | * 46 | * Emits a {VotingDelaySet} event. 47 | */ 48 | function setVotingDelay(uint256 newVotingDelay) public virtual onlyGovernance { 49 | _setVotingDelay(newVotingDelay); 50 | } 51 | 52 | /** 53 | * @dev Update the voting period. This operation can only be performed through a governance proposal. 54 | * 55 | * Emits a {VotingPeriodSet} event. 56 | */ 57 | function setVotingPeriod(uint256 newVotingPeriod) public virtual onlyGovernance { 58 | _setVotingPeriod(newVotingPeriod); 59 | } 60 | 61 | /** 62 | * @dev Update the proposal threshold. This operation can only be performed through a governance proposal. 63 | * 64 | * Emits a {ProposalThresholdSet} event. 65 | */ 66 | function setProposalThreshold(uint256 newProposalThreshold) public virtual onlyGovernance { 67 | _setProposalThreshold(newProposalThreshold); 68 | } 69 | 70 | /** 71 | * @dev Internal setter for the voting delay. 72 | * 73 | * Emits a {VotingDelaySet} event. 74 | */ 75 | function _setVotingDelay(uint256 newVotingDelay) internal virtual { 76 | emit VotingDelaySet(_votingDelay, newVotingDelay); 77 | _votingDelay = newVotingDelay; 78 | } 79 | 80 | /** 81 | * @dev Internal setter for the voting period. 82 | * 83 | * Emits a {VotingPeriodSet} event. 84 | */ 85 | function _setVotingPeriod(uint256 newVotingPeriod) internal virtual { 86 | // voting period must be at least one block long 87 | require(newVotingPeriod > 0, "GovernorSettings: voting period too low"); 88 | emit VotingPeriodSet(_votingPeriod, newVotingPeriod); 89 | _votingPeriod = newVotingPeriod; 90 | } 91 | 92 | /** 93 | * @dev Internal setter for the proposal threshold. 94 | * 95 | * Emits a {ProposalThresholdSet} event. 96 | */ 97 | function _setProposalThreshold(uint256 newProposalThreshold) internal virtual { 98 | emit ProposalThresholdSet(_proposalThreshold, newProposalThreshold); 99 | _proposalThreshold = newProposalThreshold; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /contracts/governor/InitializableGovernorVotes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v4.5.0) (governance/extensions/GovernorVotes.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import "./GovernorNoEIP712NoName.sol"; 7 | import "@openzeppelin/contracts/governance/utils/IVotes.sol"; 8 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 9 | 10 | /** 11 | * @dev Extension of {Governor} for voting weight extraction from an {ERC20Votes} token, or since v4.5 an {ERC721Votes} token. 12 | * 13 | * _Available since v4.3._ 14 | */ 15 | abstract contract InitializableGovernorVotes is Initializable, GovernorNoEIP712NoName { 16 | IVotes public token; 17 | 18 | function __GovernorVotes_init(IVotes tokenAddress) internal onlyInitializing { 19 | __GovernorVotes_init_unchained(tokenAddress); 20 | } 21 | 22 | function __GovernorVotes_init_unchained(IVotes tokenAddress) internal onlyInitializing { 23 | token = tokenAddress; 24 | } 25 | 26 | /** 27 | * Read the voting weight from the token's built in snapshot mechanism (see {IGovernor-getVotes}). 28 | */ 29 | function getVotes(address account, uint256 blockNumber) public view virtual override returns (uint256) { 30 | return token.getPastVotes(account, blockNumber); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contracts/initializers/GovernorInitializer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import '@openzeppelin/contracts/proxy/Clones.sol'; 5 | 6 | interface ISeriesToken { 7 | function initialize(string memory name, string memory symbol) external; 8 | function mint(address to, uint256 amount) external; 9 | function transferOwnership(address newOwner) external; 10 | } 11 | 12 | interface IOtoCoGovernor { 13 | function initialize( 14 | address _token, 15 | address _firstManager, 16 | address[] calldata _allowed, 17 | uint256 _votingPeriod, 18 | string calldata _contractName 19 | ) external; 20 | } 21 | 22 | /** 23 | * Tokenized LLCs factory plugin 24 | */ 25 | contract GovernorInitializer { 26 | 27 | /** 28 | * Create a new Gnovernor instance contract and return it. 29 | * 30 | * @dev governorInstance the token instance to be cloned 31 | * @param pluginData Encoded parameters to create a new token. 32 | */ 33 | function setup(address governorInstance, bytes calldata pluginData) 34 | public returns (address governorProxy, address tokenProxy) 35 | { 36 | ( 37 | // Token and Governor name 38 | string memory name, 39 | // Token Symbol 40 | string memory symbol, 41 | address[] memory allowedContracts, 42 | // [0] Manager address 43 | // [1] Token Source to be Cloned 44 | // [2..n] Member Addresses 45 | address[] memory addresses, 46 | // [0] Members size, 47 | // [1] Voting period in days 48 | // [2..n] Member shares 49 | uint256[] memory settings 50 | ) = abi.decode(pluginData, (string, string, address[], address[], uint256[])); 51 | 52 | bytes32 salt = 53 | keccak256(abi.encode(msg.sender, pluginData)); 54 | 55 | ISeriesToken newToken = 56 | ISeriesToken(Clones.cloneDeterministic(addresses[1], salt)); 57 | IOtoCoGovernor newGovernor = 58 | IOtoCoGovernor(Clones.cloneDeterministic(governorInstance, salt)); 59 | 60 | // Initialize token 61 | newToken.initialize(name, symbol); 62 | 63 | // Count the amount of members to assign balances 64 | uint256 index = settings[0]; 65 | while (index > 0) { 66 | // Members start at addresses index 2 67 | // Shares start at settings index 2 68 | newToken.mint(addresses[index+1], settings[index+1]); 69 | --index; 70 | } 71 | // Transfer ownership of the token to Governor contract 72 | newToken.transferOwnership(address(newGovernor)); 73 | // Initialize governor 74 | newGovernor.initialize( 75 | address(newToken), 76 | addresses[0], 77 | allowedContracts, 78 | settings[1], 79 | name 80 | ); 81 | 82 | governorProxy = address(newGovernor); 83 | tokenProxy = address(newToken); 84 | } 85 | } -------------------------------------------------------------------------------- /contracts/jurisdictions/Delaware.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "../OtoCoJurisdiction.sol"; 5 | 6 | contract JurisdictionDelaware is OtoCoJurisdiction { 7 | 8 | constructor ( 9 | string memory _name, 10 | string memory _defaultBadge, 11 | string memory _goldBadge 12 | ) OtoCoJurisdiction(_name, _defaultBadge, _goldBadge) {} 13 | 14 | /** 15 | * @dev See {OtoCoJurisdiction-getSeriesNameFormatted}. 16 | */ 17 | function getSeriesNameFormatted ( 18 | uint256 count, 19 | string calldata nameToFormat 20 | ) public pure override returns(string memory){ 21 | return string(abi.encodePacked(nameToFormat, ' LLC')); 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /contracts/jurisdictions/Unincorporated.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "../OtoCoJurisdiction.sol"; 5 | 6 | contract JurisdictionUnincorporated is OtoCoJurisdiction { 7 | 8 | constructor ( 9 | string memory _name, 10 | string memory _defaultBadge, 11 | string memory _goldBadge 12 | ) OtoCoJurisdiction(_name, _defaultBadge, _goldBadge) {} 13 | 14 | /** 15 | * @dev See {OtoCoJurisdiction-getSeriesNameFormatted}. 16 | */ 17 | function getSeriesNameFormatted ( 18 | uint256 count, 19 | string calldata nameToFormat 20 | ) public pure override returns(string memory){ 21 | return nameToFormat; 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /contracts/jurisdictions/Wyoming.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/utils/Strings.sol"; 5 | import "../OtoCoJurisdiction.sol"; 6 | 7 | contract JurisdictionWyoming is OtoCoJurisdiction { 8 | 9 | // Libraries 10 | using Strings for uint256; 11 | 12 | constructor ( 13 | string memory _name, 14 | string memory _defaultBadge, 15 | string memory _goldBadge 16 | ) OtoCoJurisdiction(_name, _defaultBadge, _goldBadge) {} 17 | 18 | /** 19 | * @dev See {OtoCoJurisdiction-getSeriesNameFormatted}. 20 | */ 21 | function getSeriesNameFormatted ( 22 | uint256 count, 23 | string calldata nameToFormat 24 | ) public pure override returns(string memory){ 25 | return string(abi.encodePacked(nameToFormat, ' - Series ', uint256(count+1).toString())); 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /contracts/jurisdictionsV2/DelawareV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "../OtoCoJurisdictionV2.sol"; 5 | 6 | contract JurisdictionDelawareV2 is OtoCoJurisdictionV2 { 7 | 8 | constructor ( 9 | uint256 renewPrice, 10 | uint256 deployPrice, 11 | uint256 closePrice, 12 | string memory name, 13 | string memory defaultBadge, 14 | string memory goldBadge 15 | ) OtoCoJurisdictionV2(renewPrice, deployPrice, closePrice, name, defaultBadge, goldBadge, false) {} 16 | 17 | /** 18 | * @dev See {OtoCoJurisdiction-getSeriesNameFormatted}. 19 | */ 20 | function getSeriesNameFormatted ( 21 | uint256 count, 22 | string calldata nameToFormat 23 | ) public pure override returns(string memory){ 24 | return string(abi.encodePacked(nameToFormat, ' LLC')); 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /contracts/jurisdictionsV2/MarshallIslandsV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "../OtoCoJurisdictionV2.sol"; 5 | 6 | contract JurisdictionMarshallIslandsV2 is OtoCoJurisdictionV2 { 7 | 8 | constructor ( 9 | uint256 renewPrice, 10 | uint256 deployPrice, 11 | uint256 closePrice, 12 | string memory name, 13 | string memory defaultBadge, 14 | string memory goldBadge 15 | ) OtoCoJurisdictionV2(renewPrice, deployPrice, closePrice, name, defaultBadge, goldBadge, false) {} 16 | 17 | /** 18 | * @dev See {OtoCoJurisdiction-getSeriesNameFormatted}. 19 | */ 20 | function getSeriesNameFormatted ( 21 | uint256 count, 22 | string calldata nameToFormat 23 | ) public pure override returns(string memory){ 24 | return string(abi.encodePacked(nameToFormat, ' LLC')); 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /contracts/jurisdictionsV2/SwissAssociationV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "../OtoCoJurisdictionV2.sol"; 5 | 6 | contract JurisdictionSwissAssociationV2 is OtoCoJurisdictionV2 { 7 | 8 | constructor ( 9 | uint256 renewPrice, 10 | uint256 deployPrice, 11 | uint256 closePrice, 12 | string memory name, 13 | string memory defaultBadge, 14 | string memory goldBadge 15 | ) OtoCoJurisdictionV2(renewPrice, deployPrice, closePrice, name, defaultBadge, goldBadge, false) {} 16 | 17 | /** 18 | * @dev See {OtoCoJurisdiction-getSeriesNameFormatted}. 19 | */ 20 | function getSeriesNameFormatted ( 21 | uint256 count, 22 | string calldata nameToFormat 23 | ) public pure override returns(string memory){ 24 | return string(abi.encodePacked(nameToFormat, ' Association')); 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /contracts/jurisdictionsV2/UnaDunaV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/utils/Strings.sol"; 5 | import "../OtoCoJurisdictionV2.sol"; 6 | 7 | contract JurisdictionUnaDunaV2 is OtoCoJurisdictionV2 { 8 | 9 | // Libraries 10 | using Strings for uint256; 11 | 12 | constructor ( 13 | uint256 renewPrice, 14 | uint256 deployPrice, 15 | uint256 closePrice, 16 | string memory name, 17 | string memory defaultBadge, 18 | string memory goldBadge 19 | ) OtoCoJurisdictionV2(renewPrice, deployPrice, closePrice, name, defaultBadge, goldBadge, false) {} 20 | 21 | 22 | /** 23 | * @dev See {OtoCoJurisdiction-getSeriesNameFormatted}. 24 | */ 25 | function getSeriesNameFormatted ( 26 | uint256 count, 27 | string calldata nameToFormat 28 | ) public pure override returns(string memory){ 29 | return string(abi.encodePacked(nameToFormat, ' Association')); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /contracts/jurisdictionsV2/UnincorporatedV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "../OtoCoJurisdictionV2.sol"; 5 | 6 | contract JurisdictionUnincorporatedV2 is OtoCoJurisdictionV2 { 7 | 8 | constructor ( 9 | uint256 renewPrice, 10 | uint256 deployPrice, 11 | uint256 closePrice, 12 | string memory name, 13 | string memory defaultBadge, 14 | string memory goldBadge 15 | ) OtoCoJurisdictionV2(renewPrice, deployPrice, closePrice, name, defaultBadge, goldBadge, false) {} 16 | 17 | 18 | /** 19 | * @dev See {OtoCoJurisdiction-getSeriesNameFormatted}. 20 | */ 21 | function getSeriesNameFormatted ( 22 | uint256 count, 23 | string calldata nameToFormat 24 | ) public pure override returns(string memory){ 25 | return nameToFormat; 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /contracts/jurisdictionsV2/WyomingV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/utils/Strings.sol"; 5 | import "../OtoCoJurisdictionV2.sol"; 6 | 7 | contract JurisdictionWyomingV2 is OtoCoJurisdictionV2 { 8 | 9 | // Libraries 10 | using Strings for uint256; 11 | 12 | constructor ( 13 | uint256 renewPrice, 14 | uint256 deployPrice, 15 | uint256 closePrice, 16 | string memory name, 17 | string memory defaultBadge, 18 | string memory goldBadge 19 | ) OtoCoJurisdictionV2(renewPrice, deployPrice, closePrice, name, defaultBadge, goldBadge, false) {} 20 | 21 | 22 | /** 23 | * @dev See {OtoCoJurisdiction-getSeriesNameFormatted}. 24 | */ 25 | function getSeriesNameFormatted ( 26 | uint256 count, 27 | string calldata nameToFormat 28 | ) public pure override returns(string memory){ 29 | return string(abi.encodePacked(nameToFormat, ' - Series ', uint256(count+1).toString())); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /contracts/plugins/ENS.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "../OtoCoPlugin.sol"; 5 | 6 | interface IENS { 7 | function setSubnodeRecord(bytes32 node, bytes32 label, address _owner, address resolver, uint64 ttl) external; 8 | function setSubnodeOwner(bytes32 node, bytes32 label, address _owner) external returns(bytes32); 9 | function setOwner(bytes32 node, address _owner) external; 10 | function owner(bytes32 node) external view returns (address); 11 | } 12 | 13 | interface IResolver{ 14 | function setAddr(bytes32 node, address addr) external; 15 | function setAddr(bytes32 node, uint coinType, bytes calldata a) external; 16 | } 17 | 18 | /** 19 | * A registrar that stores subdomains to the first person who claim them. 20 | */ 21 | contract ENS is OtoCoPlugin { 22 | 23 | event SubdomainClaimed(uint256 indexed series, string value); 24 | 25 | // Master ENS registry 26 | IENS public ens; 27 | // The otoco.eth node reference 28 | bytes32 public rootNode; 29 | // Default resolver to deal with data storage 30 | IResolver public defaultResolver; 31 | // Mapping from entities to created domains 32 | mapping(uint256 => uint256) public domainsPerEntity; 33 | // Mapping of Company address => Domains 34 | mapping(uint256 => string[]) public seriesDomains; 35 | 36 | modifier notOwned(bytes32 label) { 37 | address currentOwner = ens.owner(keccak256(abi.encodePacked(rootNode, label))); 38 | require(currentOwner == address(0x0), "ENSPlugin: Domain alredy registered."); 39 | _; 40 | } 41 | 42 | /* 43 | * Constructor. 44 | * 45 | * @param ensAddr The address of the ENS registry. 46 | * @param resolverAddr The resolver where domains will use to register. 47 | * @param node The node that this registrar administers. 48 | * @param previousSeries Previous series to be migrated. 49 | * @param previousDomains Previous domains to be migrated. 50 | */ 51 | constructor ( 52 | address payable otocoMaster, 53 | IENS ensAddr, 54 | IResolver resolverAddr, 55 | bytes32 node, 56 | uint256[] memory prevSeries, 57 | string[] memory prevDomains 58 | ) OtoCoPlugin(otocoMaster) { 59 | ens = ensAddr; 60 | rootNode = node; 61 | defaultResolver = resolverAddr; 62 | for (uint i = 0; i < prevSeries.length; i++ ) { 63 | emit SubdomainClaimed(prevSeries[i], prevDomains[i]); 64 | domainsPerEntity[prevSeries[i]]++; 65 | seriesDomains[prevSeries[i]].push(prevDomains[i]); 66 | } 67 | } 68 | 69 | /** 70 | * Register a name, and store the domain to reverse lookup. 71 | * 72 | * @param pluginData Encoded parameters to create a new token. 73 | * @dev domain The string containing the domain. 74 | * @dev target Series contract that registry will point. 75 | * @dev addr Address to redirect domain 76 | */ 77 | function addPlugin(uint256 seriesId, bytes calldata pluginData) public onlySeriesOwner(seriesId) transferFees() payable override { 78 | ( 79 | string memory domain, 80 | address owner 81 | ) = abi.decode(pluginData, (string, address)); 82 | bytes32 label = keccak256(abi.encodePacked(domain)); 83 | register(label, owner); 84 | seriesDomains[seriesId].push(domain); 85 | domainsPerEntity[seriesId]++; 86 | emit SubdomainClaimed(seriesId, domain); 87 | } 88 | 89 | /** 90 | * Register a name, or change the owner of an existing registration. 91 | * @param label The hash of the label to register. 92 | * @param owner The address of the new owner. 93 | */ 94 | function register(bytes32 label, address owner) internal notOwned(label) { 95 | bytes32 node = keccak256(abi.encodePacked(rootNode, label)); 96 | ens.setSubnodeRecord(rootNode, label, address(this), address(defaultResolver), 63072000000000); 97 | defaultResolver.setAddr(node, owner); 98 | ens.setOwner(node, owner); 99 | } 100 | 101 | } -------------------------------------------------------------------------------- /contracts/plugins/Launchpool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "../OtoCoPlugin.sol"; 5 | import '@openzeppelin/contracts/proxy/Clones.sol'; 6 | 7 | interface PoolInterface { 8 | function initialize( 9 | address[] memory allowedTokens, 10 | uint256[] memory uintArgs, 11 | string memory _metadata, 12 | address _sponsor, 13 | address _shares, 14 | address _curve 15 | ) external; 16 | 17 | function sponsor() external view returns(address); 18 | function metadata() external view returns(string memory); 19 | } 20 | 21 | /** 22 | * Launchpool Factory 23 | */ 24 | contract Launchpool is OtoCoPlugin { 25 | 26 | // Launchpool creation and removal events 27 | event LaunchpoolCreated(uint256 indexed seriesId, address sponsor, address pool, string metadata); 28 | event LaunchpoolRemoved(uint256 indexed seriesId, address pool); 29 | 30 | // The source of launchpool to be deployed 31 | address private _poolSource; 32 | // The curve sources that could be used on launchpool 33 | address[] private _curveSources; 34 | 35 | // The assignment of launchpools to entities 36 | mapping(uint256 => address) public launchpoolDeployed; 37 | 38 | constructor( 39 | address payable otocoMaster, 40 | address poolSource, 41 | address curveSource, 42 | uint256[] memory prevIds, 43 | address[] memory prevLaunchpools 44 | ) OtoCoPlugin(otocoMaster) { 45 | _poolSource = poolSource; 46 | _curveSources.push(curveSource); 47 | for (uint i = 0; i < prevIds.length; i++ ) { 48 | launchpoolDeployed[prevIds[i]] = prevLaunchpools[i]; 49 | PoolInterface pool = PoolInterface(launchpoolDeployed[prevIds[i]]); 50 | emit LaunchpoolCreated(prevIds[i], pool.sponsor(), prevLaunchpools[i], pool.metadata()); 51 | } 52 | } 53 | 54 | /** 55 | * Update launchpool Source 56 | * 57 | * @param newAddress The new launchpool source to be used on clones 58 | */ 59 | function updatePoolSource(address newAddress) public onlyOwner { 60 | _poolSource = newAddress; 61 | } 62 | 63 | /** 64 | * Add a new curve source to the curve options 65 | * 66 | * @param newAddress The new curve source to be added to curve options 67 | */ 68 | function addCurveSource(address newAddress) public onlyOwner { 69 | _curveSources.push(newAddress); 70 | } 71 | 72 | function addPlugin(uint256 seriesId, bytes calldata pluginData) onlySeriesOwner(seriesId) transferFees() public payable override { 73 | ( 74 | address[] memory _allowedTokens, 75 | uint256[] memory _uintArgs, 76 | string memory _metadata, 77 | address _shares, 78 | uint16 _curve, 79 | address sponsor 80 | ) = abi.decode(pluginData, (address[], uint256[], string, address, uint16, address)); 81 | address pool = Clones.clone(_poolSource); 82 | PoolInterface(pool).initialize(_allowedTokens, _uintArgs, _metadata, sponsor, _shares, _curveSources[_curve]); 83 | launchpoolDeployed[seriesId] = pool; 84 | emit LaunchpoolCreated(seriesId, sponsor, pool, _metadata); 85 | } 86 | 87 | function removePlugin(uint256 seriesId, bytes calldata pluginData) onlySeriesOwner(seriesId) transferFees() public payable override { 88 | // Remove the last token from array 89 | address pool = launchpoolDeployed[seriesId]; 90 | launchpoolDeployed[seriesId] = address(0); 91 | emit LaunchpoolRemoved(seriesId, pool); 92 | } 93 | } -------------------------------------------------------------------------------- /contracts/plugins/Multisig.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 5 | import "../OtoCoPlugin.sol"; 6 | 7 | interface GnosisSafeProxyFactory { 8 | function createProxy(address singleton, bytes memory data) external returns (address proxy); 9 | } 10 | 11 | /** 12 | * Multisig 13 | */ 14 | contract Multisig is OtoCoPlugin { 15 | 16 | event MultisigAdded(uint256 indexed series, address multisig); 17 | event MultisigRemoved(uint256 indexed series, address multisig); 18 | 19 | address public gnosisMasterCopy; 20 | address public gnosisProxyFactory; 21 | 22 | mapping(uint256 => uint256) public multisigPerEntity; 23 | mapping(uint256 => address[]) public multisigDeployed; 24 | 25 | constructor( 26 | address payable otocoMaster, 27 | address masterCopy, 28 | address proxyFactory, 29 | uint256[] memory prevIds, 30 | address[] memory prevMultisig 31 | ) OtoCoPlugin(otocoMaster) { 32 | gnosisMasterCopy = masterCopy; 33 | gnosisProxyFactory = proxyFactory; 34 | for (uint i = 0; i < prevIds.length; i++ ) { 35 | multisigDeployed[prevIds[i]].push(prevMultisig[i]); 36 | multisigPerEntity[prevIds[i]]++; 37 | emit MultisigAdded(prevIds[i], prevMultisig[i]); 38 | } 39 | } 40 | 41 | function updateGnosisMasterCopy(address newAddress) public onlyOwner { 42 | gnosisMasterCopy = newAddress; 43 | } 44 | 45 | function updateGnosisProxyFactory(address newAddress) public onlyOwner { 46 | gnosisProxyFactory = newAddress; 47 | } 48 | 49 | function addPlugin(uint256 seriesId, bytes calldata pluginData) public onlySeriesOwner(seriesId) transferFees() payable override { 50 | address proxy = GnosisSafeProxyFactory(gnosisProxyFactory).createProxy(gnosisMasterCopy, pluginData); 51 | multisigDeployed[seriesId].push(proxy); 52 | multisigPerEntity[seriesId]++; 53 | emit MultisigAdded(seriesId, proxy); 54 | } 55 | 56 | /** 57 | * Attaching a pre-existing multisig to the entity 58 | * @dev seriesId Series to remove token from 59 | * @dev newMultisig Multisig address to be attached 60 | * 61 | * @param pluginData Encoded parameters to create a new token. 62 | */ 63 | function attachPlugin(uint256 seriesId, bytes calldata pluginData) public onlySeriesOwner(seriesId) transferFees() payable override { 64 | ( 65 | address newMultisig 66 | ) = abi.decode(pluginData, ( address)); 67 | multisigDeployed[seriesId].push(newMultisig); 68 | multisigPerEntity[seriesId]++; 69 | emit MultisigAdded(seriesId, newMultisig); 70 | } 71 | 72 | function removePlugin(uint256 seriesId, bytes calldata pluginData) public onlySeriesOwner(seriesId) transferFees() payable override { 73 | ( 74 | uint256 toRemove 75 | ) = abi.decode(pluginData, (uint256)); 76 | address multisigRemoved = multisigDeployed[seriesId][toRemove]; 77 | // Copy last token to the removed slot 78 | multisigDeployed[seriesId][toRemove] = multisigDeployed[seriesId][multisigDeployed[seriesId].length - 1]; 79 | // Remove the last token from array 80 | multisigDeployed[seriesId].pop(); 81 | multisigPerEntity[seriesId]--; 82 | emit MultisigRemoved(seriesId, multisigRemoved); 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /contracts/plugins/Timestamp.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol'; 5 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 6 | import "../OtoCoPlugin.sol"; 7 | 8 | /** 9 | * Master Registry Contract. 10 | */ 11 | contract Timestamp is OtoCoPlugin { 12 | 13 | event DocumentTimestamped(uint256 indexed seriesId, uint256 timestamp, string filename, string cid); 14 | 15 | // Upgradeable contract initializer 16 | constructor (address payable otocoMaster) OtoCoPlugin(otocoMaster) {} 17 | 18 | /** 19 | * Create a new timestamp for the entity. May only be called by the owner of the series. 20 | * 21 | * @param seriesId The series ID be updated. 22 | * @param pluginData filename and cid of the document to be timestamped abi encoded. 23 | */ 24 | function addPlugin(uint256 seriesId, bytes calldata pluginData) public onlySeriesOwner(seriesId) transferFees() payable override { 25 | ( 26 | string memory filename, 27 | string memory cid 28 | ) = abi.decode(pluginData, (string, string)); 29 | emit DocumentTimestamped(seriesId, block.timestamp, filename, cid); 30 | } 31 | 32 | /** 33 | * Allow manager of the plugin to migrate previous Timestamps. 34 | * 35 | * @param seriesId The series ID be updated. 36 | * @param pluginData filename, cid and timestamp of the document to be timestamped abi encoded. 37 | */ 38 | function migrateTimestamp(uint256 seriesId, bytes calldata pluginData) public onlyOwner { 39 | ( 40 | string memory filename, 41 | string memory cid, 42 | uint256 timestamp 43 | ) = abi.decode(pluginData, (string, string, uint256)); 44 | emit DocumentTimestamped(seriesId, timestamp, filename, cid); 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /contracts/plugins/Token.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import '@openzeppelin/contracts/proxy/Clones.sol'; 6 | import "../OtoCoPlugin.sol"; 7 | 8 | interface ISeriesToken { 9 | function initialize (string memory name, string memory symbol, uint256 supply, address member) external; 10 | } 11 | 12 | /** 13 | * Token factory plugin 14 | */ 15 | contract Token is OtoCoPlugin { 16 | 17 | event TokenAdded(uint256 indexed series, address token); 18 | event TokenRemoved(uint256 indexed series, address token); 19 | 20 | // Token source contract to be cloned 21 | address public tokenContract; 22 | // Mapping from entities to deployed tokens 23 | mapping(uint256 => uint256) public tokensPerEntity; 24 | // Mapping from entities to deployed tokens 25 | mapping(uint256 => address[]) public tokensDeployed; 26 | 27 | 28 | /** 29 | * Constructor for Token Plugin. 30 | * 31 | * @param otocoMaster Address from the Master contract. 32 | * @param token Address from the token source contract to be cloned. 33 | * @param prevIds Previously deployed token series indexes. 34 | * @param prevTokens Addresses from the tokens previously deployed. 35 | */ 36 | constructor( 37 | address payable otocoMaster, 38 | address token, 39 | uint256[] memory prevIds, 40 | address[] memory prevTokens 41 | ) OtoCoPlugin(otocoMaster) { 42 | tokenContract = token; 43 | for (uint i = 0; i < prevIds.length; i++ ) { 44 | tokensDeployed[prevIds[i]].push(prevTokens[i]); 45 | tokensPerEntity[prevIds[i]]++; 46 | emit TokenAdded(prevIds[i], prevTokens[i]); 47 | } 48 | } 49 | 50 | /** 51 | * Update token contract base source. 52 | * 53 | * @param newAddress New token source to be used 54 | */ 55 | function updateTokenContract(address newAddress) public onlyOwner { 56 | tokenContract = newAddress; 57 | } 58 | 59 | /** 60 | * Create a new token for the entity. May only be called by the owner of the series. 61 | * 62 | * @param pluginData Encoded parameters to create a new token. 63 | * @dev seriesId would be the series that will own the token. 64 | * @dev supply the total supply of tokens to be issued. 65 | * @dev name the name of the token as string. 66 | * @dev symbol the symbol that respresent the token. 67 | */ 68 | function addPlugin(uint256 seriesId, bytes calldata pluginData) public onlySeriesOwner(seriesId) transferFees() payable override { 69 | ( 70 | uint256 supply, 71 | string memory name, 72 | string memory symbol, 73 | address holder 74 | ) = abi.decode(pluginData, (uint256, string, string, address)); 75 | address newToken = Clones.clone(tokenContract); 76 | ISeriesToken(newToken).initialize(name, symbol, supply, holder); 77 | tokensDeployed[seriesId].push(newToken); 78 | tokensPerEntity[seriesId]++; 79 | emit TokenAdded(seriesId, newToken); 80 | } 81 | 82 | /** 83 | * Attaching a pre-existing token to the entity. May only be called by the entity owner. 84 | * 85 | * @param pluginData Encoded parameters to create a new token. 86 | * @dev seriesId Series to remove token from 87 | * @dev newToken Token address to be attached 88 | */ 89 | function attachPlugin(uint256 seriesId, bytes calldata pluginData) public onlySeriesOwner(seriesId) transferFees() payable override { 90 | ( 91 | address newToken 92 | ) = abi.decode(pluginData, (address)); 93 | tokensDeployed[seriesId].push(newToken); 94 | tokensPerEntity[seriesId]++; 95 | emit TokenAdded(seriesId, newToken); 96 | } 97 | 98 | /** 99 | * Remove token from entity 100 | * 101 | * @param pluginData Encoded parameters to create a new token. 102 | * @dev seriesId Series to remove token from 103 | * @dev toRemove Token index to be removed 104 | */ 105 | function removePlugin(uint256 seriesId, bytes calldata pluginData) public onlySeriesOwner(seriesId) transferFees() payable override { 106 | ( 107 | uint256 toRemove 108 | ) = abi.decode(pluginData, (uint256)); 109 | address tokenRemoved = tokensDeployed[seriesId][toRemove]; 110 | // Copy last token to the removed slot 111 | tokensDeployed[seriesId][toRemove] = tokensDeployed[seriesId][tokensDeployed[seriesId].length - 1]; 112 | // Remove the last token from array 113 | tokensDeployed[seriesId].pop(); 114 | tokensPerEntity[seriesId]--; 115 | emit TokenRemoved(seriesId, tokenRemoved); 116 | } 117 | } -------------------------------------------------------------------------------- /contracts/pluginsV2/ENSV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "../OtoCoPluginV2.sol"; 5 | 6 | interface IENS { 7 | function setSubnodeRecord(bytes32 node, bytes32 label, address _owner, address resolver, uint64 ttl) external; 8 | function setSubnodeOwner(bytes32 node, bytes32 label, address _owner) external returns(bytes32); 9 | function setOwner(bytes32 node, address _owner) external; 10 | function owner(bytes32 node) external view returns (address); 11 | } 12 | 13 | interface IResolver{ 14 | function setAddr(bytes32 node, address addr) external; 15 | function setAddr(bytes32 node, uint coinType, bytes calldata a) external; 16 | } 17 | 18 | /** 19 | * A registrar that stores subdomains to the first person who claim them. 20 | */ 21 | contract ENSV2 is OtoCoPluginV2 { 22 | 23 | // Custom Errors 24 | error InvalidDomain(); 25 | 26 | event SubdomainClaimed(uint256 indexed series, string value); 27 | 28 | // The otoco.eth node reference 29 | bytes32 public rootNode; 30 | // Master ENS registry 31 | IENS public ens; 32 | // Default resolver to deal with data storage 33 | IResolver public defaultResolver; 34 | 35 | // Mapping from entities to created domains 36 | mapping(uint256 => uint256) public domainsPerEntity; 37 | // Mapping of Company address => Domains 38 | mapping(uint256 => string[]) public seriesDomains; 39 | 40 | modifier notOwned(bytes32 label) { 41 | address currentOwner = 42 | ens.owner( 43 | keccak256(abi.encodePacked(rootNode, label)) 44 | ); 45 | if(currentOwner != address(0x0)) 46 | revert InvalidDomain(); 47 | _; 48 | } 49 | 50 | /* 51 | * Constructor 52 | * 53 | * @param ensAddr The address of the ENS registry. 54 | * @param resolverAddr The resolver which domains will use to register. 55 | * @param node The node that this registrar administers. 56 | * @param previousSeries Previous series to be migrated. 57 | * @param previousDomains Previous domains to be migrated. 58 | */ 59 | constructor ( 60 | address payable otocoMaster, 61 | IENS ensAddr, 62 | IResolver resolverAddr, 63 | bytes32 node, 64 | uint256[] memory prevSeries, 65 | string[] memory prevDomains 66 | ) OtoCoPluginV2(otocoMaster) { 67 | ens = ensAddr; 68 | rootNode = node; 69 | defaultResolver = resolverAddr; 70 | 71 | for (uint i = 0; i < prevSeries.length; i++ ) { 72 | emit SubdomainClaimed(prevSeries[i], prevDomains[i]); 73 | domainsPerEntity[prevSeries[i]]++; 74 | seriesDomains[prevSeries[i]].push(prevDomains[i]); 75 | } 76 | } 77 | 78 | /** 79 | * Register a name, and store the domain to reverse lookup. 80 | * 81 | * @param pluginData Encoded parameters to create a new token. 82 | * @dev domain The string containing the domain. 83 | * @dev target Series contract that registry will point. 84 | * @dev addr Address to redirect domain 85 | */ 86 | function addPlugin(uint256 seriesId, bytes calldata pluginData) 87 | public onlySeriesOwner(seriesId) transferFees() payable override 88 | { 89 | ( 90 | string memory domain, 91 | address owner 92 | ) = abi.decode(pluginData, (string, address)); 93 | 94 | bytes32 label = keccak256(abi.encodePacked(domain)); 95 | register(label, owner); 96 | seriesDomains[seriesId].push(domain); 97 | domainsPerEntity[seriesId]++; 98 | 99 | emit SubdomainClaimed(seriesId, domain); 100 | } 101 | 102 | /** 103 | * Register a name, or change the owner of an existing registration. 104 | * @param label The hash of the label to register. 105 | * @param owner The address of the new owner. 106 | */ 107 | function register(bytes32 label, address owner) internal notOwned(label) { 108 | bytes32 node = keccak256(abi.encodePacked(rootNode, label)); 109 | ens.setSubnodeRecord(rootNode, label, address(this), address(defaultResolver), 63072000000000); 110 | defaultResolver.setAddr(node, owner); 111 | ens.setOwner(node, owner); 112 | } 113 | 114 | } -------------------------------------------------------------------------------- /contracts/pluginsV2/LaunchpoolV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "../OtoCoPluginV2.sol"; 5 | import '@openzeppelin/contracts/proxy/Clones.sol'; 6 | 7 | interface PoolInterface { 8 | function initialize( 9 | address[] memory allowedTokens, 10 | uint256[] memory uintArgs, 11 | string memory _metadata, 12 | address _sponsor, 13 | address _shares, 14 | address _curve 15 | ) external; 16 | 17 | function sponsor() external view returns(address); 18 | function metadata() external view returns(string memory); 19 | } 20 | 21 | /** 22 | * Launchpool Factory 23 | */ 24 | contract LaunchpoolV2 is OtoCoPluginV2 { 25 | 26 | // Launchpool creation and removal events 27 | event LaunchpoolCreated(uint256 indexed seriesId, address sponsor, address pool, string metadata); 28 | event LaunchpoolRemoved(uint256 indexed seriesId, address pool); 29 | 30 | // The source of launchpool to be deployed 31 | address private _poolSource; 32 | // The curve sources that could be used on launchpool 33 | address[] private _curveSources; 34 | 35 | // The assignment of launchpools to entities 36 | mapping(uint256 => address) public launchpoolDeployed; 37 | 38 | constructor( 39 | address payable otocoMaster, 40 | address poolSource, 41 | address curveSource, 42 | uint256[] memory prevIds, 43 | address[] memory prevLaunchpools 44 | ) OtoCoPluginV2(otocoMaster) { 45 | 46 | _poolSource = poolSource; 47 | _curveSources.push(curveSource); 48 | 49 | for (uint i = 0; i < prevIds.length; i++ ) { 50 | launchpoolDeployed[prevIds[i]] = prevLaunchpools[i]; 51 | PoolInterface pool = PoolInterface(launchpoolDeployed[prevIds[i]]); 52 | 53 | emit LaunchpoolCreated(prevIds[i], pool.sponsor(), prevLaunchpools[i], pool.metadata()); 54 | } 55 | } 56 | 57 | /** 58 | * Update launchpool Source 59 | * 60 | * @param newAddress The new launchpool source to be used on clones 61 | */ 62 | function updatePoolSource(address newAddress) public onlyOwner { 63 | _poolSource = newAddress; 64 | } 65 | 66 | /** 67 | * Add a new curve source to the curve options 68 | * 69 | * @param newAddress The new curve source to be added to curve options 70 | */ 71 | function addCurveSource(address newAddress) public onlyOwner { 72 | _curveSources.push(newAddress); 73 | } 74 | 75 | function addPlugin(uint256 seriesId, bytes calldata pluginData) 76 | onlySeriesOwner(seriesId) transferFees() public payable override 77 | { 78 | ( 79 | address[] memory _allowedTokens, 80 | uint256[] memory _uintArgs, 81 | string memory _metadata, 82 | address _shares, 83 | uint16 _curve, 84 | address sponsor 85 | ) = abi.decode(pluginData, (address[], uint256[], string, address, uint16, address)); 86 | 87 | address pool = Clones.clone(_poolSource); 88 | PoolInterface(pool).initialize(_allowedTokens, _uintArgs, _metadata, sponsor, _shares, _curveSources[_curve]); 89 | launchpoolDeployed[seriesId] = pool; 90 | 91 | emit LaunchpoolCreated(seriesId, sponsor, pool, _metadata); 92 | } 93 | 94 | function removePlugin(uint256 seriesId, bytes calldata pluginData) 95 | onlySeriesOwner(seriesId) transferFees() public payable override 96 | { 97 | // Remove the last token from array 98 | address pool = launchpoolDeployed[seriesId]; 99 | launchpoolDeployed[seriesId] = address(0); 100 | 101 | emit LaunchpoolRemoved(seriesId, pool); 102 | } 103 | } -------------------------------------------------------------------------------- /contracts/pluginsV2/MultisigV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | // import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 5 | import "../OtoCoPluginV2.sol"; 6 | 7 | interface GnosisSafeProxyFactory { 8 | function createProxy(address singleton, bytes memory data) external returns (address proxy); 9 | } 10 | 11 | /** 12 | * Multisig 13 | */ 14 | contract MultisigV2 is OtoCoPluginV2 { 15 | 16 | event MultisigAdded(uint256 indexed series, address multisig); 17 | event MultisigRemoved(uint256 indexed series, address multisig); 18 | 19 | address public gnosisMasterCopy; 20 | address public gnosisProxyFactory; 21 | 22 | mapping(uint256 => uint256) public multisigPerEntity; 23 | mapping(uint256 => address[]) public multisigDeployed; 24 | 25 | constructor( 26 | address payable otocoMaster, 27 | address masterCopy, 28 | address proxyFactory, 29 | uint256[] memory prevIds, 30 | address[] memory prevMultisig 31 | ) OtoCoPluginV2(otocoMaster) { 32 | gnosisMasterCopy = masterCopy; 33 | gnosisProxyFactory = proxyFactory; 34 | 35 | for (uint i = 0; i < prevIds.length; i++ ) { 36 | multisigDeployed[prevIds[i]].push(prevMultisig[i]); 37 | multisigPerEntity[prevIds[i]]++; 38 | 39 | emit MultisigAdded(prevIds[i], prevMultisig[i]); 40 | } 41 | } 42 | 43 | function updateGnosisMasterCopy(address newAddress) public onlyOwner { 44 | gnosisMasterCopy = newAddress; 45 | } 46 | 47 | function updateGnosisProxyFactory(address newAddress) public onlyOwner { 48 | gnosisProxyFactory = newAddress; 49 | } 50 | 51 | function addPlugin(uint256 seriesId, bytes calldata pluginData) 52 | public onlySeriesOwner(seriesId) transferFees() payable override 53 | { 54 | address proxy = 55 | GnosisSafeProxyFactory(gnosisProxyFactory) 56 | .createProxy(gnosisMasterCopy, pluginData); 57 | 58 | multisigDeployed[seriesId].push(proxy); 59 | multisigPerEntity[seriesId]++; 60 | 61 | emit MultisigAdded(seriesId, proxy); 62 | } 63 | 64 | /** 65 | * Attaching a pre-existing multisig to the entity 66 | * @dev seriesId Series to remove token from 67 | * @dev newMultisig Multisig address to be attached 68 | * 69 | * @param pluginData Encoded parameters to create a new token. 70 | */ 71 | function attachPlugin(uint256 seriesId, bytes calldata pluginData) 72 | public onlySeriesOwner(seriesId) transferFees() payable override 73 | { 74 | ( address newMultisig ) = 75 | abi.decode(pluginData, (address)); 76 | 77 | multisigDeployed[seriesId].push(newMultisig); 78 | multisigPerEntity[seriesId]++; 79 | 80 | emit MultisigAdded(seriesId, newMultisig); 81 | } 82 | 83 | function removePlugin(uint256 seriesId, bytes calldata pluginData) 84 | public onlySeriesOwner(seriesId) transferFees() payable override 85 | { 86 | ( uint256 toRemove ) = 87 | abi.decode(pluginData, (uint256)); 88 | 89 | address multisigRemoved = 90 | multisigDeployed[seriesId][toRemove]; 91 | 92 | // Copy last token to the removed slot 93 | multisigDeployed[seriesId][toRemove] = 94 | multisigDeployed[seriesId][multisigDeployed[seriesId].length - 1]; 95 | 96 | // Remove the last token from array 97 | multisigDeployed[seriesId].pop(); 98 | multisigPerEntity[seriesId]--; 99 | 100 | emit MultisigRemoved(seriesId, multisigRemoved); 101 | } 102 | 103 | } -------------------------------------------------------------------------------- /contracts/pluginsV2/TimestampV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | // import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol'; 5 | // import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 6 | import "../OtoCoPluginV2.sol"; 7 | 8 | /** 9 | * Master Registry Contract. 10 | */ 11 | contract TimestampV2 is OtoCoPluginV2 { 12 | 13 | event DocumentTimestamped( 14 | uint256 indexed seriesId, 15 | uint256 timestamp, 16 | string filename, 17 | string cid 18 | ); 19 | 20 | // Upgradeable contract initializer 21 | constructor (address payable otocoMaster) OtoCoPluginV2(otocoMaster) {} 22 | 23 | /** 24 | * Create a new timestamp for the entity. May only be called by the owner of the series. 25 | * 26 | * @param seriesId The series ID to be updated. 27 | * @param pluginData filename and cid of the document to be timestamped abi encoded. 28 | */ 29 | function addPlugin(uint256 seriesId, bytes calldata pluginData) 30 | public onlySeriesOwner(seriesId) transferFees() payable override 31 | { 32 | ( 33 | string memory filename, 34 | string memory cid 35 | ) = abi.decode(pluginData, (string, string)); 36 | 37 | emit DocumentTimestamped(seriesId, block.timestamp, filename, cid); 38 | } 39 | 40 | /** 41 | * Allow manager of the plugin to migrate previous Timestamps. 42 | * 43 | * @param seriesId The series ID be updated. 44 | * @param pluginData filename, cid and timestamp of the document to be timestamped abi encoded. 45 | */ 46 | function migrateTimestamp(uint256 seriesId, bytes calldata pluginData) public onlyOwner { 47 | ( 48 | string memory filename, 49 | string memory cid, 50 | uint256 timestamp 51 | ) = abi.decode(pluginData, (string, string, uint256)); 52 | 53 | emit DocumentTimestamped(seriesId, timestamp, filename, cid); 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /contracts/pluginsV2/TokenV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | // import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import '@openzeppelin/contracts/proxy/Clones.sol'; 6 | import "../OtoCoPluginV2.sol"; 7 | 8 | interface ISeriesToken { 9 | function initialize ( 10 | string memory name, 11 | string memory symbol, 12 | uint256 supply, 13 | address member 14 | ) external; 15 | } 16 | 17 | /** 18 | * Token factory plugin 19 | */ 20 | contract TokenV2 is OtoCoPluginV2 { 21 | 22 | event TokenAdded(uint256 indexed series, address token); 23 | event TokenRemoved(uint256 indexed series, address token); 24 | 25 | // Token source contract to be cloned 26 | address public tokenContract; 27 | 28 | // Mapping from entities to deployed tokens 29 | mapping(uint256 => uint256) public tokensPerEntity; 30 | // Mapping from entities to deployed tokens 31 | mapping(uint256 => address[]) public tokensDeployed; 32 | 33 | 34 | /** 35 | * Constructor for Token Plugin. 36 | * 37 | * @param otocoMaster Address from the Master contract. 38 | * @param token Address from the token source contract to be cloned. 39 | * @param prevIds Previously deployed token series indexes. 40 | * @param prevTokens Addresses from the tokens previously deployed. 41 | */ 42 | constructor( 43 | address payable otocoMaster, 44 | address token, 45 | uint256[] memory prevIds, 46 | address[] memory prevTokens 47 | ) OtoCoPluginV2(otocoMaster) { 48 | tokenContract = token; 49 | 50 | for (uint i = 0; i < prevIds.length; i++ ) { 51 | tokensDeployed[prevIds[i]].push(prevTokens[i]); 52 | tokensPerEntity[prevIds[i]]++; 53 | 54 | emit TokenAdded(prevIds[i], prevTokens[i]); 55 | } 56 | } 57 | 58 | /** 59 | * Update token contract base source. 60 | * 61 | * @param newAddress New token source to be used 62 | */ 63 | function updateTokenContract(address newAddress) public onlyOwner { 64 | tokenContract = newAddress; 65 | } 66 | 67 | /** 68 | * Create a new token for the entity. May only be called by the owner of the series. 69 | * 70 | * @param pluginData Encoded parameters to create a new token. 71 | * @dev seriesId would be the series that will own the token. 72 | * @dev supply the total supply of tokens to be issued. 73 | * @dev name the name of the token as string. 74 | * @dev symbol the symbol that respresent the token. 75 | */ 76 | function addPlugin(uint256 seriesId, bytes calldata pluginData) 77 | public onlySeriesOwner(seriesId) transferFees() payable override 78 | { 79 | ( 80 | uint256 supply, 81 | string memory name, 82 | string memory symbol, 83 | address holder 84 | ) = abi.decode(pluginData, (uint256, string, string, address)); 85 | 86 | address newToken = Clones.clone(tokenContract); 87 | ISeriesToken(newToken).initialize(name, symbol, supply, holder); 88 | tokensDeployed[seriesId].push(newToken); 89 | tokensPerEntity[seriesId]++; 90 | 91 | emit TokenAdded(seriesId, newToken); 92 | } 93 | 94 | /** 95 | * Attaching a pre-existing token to the entity. May only be called by the entity owner. 96 | * 97 | * @param pluginData Encoded parameters to create a new token. 98 | * @dev seriesId Series to remove token from 99 | * @dev newToken Token address to be attached 100 | */ 101 | function attachPlugin(uint256 seriesId, bytes calldata pluginData) 102 | public onlySeriesOwner(seriesId) transferFees() payable override 103 | { 104 | ( address newToken ) = 105 | abi.decode(pluginData, (address)); 106 | 107 | tokensDeployed[seriesId].push(newToken); 108 | tokensPerEntity[seriesId]++; 109 | 110 | emit TokenAdded(seriesId, newToken); 111 | } 112 | 113 | /** 114 | * Remove token from entity 115 | * 116 | * @param pluginData Encoded parameters to create a new token. 117 | * @dev seriesId Series to remove token from 118 | * @dev toRemove Token index to be removed 119 | */ 120 | function removePlugin(uint256 seriesId, bytes calldata pluginData) 121 | public onlySeriesOwner(seriesId) transferFees() payable override 122 | { 123 | ( uint256 toRemove ) = 124 | abi.decode(pluginData, (uint256)); 125 | 126 | address tokenRemoved = tokensDeployed[seriesId][toRemove]; 127 | 128 | // Copy last token to the removed slot 129 | tokensDeployed[seriesId][toRemove] = 130 | tokensDeployed[seriesId][tokensDeployed[seriesId].length - 1]; 131 | 132 | // Remove the last token from array 133 | tokensDeployed[seriesId].pop(); 134 | tokensPerEntity[seriesId]--; 135 | 136 | emit TokenRemoved(seriesId, tokenRemoved); 137 | } 138 | } -------------------------------------------------------------------------------- /contracts/utils/IOtoCoJurisdiction.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | interface IOtoCoJurisdiction { 5 | function getSeriesNameFormatted(uint256 count, string calldata nameToFormat) external pure returns(string memory); 6 | function getJurisdictionName() external view returns(string memory); 7 | function getJurisdictionBadge() external view returns(string memory); 8 | function getJurisdictionGoldBadge() external view returns(string memory); 9 | function getJurisdictionRenewalPrice() external view returns(uint256); 10 | function getJurisdictionDeployPrice() external view returns(uint256); 11 | function getJurisdictionClosePrice() external view returns(uint256); 12 | function isStandalone() external view returns(bool); 13 | } -------------------------------------------------------------------------------- /contracts/utils/IOtoCoMaster.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | interface IOtoCoMaster { 5 | 6 | /** 7 | * @dev See {IERC721-ownerOf}. 8 | */ 9 | function ownerOf(uint256 tokenId) external view returns (address owner); 10 | 11 | /** 12 | * @dev See {OtoCoMaster-baseFee}. 13 | */ 14 | function baseFee() external view returns (uint256 fee); 15 | 16 | receive() external payable; 17 | } -------------------------------------------------------------------------------- /contracts/utils/IOtoCoMasterV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "./IOtoCoJurisdiction.sol"; 5 | 6 | interface IOtoCoMasterV2 { 7 | 8 | struct Series { 9 | uint16 jurisdiction; 10 | uint16 entityType; 11 | uint64 creation; 12 | uint64 expiration; 13 | string name; 14 | } 15 | 16 | function owner() external view returns (address); 17 | 18 | function series(uint256 tokenId) external view returns (uint16, uint16, uint64, uint64, string memory); 19 | function jurisdictionAddress(uint16 jurisdiction) external view returns (IOtoCoJurisdiction j); 20 | /** 21 | * @dev See {IERC721-ownerOf}. 22 | */ 23 | function ownerOf(uint256 tokenId) external view returns (address owner); 24 | 25 | /** 26 | * @dev See {OtoCoMaster-baseFee}. 27 | */ 28 | function baseFee() external view returns (uint256 fee); 29 | function externalUrl() external view returns (string calldata); 30 | function getSeries(uint256 tokenId) external view returns (Series memory); 31 | receive() external payable; 32 | function docs(uint256 tokenId) external view returns(string memory); 33 | } -------------------------------------------------------------------------------- /contracts/utils/IOtoCoPlugin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "./IOtoCoMaster.sol"; 5 | 6 | interface IOtoCoPlugin { 7 | 8 | /** 9 | * Plugin initializer with a function template to be used. 10 | * @dev To decode initialization data use i.e.: (string memory name) = abi.decode(pluginData, (string)); 11 | * 12 | * @param pluginData The parameters to create a new instance of plugin. 13 | */ 14 | function addPlugin(uint256 seriesId, bytes calldata pluginData) external payable; 15 | 16 | /** 17 | * Allow attach a previously deployed plugin if possible 18 | * @dev This function should run enumerous amounts of verifications before allow the attachment. 19 | * @dev To decode initialization data use i.e.: (string memory name) = abi.decode(pluginData, (string)); 20 | * 21 | * @param pluginData The parameters to remove a instance of the plugin. 22 | */ 23 | function attachPlugin(uint256 seriesId, bytes calldata pluginData) external payable; 24 | 25 | /** 26 | * Plugin initializer with a function template to be used. 27 | * @dev To decode initialization data use i.e.: (string memory name) = abi.decode(pluginData, (string)); 28 | * 29 | * @param pluginData The parameters to remove a instance of the plugin. 30 | */ 31 | function removePlugin(uint256 seriesId, bytes calldata pluginData) external payable; 32 | } -------------------------------------------------------------------------------- /contracts/utils/IOtoCoPluginV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "./IOtoCoMaster.sol"; 5 | 6 | interface IOtoCoPluginV2 { 7 | 8 | /** 9 | * Plugin initializer with a function template to be used. 10 | * @dev To decode initialization data use i.e.: (string memory name) = abi.decode(pluginData, (string)); 11 | * 12 | * @param pluginData The parameters to create a new instance of plugin. 13 | */ 14 | function addPlugin(uint256 seriesId, bytes calldata pluginData) external payable; 15 | 16 | /** 17 | * Allow attach a previously deployed plugin if possible 18 | * @dev This function should run enumerous amounts of verifications before allow the attachment. 19 | * @dev To decode initialization data use i.e.: (string memory name) = abi.decode(pluginData, (string)); 20 | * 21 | * @param pluginData The parameters to remove a instance of the plugin. 22 | */ 23 | function attachPlugin(uint256 seriesId, bytes calldata pluginData) external payable; 24 | 25 | /** 26 | * Plugin initializer with a function template to be used. 27 | * @dev To decode initialization data use i.e.: (string memory name) = abi.decode(pluginData, (string)); 28 | * 29 | * @param pluginData The parameters to remove a instance of the plugin. 30 | */ 31 | function removePlugin(uint256 seriesId, bytes calldata pluginData) external payable; 32 | } -------------------------------------------------------------------------------- /contracts/utils/IOtoCoURI.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | interface IOtoCoURI { 5 | function tokenExternalURI(uint256 tokenId, uint256 lastMigrated) external view returns (string memory); 6 | } -------------------------------------------------------------------------------- /contracts/utils/MockAggregatorV3.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | contract MockAggregatorV3 { 5 | 6 | int256 lastPrice; 7 | 8 | constructor() { 9 | lastPrice = 100000000000; 10 | } 11 | 12 | /** 13 | * Get the tokenURI that points to a SVG image. 14 | * Returns the svg formatted accordingly. 15 | */ 16 | function latestRoundData() 17 | external view returns ( 18 | uint80 roundId, 19 | int256 answer, 20 | uint256 startedAt, 21 | uint256 updatedAt, 22 | uint80 answeredInRound 23 | ) { 24 | return ( 25 | 10, 26 | lastPrice, // 1000 USD fixed value 27 | 100000, 28 | 100000, 29 | 100 30 | ); 31 | } 32 | } -------------------------------------------------------------------------------- /contracts/utils/OtoCoToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 6 | 7 | contract OtoCoToken is ERC20, Initializable { 8 | 9 | // uint256 private _totalSupply; 10 | string private __name; 11 | string private __symbol; 12 | 13 | constructor() ERC20("", "") { 14 | _disableInitializers(); 15 | } 16 | 17 | function initialize (string memory name_, string memory symbol_, uint256 supply_, address member_) public initializer { 18 | __name = name_; 19 | __symbol = symbol_; 20 | _mint(member_, supply_); 21 | } 22 | 23 | /** 24 | * @dev Returns the name of the token. 25 | */ 26 | function name() public view override returns (string memory) { 27 | return __name; 28 | } 29 | 30 | /** 31 | * @dev Returns the symbol of the token, usually a shorter version of the 32 | * name. 33 | */ 34 | function symbol() public view override returns (string memory) { 35 | return __symbol; 36 | } 37 | } -------------------------------------------------------------------------------- /contracts/utils/OtoCoTokenMintable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; 5 | // import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol"; 6 | import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 7 | import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol"; 8 | import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol"; 9 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 10 | 11 | contract OtoCoTokenMintable is Initializable, ERC20Upgradeable, OwnableUpgradeable, ERC20PermitUpgradeable, ERC20VotesUpgradeable { 12 | /// @custom:oz-upgrades-unsafe-allow constructor 13 | constructor() { 14 | _disableInitializers(); 15 | } 16 | 17 | function initialize(string memory name_, string memory symbol_) initializer public { 18 | __ERC20_init(name_, symbol_); 19 | // __ERC20Burnable_init(); 20 | __Ownable_init(); 21 | __ERC20Permit_init(name_); 22 | __ERC20Votes_init(); 23 | } 24 | 25 | function mint(address to, uint256 amount) public onlyOwner { 26 | if (balanceOf(to) == 0) _delegate(to, to); 27 | _mint(to, amount); 28 | } 29 | 30 | function burnFrom(address account, uint256 amount) public virtual onlyOwner { 31 | _burn(account, amount); 32 | } 33 | 34 | // The following functions are overrides required by Solidity. 35 | 36 | function _afterTokenTransfer(address from, address to, uint256 amount) 37 | internal 38 | override(ERC20Upgradeable, ERC20VotesUpgradeable) 39 | { 40 | super._afterTokenTransfer(from, to, amount); 41 | if (balanceOf(to) == amount) _delegate(to, to); 42 | } 43 | 44 | function _mint(address to, uint256 amount) 45 | internal 46 | override(ERC20Upgradeable, ERC20VotesUpgradeable) 47 | { 48 | super._mint(to, amount); 49 | } 50 | 51 | function _burn(address account, uint256 amount) 52 | internal 53 | override(ERC20Upgradeable, ERC20VotesUpgradeable) 54 | { 55 | super._burn(account, amount); 56 | } 57 | } -------------------------------------------------------------------------------- /contracts/utils/OtoCoTokenNonTransferable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; 5 | // import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol"; 6 | import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 7 | import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol"; 8 | import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol"; 9 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 10 | 11 | contract OtoCoTokenNonTransferable is Initializable, ERC20Upgradeable, OwnableUpgradeable, ERC20VotesUpgradeable { 12 | 13 | /// @custom:oz-upgrades-unsafe-allow constructor 14 | constructor() { 15 | _disableInitializers(); 16 | } 17 | 18 | function initialize(string memory name_, string memory symbol_) initializer public { 19 | __ERC20_init(name_, symbol_); 20 | // __ERC20Burnable_init(); 21 | __Ownable_init(); 22 | __ERC20Permit_init(name_); 23 | __ERC20Votes_init(); 24 | } 25 | 26 | function mint(address to, uint256 amount) public onlyOwner { 27 | if (balanceOf(to) == 0) _delegate(to, to); 28 | _mint(to, amount); 29 | } 30 | 31 | function burnFrom(address account, uint256 amount) public virtual onlyOwner { 32 | _burn(account, amount); 33 | } 34 | 35 | function transferFrom(address from, address to, uint256 amount) 36 | public 37 | virtual 38 | override 39 | onlyOwner 40 | returns (bool) 41 | { 42 | _transfer(from, to, amount); 43 | return true; 44 | } 45 | 46 | /** 47 | * @dev Hook that is called before any transfer of tokens. This includes 48 | * minting and burning. 49 | * 50 | * Calling conditions: 51 | * 52 | * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens 53 | * will be transferred to `to`. 54 | * - when `from` is zero, `amount` tokens will be minted for `to`. 55 | * - when `to` is zero, `amount` of ``from``'s tokens will be burned. 56 | * - `from` and `to` are never both zero. 57 | * 58 | * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. 59 | */ 60 | function _beforeTokenTransfer( 61 | address from, 62 | address to, 63 | uint256 amount 64 | ) internal virtual override onlyOwner { 65 | super._beforeTokenTransfer(from, to, amount); 66 | } 67 | 68 | // The following functions are overrides required by Solidity. 69 | 70 | function _afterTokenTransfer(address from, address to, uint256 amount) 71 | internal 72 | override(ERC20Upgradeable, ERC20VotesUpgradeable) 73 | { 74 | super._afterTokenTransfer(from, to, amount); 75 | if (balanceOf(to) == amount) _delegate(to, to); 76 | } 77 | 78 | function _mint(address to, uint256 amount) 79 | internal 80 | override(ERC20Upgradeable, ERC20VotesUpgradeable) 81 | { 82 | super._mint(to, amount); 83 | } 84 | 85 | function _burn(address account, uint256 amount) 86 | internal 87 | override(ERC20Upgradeable, ERC20VotesUpgradeable) 88 | { 89 | super._burn(account, amount); 90 | } 91 | 92 | } -------------------------------------------------------------------------------- /contracts/utils/OtoCoURI.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/utils/Strings.sol"; 5 | import "@openzeppelin/contracts/utils/Base64.sol"; 6 | 7 | import "./IOtoCoMasterV2.sol"; 8 | import "./IOtoCoJurisdiction.sol"; 9 | 10 | contract OtoCoURI { 11 | 12 | // Libraries 13 | using Strings for uint256; 14 | 15 | IOtoCoMasterV2 private otocoMaster; 16 | string private networkPrefix; 17 | 18 | constructor (address payable masterAddress, string memory prefix) { 19 | otocoMaster = IOtoCoMasterV2(masterAddress); 20 | networkPrefix = prefix; 21 | } 22 | 23 | // -- TOKEN VISUALS AND DESCRIPTIVE ELEMENTS -- 24 | 25 | /** 26 | * Get the tokenURI that points to a SVG image. 27 | * Returns the svg formatted accordingly. 28 | * 29 | * @param tokenId must exist. 30 | * @return svg file formatted. 31 | */ 32 | function tokenExternalURI(uint256 tokenId, uint256 lastMigrated) external view returns (string memory) { 33 | (uint16 jurisdiction,,uint64 creation,,string memory name) = otocoMaster.series(tokenId); 34 | string memory badge = IOtoCoJurisdiction(otocoMaster.jurisdictionAddress(jurisdiction)).getJurisdictionBadge(); 35 | if (tokenId < lastMigrated) badge = IOtoCoJurisdiction(otocoMaster.jurisdictionAddress(jurisdiction)).getJurisdictionGoldBadge(); 36 | string memory docs = otocoMaster.docs(tokenId); 37 | string memory json = Base64.encode(bytes(string(abi.encodePacked( 38 | '{"name": "', 39 | name, 40 | '", "description": "', 41 | "OtoCo NFTs are minted to represent each entity and their jurisdiction as created by the OtoCo dapp. ", 42 | "The holder of this NFT as recorded on the blockchain is the owner of ", 43 | name, 44 | " and is authorized to access the entity's dashboard on https://otoco.io.", 45 | '", "image": "', 46 | badge, 47 | '", "external_url": "', 48 | otocoMaster.externalUrl(), 49 | networkPrefix, 50 | ':', 51 | tokenId.toString(), 52 | '"', 53 | ',"attributes":[', 54 | '{"display_type": "date","trait_type": "Creation", "value": "', 55 | uint256(creation).toString(), 56 | '"},{"trait_type": "Jurisdiction", "value": "', 57 | IOtoCoJurisdiction(otocoMaster.jurisdictionAddress(jurisdiction)).getJurisdictionName(), 58 | '"}]', 59 | bytes(docs).length != 0 ? string(abi.encodePacked(', "docs": ', docs)) : "", 60 | '}' 61 | )))); 62 | return string(abi.encodePacked('data:application/json;base64,', json)); 63 | } 64 | } -------------------------------------------------------------------------------- /deploys/v1/base.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /deploys/v1/basesepolia.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /deploys/v1/goerli.json: -------------------------------------------------------------------------------- 1 | { 2 | "jurisdictions": [ 3 | "0xf1d275ad3b4f3540a7a5884029d5672f69f3c492", 4 | "0x68Ac7D256668aE74730B17B32b1b8359C0de6Ffa", 5 | "0x746aAC8f3C7596808893618865888f6345077e0b" 6 | ], 7 | "priceFeed": "0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e", 8 | "master": "0x5B9aE6234Cf1E447680c245200E066091E631Bf3", 9 | "timestamp": "0x0d3BC598F0F75590CD75D60D40e0510F515EBE51", 10 | "token": "0xbaAbb166463221ffE04Fd7F06262bD670e26F823", 11 | "multisig": "0x6a4E318410521b97feA47c52E1ae3Ab0ca67335D", 12 | "launchpoolSource": "0xf77ea923E60FB522B023A49cADe467e219e674A7", 13 | "launchpoolCurve": "0xbb35127e86C19F934f602bb0FFcb9EaaDc99e0Cd", 14 | "launchpool": "0x26b17999f109b3ee24Ef26a60495d5c583f3EE6f", 15 | "ens": "0x58F6C99Ded465950cc06113Ca168404ec842B8c8", 16 | "tokenMintable": "0xf76A3d9425dD23dE0DC78Ee66dE5cF7d14807157", 17 | "tokenNonTransferable": "0x2e958d4E1902ed6FBD57DBc99fbf179F0A62787f", 18 | "tokenization": "0xd1708cA7b51554689435f0A3F6920701e900e720", 19 | "governor": "0xF9cbf04570B957ad9262cF5A901c8b46734fe3B8", 20 | "governorInitializer": "0x355894349219dccf4825b66b2f383877c77304c9" 21 | } -------------------------------------------------------------------------------- /deploys/v1/localhost.json: -------------------------------------------------------------------------------- 1 | { 2 | "jurisdictions": [ 3 | "0xf1d275ad3b4f3540a7a5884029d5672f69f3c492", 4 | "0x68Ac7D256668aE74730B17B32b1b8359C0de6Ffa", 5 | "0x746aAC8f3C7596808893618865888f6345077e0b" 6 | ], 7 | "priceFeed": "0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e", 8 | "master": "0x5B9aE6234Cf1E447680c245200E066091E631Bf3", 9 | "timestamp": "0x0d3BC598F0F75590CD75D60D40e0510F515EBE51", 10 | "token": "0xbaAbb166463221ffE04Fd7F06262bD670e26F823", 11 | "multisig": "0x6a4E318410521b97feA47c52E1ae3Ab0ca67335D", 12 | "launchpoolSource": "0xf77ea923E60FB522B023A49cADe467e219e674A7", 13 | "launchpoolCurve": "0xbb35127e86C19F934f602bb0FFcb9EaaDc99e0Cd", 14 | "launchpool": "0x26b17999f109b3ee24Ef26a60495d5c583f3EE6f", 15 | "ens": "0x58F6C99Ded465950cc06113Ca168404ec842B8c8", 16 | "tokenMintable": "0xf76A3d9425dD23dE0DC78Ee66dE5cF7d14807157", 17 | "tokenNonTransferable": "0x2e958d4E1902ed6FBD57DBc99fbf179F0A62787f", 18 | "tokenization": "0xd1708cA7b51554689435f0A3F6920701e900e720", 19 | "governor": "0xF9cbf04570B957ad9262cF5A901c8b46734fe3B8" 20 | } -------------------------------------------------------------------------------- /deploys/v1/main.json: -------------------------------------------------------------------------------- 1 | { 2 | "jurisdictions": [ 3 | "0x9dF31c016f46f0285Af25Ae6F316182dd99FC93E", 4 | "0xb0BE3d086aFeFF531A75FD8f41C9bdEbfBC90e9c", 5 | "0xF7a33F4F0858E93D41144DAd561BE8B5bf42644B" 6 | ], 7 | "master": "0x752B0073422A7F9Cda7f71B5fE7F12a1789e6506", 8 | "timestamp": "0xaA6e8D5baB906B5d1F270f95d38e92d51a7a9C8A", 9 | "token": "0x0650601Bc69e8BBf7Dc9ca0F032fB057135103e3", 10 | "multisig": "0xD9a0927345244F2c45511CFaA5bDD00db7a8decc", 11 | "launchpool": "0x1477E9f01B78Ec20405542e5ee07469a7c93c83e", 12 | "ens": "0x355894349219dccf4825B66b2f383877C77304c9", 13 | "tokenMintable": "0x672Ea317261ad471a04AD751a0E79DF173974c76", 14 | "tokenNonTransferable": "0x64a7fB0a2fFF835AB1FCe103C5F5CbE86364F68B", 15 | "tokenization": "0x55d84299750031d56CA71ee604Caa1E973e76D4c" 16 | } -------------------------------------------------------------------------------- /deploys/v1/mainnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "jurisdictions": [ 3 | "0x9dF31c016f46f0285Af25Ae6F316182dd99FC93E", 4 | "0xb0BE3d086aFeFF531A75FD8f41C9bdEbfBC90e9c", 5 | "0xF7a33F4F0858E93D41144DAd561BE8B5bf42644B" 6 | ], 7 | "priceFeed": "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419", 8 | "master": "0x752B0073422A7F9Cda7f71B5fE7F12a1789e6506", 9 | "timestamp": "0xaA6e8D5baB906B5d1F270f95d38e92d51a7a9C8A", 10 | "token": "0x0650601Bc69e8BBf7Dc9ca0F032fB057135103e3", 11 | "multisig": "0xD9a0927345244F2c45511CFaA5bDD00db7a8decc", 12 | "launchpool": "0x1477E9f01B78Ec20405542e5ee07469a7c93c83e", 13 | "ens": "0x355894349219dccf4825B66b2f383877C77304c9", 14 | "tokenMintable": "0x672Ea317261ad471a04AD751a0E79DF173974c76", 15 | "tokenNonTransferable": "0x64a7fB0a2fFF835AB1FCe103C5F5CbE86364F68B", 16 | "tokenization": "0x55d84299750031d56CA71ee604Caa1E973e76D4c", 17 | "governor": "0x06005FD505b752797a99392908Efe67721C26683" 18 | } -------------------------------------------------------------------------------- /deploys/v1/mumbai.json: -------------------------------------------------------------------------------- 1 | { 2 | "jurisdictions": [ 3 | "0xB3a34259eC8C4007a490722B2cECa5C53B85A0F1", 4 | "0x146B111703b54E4A03A1B6f9f1a4D02F87f2Ee35", 5 | "0xf1D275Ad3B4F3540a7a5884029D5672F69f3C492" 6 | ], 7 | "priceFeed":"0xd0D5e3DB44DE05E9F294BB0a3bEEaF030DE24Ada", 8 | "dai": "0x001B3B4d0F3714Ca98ba10F6042DaEbF0B1B7b6F", 9 | "master": "0x54dED98a6720EcEAA54EB0F858c81737CDe9FF9E", 10 | "timestamp": "0x66ef60f480A269F5e1e358699DD774180B2Fa8eE", 11 | "token": "0x0d3BC598F0F75590CD75D60D40e0510F515EBE51", 12 | "multisig": "0xF85a03f10b93D3275b8065f7c1Ec37BC1d501243", 13 | "launchpoolSource": "0xbaAbb166463221ffE04Fd7F06262bD670e26F823", 14 | "launchpoolCurve": "0x6a4E318410521b97feA47c52E1ae3Ab0ca67335D", 15 | "launchpool": "0xf77ea923E60FB522B023A49cADe467e219e674A7", 16 | "tokenMintable": "0xD183CC74354c70887250bfd68afcb1ACb04C00A6", 17 | "tokenNonTransferable": "0x3dC10d52810b424591B3c8e2f3ece279EF9D4Cc3", 18 | "tokenization": "0x3C61325455b6eD3E61af5F0cB3332Ca274636Be1", 19 | "governor": "0xD0d5656ccDe7825E990c74c42E6ae8Fcd3c38a99" 20 | } -------------------------------------------------------------------------------- /deploys/v1/polygon.json: -------------------------------------------------------------------------------- 1 | { 2 | "jurisdictions": [ 3 | "0xB3a34259eC8C4007a490722B2cECa5C53B85A0F1", 4 | "0x146B111703b54E4A03A1B6f9f1a4D02F87f2Ee35", 5 | "0xf1D275Ad3B4F3540a7a5884029D5672F69f3C492" 6 | ], 7 | "priceFeed": "0xAB594600376Ec9fD91F8e885dADF0CE036862dE0", 8 | "master": "0x54dED98a6720EcEAA54EB0F858c81737CDe9FF9E", 9 | "timestamp": "0xF85a03f10b93D3275b8065f7c1Ec37BC1d501243", 10 | "token": "0x26b17999f109b3ee24Ef26a60495d5c583f3EE6f", 11 | "multisig": "0x58F6C99Ded465950cc06113Ca168404ec842B8c8", 12 | "launchpool": "0x01a1fB25161561A29ae8a316493213aA50Ad7057", 13 | "tokenMintable": "0x1ce22Ba8c7ce7E64aDFF898D9691C7f298BC46B7", 14 | "tokenNonTransferable": "0x4fC4AB80A5a602E2D67A44D1F4Fb6c9fc2E70144", 15 | "tokenization": "0x2b839Def421114b5c105B97FA70F53F75F61418B", 16 | "governor": "0xa27B390Ca1954Bff56E5fe43166329961dFa843F" 17 | } -------------------------------------------------------------------------------- /deploys/v1/sepolia.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /deploys/v2/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "dai": "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb", 3 | "usdc": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", 4 | "master": "0x0d3BC598F0F75590CD75D60D40e0510F515EBE51", 5 | "jurisdictions": [ 6 | "0x6a4E318410521b97feA47c52E1ae3Ab0ca67335D", 7 | "0xf77ea923E60FB522B023A49cADe467e219e674A7", 8 | "0xbb35127e86C19F934f602bb0FFcb9EaaDc99e0Cd", 9 | "0x26b17999f109b3ee24Ef26a60495d5c583f3EE6f", 10 | "0x58F6C99Ded465950cc06113Ca168404ec842B8c8" 11 | ], 12 | "uri": "0x56a1B6fc6057dB6C65DE107d63609E31E662B4dd", 13 | "governorInitializer": "0x9efF2F28227f93F7c9FcC3751E39Ed5042F30B32", 14 | "verifier": "0xDf786Ac51d177aeCe3D34Ffa687D57c493C3Af79", 15 | "token": "0xbb8532cEfd063da4442429b7f43aF7E69e8c363c", 16 | "multisig": "0xc13a0dDAE3bb4FD61508A2CD745F17c65CA7253D", 17 | "timestamp": "0xD183CC74354c70887250bfd68afcb1ACb04C00A6", 18 | "tokenization": "0x94DF43bACaA27a4c549a8c8f2df3E9222A7C05c0", 19 | "otocoTokenMintable": "0x3dC10d52810b424591B3c8e2f3ece279EF9D4Cc3", 20 | "otocoTokenNonTransferable": "0x23eC026590d6CCCfEce04097F9B49aE6A442C3BA", 21 | "launchpool": "0x107F4a0e9b11c6910705Ba4cfaCC16D2c29dD329", 22 | "launchpoolCurve": "0x49315B484f3CE98520DBC8d8a2B819B03128d3Ae", 23 | "launchpoolSource": "0x999e448096A823FE2B973F8E89A23aBeB7123705" 24 | } 25 | -------------------------------------------------------------------------------- /deploys/v2/basesepolia.json: -------------------------------------------------------------------------------- 1 | { 2 | "master": "0xf1D275Ad3B4F3540a7a5884029D5672F69f3C492", 3 | "dai": "0x107f4a0e9b11c6910705ba4cfacc16d2c29dd329", 4 | "usdc": "0xD0d5656ccDe7825E990c74c42E6ae8Fcd3c38a99", 5 | "usdt": "0x56a1B6fc6057dB6C65DE107d63609E31E662B4dd", 6 | "jurisdictions": [ 7 | "0x26b17999f109b3ee24Ef26a60495d5c583f3EE6f", 8 | "0x58F6C99Ded465950cc06113Ca168404ec842B8c8", 9 | "0x0dFa4294171C31fcAFE9Bb9E8Cf268c11AA2caf2", 10 | "0xDa2AD44d3c64782aAf70B8f7C8dC45806201c44f", 11 | "0x01a1fB25161561A29ae8a316493213aA50Ad7057" 12 | ], 13 | "uri": "0xc15eEfBb1bA15E3944d8425dC68D707b86Db4d99", 14 | "governorInitializer": "0x17605611f18B093c96B2dA9AD54308bAcd92b4ef", 15 | "verifier": "0x9efF2F28227f93F7c9FcC3751E39Ed5042F30B32", 16 | "token": "0x8082a2fEFc36FeF1EaFa164B1F723C319a3E911E", 17 | "multisig": "0xbb8532cEfd063da4442429b7f43aF7E69e8c363c", 18 | "timestamp": "0xc13a0dDAE3bb4FD61508A2CD745F17c65CA7253D", 19 | "tokenization": "0xB8A93c1DD6dA2b29d9b46D483bE690388a0f9C03", 20 | "otocoTokenMintable": "0xD183CC74354c70887250bfd68afcb1ACb04C00A6", 21 | "otocoTokenNonTransferable": "0x3dC10d52810b424591B3c8e2f3ece279EF9D4Cc3", 22 | "launchpool": "0x49315B484f3CE98520DBC8d8a2B819B03128d3Ae", 23 | "launchpoolCurve": "0x999e448096A823FE2B973F8E89A23aBeB7123705", 24 | "launchpoolSource": "0x94DF43bACaA27a4c549a8c8f2df3E9222A7C05c0" 25 | } -------------------------------------------------------------------------------- /deploys/v2/goerli.json: -------------------------------------------------------------------------------- 1 | { 2 | "jurisdictions": [ 3 | "0x5f11A5175b08e01f5F75682A0B3904096e974BF5", 4 | "0x29ABEF1031858d55ACA21470C136A886Cb66C96D", 5 | "0xcf1e70FbDaFcDa3Fe3344f4b89330F6b003717D8", 6 | "0x88D830C7aDa24467BC51c51fF5Fd0EA5804ddF01" 7 | ], 8 | "master": "0x5B9aE6234Cf1E447680c245200E066091E631Bf3", 9 | "uri": "0x41783B91CB3469e1138fda69e4568c5f14B8008C", 10 | "governorInitializer": "0x3eA955823555E90C5590b5494BCeE78dBD79BD7c", 11 | "verifier": "0xBD12D84b775b9DA9cea93D3034dccF50aA60C72F" 12 | } -------------------------------------------------------------------------------- /deploys/v2/hardhat.json: -------------------------------------------------------------------------------- 1 | { 2 | "jurisdictions": [ 3 | "0x75b7b8F37bE5223C735C169cB6199a2Dc9A98E93", 4 | "0x7aC788772Aa05E9e98b66836955ea14C3dfDf446", 5 | "0xFAD1a1548b5eFA40d26edc00C7759534a1eCA563" 6 | ] 7 | } -------------------------------------------------------------------------------- /deploys/v2/localhost.json: -------------------------------------------------------------------------------- 1 | { 2 | "jurisdictions": [ 3 | "0x29e5A15594221D48A485C5d2c5BCc50002901539", 4 | "0x624162f1D184BF451087978770911434c3165f49", 5 | "0x03B60E4a989868C0022B7d91812afe32810E53cA", 6 | "0xEBAF768e52d15555aeDE7772a5f91013bDB56270" 7 | ], 8 | "governorInitializer": "0x050cbF2C95DFD60Fc36b1117984B470a4d31A422", 9 | "master": "0x752B0073422A7F9Cda7f71B5fE7F12a1789e6506", 10 | "uri": "0x59C85458410DD99ad150C219FB7EFBF1B237f14a" 11 | } -------------------------------------------------------------------------------- /deploys/v2/mainnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "jurisdictions": [ 3 | "0x624162f1D184BF451087978770911434c3165f49", 4 | "0x03B60E4a989868C0022B7d91812afe32810E53cA", 5 | "0xEBAF768e52d15555aeDE7772a5f91013bDB56270", 6 | "0xA94859E5Dbb893e4DE9C130434F76dD242124Fad", 7 | "0x621034aC7ac79599fECD5C6288791Fe5384D384c" 8 | ], 9 | "governorInitializer": "0x050cbF2C95DFD60Fc36b1117984B470a4d31A422", 10 | "master": "0x752B0073422A7F9Cda7f71B5fE7F12a1789e6506", 11 | "uri": "0x80003ceE6e446c0935aE928b31490B5e84E7B9d4", 12 | "verifier": "0x2f2d9658ddCF387324E5870dcdBb711171572E34" 13 | } -------------------------------------------------------------------------------- /deploys/v2/mumbai.json: -------------------------------------------------------------------------------- 1 | { 2 | "jurisdictions": [ 3 | "0x11cC4c46c986A0802bb9F5F29ef229383c5C626d", 4 | "0x752B0073422A7F9Cda7f71B5fE7F12a1789e6506", 5 | "0x25420d229A152264827663FB01e7E61F2b330CF5", 6 | "0x9671c358CBE65344Bc058eEe26a65d1517947d6b", 7 | "0x64CE885B47b6d1C3790c87096C96d7981D3E0218" 8 | ], 9 | "master": "0x54dED98a6720EcEAA54EB0F858c81737CDe9FF9E", 10 | "uri": "0xb0BE3d086aFeFF531A75FD8f41C9bdEbfBC90e9c", 11 | "governorInitializer": "0x5D65989F05DD57cCee713547db17ddEcE36055eb", 12 | "verifier": "0x632F76793cC026B2826c76A534523eBcd0bcD916" 13 | } -------------------------------------------------------------------------------- /deploys/v2/polygon.json: -------------------------------------------------------------------------------- 1 | { 2 | "jurisdictions": [ 3 | "0x999e448096A823FE2B973F8E89A23aBeB7123705", 4 | "0x49315B484f3CE98520DBC8d8a2B819B03128d3Ae", 5 | "0x107F4a0e9b11c6910705Ba4cfaCC16D2c29dD329", 6 | "0xc15eEfBb1bA15E3944d8425dC68D707b86Db4d99", 7 | "0x7defC828ac596c1f69e7eeDdFD494E5F68a1c762" 8 | ], 9 | "master": "0x54dED98a6720EcEAA54EB0F858c81737CDe9FF9E", 10 | "uri": "0x8D91C6528b1eD31e5Cf042A7d98665DD7F91c898", 11 | "governorInitializer": "0x94DF43bACaA27a4c549a8c8f2df3E9222A7C05c0", 12 | "verifier": "0xF3e365aAE8fFB292cc88f4aBAF1a7C2900a340F4" 13 | } -------------------------------------------------------------------------------- /deploys/v2/previous.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "safe": "0x29fcB43b46531BcA003ddC8FCB67FFE91900C762", 3 | "safeFactory": "0xC22834581EbC8527d974F8a1c97E1bEA4EF910BC", 4 | "priceFeed": "0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70" 5 | } 6 | -------------------------------------------------------------------------------- /deploys/v2/previous.basesepolia.json: -------------------------------------------------------------------------------- 1 | { 2 | "safe": "0x41675C099F32341bf84BFc5382aF534df5C7461a", 3 | "safeFactory": "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67", 4 | "priceFeed": "0x4aDC67696bA383F43DD60A9e78F2C97Fbbfc7cb1" 5 | } 6 | -------------------------------------------------------------------------------- /deploys/v2/previous.main.json: -------------------------------------------------------------------------------- 1 | { 2 | "registry": "0x54dED98a6720EcEAA54EB0F858c81737CDe9FF9E", 3 | "launchpoolSource": "0xDf786Ac51d177aeCe3D34Ffa687D57c493C3Af79", 4 | "launchpoolCurve": "0x8082a2fEFc36FeF1EaFa164B1F723C319a3E911E", 5 | "ens": "0x01a1fB25161561A29ae8a316493213aA50Ad7057", 6 | "ensregistry": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", 7 | "resolver": "0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41", 8 | "safe": "0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552", 9 | "safeFactory": "0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2", 10 | "priceFeed": "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419" 11 | } 12 | -------------------------------------------------------------------------------- /deploys/v2/previous.sepolia.json: -------------------------------------------------------------------------------- 1 | { 2 | "ensregistry": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", 3 | "resolver": "0x8FADE66B79cC9f707aB26799354482EB93a5B7dD", 4 | "safe": "0x41675C099F32341bf84BFc5382aF534df5C7461a", 5 | "safeFactory": "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67", 6 | "priceFeed": "0x4aDC67696bA383F43DD60A9e78F2C97Fbbfc7cb1" 7 | } 8 | -------------------------------------------------------------------------------- /deploys/v2/sepolia.json: -------------------------------------------------------------------------------- 1 | { 2 | "master": "0x746aAC8f3C7596808893618865888f6345077e0b", 3 | "jurisdictions": [ 4 | "0x66ef60f480A269F5e1e358699DD774180B2Fa8eE", 5 | "0x5B9aE6234Cf1E447680c245200E066091E631Bf3", 6 | "0x0d3BC598F0F75590CD75D60D40e0510F515EBE51", 7 | "0xF85a03f10b93D3275b8065f7c1Ec37BC1d501243", 8 | "0x3895acd27661f850871edf5E4c96dd2E646Fa9E4" 9 | ], 10 | "uri": "0x28E717Fe43E839c0b0b8ea7D1363B553427199c1", 11 | "governorInitializer": "0x01a1fB25161561A29ae8a316493213aA50Ad7057", 12 | "verifier": "0x1ce22Ba8c7ce7E64aDFF898D9691C7f298BC46B7", 13 | "token": "0x69a34f6078587ab69a96562d38893B890aedfB84", 14 | "multisig": "0x17605611f18B093c96B2dA9AD54308bAcd92b4ef", 15 | "ens": "0x9efF2F28227f93F7c9FcC3751E39Ed5042F30B32", 16 | "timestamp": "0xDf786Ac51d177aeCe3D34Ffa687D57c493C3Af79", 17 | "tokenization": "0xD183CC74354c70887250bfd68afcb1ACb04C00A6", 18 | "otocoTokenMintable": "0x8082a2fEFc36FeF1EaFa164B1F723C319a3E911E", 19 | "otocoTokenNonTransferable": "0xbb8532cEfd063da4442429b7f43aF7E69e8c363c", 20 | "launchpool": "0xB8A93c1DD6dA2b29d9b46D483bE690388a0f9C03", 21 | "launchpoolCurve": "0x23eC026590d6CCCfEce04097F9B49aE6A442C3BA", 22 | "launchpoolSource": "0x3dC10d52810b424591B3c8e2f3ece279EF9D4Cc3" 23 | } -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomicfoundation/hardhat-chai-matchers"); 2 | require("@nomicfoundation/hardhat-verify"); 3 | require('@openzeppelin/hardhat-upgrades'); 4 | require("@nomiclabs/hardhat-ethers"); 5 | require('hardhat-contract-sizer'); 6 | require('solidity-coverage'); 7 | require('solidity-docgen'); 8 | require("hardhat-tracer"); 9 | 10 | require('dotenv').config(); 11 | require("./tasks/setup"); 12 | // require("hardhat-gas-reporter"); 13 | 14 | 15 | process.removeAllListeners('warning'); 16 | 17 | const chainIds = { 18 | hardhat: 31337, 19 | mainnet: 1, 20 | goerli: 5, 21 | polygon: 137, 22 | mumbai: 80001, 23 | sepolia: 11155111, 24 | base: 8453, 25 | basesepolia: 84532 26 | }; 27 | 28 | function getChainConfig(chain) { 29 | 30 | const jsonRpcUrl = process.env[`RPC_${chain.toUpperCase()}_API`] 31 | if (!jsonRpcUrl) throw new Error("API KEY not fount for " + chain); 32 | 33 | return { 34 | // Comment out for default hardhat account settings 35 | accounts: { 36 | count: 10, 37 | mnemonic: process.env.MNEMONIC_PHRASE, 38 | path: "m/44'/60'/0'/0", 39 | }, 40 | chainId: chainIds[chain], 41 | url: jsonRpcUrl 42 | }; 43 | } 44 | 45 | 46 | module.exports = { 47 | defaultNetwork: "hardhat", 48 | networks: { 49 | localhost: { chainId: chainIds.hardhat }, 50 | hardhat: { 51 | blockGasLimit: 30000000, 52 | chainId: chainIds.hardhat, 53 | accounts: process.env.MNEMONIC_PHRASE ? { 54 | mnemonic: process.env.MNEMONIC_PHRASE, 55 | path: "m/44'/60'/0'/0", 56 | } : undefined, 57 | forking: process.env.FORK_ENABLED === "true" ? { 58 | url: getChainConfig(process.env.FORKED_NETWORK).url, 59 | } : undefined, 60 | }, 61 | mainnet: getChainConfig("mainnet"), 62 | goerli: getChainConfig("goerli"), 63 | polygon: getChainConfig("polygon"), 64 | mumbai: getChainConfig("mumbai"), 65 | sepolia: getChainConfig("sepolia"), 66 | basesepolia: getChainConfig("basesepolia"), 67 | base: getChainConfig("base"), 68 | }, 69 | etherscan: { 70 | apiKey: { 71 | mainnet: process.env.ETHERSCAN_API_KEY || "", 72 | goerli: process.env.ETHERSCAN_API_KEY || "", 73 | polygon: process.env.POLYGONSCAN_API_KEY || "", 74 | polygonMumbai: process.env.POLYGONSCAN_API_KEY || "", 75 | sepolia: process.env.ETHERSCAN_API_KEY || "", 76 | base: process.env.BASESCAN_API_KEY || "", 77 | basesepolia: process.env.BASESCAN_API_KEY || "", 78 | }, 79 | customChains: [ 80 | { 81 | network: "basesepolia", 82 | chainId: chainIds.basesepolia, 83 | urls: { 84 | apiURL: "https://api-sepolia.basescan.org/api", 85 | browserURL: "https://sepolia.basescan.org/" 86 | } 87 | }, 88 | { 89 | network: "base", 90 | chainId: chainIds.base, 91 | urls: { 92 | apiURL: "https://api.basescan.org/api", 93 | browserURL: "https://basescan.org" 94 | } 95 | } 96 | ] 97 | }, 98 | solidity: { 99 | compilers: [ 100 | { version: "0.8.0" }, 101 | { version: "0.8.4" }, 102 | ], 103 | settings: { 104 | metadata: { 105 | bytecodeHash: "none", 106 | }, 107 | optimizer: { 108 | enabled: false, 109 | // runs: 2_000, 110 | }, 111 | }, 112 | }, 113 | }; 114 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "otoco-smart-contracts", 3 | "version": "2.0.0", 4 | "description": "Onchain entity assembly and tooling marketplace for Web3 projects.", 5 | "devDependencies": { 6 | "@chainlink/contracts": "^0.4.1", 7 | "@nomicfoundation/hardhat-chai-matchers": "^2.0.6", 8 | "@nomicfoundation/hardhat-ethers": "^3.0.5", 9 | "@nomicfoundation/hardhat-verify": "^2.0.4", 10 | "@nomiclabs/hardhat-ethers": "^2.2.3", 11 | "@openzeppelin/contracts": "4.5.0", 12 | "@openzeppelin/hardhat-upgrades": "1.21.0", 13 | "@openzeppelin/contracts-upgradeable": "4.6.0", 14 | "axios": "^1.6.7", 15 | "dotenv": "^16.4.5", 16 | "hardhat": "^2.20.1", 17 | "hardhat-contract-sizer": "^2.10.0", 18 | "hardhat-gas-reporter": "^1.0.10", 19 | "hardhat-tracer": "^2.8.0", 20 | "nodemon": "^3.0.3", 21 | "solidity-coverage": "^0.8.8", 22 | "solidity-docgen": "^0.6.0-beta.36", 23 | "tarantula-fl": "^1.0.0" 24 | }, 25 | "scripts": { 26 | "test": "npx hardhat test", 27 | "setup": "npx hardhat node --show-stack--traces & sleep 9 && npx hardhat setup --network localhost && pkill -s0", 28 | "coverage": "npx hardhat coverage && npx hardhat coverage --matrix", 29 | "tarantula": "npx hardhat test --grep Tarantula", 30 | "mockify": "./test/halmos/mocks/mockify.sh", 31 | "halmos:set": "npm ci && python3.9 -m pip install --upgrade halmos && cp test/halmos/**/*.sol contracts/ && halmos", 32 | "halmos:run": "cp test/halmos/**/*.sol contracts/ && halmos", 33 | "halmos:watch": "nodemon --watch test/halmos/tests/OtoCoMasterV2.t.sol --exec 'npm run halmos:run'" 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "git+https://github.com/otocorp/SmartContract-POC.git" 38 | }, 39 | "author": "Filipe Soccol", 40 | "license": "ISC", 41 | "bugs": { 42 | "url": "https://github.com/otocorp/SmartContract-POC/issues" 43 | }, 44 | "homepage": "https://github.com/otocorp/SmartContract-POC#readme" 45 | } 46 | -------------------------------------------------------------------------------- /scripts/1-deploy-master.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { network } = require("hardhat"); 3 | const hre = require("hardhat"); 4 | 5 | const Reset = "\x1b[0m"; 6 | const Bright = "\x1b[1m"; 7 | 8 | const FgRed = "\x1b[31m"; 9 | const FgGreen = "\x1b[32m"; 10 | const FgMagenta = "\x1b[35m"; 11 | const FgCyan = "\x1b[36m"; 12 | 13 | async function main() { 14 | 15 | /***************** 16 | * GENERAL SETUP * 17 | *****************/ 18 | 19 | const networkId = network.config.chainId; 20 | const [deployer] = await ethers.getSigners(); 21 | 22 | let deploysJson; 23 | 24 | // Load deployed jurisdictions 25 | try { 26 | const data = fs.readFileSync(`./deploys/v2/${network.name}.json`, { encoding: "utf-8" }); 27 | deploysJson = JSON.parse(data); 28 | } catch (err) { 29 | console.log(`${FgRed}Error loading deploys: ${err}${Reset}`); 30 | process.exit(1); 31 | } 32 | 33 | /****************** 34 | * USER PROMPTING * 35 | ******************/ 36 | 37 | console.log( 38 | `\n${Bright}\t👤 Deploying contracts with ${FgCyan}${deployer.address}${Reset}`); 39 | 40 | const deployerBalance = 41 | parseInt((await deployer.getBalance()).toString()) / 1e18; 42 | 43 | console.log( 44 | `\t${Bright}💰 Balance: ${FgCyan}${deployerBalance.toFixed(4)} ETH${Reset}\n`); 45 | 46 | const explorer = networkId == '1' ? 'Etherscan' : networkId == '137' ? 'Polygonscan' : null; 47 | 48 | if (explorer != null) { 49 | console.log(`${Bright} ${explorer} Gas Tracker Data: ${Reset}`); 50 | await hre.run("gas", { [network.name]: true }); 51 | } 52 | 53 | 54 | /**************** 55 | * ONCHAIN TASK * 56 | ****************/ 57 | 58 | const master = await hre.run("master", { 59 | jurisdictions: JSON.stringify(deploysJson.jurisdictions), 60 | }); 61 | 62 | console.log(`${Bright}🚀 OtoCo V2 Master Deployed: ${FgMagenta}${master.address}${Reset}`); 63 | 64 | 65 | /****************** 66 | * STORAGE CHECKS * 67 | ******************/ 68 | 69 | deploysJson.master = master.address; 70 | 71 | fs.writeFileSync(`./deploys/v2/${network.name}.json`, JSON.stringify(deploysJson, undefined, 2)); 72 | 73 | const defaultUrl = (await master.callStatic.externalUrl()); 74 | console.log(`\n ${FgCyan} externalUrl written to storage: ${Reset}`, defaultUrl, `\n`); 75 | 76 | 77 | /********************** 78 | * SOURCE VERIFICATON * 79 | **********************/ 80 | 81 | if (network.config.chainId != '31337') { 82 | await hre.run("verification", { addr: master.address }); 83 | } 84 | } 85 | 86 | main() 87 | .then(() => process.exit(0)) 88 | .catch((error) => { 89 | console.error(error); 90 | process.exit(1); 91 | }); -------------------------------------------------------------------------------- /scripts/10-deploy-plugin-timestamp.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { network } = require("hardhat"); 3 | const hre = require("hardhat"); 4 | 5 | const Reset = "\x1b[0m"; 6 | const Bright = "\x1b[1m"; 7 | 8 | const FgRed = "\x1b[31m"; 9 | const FgGreen = "\x1b[32m"; 10 | const FgMagenta = "\x1b[35m"; 11 | const FgCyan = "\x1b[36m"; 12 | 13 | 14 | async function main() { 15 | 16 | /***************** 17 | * GENERAL SETUP * 18 | *****************/ 19 | 20 | const networkId = network.config.chainId; 21 | const [deployer] = await ethers.getSigners(); 22 | 23 | let deploysJson; 24 | 25 | // Load deployed master contract 26 | try { 27 | const data = fs.readFileSync(`./deploys/v2/${network.name}.json`, { encoding: "utf-8" }); 28 | deploysJson = JSON.parse(data); 29 | } catch (err) { 30 | console.log(`${FgRed}Error loading Master address: ${err}${Reset}`); 31 | process.exit(1); 32 | } 33 | 34 | 35 | let previousJson; 36 | // Load deployed master contract 37 | try { 38 | const data = fs.readFileSync(`./deploys/v2/previous.${network.name}.json`, { encoding: "utf-8" }); 39 | previousJson = JSON.parse(data); 40 | } catch (err) { 41 | console.log(`${FgRed}Error loading Previous/External addresses: ${err}${Reset}`); 42 | } 43 | 44 | /****************** 45 | * USER PROMPTING * 46 | ******************/ 47 | 48 | console.log( 49 | `\n${Bright}\t👤 Deploying contracts with ${FgCyan}${deployer.address}${Reset}`); 50 | 51 | const deployerBalance = 52 | parseInt((await deployer.getBalance()).toString()) / 1e18; 53 | 54 | console.log( 55 | `\t${Bright}💰 Balance: ${FgCyan}${deployerBalance.toFixed(4)} ETH${Reset}\n`); 56 | 57 | const explorer = networkId == '1' ? 'Etherscan' : networkId == '137' ? 'Polygonscan' : null; 58 | 59 | if (explorer != null) { 60 | console.log(`${Bright} ${explorer} Gas Tracker Data: ${Reset}`); 61 | await hre.run("gas", { [network.name]: true }); 62 | } 63 | 64 | 65 | /**************** 66 | * ONCHAIN TASK * 67 | ****************/ 68 | 69 | const { timestamp } = 70 | await hre.run("timestamp", { 71 | deployed: JSON.stringify(deploysJson) 72 | }); 73 | 74 | 75 | /****************** 76 | * STORAGE CHECKS * 77 | ******************/ 78 | 79 | console.log(`${Bright}🚀 Timestamp plugin Address:${Reset}${FgMagenta}${timestamp.address}${Reset}`); 80 | 81 | deploysJson.timestamp = timestamp.address; 82 | 83 | fs.writeFileSync(`./deploys/v2/${network.name}.json`, JSON.stringify(deploysJson, undefined, 2)); 84 | 85 | 86 | /********************** 87 | * SOURCE VERIFICATON * 88 | **********************/ 89 | 90 | if (network.config.chainId != '31337') { 91 | await hre.run("verification", { 92 | addr: timestamp.address, 93 | args: JSON.stringify([deploysJson.master]), 94 | }); 95 | } 96 | } 97 | 98 | main() 99 | .then(() => process.exit(0)) 100 | .catch((error) => { 101 | console.error(error); 102 | process.exit(1); 103 | }); -------------------------------------------------------------------------------- /scripts/11-deploy-plugin-tokenization.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { network } = require("hardhat"); 3 | const hre = require("hardhat"); 4 | 5 | const Reset = "\x1b[0m"; 6 | const Bright = "\x1b[1m"; 7 | 8 | const FgRed = "\x1b[31m"; 9 | const FgGreen = "\x1b[32m"; 10 | const FgMagenta = "\x1b[35m"; 11 | const FgCyan = "\x1b[36m"; 12 | 13 | 14 | async function main() { 15 | 16 | /***************** 17 | * GENERAL SETUP * 18 | *****************/ 19 | 20 | const networkId = network.config.chainId; 21 | const [deployer] = await ethers.getSigners(); 22 | 23 | let deploysJson; 24 | 25 | // Load deployed master contract 26 | try { 27 | const data = fs.readFileSync(`./deploys/v2/${network.name}.json`, { encoding: "utf-8" }); 28 | deploysJson = JSON.parse(data); 29 | } catch (err) { 30 | console.log(`${FgRed}Error loading Master address: ${err}${Reset}`); 31 | process.exit(1); 32 | } 33 | 34 | 35 | let previousJson; 36 | // Load deployed master contract 37 | try { 38 | const data = fs.readFileSync(`./deploys/v2/previous.${network.name}.json`, { encoding: "utf-8" }); 39 | previousJson = JSON.parse(data); 40 | } catch (err) { 41 | console.log(`${FgRed}Error loading Previous/External addresses: ${err}${Reset}`); 42 | } 43 | 44 | /****************** 45 | * USER PROMPTING * 46 | ******************/ 47 | 48 | console.log( 49 | `\n${Bright}\t👤 Deploying contracts with ${FgCyan}${deployer.address}${Reset}`); 50 | 51 | const deployerBalance = 52 | parseInt((await deployer.getBalance()).toString()) / 1e18; 53 | 54 | console.log( 55 | `\t${Bright}💰 Balance: ${FgCyan}${deployerBalance.toFixed(4)} ETH${Reset}\n`); 56 | 57 | const explorer = networkId == '1' ? 'Etherscan' : networkId == '137' ? 'Polygonscan' : null; 58 | 59 | if (explorer != null) { 60 | console.log(`${Bright} ${explorer} Gas Tracker Data: ${Reset}`); 61 | await hre.run("gas", { [network.name]: true }); 62 | } 63 | 64 | 65 | /**************** 66 | * ONCHAIN TASK * 67 | ****************/ 68 | 69 | const { otocoTokenMintable, otocoTokenNonTransferable, otocoGovernor, tokenization } = 70 | await hre.run("tokenization", { 71 | deployed: JSON.stringify(deploysJson) 72 | }); 73 | 74 | 75 | /****************** 76 | * STORAGE CHECKS * 77 | ******************/ 78 | 79 | console.log(`${Bright}🚀 Token Mintable Address:${Reset}${FgMagenta}${otocoTokenMintable.address}${Reset}`); 80 | console.log(`${Bright}🚀 Token Non-Transferable Address:${Reset}${FgMagenta}${otocoTokenNonTransferable.address}${Reset}`); 81 | console.log(`${Bright}🚀 OtoCo Governor Address:${Reset}${FgMagenta}${otocoGovernor.address}${Reset}`); 82 | console.log(`${Bright}🚀 Tokenization Plugin Address:${Reset}${FgMagenta}${tokenization.address}${Reset}`); 83 | 84 | deploysJson.tokenization = tokenization.address; 85 | deploysJson.otocoTokenMintable = otocoTokenMintable.address; 86 | deploysJson.otocoTokenNonTransferable = otocoTokenNonTransferable.address; 87 | 88 | fs.writeFileSync(`./deploys/v2/${network.name}.json`, JSON.stringify(deploysJson, undefined, 2)); 89 | 90 | /********************** 91 | * SOURCE VERIFICATON * 92 | **********************/ 93 | 94 | if (network.config.chainId != '31337') { 95 | await hre.run("verification", { 96 | addr: otocoTokenMintable.address, 97 | args: JSON.stringify([]), 98 | }); 99 | await hre.run("verification", { 100 | addr: otocoTokenNonTransferable.address, 101 | args: JSON.stringify([]), 102 | }); 103 | await hre.run("verification", { 104 | addr: otocoGovernor.address, 105 | args: JSON.stringify([]), 106 | }); 107 | await hre.run("verification", { 108 | addr: tokenization.address, 109 | args: JSON.stringify([deploysJson.master, otocoGovernor.address]), 110 | }); 111 | } 112 | } 113 | 114 | main() 115 | .then(() => process.exit(0)) 116 | .catch((error) => { 117 | console.error(error); 118 | process.exit(1); 119 | }); -------------------------------------------------------------------------------- /scripts/12-deploy-plugin-launchpool.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { network } = require("hardhat"); 3 | const hre = require("hardhat"); 4 | 5 | const Reset = "\x1b[0m"; 6 | const Bright = "\x1b[1m"; 7 | 8 | const FgRed = "\x1b[31m"; 9 | const FgGreen = "\x1b[32m"; 10 | const FgMagenta = "\x1b[35m"; 11 | const FgCyan = "\x1b[36m"; 12 | 13 | 14 | async function main() { 15 | 16 | /***************** 17 | * GENERAL SETUP * 18 | *****************/ 19 | 20 | const networkId = network.config.chainId; 21 | const [deployer] = await ethers.getSigners(); 22 | 23 | let deploysJson; 24 | 25 | // Load deployed master contract 26 | try { 27 | const data = fs.readFileSync(`./deploys/v2/${network.name}.json`, { encoding: "utf-8" }); 28 | deploysJson = JSON.parse(data); 29 | } catch (err) { 30 | console.log(`${FgRed}Error loading Master address: ${err}${Reset}`); 31 | process.exit(1); 32 | } 33 | 34 | 35 | /****************** 36 | * USER PROMPTING * 37 | ******************/ 38 | 39 | console.log( 40 | `\n${Bright}\t👤 Deploying contracts with ${FgCyan}${deployer.address}${Reset}`); 41 | 42 | const deployerBalance = 43 | parseInt((await deployer.getBalance()).toString()) / 1e18; 44 | 45 | console.log( 46 | `\t${Bright}💰 Balance: ${FgCyan}${deployerBalance.toFixed(4)} ETH${Reset}\n`); 47 | 48 | const explorer = networkId == '1' ? 'Etherscan' : networkId == '137' ? 'Polygonscan' : null; 49 | 50 | if (explorer != null) { 51 | console.log(`${Bright} ${explorer} Gas Tracker Data: ${Reset}`); 52 | await hre.run("gas", { [network.name]: true }); 53 | } 54 | 55 | 56 | /**************** 57 | * ONCHAIN TASK * 58 | ****************/ 59 | 60 | const { launchpool, launchpoolSource, launchpoolCurve } = 61 | await hre.run("launchpool", { 62 | deployed: JSON.stringify(deploysJson) 63 | }); 64 | 65 | 66 | /****************** 67 | * STORAGE CHECKS * 68 | ******************/ 69 | 70 | console.log(`${Bright}🚀 LaunchPool Source Address:${Reset}${FgMagenta}${launchpoolSource.address}${Reset}`); 71 | console.log(`${Bright}🚀 LaunchPool Curve Address:${Reset}${FgMagenta}${launchpoolCurve.address}${Reset}`); 72 | console.log(`${Bright}🚀 LaunchPool Plugin Address:${Reset}${FgMagenta}${launchpool.address}${Reset}`); 73 | 74 | deploysJson.launchpool = launchpool.address; 75 | deploysJson.launchpoolCurve = launchpoolCurve.address; 76 | deploysJson.launchpoolSource = launchpoolSource.address; 77 | 78 | fs.writeFileSync(`./deploys/v2/${network.name}.json`, JSON.stringify(deploysJson, undefined, 2)); 79 | 80 | /********************** 81 | * SOURCE VERIFICATON * 82 | **********************/ 83 | 84 | if (network.config.chainId != '31337') { 85 | await hre.run("verification", { 86 | addr: launchpoolSource.address, 87 | args: JSON.stringify([]), 88 | }); 89 | await hre.run("verification", { 90 | addr: launchpoolCurve.address, 91 | args: JSON.stringify([]), 92 | }); 93 | await hre.run("verification", { 94 | addr: launchpool.address, 95 | args: JSON.stringify([deploysJson.master, launchpoolSource.address, launchpoolCurve.address, [], []]), 96 | }); 97 | } 98 | } 99 | 100 | main() 101 | .then(() => process.exit(0)) 102 | .catch((error) => { 103 | console.error(error); 104 | process.exit(1); 105 | }); -------------------------------------------------------------------------------- /scripts/2-deploy-jurisdictions.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const { network } = require("hardhat"); 3 | const hre = require("hardhat"); 4 | 5 | const Reset = "\x1b[0m"; 6 | const Bright = "\x1b[1m"; 7 | const FgGreen = "\x1b[32m"; 8 | const FgRed = "\x1b[31m"; 9 | const FgCyan = "\x1b[36m"; 10 | 11 | async function main() { 12 | /***************** 13 | * GENERAL SETUP * 14 | *****************/ 15 | 16 | const networkId = network.config.chainId; 17 | const [deployer] = await ethers.getSigners(); 18 | 19 | let deployed = {}; 20 | let settings; 21 | 22 | try { 23 | settings = JSON.parse( 24 | fs.readFileSync("./scripts/jurisdiction-settings.json", { 25 | encoding: "utf-8", 26 | }) 27 | ); 28 | } catch (err) { 29 | console.log(`${FgRed}Error loading jurisdiction badges: ${err}${Reset}`); 30 | process.exit(1); 31 | } 32 | 33 | try { 34 | deployed = JSON.parse( 35 | fs.readFileSync(`./deploys/v2/${network.name}.json`, { 36 | encoding: "utf-8", 37 | }) 38 | ); 39 | } catch (err) { 40 | console.log( 41 | `${FgRed}Error loading jurisdiction predeploys: ${err}${Reset}` 42 | ); 43 | } 44 | 45 | switch (network.name) { 46 | case "mainnet": 47 | settings = settings.mainnet; 48 | break; 49 | case "polygon": 50 | settings = settings.polygon; 51 | break; 52 | case "base": 53 | settings = settings.base; 54 | break; 55 | case "basesepolia": 56 | settings = settings.basesepolia; 57 | break; 58 | default: 59 | settings = settings.default; 60 | break; 61 | } 62 | 63 | /****************** 64 | * USER PROMPTING * 65 | ******************/ 66 | 67 | console.log( 68 | `\n${Bright}\t👤 Deploying contracts with ${FgCyan}${deployer.address}${Reset}` 69 | ); 70 | 71 | const deployerBalance = 72 | parseInt((await deployer.getBalance()).toString()) / 1e18; 73 | 74 | console.log( 75 | `\t${Bright}💰 Balance: ${FgCyan}${deployerBalance.toFixed( 76 | 4 77 | )} ETH${Reset}\n` 78 | ); 79 | 80 | const explorer = 81 | networkId == "1" ? "Etherscan" : networkId == "137" ? "Polygonscan" : null; 82 | 83 | if (explorer != null) { 84 | console.log(`${Bright} ${explorer} Gas Tracker Data: ${Reset}`); 85 | await hre.run("gas", { [network.name]: true }); 86 | } 87 | 88 | /**************** 89 | * ONCHAIN TASK * 90 | ****************/ 91 | 92 | const jurisdictions = await hre.run("jurisdictions", { 93 | settings: JSON.stringify(settings), 94 | predeployed: JSON.stringify(deployed.jurisdictions), 95 | master: deployed.master, 96 | }); 97 | deployed.jurisdictions = jurisdictions.map(({ address }) => address); 98 | 99 | /****************** 100 | * STORAGE CHECKS * 101 | ******************/ 102 | 103 | const jurisdictionData = {}; 104 | 105 | for (const jurisdiction of jurisdictions) { 106 | const renewalPrice = ( 107 | await jurisdiction.callStatic.getJurisdictionRenewalPrice() 108 | ).toString(); 109 | const deployPrice = ( 110 | await jurisdiction.callStatic.getJurisdictionDeployPrice() 111 | ).toString(); 112 | const closePrice = ( 113 | await jurisdiction.callStatic.getJurisdictionClosePrice() 114 | ).toString(); 115 | const name = ( 116 | await jurisdiction.callStatic.getJurisdictionName() 117 | ).toString(); 118 | const defaultBadge = ( 119 | await jurisdiction.callStatic.getJurisdictionBadge() 120 | ).toString(); 121 | const goldBadge = ( 122 | await jurisdiction.callStatic.getJurisdictionGoldBadge() 123 | ).toString(); 124 | jurisdictionData[jurisdiction.address] = { 125 | renewalPrice, 126 | deployPrice, 127 | closePrice, 128 | name, 129 | defaultBadge, 130 | goldBadge, 131 | }; 132 | } 133 | 134 | console.log( 135 | `${Bright}🚀 OtoCo V2 Jurisdictions Deployed:${Reset}`, 136 | jurisdictionData 137 | ); 138 | 139 | fs.writeFileSync( 140 | `./deploys/v2/${network.name}.json`, 141 | JSON.stringify(deployed, undefined, 2) 142 | ); 143 | 144 | /********************** 145 | * SOURCE VERIFICATON * 146 | **********************/ 147 | 148 | if (networkId != "31337") { 149 | for (const jurisdiction of jurisdictions) { 150 | await hre.run("verification", { 151 | addr: jurisdiction.address, 152 | args: JSON.stringify( 153 | Object.values(jurisdictionData[jurisdiction.address]) 154 | ), 155 | }); 156 | } 157 | } 158 | } 159 | 160 | main() 161 | .then(() => process.exit(0)) 162 | .catch((error) => { 163 | console.error(error); 164 | process.exit(1); 165 | }); 166 | -------------------------------------------------------------------------------- /scripts/3-deploy-uri.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { network } = require("hardhat"); 3 | const hre = require("hardhat"); 4 | 5 | const Reset = "\x1b[0m"; 6 | const Bright = "\x1b[1m"; 7 | 8 | const FgRed = "\x1b[31m"; 9 | const FgGreen = "\x1b[32m"; 10 | const FgMagenta = "\x1b[35m"; 11 | const FgCyan = "\x1b[36m"; 12 | 13 | const networkPrefixes = { 14 | 1: "eth", 15 | 10: "oeth", 16 | 56: "bnb", 17 | 100: "gno", 18 | 137: "pol", 19 | 1337: "ganache", 20 | 31337: "hardhat", 21 | 42161: 'arb1', 22 | 11155111: "sep", 23 | 31337: "localhost", 24 | 1088: "avax", 25 | 8453: "base", 26 | 84532: "basesep", 27 | 42220: "celo", 28 | 420: "oeth", 29 | 1313161554: "aurora", 30 | } 31 | 32 | async function main() { 33 | 34 | /***************** 35 | * GENERAL SETUP * 36 | *****************/ 37 | 38 | const networkId = network.config.chainId; 39 | const [deployer] = await ethers.getSigners(); 40 | 41 | let deploysJson; 42 | 43 | // Load deployed master contract 44 | try { 45 | const data = fs.readFileSync(`./deploys/v2/${network.name}.json`, { encoding: "utf-8" }); 46 | deploysJson = JSON.parse(data); 47 | } catch (err) { 48 | console.log(`${FgRed}Error loading Master address: ${err}${Reset}`); 49 | process.exit(1); 50 | } 51 | 52 | 53 | /****************** 54 | * USER PROMPTING * 55 | ******************/ 56 | 57 | console.log( 58 | `\n${Bright}\t👤 Deploying contracts with ${FgCyan}${deployer.address}${Reset}`); 59 | 60 | const deployerBalance = 61 | parseInt((await deployer.getBalance()).toString()) / 1e18; 62 | 63 | console.log( 64 | `\t${Bright}💰 Balance: ${FgCyan}${deployerBalance.toFixed(4)} ETH${Reset}\n`); 65 | 66 | const explorer = networkId == '1' ? 'Etherscan' : networkId == '137' ? 'Polygonscan' : null; 67 | 68 | if (explorer != null) { 69 | console.log(`${Bright} ${explorer} Gas Tracker Data: ${Reset}`); 70 | await hre.run("gas", { [network.name]: true }); 71 | } 72 | 73 | 74 | /**************** 75 | * ONCHAIN TASK * 76 | ****************/ 77 | 78 | const { uri, master } = 79 | await hre.run("uri", { master: deploysJson.master, networkPrefix: networkPrefixes[networkId] }); 80 | 81 | 82 | /****************** 83 | * STORAGE CHECKS * 84 | ******************/ 85 | 86 | const returnedAddress = await master.callStatic.entitiesURI(); 87 | 88 | if (returnedAddress === uri.address) { 89 | console.log(`${Bright}🚀 URI Source has been updated correctly!${Reset} 90 | Deployed Address: ${FgMagenta}${uri.address}${Reset}`); 91 | } else { 92 | console.log(`${Bright}URI Source address differs from the expected!${Reset} 93 | ${FgCyan}Expected: ${FgGreen}${uri.address} 94 | ${FgCyan}Actual: ${FgRed}${returnedAddress}` 95 | ); 96 | } 97 | 98 | deploysJson.uri = uri.address; 99 | 100 | fs.writeFileSync(`./deploys/v2/${network.name}.json`, JSON.stringify(deploysJson, undefined, 2)); 101 | 102 | 103 | /********************** 104 | * SOURCE VERIFICATON * 105 | **********************/ 106 | 107 | if (network.config.chainId != '31337') { 108 | await hre.run("verification", { 109 | addr: deploysJson.uri, 110 | args: JSON.stringify([deploysJson.master]), 111 | }); 112 | } 113 | } 114 | 115 | main() 116 | .then(() => process.exit(0)) 117 | .catch((error) => { 118 | console.error(error); 119 | process.exit(1); 120 | }); -------------------------------------------------------------------------------- /scripts/4-post-configs.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { network } = require("hardhat"); 3 | const hre = require("hardhat"); 4 | require('dotenv').config(); 5 | 6 | const Reset = "\x1b[0m"; 7 | const Bright = "\x1b[1m"; 8 | 9 | const FgRed = "\x1b[31m"; 10 | const FgGreen = "\x1b[32m"; 11 | const FgMagenta = "\x1b[35m"; 12 | const FgCyan = "\x1b[36m"; 13 | 14 | 15 | async function main() { 16 | 17 | /***************** 18 | * GENERAL SETUP * 19 | *****************/ 20 | 21 | const networkId = network.config.chainId; 22 | const [deployer] = await ethers.getSigners(); 23 | 24 | let deploysJson; 25 | 26 | // Load deployed master contract 27 | try { 28 | const data = fs.readFileSync(`./deploys/v2/${network.name}.json`, {encoding:"utf-8"}); 29 | deploysJson = JSON.parse(data); 30 | } catch (err) { 31 | console.log(`${FgRed}Error loading Master address: ${err}${Reset}`); 32 | process.exit(1); 33 | } 34 | 35 | 36 | /****************** 37 | * USER PROMPTING * 38 | ******************/ 39 | 40 | console.log( 41 | `\n${Bright}\t👤 Deploying contracts with ${FgCyan}${deployer.address}${Reset}`); 42 | 43 | const deployerBalance = 44 | parseInt((await deployer.getBalance()).toString()) / 1e18; 45 | 46 | console.log( 47 | `\t${Bright}💰 Balance: ${FgCyan}${deployerBalance.toFixed(4)} ETH${Reset}\n`); 48 | 49 | const explorer = networkId == '1' ? 'Etherscan' : networkId == '137' ? 'Polygonscan' : null; 50 | 51 | if (explorer != null) { 52 | console.log(`${Bright} ${explorer} Gas Tracker Data: ${Reset}`); 53 | await hre.run("gas", { [network.name]: true }); 54 | } 55 | 56 | 57 | /**************** 58 | * ONCHAIN TASK * 59 | ****************/ 60 | 61 | // Set required additional settings 62 | const {priceFeed, baseFee, jurisdictions} = await hre.run("postsetup", { 63 | master: deploysJson.master, 64 | jurisdictions: JSON.stringify(deploysJson.jurisdictions), 65 | baseFee: process.env.BASE_FEE 66 | }); 67 | 68 | /****************** 69 | * STORAGE CHECKS * 70 | ******************/ 71 | 72 | const MasterFactoryV2 = await ethers.getContractFactory("OtoCoMasterV2"); 73 | const master = MasterFactoryV2.attach(deploysJson.master) 74 | 75 | // Check base Fee 76 | const baseFeeReturned = await master.callStatic.baseFee(); 77 | if(baseFeeReturned.toString() === process.env.BASE_FEE) { 78 | console.log(`${Bright}🚀 Base Fee has been updated correctly!${Reset}`) 79 | } else { 80 | console.log(`${Bright} Base Fee differs from the expected!${Reset} 81 | ${FgCyan}Expected: ${FgGreen}${process.env.BASE_FEE} 82 | ${FgCyan} Actual: ${FgRed}${baseFeeReturned}` 83 | ); 84 | } 85 | // Check Jurisdictions 86 | for (const [idx, j] of deploysJson.jurisdictions.entries()) { 87 | const address = await master.callStatic.jurisdictionAddress(idx); 88 | if(address === j) { 89 | console.log(`${Bright}🚀 Jurisdiction ${idx} has been updated correctly!${Reset} 90 | Deployed Address: ${FgMagenta}${address}${Reset}`); 91 | } else { 92 | console.log(`${Bright}URI Source address differs from the expected!${Reset} 93 | ${FgCyan}Expected: ${FgGreen}${j} 94 | ${FgCyan}Actual: ${FgRed}${address}` 95 | ); 96 | } 97 | } 98 | 99 | } 100 | 101 | main() 102 | .then(() => process.exit(0)) 103 | .catch((error) => { 104 | console.error(error); 105 | process.exit(1); 106 | }); -------------------------------------------------------------------------------- /scripts/5-deploy-initializers.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { network } = require("hardhat"); 3 | const hre = require("hardhat"); 4 | 5 | const Reset = "\x1b[0m"; 6 | const Bright = "\x1b[1m"; 7 | 8 | const FgRed = "\x1b[31m"; 9 | const FgGreen = "\x1b[32m"; 10 | const FgMagenta = "\x1b[35m"; 11 | const FgCyan = "\x1b[36m"; 12 | 13 | 14 | async function main() { 15 | 16 | /***************** 17 | * GENERAL SETUP * 18 | *****************/ 19 | 20 | const networkId = network.config.chainId; 21 | const [deployer] = await ethers.getSigners(); 22 | 23 | let deploysJson; 24 | 25 | // Load deployed master contract 26 | try { 27 | const data = fs.readFileSync(`./deploys/v2/${network.name}.json`, { encoding: "utf-8" }); 28 | deploysJson = JSON.parse(data); 29 | } catch (err) { 30 | console.log(`${FgRed}Error loading Master address: ${err}${Reset}`); 31 | process.exit(1); 32 | } 33 | 34 | 35 | /****************** 36 | * USER PROMPTING * 37 | ******************/ 38 | 39 | console.log( 40 | `\n${Bright}\t👤 Deploying contracts with ${FgCyan}${deployer.address}${Reset}`); 41 | 42 | const deployerBalance = 43 | parseInt((await deployer.getBalance()).toString()) / 1e18; 44 | 45 | console.log( 46 | `\t${Bright}💰 Balance: ${FgCyan}${deployerBalance.toFixed(4)} ETH${Reset}\n`); 47 | 48 | const explorer = networkId == '1' ? 'Etherscan' : networkId == '137' ? 'Polygonscan' : null; 49 | 50 | if (explorer != null) { 51 | console.log(`${Bright} ${explorer} Gas Tracker Data: ${Reset}`); 52 | await hre.run("gas", { [network.name]: true }); 53 | } 54 | 55 | 56 | /**************** 57 | * ONCHAIN TASK * 58 | ****************/ 59 | 60 | let governorInitializer; 61 | if (!deploysJson.governorInitializer) { 62 | // Deploy Initializers contracts 63 | governorInitializer = await hre.run("initializers", {}); 64 | } else { 65 | governorInitializer = { address: deploysJson.governorInitializer } 66 | } 67 | 68 | /****************** 69 | * STORAGE CHECKS * 70 | ******************/ 71 | 72 | console.log(`${Bright}🚀 Governor intializer was sucessfully deployed!${Reset} 73 | Deployed Address: ${FgMagenta}${governorInitializer.address}${Reset}`); 74 | 75 | deploysJson.governorInitializer = governorInitializer.address; 76 | 77 | fs.writeFileSync(`./deploys/v2/${network.name}.json`, JSON.stringify(deploysJson, undefined, 2)); 78 | 79 | 80 | /********************** 81 | * SOURCE VERIFICATON * 82 | **********************/ 83 | 84 | if (network.config.chainId != '31337') { 85 | await hre.run("verification", { 86 | addr: deploysJson.governorInitializer, 87 | }); 88 | } 89 | } 90 | 91 | main() 92 | .then(() => process.exit(0)) 93 | .catch((error) => { 94 | console.error(error); 95 | process.exit(1); 96 | }); -------------------------------------------------------------------------------- /scripts/6-deploy-badge-verifier.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { network } = require("hardhat"); 3 | const hre = require("hardhat"); 4 | 5 | const Reset = "\x1b[0m"; 6 | const Bright = "\x1b[1m"; 7 | 8 | const FgRed = "\x1b[31m"; 9 | const FgGreen = "\x1b[32m"; 10 | const FgMagenta = "\x1b[35m"; 11 | const FgCyan = "\x1b[36m"; 12 | 13 | 14 | async function main() { 15 | 16 | /***************** 17 | * GENERAL SETUP * 18 | *****************/ 19 | 20 | const networkId = network.config.chainId; 21 | const [deployer] = await ethers.getSigners(); 22 | 23 | let deploysJson; 24 | 25 | // Load deployed master contract 26 | try { 27 | const data = fs.readFileSync(`./deploys/v2/${network.name}.json`, {encoding:"utf-8"}); 28 | deploysJson = JSON.parse(data); 29 | } catch (err) { 30 | console.log(`${FgRed}Error loading Master address: ${err}${Reset}`); 31 | process.exit(1); 32 | } 33 | 34 | 35 | /****************** 36 | * USER PROMPTING * 37 | ******************/ 38 | 39 | console.log( 40 | `\n${Bright}\t👤 Deploying contracts with ${FgCyan}${deployer.address}${Reset}`); 41 | 42 | const deployerBalance = 43 | parseInt((await deployer.getBalance()).toString()) / 1e18; 44 | 45 | console.log( 46 | `\t${Bright}💰 Balance: ${FgCyan}${deployerBalance.toFixed(4)} ETH${Reset}\n`); 47 | 48 | const explorer = networkId == '1' ? 'Etherscan' : networkId == '137' ? 'Polygonscan' : null; 49 | 50 | if (explorer != null) { 51 | console.log(`${Bright} ${explorer} Gas Tracker Data: ${Reset}`); 52 | await hre.run("gas", { [network.name]: true }); 53 | } 54 | 55 | 56 | /**************** 57 | * ONCHAIN TASK * 58 | ****************/ 59 | 60 | const {verifier} = 61 | await hre.run("verifier", { master: deploysJson.master }); 62 | 63 | 64 | /****************** 65 | * STORAGE CHECKS * 66 | ******************/ 67 | 68 | console.log(`${Bright}🚀 Badge Verifier Deployed Address:${Reset}${FgMagenta}${verifier.address}${Reset}`); 69 | 70 | deploysJson.verifier = verifier.address; 71 | 72 | fs.writeFileSync(`./deploys/v2/${network.name}.json`, JSON.stringify(deploysJson, undefined, 2)); 73 | 74 | 75 | /********************** 76 | * SOURCE VERIFICATON * 77 | **********************/ 78 | 79 | if (network.config.chainId != '31337') { 80 | await hre.run( "verification", { 81 | addr: deploysJson.verifier, 82 | args: JSON.stringify([deploysJson.master]), 83 | }); 84 | } 85 | } 86 | 87 | main() 88 | .then(() => process.exit(0)) 89 | .catch((error) => { 90 | console.error(error); 91 | process.exit(1); 92 | }); -------------------------------------------------------------------------------- /scripts/7-deploy-plugin-token.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { network } = require("hardhat"); 3 | const hre = require("hardhat"); 4 | 5 | const Reset = "\x1b[0m"; 6 | const Bright = "\x1b[1m"; 7 | 8 | const FgRed = "\x1b[31m"; 9 | const FgGreen = "\x1b[32m"; 10 | const FgMagenta = "\x1b[35m"; 11 | const FgCyan = "\x1b[36m"; 12 | 13 | 14 | async function main() { 15 | 16 | /***************** 17 | * GENERAL SETUP * 18 | *****************/ 19 | 20 | const networkId = network.config.chainId; 21 | const [deployer] = await ethers.getSigners(); 22 | 23 | let deploysJson; 24 | 25 | // Load deployed master contract 26 | try { 27 | const data = fs.readFileSync(`./deploys/v2/${network.name}.json`, { encoding: "utf-8" }); 28 | deploysJson = JSON.parse(data); 29 | } catch (err) { 30 | console.log(`${FgRed}Error loading Master address: ${err}${Reset}`); 31 | process.exit(1); 32 | } 33 | 34 | 35 | let previousJson; 36 | // Load deployed master contract 37 | try { 38 | const data = fs.readFileSync(`./deploys/v2/previous.${network.name}.json`, { encoding: "utf-8" }); 39 | previousJson = JSON.parse(data); 40 | } catch (err) { 41 | console.log(`${FgRed}Error loading Previous/External addresses: ${err}${Reset}`); 42 | } 43 | 44 | /****************** 45 | * USER PROMPTING * 46 | ******************/ 47 | 48 | console.log( 49 | `\n${Bright}\t👤 Deploying contracts with ${FgCyan}${deployer.address}${Reset}`); 50 | 51 | const deployerBalance = 52 | parseInt((await deployer.getBalance()).toString()) / 1e18; 53 | 54 | console.log( 55 | `\t${Bright}💰 Balance: ${FgCyan}${deployerBalance.toFixed(4)} ETH${Reset}\n`); 56 | 57 | const explorer = networkId == '1' ? 'Etherscan' : networkId == '137' ? 'Polygonscan' : null; 58 | 59 | if (explorer != null) { 60 | console.log(`${Bright} ${explorer} Gas Tracker Data: ${Reset}`); 61 | await hre.run("gas", { [network.name]: true }); 62 | } 63 | 64 | 65 | /**************** 66 | * ONCHAIN TASK * 67 | ****************/ 68 | 69 | const { token, tokenPlugin } = 70 | await hre.run("token", { 71 | deployed: JSON.stringify(deploysJson) 72 | }); 73 | 74 | 75 | /****************** 76 | * STORAGE CHECKS * 77 | ******************/ 78 | 79 | console.log(`${Bright}🚀 OtoCo Token Deployed Address:${Reset}${FgMagenta}${token.address}${Reset}`); 80 | console.log(`${Bright}🚀 Token plugin Deployed Address:${Reset}${FgMagenta}${tokenPlugin.address}${Reset}`); 81 | 82 | deploysJson.token = tokenPlugin.address; 83 | 84 | fs.writeFileSync(`./deploys/v2/${network.name}.json`, JSON.stringify(deploysJson, undefined, 2)); 85 | 86 | 87 | /********************** 88 | * SOURCE VERIFICATON * 89 | **********************/ 90 | 91 | if (network.config.chainId != '31337') { 92 | await hre.run("verification", { 93 | addr: token.address 94 | }); 95 | 96 | await hre.run("verification", { 97 | addr: tokenPlugin.address, 98 | args: JSON.stringify([deploysJson.master, token.address, [], []]), 99 | }); 100 | } 101 | } 102 | 103 | main() 104 | .then(() => process.exit(0)) 105 | .catch((error) => { 106 | console.error(error); 107 | process.exit(1); 108 | }); -------------------------------------------------------------------------------- /scripts/8-deploy-plugin-multisig.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { network } = require("hardhat"); 3 | const hre = require("hardhat"); 4 | 5 | const Reset = "\x1b[0m"; 6 | const Bright = "\x1b[1m"; 7 | 8 | const FgRed = "\x1b[31m"; 9 | const FgGreen = "\x1b[32m"; 10 | const FgMagenta = "\x1b[35m"; 11 | const FgCyan = "\x1b[36m"; 12 | 13 | 14 | async function main() { 15 | 16 | /***************** 17 | * GENERAL SETUP * 18 | *****************/ 19 | 20 | const networkId = network.config.chainId; 21 | const [deployer] = await ethers.getSigners(); 22 | 23 | let deploysJson; 24 | 25 | // Load deployed master contract 26 | try { 27 | const data = fs.readFileSync(`./deploys/v2/${network.name}.json`, { encoding: "utf-8" }); 28 | deploysJson = JSON.parse(data); 29 | } catch (err) { 30 | console.log(`${FgRed}Error loading Master address: ${err}${Reset}`); 31 | process.exit(1); 32 | } 33 | 34 | 35 | let previousJson; 36 | // Load deployed master contract 37 | try { 38 | const data = fs.readFileSync(`./deploys/v2/previous.${network.name}.json`, { encoding: "utf-8" }); 39 | previousJson = JSON.parse(data); 40 | } catch (err) { 41 | console.log(`${FgRed}Error loading Previous/External addresses: ${err}${Reset}`); 42 | } 43 | 44 | /****************** 45 | * USER PROMPTING * 46 | ******************/ 47 | 48 | console.log( 49 | `\n${Bright}\t👤 Deploying contracts with ${FgCyan}${deployer.address}${Reset}`); 50 | 51 | const deployerBalance = 52 | parseInt((await deployer.getBalance()).toString()) / 1e18; 53 | 54 | console.log( 55 | `\t${Bright}💰 Balance: ${FgCyan}${deployerBalance.toFixed(4)} ETH${Reset}\n`); 56 | 57 | const explorer = networkId == '1' ? 'Etherscan' : networkId == '137' ? 'Polygonscan' : null; 58 | 59 | if (explorer != null) { 60 | console.log(`${Bright} ${explorer} Gas Tracker Data: ${Reset}`); 61 | await hre.run("gas", { [network.name]: true }); 62 | } 63 | 64 | 65 | /**************** 66 | * ONCHAIN TASK * 67 | ****************/ 68 | 69 | const { multisig, safe, safeFactory } = 70 | await hre.run("multisig", { 71 | deployed: JSON.stringify(deploysJson), 72 | previous: JSON.stringify(previousJson) 73 | }); 74 | 75 | 76 | /****************** 77 | * STORAGE CHECKS * 78 | ******************/ 79 | 80 | console.log(`${Bright}🚀 Multisig Plugin Deployed Address:${Reset}${FgMagenta}${multisig.address}${Reset}`); 81 | 82 | deploysJson.multisig = multisig.address; 83 | 84 | fs.writeFileSync(`./deploys/v2/${network.name}.json`, JSON.stringify(deploysJson, undefined, 2)); 85 | 86 | 87 | /********************** 88 | * SOURCE VERIFICATON * 89 | **********************/ 90 | 91 | if (network.config.chainId != '31337') { 92 | await hre.run("verification", { 93 | addr: deploysJson.multisig, 94 | args: JSON.stringify([deploysJson.master, previousJson.safe, previousJson.safeFactory, [], []]), 95 | }); 96 | } 97 | } 98 | 99 | main() 100 | .then(() => process.exit(0)) 101 | .catch((error) => { 102 | console.error(error); 103 | process.exit(1); 104 | }); -------------------------------------------------------------------------------- /scripts/9-deploy-plugin-ens.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { network } = require("hardhat"); 3 | const hre = require("hardhat"); 4 | 5 | const Reset = "\x1b[0m"; 6 | const Bright = "\x1b[1m"; 7 | 8 | const FgRed = "\x1b[31m"; 9 | const FgGreen = "\x1b[32m"; 10 | const FgMagenta = "\x1b[35m"; 11 | const FgCyan = "\x1b[36m"; 12 | 13 | 14 | async function main() { 15 | 16 | /***************** 17 | * GENERAL SETUP * 18 | *****************/ 19 | 20 | const networkId = network.config.chainId; 21 | const [deployer] = await ethers.getSigners(); 22 | 23 | let deploysJson; 24 | 25 | // Load deployed master contract 26 | try { 27 | const data = fs.readFileSync(`./deploys/v2/${network.name}.json`, { encoding: "utf-8" }); 28 | deploysJson = JSON.parse(data); 29 | } catch (err) { 30 | console.log(`${FgRed}Error loading Master address: ${err}${Reset}`); 31 | process.exit(1); 32 | } 33 | 34 | 35 | let previousJson; 36 | // Load deployed master contract 37 | try { 38 | const data = fs.readFileSync(`./deploys/v2/previous.${network.name}.json`, { encoding: "utf-8" }); 39 | previousJson = JSON.parse(data); 40 | } catch (err) { 41 | console.log(`${FgRed}Error loading Previous/External addresses: ${err}${Reset}`); 42 | } 43 | 44 | /****************** 45 | * USER PROMPTING * 46 | ******************/ 47 | 48 | console.log( 49 | `\n${Bright}\t👤 Deploying contracts with ${FgCyan}${deployer.address}${Reset}`); 50 | 51 | const deployerBalance = 52 | parseInt((await deployer.getBalance()).toString()) / 1e18; 53 | 54 | console.log( 55 | `\t${Bright}💰 Balance: ${FgCyan}${deployerBalance.toFixed(4)} ETH${Reset}\n`); 56 | 57 | const explorer = networkId == '1' ? 'Etherscan' : networkId == '137' ? 'Polygonscan' : null; 58 | 59 | if (explorer != null) { 60 | console.log(`${Bright} ${explorer} Gas Tracker Data: ${Reset}`); 61 | await hre.run("gas", { [network.name]: true }); 62 | } 63 | 64 | 65 | /**************** 66 | * ONCHAIN TASK * 67 | ****************/ 68 | 69 | const { ens, verifyValues } = 70 | await hre.run("ens", { 71 | deployed: JSON.stringify(deploysJson), 72 | previous: JSON.stringify(previousJson) 73 | }); 74 | 75 | 76 | /****************** 77 | * STORAGE CHECKS * 78 | ******************/ 79 | 80 | console.log(`${Bright}🚀 ENS Plugin Address:${Reset}${FgMagenta}${ens.address}${Reset}`); 81 | 82 | deploysJson.ens = ens.address; 83 | 84 | fs.writeFileSync(`./deploys/v2/${network.name}.json`, JSON.stringify(deploysJson, undefined, 2)); 85 | 86 | 87 | /********************** 88 | * SOURCE VERIFICATON * 89 | **********************/ 90 | 91 | if (network.config.chainId != '31337') { 92 | await hre.run("verification", { 93 | addr: ens.address, 94 | args: JSON.stringify(verifyValues), 95 | }); 96 | } 97 | } 98 | 99 | main() 100 | .then(() => process.exit(0)) 101 | .catch((error) => { 102 | console.error(error); 103 | process.exit(1); 104 | }); -------------------------------------------------------------------------------- /scripts/sendEmpty.js: -------------------------------------------------------------------------------- 1 | const { network } = require("hardhat"); 2 | 3 | async function main() { 4 | const [signer] = await ethers.getSigners(); 5 | // Get your address 6 | const myAddress = await signer.getAddress(); 7 | // To address (can be any address) 8 | const toAddress = signer.getAddress(); // Replace with desired recipient address 9 | 10 | const tx = { 11 | from: myAddress, 12 | to: toAddress, 13 | data: "0x", // Empty data for simple transaction 14 | }; 15 | 16 | console.log("Sending transaction..."); 17 | 18 | const sentTx = await signer.sendTransaction(tx); 19 | 20 | console.log("Transaction hash:", sentTx.hash); 21 | console.log("Waiting for confirmation..."); 22 | 23 | await sentTx.wait(); 24 | 25 | console.log("Transaction confirmed at block:", sentTx.blockNumber); 26 | } 27 | 28 | main().catch((error) => { 29 | console.error(error); 30 | process.exit(1); 31 | }); -------------------------------------------------------------------------------- /tasks/initializers.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config"); 2 | 3 | task("initializers", "Deploys the initializers for Gnovernors and Multisigs") 4 | .setAction(async (taskArgs) => { 5 | 6 | const deployer = hre.network.config.chainId == 31337 && process.env.FORK_ENABLED == "true" ? 7 | await ethers.getImpersonatedSigner("0x1216a72b7822Bbf7c38707F9a602FC241Cd6df30") 8 | : await ethers.getSigner() 9 | 10 | governorInitializer = await (await ethers.getContractFactory("GovernorInitializer", deployer)).deploy(); 11 | governorInstance = await governorInitializer.deployed(); 12 | 13 | return governorInstance; 14 | }); 15 | 16 | -------------------------------------------------------------------------------- /tasks/jurisdictions.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config"); 2 | 3 | // deploy via cli: 4 | // e.g.: 5 | // npx hardhat jurisdictions --network localhost 6 | task("jurisdictions", "Deploys OtoCo V2 Jurisdictions") 7 | .addParam("master", "The current instance of OtoCoMasterV2") 8 | // Jurisdiction setting to be deployed stringified 9 | .addParam("settings", "The V2 jurisdiction settings") 10 | .addOptionalParam("predeployed", "Previous jurisdictions to be skipped", "[]") 11 | .setAction(async (taskArgs) => { 12 | const deployer = 13 | hre.network.config.chainId == 31337 && process.env.FORK_ENABLED == "true" 14 | ? await ethers.getImpersonatedSigner( 15 | "0x1216a72b7822Bbf7c38707F9a602FC241Cd6df30" 16 | ) 17 | : await ethers.getSigner(); 18 | 19 | const jurisdictions = JSON.parse(taskArgs.settings); 20 | const predeployed = JSON.parse(taskArgs.predeployed); 21 | console.log(predeployed); 22 | 23 | factoryNames = [ 24 | "JurisdictionUnincorporatedV2", 25 | "JurisdictionDelawareV2", 26 | "JurisdictionWyomingV2", 27 | "JurisdictionSwissAssociationV2", 28 | "JurisdictionMarshallIslandsV2", 29 | "JurisdictionUnaDunaV2", 30 | ]; 31 | 32 | let contracts = []; 33 | 34 | // Deploying contract 35 | for (const [idx, j] of jurisdictions.entries()) { 36 | const jurisdictionFactory = await ethers.getContractFactory( 37 | factoryNames[idx], 38 | deployer 39 | ); 40 | let jurisdictionInstance; 41 | if (predeployed[idx]) { 42 | jurisdictionInstance = await jurisdictionFactory.attach( 43 | predeployed[idx] 44 | ); 45 | const jurisdictionData = []; 46 | try { 47 | jurisdictionData.push( 48 | ( 49 | await jurisdictionInstance.callStatic.getJurisdictionDeployPrice() 50 | ).toString() 51 | ); 52 | jurisdictionData.push( 53 | ( 54 | await jurisdictionInstance.callStatic.getJurisdictionRenewalPrice() 55 | ).toString() 56 | ); 57 | jurisdictionData.push( 58 | ( 59 | await jurisdictionInstance.callStatic.getJurisdictionClosePrice() 60 | ).toString() 61 | ); 62 | jurisdictionData.push( 63 | ( 64 | await jurisdictionInstance.callStatic.getJurisdictionGoldBadge() 65 | ).toString() 66 | ); 67 | jurisdictionData.push( 68 | ( 69 | await jurisdictionInstance.callStatic.getJurisdictionBadge() 70 | ).toString() 71 | ); 72 | } catch (err) { 73 | // If some of the calls above fails, ignore the rest since it has to be redeployed 74 | console.log( 75 | `Predeployed ${jurisdictions[idx].name} fails to return some of the properties required on V2.` 76 | ); 77 | } 78 | const settingsData = [ 79 | jurisdictions[idx].deployPrice.toString(), 80 | jurisdictions[idx].renewPrice.toString(), 81 | jurisdictions[idx].closePrice.toString(), 82 | jurisdictions[idx].goldBadge.toString(), 83 | jurisdictions[idx].defaultBadge.toString(), 84 | ]; 85 | if (JSON.stringify(jurisdictionData) == JSON.stringify(settingsData)) { 86 | console.log( 87 | `Jurisdiction ${jurisdictions[idx].name} exactly the same, will skip redeploy.` 88 | ); 89 | contracts.push(jurisdictionInstance); 90 | continue; 91 | } 92 | } 93 | console.log( 94 | `Jurisdiction ${jurisdictions[idx].name} not equal, will redeploy.` 95 | ); 96 | jurisdictionInstance = await jurisdictionFactory.deploy( 97 | ...Object.values(j) 98 | ); 99 | await jurisdictionInstance.deployed(); 100 | contracts.push(jurisdictionInstance); 101 | } 102 | 103 | // Setting addresses on mainnet 104 | const master = (await ethers.getContractFactory("OtoCoMasterV2")).attach( 105 | taskArgs.master 106 | ); 107 | let transaction; 108 | for (const [idx, j] of contracts.entries()) { 109 | if (predeployed[idx]) { 110 | if (j.address != predeployed[idx]) { 111 | console.log("Will update from ", j.address, " to ", predeployed[idx]); 112 | transaction = await master 113 | .connect(deployer) 114 | .updateJurisdiction(idx, j.address); 115 | await transaction.wait(1); 116 | } else { 117 | console.log( 118 | "Same address for ", 119 | idx, 120 | " will keep ", 121 | predeployed[idx] 122 | ); 123 | } 124 | const jurisdictionSet = ( 125 | await master.callStatic.jurisdictionAddress(idx) 126 | ).toString(); 127 | if (jurisdictionSet != j.address) { 128 | transaction = await master 129 | .connect(deployer) 130 | .updateJurisdiction(idx, j.address); 131 | await transaction.wait(1); 132 | } else { 133 | console.log("Same address for ", idx, " will keep ", j.address); 134 | } 135 | continue; 136 | } 137 | console.log("Will add jurisdiction", idx, j.address); 138 | transaction = await master.connect(deployer).addJurisdiction(j.address); 139 | await transaction.wait(1); 140 | } 141 | 142 | return contracts; 143 | }); 144 | -------------------------------------------------------------------------------- /tasks/master.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config"); 2 | 3 | const defaultUrl = "https://otoco.io/dashpanel/entity/"; 4 | task("master", "Deploys a OtoCo V2 Master proxy") 5 | // leave empty for default value 6 | .addOptionalParam("url", "The entities page base external url", defaultUrl) 7 | 8 | .setAction(async (taskArgs, hre) => { 9 | 10 | const isForkedLocalNode = hre.network.config.chainId == 31337 && process.env.FORK_ENABLED == "true" 11 | 12 | // IN case of FORKED LOCAL NODE will grab deploys from forked network 13 | // Otherwise will grab addresses directly from connected network 14 | const deploysSource = isForkedLocalNode ? process.env.FORKED_NETWORK : hre.network.name; 15 | const deploys = require(`../deploys/v1/${deploysSource}.json`) 16 | 17 | // In case of FORKED LOCAL NODE will impersonate OtoCo deployer 18 | const deployer = isForkedLocalNode ? 19 | await ethers.getImpersonatedSigner("0x1216a72b7822Bbf7c38707F9a602FC241Cd6df30") 20 | : await ethers.getSigner() 21 | 22 | const MasterFactoryV1 = await ethers.getContractFactory("OtoCoMaster", deployer); 23 | const MasterFactoryV2 = await ethers.getContractFactory("OtoCoMasterV2", deployer); 24 | // In case of running locally and forked will force implementation locally 25 | if (isForkedLocalNode) { 26 | await upgrades.forceImport(deploys.master, MasterFactoryV1) 27 | otocoMaster = await upgrades.upgradeProxy( 28 | deploys.master, 29 | MasterFactoryV2 30 | ); 31 | // In case of running a migration on testnets/mainnets 32 | } else if (hre.network.config.chainId != 31337 && deploys.master) { 33 | otocoMaster = await upgrades.upgradeProxy( 34 | deploys.master, 35 | MasterFactoryV2 36 | ); 37 | // In case of running locally but not forked 38 | } else { 39 | otocoMaster = await upgrades.deployProxy( 40 | MasterFactoryV2, 41 | [[], taskArgs.url], 42 | ); 43 | } 44 | 45 | return await otocoMaster.deployed() 46 | 47 | }); -------------------------------------------------------------------------------- /tasks/plugins/ens.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config"); 2 | const { Artifacts } = require("hardhat/internal/artifacts"); 3 | 4 | async function getExternalArtifact(contract) { 5 | const artifactsPath = "./artifacts-external"; 6 | const artifacts = new Artifacts(artifactsPath); 7 | return artifacts.readArtifact(contract); 8 | } 9 | 10 | task("ens", "Deploy ENS plugin") 11 | .addOptionalParam("deployed", "Current deployed contract", '{}') 12 | .addOptionalParam("previous", "Previous deployments and external contracts", '{}') 13 | .setAction(async (taskArgs) => { 14 | 15 | // IN case of FORKED LOCAL NODE will grab deploys from forked network 16 | // Otherwise will grab addresses directly from connected network 17 | const deploysJson = JSON.parse(taskArgs.deployed) 18 | const previousJson = JSON.parse(taskArgs.previous) 19 | 20 | const deployer = hre.network.config.chainId == 31337 && process.env.FORK_ENABLED == "true" ? 21 | await ethers.getImpersonatedSigner("0x1216a72b7822Bbf7c38707F9a602FC241Cd6df30") 22 | : await ethers.getSigner() 23 | 24 | if (hre.network.config.chainId == 31337) { 25 | const ENSRegistryArtifact = await getExternalArtifact("ENSRegistry"); 26 | const ENSRegistryFactory = await ethers.getContractFactoryFromArtifact(ENSRegistryArtifact); 27 | ensRegistry = await ENSRegistryFactory.deploy(); 28 | 29 | const FIFSRegistrarArtifact = await getExternalArtifact("FIFSRegistrar"); 30 | const FIFSRegistrarFactory = await ethers.getContractFactoryFromArtifact(FIFSRegistrarArtifact); 31 | fifsRegistrar = await FIFSRegistrarFactory.deploy(ensRegistry.address, ethers.utils.namehash("eth")); 32 | 33 | const PublicResolverArtifact = await getExternalArtifact("PublicResolver"); 34 | const PublicResolverFactory = await ethers.getContractFactoryFromArtifact(PublicResolverArtifact); 35 | publicResolver = await PublicResolverFactory.deploy(ensRegistry.address, zeroAddress(), zeroAddress(), zeroAddress()); 36 | 37 | const resolverNode = ethers.utils.namehash("resolver"); 38 | const resolverLabel = labelhash("resolver"); 39 | await ensRegistry.setSubnodeOwner(ethers.utils.formatBytes32String(''), resolverLabel, owner.address); 40 | await ensRegistry.setResolver(resolverNode, publicResolver.address); 41 | await ensRegistry.setOwner(ethers.utils.formatBytes32String(''), owner.address); 42 | await ensRegistry.setSubnodeOwner(ethers.utils.formatBytes32String(''), labelhash('eth'), fifsRegistrar.address); 43 | 44 | previousJson.ensRegistry = ensRegistry.address 45 | previousJson.ensResolver = publicResolver.address 46 | } else { 47 | previousJson.ensRegistry = previousJson.ensregistry 48 | previousJson.ensResolver = previousJson.resolver 49 | } 50 | 51 | const rootNode = '0xd60cd0a683332ca8ad4a4d342320945cb769f25760b42a21f2d88d3be25cc6aa' // otoco.eth 52 | 53 | const ENSPluginFactory = await ethers.getContractFactory("ENS", deployer); 54 | const ens = await ENSPluginFactory.deploy( 55 | deploysJson.master, previousJson.ensRegistry, previousJson.ensResolver, rootNode, [], [] 56 | ); 57 | await ens.deployed() 58 | 59 | if (hre.network.config.chainId == 31337) { 60 | await fifsRegistrar.register(labelhash('otoco'), ens.address); 61 | } 62 | 63 | return { 64 | ens, 65 | verifyValues: [deploysJson.master, previousJson.ensRegistry, previousJson.ensResolver, rootNode, [], []] 66 | }; 67 | }); 68 | 69 | -------------------------------------------------------------------------------- /tasks/plugins/launchpool.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config"); 2 | const { Artifacts } = require("hardhat/internal/artifacts"); 3 | 4 | async function getExternalArtifact(contract) { 5 | const artifactsPath = "./artifacts-external"; 6 | const artifacts = new Artifacts(artifactsPath); 7 | return artifacts.readArtifact(contract); 8 | } 9 | 10 | task("launchpool", "Deploy launchpool plugin") 11 | .addOptionalParam("deployed", "Current deployed contract", '{}') 12 | .setAction(async (taskArgs) => { 13 | 14 | // IN case of FORKED LOCAL NODE will grab deploys from forked network 15 | // Otherwise will grab addresses directly from connected network 16 | const deploysJson = JSON.parse(taskArgs.deployed) 17 | 18 | const deployer = hre.network.config.chainId == 31337 && process.env.FORK_ENABLED == "true" ? 19 | await ethers.getImpersonatedSigner("0x1216a72b7822Bbf7c38707F9a602FC241Cd6df30") 20 | : await ethers.getSigner() 21 | 22 | const LaunchPoolArtifact = await getExternalArtifact("LaunchPool", deployer); 23 | const launchpoolSource = await (await ethers.getContractFactoryFromArtifact(LaunchPoolArtifact)).deploy(); 24 | await launchpoolSource.deployed() 25 | 26 | const LaunchCurveArtifact = await getExternalArtifact("LaunchCurveExponential", deployer); 27 | const launchpoolCurve = await (await ethers.getContractFactoryFromArtifact(LaunchCurveArtifact)).deploy(); 28 | await launchpoolCurve.deployed() 29 | 30 | const LaunchpoolPluginFactory = await ethers.getContractFactory("Launchpool", deployer); 31 | const launchpool = await LaunchpoolPluginFactory.deploy( 32 | deploysJson.master, 33 | launchpoolSource.address, 34 | launchpoolCurve.address, 35 | [], 36 | [] 37 | ); 38 | await launchpool.deployed() 39 | 40 | 41 | return { launchpool, launchpoolSource, launchpoolCurve }; 42 | }); 43 | 44 | -------------------------------------------------------------------------------- /tasks/plugins/multisig.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config"); 2 | const { Artifacts } = require("hardhat/internal/artifacts"); 3 | 4 | async function getExternalArtifact(contract) { 5 | const artifactsPath = "./artifacts-external"; 6 | const artifacts = new Artifacts(artifactsPath); 7 | return artifacts.readArtifact(contract); 8 | } 9 | 10 | task("multisig", "Deploy multisig plugin") 11 | .addOptionalParam("deployed", "Current deployed contract", '{}') 12 | .addOptionalParam("previous", "Previous deployments and external contracts", '{}') 13 | .setAction(async (taskArgs) => { 14 | 15 | // IN case of FORKED LOCAL NODE will grab deploys from forked network 16 | // Otherwise will grab addresses directly from connected network 17 | const deploysJson = JSON.parse(taskArgs.deployed) 18 | const previousJson = JSON.parse(taskArgs.previous) 19 | 20 | const deployer = hre.network.config.chainId == 31337 && process.env.FORK_ENABLED == "true" ? 21 | await ethers.getImpersonatedSigner("0x1216a72b7822Bbf7c38707F9a602FC241Cd6df30") 22 | : await ethers.getSigner() 23 | 24 | if (!previousJson.safe) { 25 | const GnosisSafeArtifact = await getExternalArtifact("GnosisSafe", deployer); 26 | const GnosisSafeFactory = await ethers.getContractFactoryFromArtifact(GnosisSafeArtifact); 27 | deploysJson.gnosisSafe = (await GnosisSafeFactory.deploy()).address; 28 | } 29 | 30 | if (!previousJson.safeFactory) { 31 | const GnosisSafeProxyFactoryArtifact = await getExternalArtifact("GnosisSafeProxyFactory", deployer); 32 | const GnosisSafeProxyFactoryFactory = await ethers.getContractFactoryFromArtifact(GnosisSafeProxyFactoryArtifact); 33 | deploysJson.gnosisSafeProxyFactory = (await GnosisSafeProxyFactoryFactory.deploy()).address; 34 | } 35 | 36 | const MultisigPluginFactory = await ethers.getContractFactory("Multisig", deployer); 37 | const multisig = await MultisigPluginFactory.deploy( 38 | deploysJson.master, 39 | previousJson.safe ? previousJson.safe : deploysJson.gnosisSafe, 40 | previousJson.safeFactory ? previousJson.safeFactory : deploysJson.gnosisSafeProxyFactory, 41 | [], 42 | [] 43 | ); 44 | await multisig.deployed() 45 | 46 | return { 47 | multisig, 48 | safe: deploysJson.gnosisSafe, 49 | safeFactory: deploysJson.gnosisSafeProxyFactory, 50 | }; 51 | }); 52 | -------------------------------------------------------------------------------- /tasks/plugins/timestamp.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config"); 2 | 3 | task("timestamp", "Deploy Timestamp plugin") 4 | .addOptionalParam("deployed", "Current deployed contract", '{}') 5 | .setAction(async (taskArgs) => { 6 | 7 | // IN case of FORKED LOCAL NODE will grab deploys from forked network 8 | // Otherwise will grab addresses directly from connected network 9 | const deploysJson = JSON.parse(taskArgs.deployed) 10 | 11 | const deployer = hre.network.config.chainId == 31337 && process.env.FORK_ENABLED == "true" ? 12 | await ethers.getImpersonatedSigner("0x1216a72b7822Bbf7c38707F9a602FC241Cd6df30") 13 | : await ethers.getSigner() 14 | 15 | // Deploy Timestamp contract 16 | const timestamp = await (await ethers.getContractFactory("Timestamp", deployer)).deploy(deploysJson.master); 17 | await timestamp.deployed() 18 | 19 | return { timestamp }; 20 | }); 21 | 22 | -------------------------------------------------------------------------------- /tasks/plugins/token.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config"); 2 | 3 | task("token", "Deploy Token plugin") 4 | .addOptionalParam("deployed", "Current deployed contracts", '{}') 5 | .setAction(async (taskArgs) => { 6 | 7 | // IN case of FORKED LOCAL NODE will grab deploys from forked network 8 | // Otherwise will grab addresses directly from connected network 9 | const deploysJson = JSON.parse(taskArgs.deployed) 10 | const deployer = hre.network.config.chainId == 31337 && process.env.FORK_ENABLED == "true" ? 11 | await ethers.getImpersonatedSigner("0x1216a72b7822Bbf7c38707F9a602FC241Cd6df30") 12 | : await ethers.getSigner() 13 | 14 | const OtoCoTokenFactory = await ethers.getContractFactory("OtoCoToken", deployer); 15 | const token = await OtoCoTokenFactory.deploy(); 16 | await token.deployed() 17 | 18 | const tokenPlugin = await (await ethers.getContractFactory("Token", deployer)).deploy(deploysJson.master, token.address, [], []); 19 | await tokenPlugin.deployed() 20 | 21 | return { token, tokenPlugin } 22 | 23 | }); 24 | 25 | -------------------------------------------------------------------------------- /tasks/plugins/tokenization.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config"); 2 | 3 | task("tokenization", "Deploy tokenization plugin") 4 | .addOptionalParam("deployed", "Current deployed contract", '{}') 5 | .addOptionalParam("previous", "Previous deployments and external contracts", '{}') 6 | .setAction(async (taskArgs) => { 7 | 8 | // IN case of FORKED LOCAL NODE will grab deploys from forked network 9 | // Otherwise will grab addresses directly from connected network 10 | const deploysJson = JSON.parse(taskArgs.deployed) 11 | 12 | const deployer = hre.network.config.chainId == 31337 && process.env.FORK_ENABLED == "true" ? 13 | await ethers.getImpersonatedSigner("0x1216a72b7822Bbf7c38707F9a602FC241Cd6df30") 14 | : await ethers.getSigner() 15 | 16 | // Deploys Tokenization tokens 17 | const OtoCoTokenMintable = await ethers.getContractFactory("OtoCoTokenMintable", deployer); 18 | const otocoTokenMintable = await OtoCoTokenMintable.deploy(); 19 | await otocoTokenMintable.deployed() 20 | 21 | const OtoCoTokenNonTransferable = await ethers.getContractFactory("OtoCoTokenNonTransferable", deployer); 22 | const otocoTokenNonTransferable = await OtoCoTokenNonTransferable.deploy(); 23 | await otocoTokenNonTransferable.deployed() 24 | 25 | // Deploys Tokenization contract + Governor 26 | const OtoCoGovernor = await ethers.getContractFactory("OtoCoGovernor", deployer); 27 | const otocoGovernor = await OtoCoGovernor.deploy(); 28 | await otocoGovernor.deployed(); 29 | 30 | const Tokenization = await ethers.getContractFactory("Tokenization", deployer); 31 | const tokenization = await Tokenization.deploy( 32 | deploysJson.master, 33 | otocoGovernor.address 34 | ); 35 | await tokenization.deployed() 36 | 37 | return { otocoTokenMintable, otocoTokenNonTransferable, otocoGovernor, tokenization }; 38 | }); 39 | 40 | -------------------------------------------------------------------------------- /tasks/postsetup.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config"); 2 | 3 | task("postsetup", "Make all post-setup changes to Master Contract") 4 | .addParam("master", "The current instance of OtoCoMasterV2") 5 | .addParam("jurisdictions", "The V2 jurisdiction addresses") 6 | 7 | .setAction(async (taskArgs, hre) => { 8 | 9 | 10 | const isForkedLocalNode = hre.network.config.chainId == 31337 && process.env.FORK_ENABLED == "true"; 11 | const isLocalButNotForked = hre.network.config.chainId == 31337 && process.env.FORK_ENABLED == "false"; 12 | 13 | // IN case of FORKED LOCAL NODE will grab deploys from forked network 14 | // Otherwise will grab addresses directly from connected network 15 | const deploysSource = isForkedLocalNode ? process.env.FORKED_NETWORK : hre.network.name; 16 | const deploys = require(`../deploys/v2/previous.${deploysSource}.json`); 17 | 18 | // In case of FORKED LOCAL NODE will impersonate OtoCo deployer 19 | const deployer = isForkedLocalNode 20 | ? await ethers.getImpersonatedSigner("0x1216a72b7822Bbf7c38707F9a602FC241Cd6df30") 21 | : await ethers.getSigner() 22 | 23 | const MasterFactoryV2 = await ethers.getContractFactory("OtoCoMasterV2"); 24 | const otocoMaster = MasterFactoryV2.attach(taskArgs.master); 25 | 26 | let priceFeedAddress = deploys.priceFeed; 27 | 28 | let transaction; 29 | // In case of NON FORKED LOCAL NODE, will deploy a new MockAggregator for price fetch 30 | if (isLocalButNotForked) { 31 | const mockAggregatorInstance = await (await ethers.getContractFactory("MockAggregatorV3", deployer)).deploy(); 32 | await mockAggregatorInstance.deployed(); 33 | priceFeedAddress = mockAggregatorInstance.address; 34 | transaction = await otocoMaster.connect(deployer).changePriceFeed(priceFeedAddress); 35 | await transaction.wait(); 36 | } 37 | 38 | transaction = await otocoMaster.connect(deployer).changePriceFeed(priceFeedAddress); 39 | await transaction.wait(); 40 | transaction = await otocoMaster.connect(deployer).changeBaseFees(process.env.BASE_FEE); 41 | await transaction.wait(); 42 | 43 | let updatedJurisdictions = [] 44 | for (const [idx, j] of JSON.parse(taskArgs.jurisdictions).entries()) { 45 | const currentAddress = await otocoMaster.jurisdictionAddress(idx); 46 | if (currentAddress == j) continue 47 | transaction = await otocoMaster.connect(deployer).updateJurisdiction(idx, j); 48 | await transaction.wait(); 49 | 50 | updatedJurisdictions.push({ 51 | index: idx, 52 | address: j, 53 | }) 54 | } 55 | 56 | return { 57 | priceFeed: priceFeedAddress, 58 | baseFee: process.env.BASE_FEE, 59 | jurisdictions: updatedJurisdictions 60 | }; 61 | 62 | }); -------------------------------------------------------------------------------- /tasks/setup.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config"); 2 | require('dotenv').config(); 3 | 4 | require("./utils/accounts"); 5 | require("./utils/gas"); 6 | require("./utils/verification"); 7 | 8 | require("./jurisdictions"); 9 | require("./master"); 10 | require("./uri"); 11 | require("./postsetup"); 12 | require("./initializers"); 13 | require("./verifier"); 14 | 15 | require("./plugins/token"); 16 | require("./plugins/multisig"); 17 | require("./plugins/ens"); 18 | require("./plugins/launchpool"); 19 | require("./plugins/timestamp"); 20 | require("./plugins/tokenization"); 21 | 22 | task("setup", "OtoCo V2 scripts setup pusher") 23 | .setAction(async (undefined, hre) => { 24 | 25 | const jurisdictionSettings = require(`../scripts/jurisdiction-settings.json`) 26 | 27 | let settings 28 | switch (process.env.FORKED_NETWORK) { 29 | case "mainnet": settings = jurisdictionSettings.mainnet; break; 30 | case "polygon": settings = jurisdictionSettings.polygon; break; 31 | default: settings = jurisdictionSettings.default; break; 32 | } 33 | 34 | let previousJson 35 | // Import previous source contracts 36 | try { 37 | const data = fs.readFileSync(`./deploys/previous.${network.name}.json`, { encoding: "utf-8" }); 38 | previousJson = JSON.parse(data); 39 | } catch (err) { 40 | previousJson = [] 41 | console.log(err); 42 | } 43 | 44 | // Mine blocks automatically to allow use with front-end 45 | if (hre.network.config.chainId == 31337) { 46 | await network.provider.send("evm_setIntervalMining", [5000]); 47 | await (await ethers.getSigner()).sendTransaction({ 48 | to: '0x1216a72b7822Bbf7c38707F9a602FC241Cd6df30', 49 | value: ethers.utils.parseEther("10"), 50 | }); 51 | } 52 | 53 | // Deploy V2 Jurisdictions contracts 54 | const jurisdictions = await hre.run( 55 | "jurisdictions", 56 | { settings: JSON.stringify(settings) } 57 | ); 58 | 59 | // Deploy/Migrate MasterV2 contract 60 | const master = await hre.run("master", { 61 | jurisdictions: JSON.stringify(jurisdictions.map(({ address }) => address)) 62 | }); 63 | // Deploy tokenURI contract 64 | await hre.run("uri", { 65 | master: master.address 66 | }); 67 | // Deploy Badge Verifier contract 68 | await hre.run("verifier", { 69 | master: master.address 70 | }); 71 | 72 | // Set required additional settings 73 | await hre.run("postsetup", { 74 | master: master.address, 75 | jurisdictions: JSON.stringify(jurisdictions.map(({ address }) => address)), 76 | }); 77 | // Deploy Initializers contracts 78 | await hre.run("initializers", {}); 79 | 80 | }); 81 | 82 | module.exports = { 83 | solidity: "0.8.4", 84 | }; -------------------------------------------------------------------------------- /tasks/uri.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config"); 2 | 3 | task("uri", "Deploys the default URI builder for OtoCo Entities") 4 | .addParam("master", "The current instance of OtoCoMasterV2") 5 | .addParam("networkPrefix", "Network prefix to identify entities on Otoco site") 6 | .setAction(async (taskArgs) => { 7 | 8 | const deployer = hre.network.config.chainId == 31337 && process.env.FORK_ENABLED == "true" ? 9 | await ethers.getImpersonatedSigner("0x1216a72b7822Bbf7c38707F9a602FC241Cd6df30") 10 | : await ethers.getSigner() 11 | 12 | entityURI = await (await ethers.getContractFactory("OtoCoURI", deployer)).deploy(taskArgs.master, taskArgs.networkPrefix); 13 | await entityURI.deployTransaction.wait(1); 14 | const uri = await entityURI.deployed(); 15 | const master = (await ethers.getContractFactory("OtoCoMasterV2")).attach(taskArgs.master); 16 | const change = await master.connect(deployer).changeURISources(uri.address); 17 | await change.wait(1); 18 | 19 | return { uri, master }; 20 | }); 21 | 22 | -------------------------------------------------------------------------------- /tasks/utils/accounts.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config"); 2 | 3 | task("accounts", `Prints the list of accounts`, async (undefined, hre) => { 4 | const accounts = await hre.ethers.getSigners(); 5 | 6 | for (const account of accounts) { 7 | console.log(await account.getAddress()); 8 | } 9 | }); -------------------------------------------------------------------------------- /tasks/utils/gas.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config"); 2 | const axios = require('axios'); 3 | require('dotenv').config(); 4 | 5 | const apiKeys = [process.env.ETHERSCAN_API_KEY, process.env.POLYGONSCAN_API_KEY]; 6 | 7 | task("gas", "Estimate gas price data from block explorer apis") 8 | .addFlag("mainnet", "Prints Ethereum Mainnet gas information") 9 | .addFlag("polygon", "Prints Polygon Mainnet gas information") 10 | 11 | .setAction(async ({mainnet, polygon}, hre) => { 12 | 13 | const formatGasPrice = (gasPrice) => `${gasPrice} GWei`; 14 | 15 | if(mainnet) { 16 | const baseUrl = `https://api.etherscan.io/api?module=gastracker&action=`; 17 | 18 | const ethGasQuery = 19 | `${baseUrl}gasoracle&apikey=${apiKeys[0]}`; 20 | 21 | const 22 | { 23 | SafeGasPrice, 24 | ProposeGasPrice, 25 | FastGasPrice, 26 | LastBlock, 27 | suggestBaseFee, 28 | gasUsedRatio, 29 | } = (await axios.get(ethGasQuery)).data.result; 30 | 31 | const gasPriceWei 32 | = [ 33 | SafeGasPrice, 34 | ProposeGasPrice, 35 | FastGasPrice 36 | ].map( 37 | price => 38 | hre.ethers.utils.parseUnits( 39 | parseInt(price).toString(), 40 | "gwei" 41 | ).toString(), 42 | ); 43 | 44 | const gasPriceTimeQuery = 45 | gasPriceWei.map(price => 46 | `${baseUrl}gasestimate&gasprice=${price}&apikey=${apiKeys[0]}`); 47 | 48 | const [ 49 | safeEthTimeInfo, 50 | proposeEthTimeInfo, 51 | fastEthTimeInfo 52 | ] = ( 53 | await axios.all( 54 | gasPriceTimeQuery 55 | .map(url => axios.get(url)))) 56 | .map(response => response.data.result, 57 | ); 58 | 59 | const responseTable = { 60 | "Last Block Mined": LastBlock, 61 | "Safe Gas Price": formatGasPrice(SafeGasPrice) 62 | + ` (Estimated Time: ${safeEthTimeInfo}s)`, 63 | "Propose Gas Price": formatGasPrice(ProposeGasPrice) 64 | + ` (Estimated Time: ${proposeEthTimeInfo}s)`, 65 | "Fast Gas Price": formatGasPrice(FastGasPrice) 66 | + ` (Estimated Time: ${fastEthTimeInfo}s)`, 67 | "Next Block's Base Fee": formatGasPrice(suggestBaseFee), 68 | "Gas Usage ratio (Last 5 Blocks)": gasUsedRatio, 69 | }; 70 | 71 | console.table(responseTable); 72 | } 73 | 74 | if(polygon) { 75 | 76 | const baseUrl = `https://api.polygonscan.com/api?module=gastracker&action=`; 77 | 78 | const polygonGasQuery = 79 | `${baseUrl}gasoracle&apikey=${apiKeys[1]}`; 80 | 81 | const 82 | { 83 | LastBlock, 84 | SafeGasPrice, 85 | ProposeGasPrice, 86 | FastGasPrice, 87 | suggestBaseFee, 88 | gasUsedRatio, 89 | } = (await axios.get(polygonGasQuery)).data.result; 90 | 91 | 92 | const responseTable = { 93 | "Last Block Mined": LastBlock, 94 | "Safe Gas Price": formatGasPrice(SafeGasPrice), 95 | "Propose Gas Price": formatGasPrice(ProposeGasPrice), 96 | "Fast Gas Price": formatGasPrice(FastGasPrice), 97 | "Next Block's Base Fee": formatGasPrice(suggestBaseFee), 98 | "Gas Usage ratio (Last 5 Blocks)": gasUsedRatio, 99 | }; 100 | 101 | console.table(responseTable); 102 | } 103 | 104 | }); -------------------------------------------------------------------------------- /tasks/utils/verification.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config"); 2 | 3 | const br = "\x1b[1m"; 4 | const gr = "\x1b[32m"; 5 | const red = "\x1b[31m"; 6 | const r = "\x1b[0m"; 7 | 8 | const defaultMaxTries = "2"; 9 | const defaultDelayTime = "10000"; 10 | const defaultArgs = "[]"; 11 | 12 | 13 | async function delay(ms) { 14 | return new Promise((resolve) => setTimeout(resolve, ms)); 15 | } 16 | 17 | task( 18 | "verification", 19 | "Handles verification of deployed contracts") 20 | 21 | .addParam( 22 | "addr", 23 | "The address of a deployed contract instance") 24 | 25 | .addOptionalParam( 26 | "args", 27 | "The constructor arguments used in deployment", 28 | defaultArgs 29 | ).addOptionalParam( 30 | "tries", 31 | "The number of verification tries", 32 | defaultMaxTries 33 | ).addOptionalParam( 34 | "delay", 35 | "The wait time before verification", 36 | defaultDelayTime 37 | ) 38 | 39 | 40 | .setAction(async (taskArgs, hre) => { 41 | let count = 0; 42 | do { 43 | await delay(Number(taskArgs.delay)); 44 | try { 45 | console.log( 46 | `${br}Verifying contract at address` + 47 | `${taskArgs.addr}${r}` 48 | ); 49 | await hre.run('verify:verify', { 50 | address: taskArgs.addr, 51 | constructorArguments: JSON.parse(taskArgs.args) 52 | }); 53 | console.log( 54 | `${br}${gr}Contract at address` + 55 | `${taskArgs.addr} has already been verified${r}`); 56 | break; 57 | } catch (error) { 58 | if ( 59 | String(error).includes('Already Verified') || 60 | String(error).includes('ProxyAdmin') 61 | ) { 62 | console.log( 63 | `${br}${gr}Contract at address ` + 64 | `${taskArgs.addr} has already been verified${r}`); 65 | break; 66 | }; 67 | console.log( 68 | `${br}Retrying verification of contract at address` + 69 | `${taskArgs.addr} - attempt #${++count}, error: ${red}${error}${r}` 70 | ); 71 | if (count === Number(taskArgs.tries)) 72 | console.log( 73 | `${br}${red}Failed to verify contract at address` + 74 | `${taskArgs.addr} after ${count} attempts, error: ${error}${r}`); 75 | } 76 | } while (count < Number(taskArgs.tries)); 77 | }); -------------------------------------------------------------------------------- /tasks/verifier.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config"); 2 | 3 | task("verifier", "Deploys the Badge Verifier for OtoCo Entities") 4 | .addParam("master", "The current instance of OtoCoMasterV2") 5 | .setAction(async (taskArgs) => { 6 | 7 | const deployer = hre.network.config.chainId == 31337 && process.env.FORK_ENABLED == "true" ? 8 | await ethers.getImpersonatedSigner("0x1216a72b7822Bbf7c38707F9a602FC241Cd6df30") 9 | : await ethers.getSigner() 10 | 11 | const badgeVerifier = await (await ethers.getContractFactory("BadgeVerifier", deployer)).deploy(taskArgs.master); 12 | await badgeVerifier.deployTransaction.wait(1); 13 | const verifier = await badgeVerifier.deployed(); 14 | 15 | return { verifier }; 16 | }); 17 | 18 | -------------------------------------------------------------------------------- /test/01-otoco-jurisdiction.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const { ethers } = require("hardhat"); 3 | 4 | describe("OtoCo Jurisdiction Test", function () { 5 | let jurisdictions; 6 | 7 | it("Create Jurisdictions and test values", async function () { 8 | 9 | const Unincorporated = await ethers.getContractFactory("JurisdictionUnincorporated"); 10 | const Delaware = await ethers.getContractFactory("JurisdictionDelaware"); 11 | const Wyoming = await ethers.getContractFactory("JurisdictionWyoming"); 12 | 13 | const unincorporated = await Unincorporated.deploy('DAO', 'defaultBadgeURL', 'goldBadgeURL'); 14 | const delaware = await Delaware.deploy('DELAWARE', 'defaultBadgeURLDE', 'goldBadgeURLDE'); 15 | const wyoming = await Wyoming.deploy('WYOMING', 'defaultBadgeURLWY', 'goldBadgeURLWY'); 16 | 17 | // Verify values at deployment 18 | expect(await unincorporated.getJurisdictionName()).to.equal('DAO'); 19 | expect(await unincorporated.getJurisdictionBadge()).to.equal('defaultBadgeURL'); 20 | expect(await unincorporated.getJurisdictionGoldBadge()).to.equal('goldBadgeURL'); 21 | expect(await delaware.getJurisdictionName()).to.equal('DELAWARE'); 22 | expect(await delaware.getJurisdictionBadge()).to.equal('defaultBadgeURLDE'); 23 | expect(await delaware.getJurisdictionGoldBadge()).to.equal('goldBadgeURLDE'); 24 | expect(await wyoming.getJurisdictionName()).to.equal('WYOMING'); 25 | expect(await wyoming.getJurisdictionBadge()).to.equal('defaultBadgeURLWY'); 26 | expect(await wyoming.getJurisdictionGoldBadge()).to.equal('goldBadgeURLWY'); 27 | 28 | // Verify name formatting 29 | expect(await unincorporated.getSeriesNameFormatted(2, 'test')).to.equal('test'); 30 | expect(await delaware.getSeriesNameFormatted(2, 'test')).to.equal('test LLC'); 31 | expect(await wyoming.getSeriesNameFormatted(0, 'test')).to.equal('test - Series 1'); 32 | expect(await wyoming.getSeriesNameFormatted(1, 'test')).to.equal('test - Series 2'); 33 | expect(await wyoming.getSeriesNameFormatted(2, 'test')).to.equal('test - Series 3'); 34 | 35 | jurisdictions = [unincorporated.address, delaware.address, wyoming.address]; 36 | }); 37 | }); -------------------------------------------------------------------------------- /test/04-otoco-timestamp.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const { ethers, upgrades } = require("hardhat"); 3 | 4 | describe("OtoCo Timestamp Plugins Test", function () { 5 | 6 | let owner, wallet2, wallet3, wallet4; 7 | let OtoCoMaster; 8 | let otocoMaster; 9 | let jurisdictions; 10 | 11 | it("Create Jurisdictions", async function () { 12 | 13 | [owner, wallet2, wallet3, wallet4] = await ethers.getSigners(); 14 | 15 | const Unincorporated = await ethers.getContractFactory("JurisdictionUnincorporated"); 16 | const Delaware = await ethers.getContractFactory("JurisdictionDelaware"); 17 | const Wyoming = await ethers.getContractFactory("JurisdictionWyoming"); 18 | 19 | const unincorporated = await Unincorporated.deploy('DAO', 'defaultBadgeURL', 'goldBadgeURL'); 20 | const delaware = await Delaware.deploy('DELAWARE', 'defaultBadgeURLDE', 'goldBadgeURLDE'); 21 | const wyoming = await Wyoming.deploy('WYOMING', 'defaultBadgeURLWY', 'goldBadgeURLWY'); 22 | 23 | jurisdictions = [unincorporated.address, delaware.address, wyoming.address]; 24 | }); 25 | 26 | it("Initialize Master, add jurisdictions and create Series", async function () { 27 | const [owner, wallet2, wallet3, wallet4] = await ethers.getSigners(); 28 | 29 | OtoCoMaster = await ethers.getContractFactory("OtoCoMaster"); 30 | otocoMaster = await upgrades.deployProxy(OtoCoMaster, [jurisdictions, 'https://otoco.io/dashpanel/entity/']); 31 | await otocoMaster.deployed(); 32 | 33 | const gasPrice = ethers.BigNumber.from("2000000000"); 34 | const gasLimit = ethers.BigNumber.from("200000"); 35 | const otocoBaseFee = await otocoMaster.baseFee(); 36 | 37 | const amountToPayForSpinUp = ethers.BigNumber.from(gasPrice).mul(gasLimit).div(otocoBaseFee); 38 | 39 | // Expected to successfully create a new entity 40 | await otocoMaster.connect(wallet2).createSeries(2, wallet2.address, "New Entity", {gasPrice, gasLimit, value:amountToPayForSpinUp}); 41 | // Expect to create another entity 42 | await otocoMaster.connect(wallet3).createSeries(1, wallet3.address, "Another Entity", {gasPrice, gasLimit, value:amountToPayForSpinUp}); 43 | }); 44 | 45 | it("Deploy Timestamp plugin", async function () { 46 | const [owner, wallet2] = await ethers.getSigners(); 47 | 48 | const gasPrice = ethers.BigNumber.from("2000000000"); 49 | const gasLimit = ethers.BigNumber.from("100000"); 50 | const otocoBaseFee = await otocoMaster.baseFee(); 51 | 52 | const amountToPay = ethers.BigNumber.from(gasPrice).mul(gasLimit).div(otocoBaseFee); 53 | 54 | const TimestampPluginFactory = await ethers.getContractFactory("Timestamp"); 55 | const timestampPlugin = await TimestampPluginFactory.deploy(otocoMaster.address); 56 | 57 | let encoded = ethers.utils.defaultAbiCoder.encode( 58 | ['string', 'string'], 59 | ['filename-test.pdf', '11223345566677778889aasbbvcccc'] 60 | ); 61 | const prevBalance = await ethers.provider.getBalance(otocoMaster.address); 62 | let transaction = await timestampPlugin.connect(wallet2).addPlugin(0, encoded, {gasPrice, gasLimit, value:amountToPay}); 63 | await expect(transaction).to.emit(timestampPlugin, 'DocumentTimestamped'); 64 | expect(await ethers.provider.getBalance(otocoMaster.address)).to.be.equals(prevBalance.add(amountToPay)); 65 | 66 | const events = (await transaction.wait()).events; 67 | expect(events[0].args.filename).to.be.equals('filename-test.pdf'); 68 | expect(events[0].args.cid).to.be.equals('11223345566677778889aasbbvcccc'); 69 | 70 | await expect(timestampPlugin.connect(wallet2).attachPlugin(0, encoded, {gasPrice, gasLimit, value:amountToPay})) 71 | .to.be.revertedWith('OtoCoPlugin: Attach elements are not possible on this plugin.'); 72 | 73 | await expect(timestampPlugin.connect(wallet2).removePlugin(0, encoded, {gasPrice, gasLimit, value:amountToPay})) 74 | .to.be.revertedWith('OtoCoPlugin: Remove elements are not possible on this plugin.'); 75 | 76 | // There's no Attach function at Launchpool plugin 77 | await expect(timestampPlugin.connect(wallet3).addPlugin(0, encoded, {gasPrice, gasLimit, value:amountToPay})) 78 | .to.be.revertedWith('OtoCoPlugin: Not the entity owner.'); 79 | 80 | // There's no Attach function at Launchpool plugin 81 | await expect(timestampPlugin.connect(wallet2).addPlugin(0, encoded, {gasPrice, gasLimit, value:0})) 82 | .to.be.revertedWith('OtoCoMaster: Not enough ETH paid for the execution.'); 83 | 84 | // There's no Attach function at Launchpool plugin 85 | await expect(timestampPlugin.connect(wallet2).migrateTimestamp(0, encoded)) 86 | .to.be.revertedWith('Ownable: caller is not the owner'); 87 | 88 | encoded = ethers.utils.defaultAbiCoder.encode( 89 | ['string', 'string', 'uint256'], 90 | ['filename-test.pdf', '11223345566677778889aasbbvcccc', 20000] 91 | ); 92 | 93 | transaction = await timestampPlugin.migrateTimestamp(0, encoded); 94 | await expect(transaction).to.emit(timestampPlugin, 'DocumentTimestamped'); 95 | 96 | const events2 = (await transaction.wait()).events; 97 | expect(events2[0].args.filename).to.be.equals('filename-test.pdf'); 98 | expect(events2[0].args.cid).to.be.equals('11223345566677778889aasbbvcccc'); 99 | expect(events2[0].args.timestamp).to.be.equals(20000); 100 | 101 | }); 102 | 103 | }); -------------------------------------------------------------------------------- /test/05-otoco-token-nofee.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const { ethers, upgrades } = require("hardhat"); 3 | 4 | 5 | describe("OtoCo Token Without Fees Plugin Test", function () { 6 | 7 | let owner, wallet2, wallet3, wallet4; 8 | let OtoCoMaster; 9 | let otocoMaster; 10 | let jurisdictions; 11 | let tokenPlugin; 12 | let tokenAddress; 13 | let TokenFactory; 14 | 15 | const zeroAddress = ethers.constants.AddressZero; 16 | 17 | it("Create Jurisdictions", async function () { 18 | 19 | [owner, wallet2, wallet3, wallet4] = await ethers.getSigners(); 20 | 21 | const Unincorporated = await ethers.getContractFactory("JurisdictionUnincorporated"); 22 | const Delaware = await ethers.getContractFactory("JurisdictionDelaware"); 23 | const Wyoming = await ethers.getContractFactory("JurisdictionWyoming"); 24 | 25 | const unincorporated = await Unincorporated.deploy('DAO', 'defaultBadgeURL', 'goldBadgeURL'); 26 | const delaware = await Delaware.deploy('DELAWARE', 'defaultBadgeURLDE', 'goldBadgeURLDE'); 27 | const wyoming = await Wyoming.deploy('WYOMING', 'defaultBadgeURLWY', 'goldBadgeURLWY'); 28 | 29 | jurisdictions = [unincorporated.address, delaware.address, wyoming.address]; 30 | }); 31 | 32 | it("Initialize Master, add jurisdictions and create Series", async function () { 33 | const [owner, wallet2, wallet3, wallet4] = await ethers.getSigners(); 34 | 35 | OtoCoMaster = await ethers.getContractFactory("OtoCoMaster"); 36 | otocoMaster = await upgrades.deployProxy(OtoCoMaster, [jurisdictions, 'https://otoco.io/dashpanel/entity/']); 37 | await otocoMaster.deployed(); 38 | 39 | await otocoMaster.changeBaseFees(0); 40 | 41 | // Expected to successfully create a new entity 42 | await otocoMaster.connect(wallet2).createSeries(2, wallet2.address, "New Entity"); 43 | // Expect to create another entity 44 | await otocoMaster.connect(wallet3).createSeries(1, wallet3.address, "Another Entity"); 45 | }); 46 | 47 | it("Deploy and remove Token plugin", async function () { 48 | const [owner, wallet2, wallet3, wallet4] = await ethers.getSigners(); 49 | 50 | TokenFactory = await ethers.getContractFactory("OtoCoToken"); 51 | const token = await TokenFactory.deploy(); 52 | expect(await token.name()).to.be.equal(""); 53 | expect(await token.symbol()).to.be.equal(""); 54 | 55 | 56 | expect(await token.name()).to.be.equal(""); 57 | expect(await token.symbol()).to.be.equal(""); 58 | 59 | const TokenPluginFactory = await ethers.getContractFactory("Token"); 60 | tokenPlugin = await TokenPluginFactory.deploy( 61 | otocoMaster.address, 62 | token.address, 63 | [1], 64 | [token.address] 65 | ); 66 | 67 | let encoded = ethers.utils.defaultAbiCoder.encode( 68 | ['uint256', 'string', 'string', 'address'], 69 | [ethers.utils.parseEther('8000000'), 'Test Token', 'TTOK', wallet2.address] 70 | ); 71 | let transaction = await tokenPlugin.connect(wallet2).addPlugin(0, encoded); 72 | await expect(transaction).to.emit(tokenPlugin, 'TokenAdded'); 73 | 74 | // There's no Attach function at Launchpool plugin 75 | await expect(tokenPlugin.connect(wallet3).addPlugin(0, encoded)) 76 | .to.be.revertedWith('OtoCoPlugin: Not the entity owner.'); 77 | 78 | //console.log((await transaction.wait()).events) 79 | tokenAddress = (await transaction.wait()).events[2].args.token; 80 | const tokenDeployed = TokenFactory.attach(tokenAddress); 81 | 82 | expect(await tokenDeployed.name()).to.be.equal("Test Token"); 83 | expect(await tokenDeployed.symbol()).to.be.equal("TTOK"); 84 | expect(await tokenDeployed.totalSupply()).to.be.equal(ethers.utils.parseEther('8000000')); 85 | expect(await tokenPlugin.tokensPerEntity(0)).to.be.equals(1); 86 | expect(await tokenPlugin.tokensDeployed(0,0)).to.be.equals(tokenAddress); 87 | 88 | await expect(tokenDeployed.initialize('', '', "100", zeroAddress)) 89 | .to.be.revertedWith('Initializable: contract is already initialized'); 90 | 91 | encoded = ethers.utils.defaultAbiCoder.encode(['uint256'],[0]); 92 | transaction = await tokenPlugin.connect(wallet2).removePlugin(0, encoded); 93 | await expect(transaction).to.emit(tokenPlugin, 'TokenRemoved').withArgs(0, tokenAddress); 94 | 95 | encoded = ethers.utils.defaultAbiCoder.encode(['address'],[tokenAddress]); 96 | transaction = await tokenPlugin.connect(wallet2).attachPlugin(0, encoded); 97 | await expect(transaction).to.emit(tokenPlugin, 'TokenAdded'); 98 | 99 | // There's no Attach function at Launchpool plugin 100 | await expect(tokenPlugin.connect(wallet3).attachPlugin(0, encoded)) 101 | .to.be.revertedWith('OtoCoPlugin: Not the entity owner.'); 102 | 103 | // There's no Attach function at Launchpool plugin 104 | await expect(tokenPlugin.connect(wallet3).removePlugin(0, encoded)) 105 | .to.be.revertedWith('OtoCoPlugin: Not the entity owner.'); 106 | 107 | await expect(tokenPlugin.connect(wallet2).updateTokenContract(zeroAddress)) 108 | .to.be.revertedWith('Ownable: caller is not the owner'); 109 | 110 | await tokenPlugin.updateTokenContract(zeroAddress) 111 | expect(await tokenPlugin.tokenContract()).to.be.equal(zeroAddress); 112 | }); 113 | 114 | }); -------------------------------------------------------------------------------- /test/09-migration.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const { ethers, upgrades } = require("hardhat"); 3 | const fs = require('fs').promises; 4 | 5 | 6 | describe("Test Entities migration", function () { 7 | 8 | let owner, wallet2, wallet3, wallet4; 9 | let OtoCoMaster; 10 | let otocoMaster; 11 | let jurisdictions = []; 12 | let controllers = []; 13 | let creations = []; 14 | let names = []; 15 | 16 | it("Create Jurisdictions", async function () { 17 | 18 | [owner, wallet2, wallet3, wallet4] = await ethers.getSigners(); 19 | 20 | const Unincorporated = await ethers.getContractFactory("JurisdictionUnincorporated"); 21 | const Delaware = await ethers.getContractFactory("JurisdictionDelaware"); 22 | const Wyoming = await ethers.getContractFactory("JurisdictionWyoming"); 23 | 24 | const unincorporated = await Unincorporated.deploy('DAO', 'defaultBadgeURL', 'goldBadgeURL'); 25 | const delaware = await Delaware.deploy('DELAWARE', 'defaultBadgeURLDE', 'goldBadgeURLDE'); 26 | const wyoming = await Wyoming.deploy('WYOMING', 'defaultBadgeURLWY', 'goldBadgeURLWY'); 27 | 28 | jurisdictions = [unincorporated.address, delaware.address, wyoming.address]; 29 | }); 30 | 31 | it("Initialize Master and add jurisdictions", async function () { 32 | const [owner, wallet2, wallet3, wallet4] = await ethers.getSigners(); 33 | 34 | OtoCoMaster = await ethers.getContractFactory("OtoCoMaster"); 35 | otocoMaster = await upgrades.deployProxy(OtoCoMaster, [jurisdictions, 'https://otoco.io/dashpanel/entity/']); 36 | await otocoMaster.deployed(); 37 | 38 | expect(await otocoMaster.name()).to.equal("OtoCo Series"); 39 | expect(await otocoMaster.symbol()).to.equal("OTOCO"); 40 | expect(await otocoMaster.owner()).to.equal(owner.address); 41 | 42 | // To be re-used after 43 | jurisdictions = []; 44 | }); 45 | 46 | it("Check jurisdiction order and count", async function () { 47 | 48 | const unincorporated = await ethers.getContractAt("OtoCoJurisdiction", await otocoMaster.jurisdictionAddress(0)); 49 | const delaware = await ethers.getContractAt("OtoCoJurisdiction", await otocoMaster.jurisdictionAddress(1)); 50 | const wyoming = await ethers.getContractAt("OtoCoJurisdiction", await otocoMaster.jurisdictionAddress(2)); 51 | 52 | expect(await otocoMaster.jurisdictionCount()).to.equal(3); 53 | 54 | expect(await unincorporated.getJurisdictionName()).to.equal("DAO"); 55 | expect(await delaware.getJurisdictionName()).to.equal("DELAWARE"); 56 | expect(await wyoming.getJurisdictionName()).to.equal("WYOMING"); 57 | 58 | }); 59 | 60 | it("Test migration of previous entities", async function () { 61 | 62 | const jurisdictionDict = { 63 | "DAO": 0, 64 | "DELAWARE": 1, 65 | "WYOMING": 2 66 | } 67 | let jsonDoc; 68 | try { 69 | const data = await fs.readFile("./test/companies.json", "binary"); 70 | jsonDoc = JSON.parse(data); 71 | } catch (err) { 72 | console.log("No companies to test migration.", err); 73 | } 74 | 75 | if (jsonDoc?.data?.companies){ 76 | jsonDoc.data.companies.map( (s) => { 77 | jurisdictions.push(jurisdictionDict[s.jurisdiction]); 78 | names.push(s.name); 79 | creations.push(s.creation) 80 | controllers.push(s.owner); 81 | }) 82 | } 83 | 84 | const slices = 100; 85 | for(let i=0; i { 11 | describe('tarantulaScore', () => { 12 | it('should score this input correctly', () => { 13 | 14 | var testData = { 15 | testResults: t.fromMocha(mochaOutput), 16 | coverage: t.fromSolCover(testMatrix), 17 | }; 18 | 19 | score = t.tarantulaScore(testData); 20 | let output = JSON.stringify(score, undefined, 2); 21 | 22 | // for (var filename in score) { 23 | // console.log(score[filename]); 24 | // } 25 | fs.writeFileSync(`./test/tarantula/output.json`, output, 'utf8'); 26 | 27 | try { 28 | const count_0 = ( 29 | fs 30 | .readFileSync(`./test/tarantula/output.json`, 'utf8') 31 | .match(/^\s*"suspiciousness": 0$/gm) || [] 32 | ).length; 33 | 34 | const count_1 = ( 35 | fs 36 | .readFileSync(`./test/tarantula/output.json`, 'utf8') 37 | .match(/^\s*"suspiciousness": 1$/gm) || [] 38 | ).length; 39 | 40 | const jsonResult = JSON.stringify([ 41 | { 42 | type: 'suspiciousness_0', 43 | count: count_0, 44 | }, 45 | { 46 | type: 'suspiciousness_1', 47 | count: count_1, 48 | }, 49 | ], undefined, 2); 50 | 51 | fs.writeFileSync('./test/tarantula/suspiciousness.json', jsonResult, 'utf-8'); 52 | 53 | console.log( 54 | '\t','\x1b[32mSuspiciousness 0:\x1b[0m', 55 | (JSON.parse(jsonResult)).map(item => item.count)[0], 56 | '|', 57 | '\x1b[31mSuspiciousness 1:\x1b[0m', 58 | (JSON.parse(jsonResult)).map(item => item.count)[1], 59 | ); 60 | 61 | } catch (error) { 62 | console.error(`Error: ${error.message}`); 63 | } 64 | 65 | }); 66 | }); 67 | }); -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | const { Artifacts } = require("hardhat/internal/artifacts"); 2 | const { ethers } = require("hardhat"); 3 | 4 | async function getExternalArtifact(contract) { 5 | const artifactsPath = "./artifacts-external"; 6 | const artifacts = new Artifacts(artifactsPath); 7 | return artifacts.readArtifact(contract); 8 | } 9 | 10 | async function getAmountToPay( 11 | jurisdiction, 12 | master, 13 | gasPrice, 14 | gasLimit, 15 | priceFeed, 16 | ) { 17 | const Factory = await ethers.getContractFactory([ 18 | "JurisdictionUnincorporatedV2", 19 | "JurisdictionDelawareV2", 20 | "JurisdictionWyomingV2", 21 | ][jurisdiction]); 22 | const EthDividend = 23 | ethers.BigNumber.from( 24 | ethers.utils.parseUnits('1', 18)) 25 | .mul(ethers.utils.parseUnits('1', 9)) 26 | .div(10); 27 | const jurisdictionContract = 28 | await Factory.attach(await master.jurisdictionAddress(jurisdiction)); 29 | const baseFee = await master.callStatic.baseFee(); 30 | const { answer } = await priceFeed.callStatic.latestRoundData(); 31 | const amountToPayForSpinUp = EthDividend.div(answer) 32 | .mul(await jurisdictionContract.callStatic.getJurisdictionDeployPrice()) 33 | .add(baseFee.mul(gasLimit)); 34 | return [ 35 | amountToPayForSpinUp, 36 | ethers.BigNumber.from(gasPrice), 37 | ethers.BigNumber.from(gasLimit), 38 | baseFee, 39 | ]; 40 | } 41 | 42 | exports.getExternalArtifact = getExternalArtifact; 43 | exports.getAmountToPay = getAmountToPay; --------------------------------------------------------------------------------