├── .env.template ├── .gitattributes ├── .github └── workflows │ ├── lint.yml │ └── tests.yml ├── .gitignore ├── .npmignore ├── .openzeppelin ├── kovan.json ├── mainnet.json ├── unknown-137.json ├── unknown-4689.json ├── unknown-4690.json └── unknown-56.json ├── .prettierignore ├── .prettierrc ├── .solcover.js ├── .solhint.json ├── .solhintignore ├── README.md ├── contracts ├── LPToken.sol ├── MathUtils.sol ├── MockSafe.sol ├── MockToken.sol ├── MockTokenNFT.sol ├── OwnerPausable.sol ├── Swap.sol ├── SwapUtils.sol ├── Timelock.sol ├── WIOTX.sol ├── interfaces │ ├── IAllowlist.sol │ └── ISwap.sol ├── v0.1 │ ├── AssetRegistry.sol │ ├── AssetUpperBound.sol │ ├── CrosschainCoinRouter.sol │ ├── CrosschainERC20.sol │ ├── CrosschainERC20Factory.sol │ ├── CrosschainERC721.sol │ ├── Ledger.sol │ ├── Lord.sol │ ├── Owned.sol │ ├── TestimonyDAO.sol │ ├── Tube.sol │ ├── TubeRouter.sol │ ├── TubeToken.sol │ ├── ValidatorRegistry.sol │ └── Verifier.sol └── v0.2 │ ├── AssetRegistryV2.sol │ ├── CrosschainERC20FactoryV2.sol │ ├── CrosschainERC20V2.sol │ ├── CrosschainERC20V2Pair.sol │ ├── ERC20Tube.sol │ ├── ERC20TubeRouter.sol │ ├── EmergencyOperator.sol │ ├── LedgerV2.sol │ ├── LordV2.sol │ ├── MinterDAO.sol │ ├── OwnedUpgradeable.sol │ └── VerifierV2.sol ├── deployments ├── bsc.json ├── iotex.json ├── iotex.registry.json ├── iotex_test.json ├── iotex_test.registry.json ├── kovan.json ├── mainnet.json └── polygon.json ├── hardhat.config.ts ├── package.json ├── scripts ├── 000-deploy-base.ts ├── 001-deploy-registrys.ts ├── 002-upgrade-lord.ts ├── ops │ ├── add-assets.ts │ ├── config-add-destination-tube.ts │ ├── config-router-fee.ts │ ├── create-crosschain-token-pair.ts │ ├── create-crosschain-token.ts │ ├── credit-pair-increase.ts │ ├── credit-pair-reduce.ts │ ├── deploy-timelock.ts │ ├── remove-crosschain-token-pair.ts │ ├── temp-deploy-factory.ts │ ├── temp-deploy-mock-token.ts │ ├── temp-deploy-registry.ts │ ├── temp-deploy-router.ts │ └── temp-deploy-tube.ts ├── utils │ └── signer.ts └── verify │ └── verify.ts ├── test ├── asset_registry.test.ts ├── crosschain_coin_router.test.ts ├── crosschain_erc20_factory_v2.test.ts ├── crosschain_erc20_pair.test.ts ├── ledger_v2.test.ts ├── lord.test.ts ├── lord_v2.test.ts ├── minter_dao.test.ts ├── tube.test.ts ├── validator_registry.test.ts └── verifier_v2.test.ts ├── tsconfig.json └── yarn.lock /.env.template: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY= 2 | MAINNET_RPC_URL= 3 | ALCHEMY_MAINNET_RPC_URL= 4 | FORKING_BLOCK_NUMBER= 5 | INFURA_PROJECT_ID= 6 | ETHERSCAN_API_KEY= 7 | REPORT_GAS= -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | run-linters: 11 | name: Run linters 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Check out Git repository 16 | uses: actions/checkout@v2 17 | 18 | - name: Set up node 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: 12 22 | 23 | - name: Install dependencies 24 | run: yarn install --frozen-lockfile 25 | 26 | - name: Run linters 27 | run: yarn lint -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | unit-tests: 11 | name: Unit Tests 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | - uses: actions/setup-node@v1 17 | with: 18 | node-version: 12.x 19 | 20 | - id: yarn-cache 21 | run: echo "::set-output name=dir::$(yarn cache dir)" 22 | 23 | - uses: actions/cache@v1 24 | with: 25 | path: ${{ steps.yarn-cache.outputs.dir }} 26 | key: yarn-${{ hashFiles('**/yarn.lock') }} 27 | restore-keys: | 28 | yarn- 29 | - name: Install dependencies 30 | run: yarn install --frozen-lockfile 31 | 32 | - name: Compile 33 | run: yarn compile 34 | 35 | - name: Run unit tests 36 | run: yarn test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | abi/ 2 | artifacts/ 3 | cache/ 4 | coverage/ 5 | exports/ 6 | node_modules/ 7 | types/ 8 | package-lock.json 9 | coverage.json 10 | .env 11 | .DS_Store 12 | /deployments/*/.chainId 13 | /deployments/*/solcInputs 14 | /.env 15 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | hardhat.config.js 2 | scripts 3 | test 4 | -------------------------------------------------------------------------------- /.openzeppelin/mainnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifestVersion": "3.2", 3 | "admin": { 4 | "address": "0xb355445c496307Cc208BD753aE80Ca0b7ABA6Bb7", 5 | "txHash": "0x37ccbbed4f150f266d5e5e5396918aa199d93dbcacea8421fc0893847dd8caa9" 6 | }, 7 | "proxies": [ 8 | { 9 | "address": "0x02554E65A1E4200c0df23C3a449a416803a69A53", 10 | "txHash": "0x5d59bc968a262d2f04f219cd433af00aea438160af695c2f03f3821ed4d0780a", 11 | "kind": "transparent" 12 | }, 13 | { 14 | "address": "0xbeACd46feE2977b55849e4608927752fCcaf490B", 15 | "txHash": "0xe0318446b2e78cc187850f0ac10e080624f7f7d8d78106e68d415d8d993b1d30", 16 | "kind": "transparent" 17 | }, 18 | { 19 | "address": "0x9EF7C039D6bffA8b49594A126c71b620448423cc", 20 | "txHash": "0xde5bfba46629667dc17a11582cda21a3b3df8d8e94b0f0e0eec60a52b342e871", 21 | "kind": "transparent" 22 | } 23 | ], 24 | "impls": { 25 | "a1936387b98546cee8172ebc5e261f8f4f7c6ea3b146ad5568d74dfd1c578be4": { 26 | "address": "0x480a613061E7eE2a82cdc57cA03d5d3BF5bC71b3", 27 | "txHash": "0x7b2fdabcff230b5a8b55254c761706d027a2b251c9661a046a7af027bb3b4050", 28 | "layout": { 29 | "storage": [ 30 | { 31 | "label": "_initialized", 32 | "offset": 0, 33 | "slot": "0", 34 | "type": "t_bool", 35 | "contract": "Initializable", 36 | "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:39" 37 | }, 38 | { 39 | "label": "_initializing", 40 | "offset": 1, 41 | "slot": "0", 42 | "type": "t_bool", 43 | "contract": "Initializable", 44 | "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:44" 45 | }, 46 | { 47 | "label": "owner", 48 | "offset": 2, 49 | "slot": "0", 50 | "type": "t_address", 51 | "contract": "OwnedUpgradeable", 52 | "src": "contracts/v0.2/OwnedUpgradeable.sol:11" 53 | }, 54 | { 55 | "label": "candidateOwner", 56 | "offset": 0, 57 | "slot": "1", 58 | "type": "t_address", 59 | "contract": "OwnedUpgradeable", 60 | "src": "contracts/v0.2/OwnedUpgradeable.sol:12" 61 | }, 62 | { 63 | "label": "operators", 64 | "offset": 0, 65 | "slot": "2", 66 | "type": "t_mapping(t_address,t_bool)", 67 | "contract": "EmergencyOperator", 68 | "src": "contracts/v0.2/EmergencyOperator.sol:8" 69 | } 70 | ], 71 | "types": { 72 | "t_address": { 73 | "label": "address", 74 | "numberOfBytes": "20" 75 | }, 76 | "t_bool": { 77 | "label": "bool", 78 | "numberOfBytes": "1" 79 | }, 80 | "t_mapping(t_address,t_bool)": { 81 | "label": "mapping(address => bool)", 82 | "numberOfBytes": "32" 83 | } 84 | } 85 | } 86 | }, 87 | "59835cabc08d2573baf826fbd3335d9d7e79b938ce70e2842a07b372b1e1a326": { 88 | "address": "0xe43136F0b5989EE688695C489625158C43837DbA", 89 | "txHash": "0x9730b09cf02a08bb639233dc0b3480c3033cd9ce415325f2bde0c5cebea73c25", 90 | "layout": { 91 | "storage": [ 92 | { 93 | "label": "_initialized", 94 | "offset": 0, 95 | "slot": "0", 96 | "type": "t_bool", 97 | "contract": "Initializable", 98 | "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:39" 99 | }, 100 | { 101 | "label": "_initializing", 102 | "offset": 1, 103 | "slot": "0", 104 | "type": "t_bool", 105 | "contract": "Initializable", 106 | "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:44" 107 | }, 108 | { 109 | "label": "owner", 110 | "offset": 2, 111 | "slot": "0", 112 | "type": "t_address", 113 | "contract": "OwnedUpgradeable", 114 | "src": "contracts/v0.2/OwnedUpgradeable.sol:11" 115 | }, 116 | { 117 | "label": "candidateOwner", 118 | "offset": 0, 119 | "slot": "1", 120 | "type": "t_address", 121 | "contract": "OwnedUpgradeable", 122 | "src": "contracts/v0.2/OwnedUpgradeable.sol:12" 123 | }, 124 | { 125 | "label": "operators", 126 | "offset": 0, 127 | "slot": "2", 128 | "type": "t_mapping(t_address,t_bool)", 129 | "contract": "LordV2", 130 | "src": "contracts/v0.2/LordV2.sol:18" 131 | } 132 | ], 133 | "types": { 134 | "t_address": { 135 | "label": "address", 136 | "numberOfBytes": "20" 137 | }, 138 | "t_bool": { 139 | "label": "bool", 140 | "numberOfBytes": "1" 141 | }, 142 | "t_mapping(t_address,t_bool)": { 143 | "label": "mapping(address => bool)", 144 | "numberOfBytes": "32" 145 | } 146 | } 147 | } 148 | }, 149 | "7803bc6ed01837156efba3eddd5b9e90e2d5fbe04c99e73cc0f6a5fa5372ccc6": { 150 | "address": "0x17CC4ec03b3B59785d4c63454D1C5d97891De726", 151 | "txHash": "0x88eed6fc62b33323a36b963ede0989afe590afb9f8b36379415a0a02208bacec", 152 | "layout": { 153 | "storage": [ 154 | { 155 | "label": "_initialized", 156 | "offset": 0, 157 | "slot": "0", 158 | "type": "t_bool", 159 | "contract": "Initializable", 160 | "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:39" 161 | }, 162 | { 163 | "label": "_initializing", 164 | "offset": 1, 165 | "slot": "0", 166 | "type": "t_bool", 167 | "contract": "Initializable", 168 | "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:44" 169 | }, 170 | { 171 | "label": "__gap", 172 | "offset": 0, 173 | "slot": "1", 174 | "type": "t_array(t_uint256)50_storage", 175 | "contract": "ContextUpgradeable", 176 | "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" 177 | }, 178 | { 179 | "label": "_owner", 180 | "offset": 0, 181 | "slot": "51", 182 | "type": "t_address", 183 | "contract": "OwnableUpgradeable", 184 | "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" 185 | }, 186 | { 187 | "label": "__gap", 188 | "offset": 0, 189 | "slot": "52", 190 | "type": "t_array(t_uint256)49_storage", 191 | "contract": "OwnableUpgradeable", 192 | "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:87" 193 | }, 194 | { 195 | "label": "_paused", 196 | "offset": 0, 197 | "slot": "101", 198 | "type": "t_bool", 199 | "contract": "PausableUpgradeable", 200 | "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29" 201 | }, 202 | { 203 | "label": "__gap", 204 | "offset": 0, 205 | "slot": "102", 206 | "type": "t_array(t_uint256)49_storage", 207 | "contract": "PausableUpgradeable", 208 | "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:102" 209 | }, 210 | { 211 | "label": "emergencyOperator", 212 | "offset": 0, 213 | "slot": "151", 214 | "type": "t_contract(EmergencyOperator)16084", 215 | "contract": "MinterDAO", 216 | "src": "contracts/v0.2/MinterDAO.sol:14" 217 | }, 218 | { 219 | "label": "lord", 220 | "offset": 0, 221 | "slot": "152", 222 | "type": "t_address", 223 | "contract": "MinterDAO", 224 | "src": "contracts/v0.2/MinterDAO.sol:15" 225 | }, 226 | { 227 | "label": "minters", 228 | "offset": 0, 229 | "slot": "153", 230 | "type": "t_mapping(t_address,t_mapping(t_address,t_bool))", 231 | "contract": "MinterDAO", 232 | "src": "contracts/v0.2/MinterDAO.sol:16" 233 | } 234 | ], 235 | "types": { 236 | "t_address": { 237 | "label": "address", 238 | "numberOfBytes": "20" 239 | }, 240 | "t_array(t_uint256)49_storage": { 241 | "label": "uint256[49]", 242 | "numberOfBytes": "1568" 243 | }, 244 | "t_array(t_uint256)50_storage": { 245 | "label": "uint256[50]", 246 | "numberOfBytes": "1600" 247 | }, 248 | "t_bool": { 249 | "label": "bool", 250 | "numberOfBytes": "1" 251 | }, 252 | "t_contract(EmergencyOperator)16084": { 253 | "label": "contract EmergencyOperator", 254 | "numberOfBytes": "20" 255 | }, 256 | "t_mapping(t_address,t_bool)": { 257 | "label": "mapping(address => bool)", 258 | "numberOfBytes": "32" 259 | }, 260 | "t_mapping(t_address,t_mapping(t_address,t_bool))": { 261 | "label": "mapping(address => mapping(address => bool))", 262 | "numberOfBytes": "32" 263 | }, 264 | "t_uint256": { 265 | "label": "uint256", 266 | "numberOfBytes": "32" 267 | } 268 | } 269 | } 270 | } 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /.openzeppelin/unknown-137.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifestVersion": "3.2", 3 | "admin": { 4 | "address": "0x5E5a2d8804A45Bba9823Ce691d99bd7d43Cb823b", 5 | "txHash": "0xfedf65cec28babfb94ab09ffe3d55d390571123fbcd918bdf5729587f355ad45" 6 | }, 7 | "proxies": [ 8 | { 9 | "address": "0xA34Df71026b5979A4815DC5F6C8d82c39bb6c75B", 10 | "txHash": "0xe980d27e9411ebda2e4fd4bcea7ae29efc7ecc2fb831547224c7c8116e0a5a14", 11 | "kind": "transparent" 12 | }, 13 | { 14 | "address": "0xCE41af82dF30894a82Eef771743e75FA9B57cF58", 15 | "txHash": "0xbd5ef760b3bc96993dd039537a04bb9d4b49b17e234b7b51541bf2125375074d", 16 | "kind": "transparent" 17 | }, 18 | { 19 | "address": "0x63e509f7e8037DAe79332bB48239E4e700fE7A28", 20 | "txHash": "0xb638471ad16e22a10c983dd521f3973a61d2c7cd18030f823c689888d2923f5e", 21 | "kind": "transparent" 22 | } 23 | ], 24 | "impls": { 25 | "a1936387b98546cee8172ebc5e261f8f4f7c6ea3b146ad5568d74dfd1c578be4": { 26 | "address": "0xEB6B4A7Ebeb4D582AE05CFA7277758d5ae9105AE", 27 | "txHash": "0xad4cc77dc69a0572a7a4d4ef858896625a1746a1d9ed47a03d8b4c85a8ac0d23", 28 | "layout": { 29 | "storage": [ 30 | { 31 | "label": "_initialized", 32 | "offset": 0, 33 | "slot": "0", 34 | "type": "t_bool", 35 | "contract": "Initializable", 36 | "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:39" 37 | }, 38 | { 39 | "label": "_initializing", 40 | "offset": 1, 41 | "slot": "0", 42 | "type": "t_bool", 43 | "contract": "Initializable", 44 | "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:44" 45 | }, 46 | { 47 | "label": "owner", 48 | "offset": 2, 49 | "slot": "0", 50 | "type": "t_address", 51 | "contract": "OwnedUpgradeable", 52 | "src": "contracts/v0.2/OwnedUpgradeable.sol:11" 53 | }, 54 | { 55 | "label": "candidateOwner", 56 | "offset": 0, 57 | "slot": "1", 58 | "type": "t_address", 59 | "contract": "OwnedUpgradeable", 60 | "src": "contracts/v0.2/OwnedUpgradeable.sol:12" 61 | }, 62 | { 63 | "label": "operators", 64 | "offset": 0, 65 | "slot": "2", 66 | "type": "t_mapping(t_address,t_bool)", 67 | "contract": "EmergencyOperator", 68 | "src": "contracts/v0.2/EmergencyOperator.sol:8" 69 | } 70 | ], 71 | "types": { 72 | "t_address": { 73 | "label": "address", 74 | "numberOfBytes": "20" 75 | }, 76 | "t_bool": { 77 | "label": "bool", 78 | "numberOfBytes": "1" 79 | }, 80 | "t_mapping(t_address,t_bool)": { 81 | "label": "mapping(address => bool)", 82 | "numberOfBytes": "32" 83 | } 84 | } 85 | } 86 | }, 87 | "59835cabc08d2573baf826fbd3335d9d7e79b938ce70e2842a07b372b1e1a326": { 88 | "address": "0x905A0c42B2D6B5ee3595833ef4BE13710574D0cB", 89 | "txHash": "0xf280ad933014607fead5d258b1d38f3b14dc765c80c0120058f0feaca4d563f1", 90 | "layout": { 91 | "storage": [ 92 | { 93 | "label": "_initialized", 94 | "offset": 0, 95 | "slot": "0", 96 | "type": "t_bool", 97 | "contract": "Initializable", 98 | "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:39" 99 | }, 100 | { 101 | "label": "_initializing", 102 | "offset": 1, 103 | "slot": "0", 104 | "type": "t_bool", 105 | "contract": "Initializable", 106 | "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:44" 107 | }, 108 | { 109 | "label": "owner", 110 | "offset": 2, 111 | "slot": "0", 112 | "type": "t_address", 113 | "contract": "OwnedUpgradeable", 114 | "src": "contracts/v0.2/OwnedUpgradeable.sol:11" 115 | }, 116 | { 117 | "label": "candidateOwner", 118 | "offset": 0, 119 | "slot": "1", 120 | "type": "t_address", 121 | "contract": "OwnedUpgradeable", 122 | "src": "contracts/v0.2/OwnedUpgradeable.sol:12" 123 | }, 124 | { 125 | "label": "operators", 126 | "offset": 0, 127 | "slot": "2", 128 | "type": "t_mapping(t_address,t_bool)", 129 | "contract": "LordV2", 130 | "src": "contracts/v0.2/LordV2.sol:18" 131 | } 132 | ], 133 | "types": { 134 | "t_address": { 135 | "label": "address", 136 | "numberOfBytes": "20" 137 | }, 138 | "t_bool": { 139 | "label": "bool", 140 | "numberOfBytes": "1" 141 | }, 142 | "t_mapping(t_address,t_bool)": { 143 | "label": "mapping(address => bool)", 144 | "numberOfBytes": "32" 145 | } 146 | } 147 | } 148 | }, 149 | "7803bc6ed01837156efba3eddd5b9e90e2d5fbe04c99e73cc0f6a5fa5372ccc6": { 150 | "address": "0xeDD9CdFe8B015e799bcE486C12Db37588786C94D", 151 | "txHash": "0x21afec20d1948a8c5182412c779d81d29d1576d0c872b05d03d0e5bdbf910949", 152 | "layout": { 153 | "storage": [ 154 | { 155 | "label": "_initialized", 156 | "offset": 0, 157 | "slot": "0", 158 | "type": "t_bool", 159 | "contract": "Initializable", 160 | "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:39" 161 | }, 162 | { 163 | "label": "_initializing", 164 | "offset": 1, 165 | "slot": "0", 166 | "type": "t_bool", 167 | "contract": "Initializable", 168 | "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:44" 169 | }, 170 | { 171 | "label": "__gap", 172 | "offset": 0, 173 | "slot": "1", 174 | "type": "t_array(t_uint256)50_storage", 175 | "contract": "ContextUpgradeable", 176 | "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" 177 | }, 178 | { 179 | "label": "_owner", 180 | "offset": 0, 181 | "slot": "51", 182 | "type": "t_address", 183 | "contract": "OwnableUpgradeable", 184 | "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" 185 | }, 186 | { 187 | "label": "__gap", 188 | "offset": 0, 189 | "slot": "52", 190 | "type": "t_array(t_uint256)49_storage", 191 | "contract": "OwnableUpgradeable", 192 | "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:87" 193 | }, 194 | { 195 | "label": "_paused", 196 | "offset": 0, 197 | "slot": "101", 198 | "type": "t_bool", 199 | "contract": "PausableUpgradeable", 200 | "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29" 201 | }, 202 | { 203 | "label": "__gap", 204 | "offset": 0, 205 | "slot": "102", 206 | "type": "t_array(t_uint256)49_storage", 207 | "contract": "PausableUpgradeable", 208 | "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:102" 209 | }, 210 | { 211 | "label": "emergencyOperator", 212 | "offset": 0, 213 | "slot": "151", 214 | "type": "t_contract(EmergencyOperator)15996", 215 | "contract": "MinterDAO", 216 | "src": "contracts/v0.2/MinterDAO.sol:14" 217 | }, 218 | { 219 | "label": "lord", 220 | "offset": 0, 221 | "slot": "152", 222 | "type": "t_address", 223 | "contract": "MinterDAO", 224 | "src": "contracts/v0.2/MinterDAO.sol:15" 225 | }, 226 | { 227 | "label": "minters", 228 | "offset": 0, 229 | "slot": "153", 230 | "type": "t_mapping(t_address,t_mapping(t_address,t_bool))", 231 | "contract": "MinterDAO", 232 | "src": "contracts/v0.2/MinterDAO.sol:16" 233 | } 234 | ], 235 | "types": { 236 | "t_address": { 237 | "label": "address", 238 | "numberOfBytes": "20" 239 | }, 240 | "t_array(t_uint256)49_storage": { 241 | "label": "uint256[49]", 242 | "numberOfBytes": "1568" 243 | }, 244 | "t_array(t_uint256)50_storage": { 245 | "label": "uint256[50]", 246 | "numberOfBytes": "1600" 247 | }, 248 | "t_bool": { 249 | "label": "bool", 250 | "numberOfBytes": "1" 251 | }, 252 | "t_contract(EmergencyOperator)15996": { 253 | "label": "contract EmergencyOperator", 254 | "numberOfBytes": "20" 255 | }, 256 | "t_mapping(t_address,t_bool)": { 257 | "label": "mapping(address => bool)", 258 | "numberOfBytes": "32" 259 | }, 260 | "t_mapping(t_address,t_mapping(t_address,t_bool))": { 261 | "label": "mapping(address => mapping(address => bool))", 262 | "numberOfBytes": "32" 263 | }, 264 | "t_uint256": { 265 | "label": "uint256", 266 | "numberOfBytes": "32" 267 | } 268 | } 269 | } 270 | } 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /.openzeppelin/unknown-56.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifestVersion": "3.2", 3 | "admin": { 4 | "address": "0xBb69915410544Ab5eA03556217dA063D63Acc1f8", 5 | "txHash": "0xea4611c7c6ec39680c5824a2c507fe4faefdc4c32507e19b65e2c95891e23229" 6 | }, 7 | "proxies": [ 8 | { 9 | "address": "0x3664f368dE9F5f04cD3165F4cFE04c8e4099ddBa", 10 | "txHash": "0xe86d7769a8b4e1e44279402880bff4634f9764957f9bb05deecbb69b0fb41b19", 11 | "kind": "transparent" 12 | }, 13 | { 14 | "address": "0xF9E4Dff1d01E44a44a64Df5450F513fbD19326C2", 15 | "txHash": "0x0c3ab7edbd13088a2c420a688c4e0b3182704f678c7eec1aabef7f7cf1b1302f", 16 | "kind": "transparent" 17 | }, 18 | { 19 | "address": "0xC7d50D0aF7F19C016FE6469Eef73803E7Be0741f", 20 | "txHash": "0x86f85a67dcf5179fa408d197973ab237589c46a61672048e3d0d2f19a5c6d7e6", 21 | "kind": "transparent" 22 | } 23 | ], 24 | "impls": { 25 | "a1936387b98546cee8172ebc5e261f8f4f7c6ea3b146ad5568d74dfd1c578be4": { 26 | "address": "0x769eD172c8054EAB3E294D0E2AD63067dd5B2Bb3", 27 | "txHash": "0x1015808a45db3a1b1c89e80d125c762b3cb645243703fae6ec9c97b49f4daa29", 28 | "layout": { 29 | "storage": [ 30 | { 31 | "label": "_initialized", 32 | "offset": 0, 33 | "slot": "0", 34 | "type": "t_bool", 35 | "contract": "Initializable", 36 | "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:39" 37 | }, 38 | { 39 | "label": "_initializing", 40 | "offset": 1, 41 | "slot": "0", 42 | "type": "t_bool", 43 | "contract": "Initializable", 44 | "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:44" 45 | }, 46 | { 47 | "label": "owner", 48 | "offset": 2, 49 | "slot": "0", 50 | "type": "t_address", 51 | "contract": "OwnedUpgradeable", 52 | "src": "contracts/v0.2/OwnedUpgradeable.sol:11" 53 | }, 54 | { 55 | "label": "candidateOwner", 56 | "offset": 0, 57 | "slot": "1", 58 | "type": "t_address", 59 | "contract": "OwnedUpgradeable", 60 | "src": "contracts/v0.2/OwnedUpgradeable.sol:12" 61 | }, 62 | { 63 | "label": "operators", 64 | "offset": 0, 65 | "slot": "2", 66 | "type": "t_mapping(t_address,t_bool)", 67 | "contract": "EmergencyOperator", 68 | "src": "contracts/v0.2/EmergencyOperator.sol:8" 69 | } 70 | ], 71 | "types": { 72 | "t_address": { 73 | "label": "address", 74 | "numberOfBytes": "20" 75 | }, 76 | "t_bool": { 77 | "label": "bool", 78 | "numberOfBytes": "1" 79 | }, 80 | "t_mapping(t_address,t_bool)": { 81 | "label": "mapping(address => bool)", 82 | "numberOfBytes": "32" 83 | } 84 | } 85 | } 86 | }, 87 | "59835cabc08d2573baf826fbd3335d9d7e79b938ce70e2842a07b372b1e1a326": { 88 | "address": "0x05Fa119A9e42D748A14CB7BC4FcBbf90630A5306", 89 | "txHash": "0xd1e415a77b1f5b26a60a998005d7b3827c265956f0efd2404b042371f7a7d262", 90 | "layout": { 91 | "storage": [ 92 | { 93 | "label": "_initialized", 94 | "offset": 0, 95 | "slot": "0", 96 | "type": "t_bool", 97 | "contract": "Initializable", 98 | "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:39" 99 | }, 100 | { 101 | "label": "_initializing", 102 | "offset": 1, 103 | "slot": "0", 104 | "type": "t_bool", 105 | "contract": "Initializable", 106 | "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:44" 107 | }, 108 | { 109 | "label": "owner", 110 | "offset": 2, 111 | "slot": "0", 112 | "type": "t_address", 113 | "contract": "OwnedUpgradeable", 114 | "src": "contracts/v0.2/OwnedUpgradeable.sol:11" 115 | }, 116 | { 117 | "label": "candidateOwner", 118 | "offset": 0, 119 | "slot": "1", 120 | "type": "t_address", 121 | "contract": "OwnedUpgradeable", 122 | "src": "contracts/v0.2/OwnedUpgradeable.sol:12" 123 | }, 124 | { 125 | "label": "operators", 126 | "offset": 0, 127 | "slot": "2", 128 | "type": "t_mapping(t_address,t_bool)", 129 | "contract": "LordV2", 130 | "src": "contracts/v0.2/LordV2.sol:18" 131 | } 132 | ], 133 | "types": { 134 | "t_address": { 135 | "label": "address", 136 | "numberOfBytes": "20" 137 | }, 138 | "t_bool": { 139 | "label": "bool", 140 | "numberOfBytes": "1" 141 | }, 142 | "t_mapping(t_address,t_bool)": { 143 | "label": "mapping(address => bool)", 144 | "numberOfBytes": "32" 145 | } 146 | } 147 | } 148 | }, 149 | "7803bc6ed01837156efba3eddd5b9e90e2d5fbe04c99e73cc0f6a5fa5372ccc6": { 150 | "address": "0x66e6a40C6395B5e742F679F7a99349cfcA49A39B", 151 | "txHash": "0xfd65e285ab6c1784ded7d462da25d29cd316c29f5ce1f4702e876a8a8ff2fa63", 152 | "layout": { 153 | "storage": [ 154 | { 155 | "label": "_initialized", 156 | "offset": 0, 157 | "slot": "0", 158 | "type": "t_bool", 159 | "contract": "Initializable", 160 | "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:39" 161 | }, 162 | { 163 | "label": "_initializing", 164 | "offset": 1, 165 | "slot": "0", 166 | "type": "t_bool", 167 | "contract": "Initializable", 168 | "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:44" 169 | }, 170 | { 171 | "label": "__gap", 172 | "offset": 0, 173 | "slot": "1", 174 | "type": "t_array(t_uint256)50_storage", 175 | "contract": "ContextUpgradeable", 176 | "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" 177 | }, 178 | { 179 | "label": "_owner", 180 | "offset": 0, 181 | "slot": "51", 182 | "type": "t_address", 183 | "contract": "OwnableUpgradeable", 184 | "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" 185 | }, 186 | { 187 | "label": "__gap", 188 | "offset": 0, 189 | "slot": "52", 190 | "type": "t_array(t_uint256)49_storage", 191 | "contract": "OwnableUpgradeable", 192 | "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:87" 193 | }, 194 | { 195 | "label": "_paused", 196 | "offset": 0, 197 | "slot": "101", 198 | "type": "t_bool", 199 | "contract": "PausableUpgradeable", 200 | "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29" 201 | }, 202 | { 203 | "label": "__gap", 204 | "offset": 0, 205 | "slot": "102", 206 | "type": "t_array(t_uint256)49_storage", 207 | "contract": "PausableUpgradeable", 208 | "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:102" 209 | }, 210 | { 211 | "label": "emergencyOperator", 212 | "offset": 0, 213 | "slot": "151", 214 | "type": "t_contract(EmergencyOperator)15996", 215 | "contract": "MinterDAO", 216 | "src": "contracts/v0.2/MinterDAO.sol:14" 217 | }, 218 | { 219 | "label": "lord", 220 | "offset": 0, 221 | "slot": "152", 222 | "type": "t_address", 223 | "contract": "MinterDAO", 224 | "src": "contracts/v0.2/MinterDAO.sol:15" 225 | }, 226 | { 227 | "label": "minters", 228 | "offset": 0, 229 | "slot": "153", 230 | "type": "t_mapping(t_address,t_mapping(t_address,t_bool))", 231 | "contract": "MinterDAO", 232 | "src": "contracts/v0.2/MinterDAO.sol:16" 233 | } 234 | ], 235 | "types": { 236 | "t_address": { 237 | "label": "address", 238 | "numberOfBytes": "20" 239 | }, 240 | "t_array(t_uint256)49_storage": { 241 | "label": "uint256[49]", 242 | "numberOfBytes": "1568" 243 | }, 244 | "t_array(t_uint256)50_storage": { 245 | "label": "uint256[50]", 246 | "numberOfBytes": "1600" 247 | }, 248 | "t_bool": { 249 | "label": "bool", 250 | "numberOfBytes": "1" 251 | }, 252 | "t_contract(EmergencyOperator)15996": { 253 | "label": "contract EmergencyOperator", 254 | "numberOfBytes": "20" 255 | }, 256 | "t_mapping(t_address,t_bool)": { 257 | "label": "mapping(address => bool)", 258 | "numberOfBytes": "32" 259 | }, 260 | "t_mapping(t_address,t_mapping(t_address,t_bool))": { 261 | "label": "mapping(address => mapping(address => bool))", 262 | "numberOfBytes": "32" 263 | }, 264 | "t_uint256": { 265 | "label": "uint256", 266 | "numberOfBytes": "32" 267 | } 268 | } 269 | } 270 | } 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage* 5 | gasReporterOutput.json 6 | package.json 7 | img 8 | .env 9 | .* 10 | README.md 11 | coverage.json 12 | deployments -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "semi": false, 5 | "singleQuote": false, 6 | "printWidth": 100 7 | } 8 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: [], 3 | }; -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "compiler-version": ["error", "^0.8.0"], 5 | "func-visibility": ["warn", { "ignoreConstructors": true }] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | contracts/test -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iotube contracts 2 | 3 | This repo is for iotube v6, multi-chain tokens. V0.2 is the version being worked on. New tokens introduced here will be CrossChain Tokens that can be wrap/unwrapped from multiple tokens from mutiple chains. 4 | 5 | 6 | ## Develop 7 | 8 | ``` 9 | yarn 10 | yarn test 11 | ``` 12 | 13 | 14 | ## Remix Connect 15 | ``` 16 | npm install -g @remix-project/remixd 17 | remixd -s . --remix-ide https://remix.ethereum.org 18 | ``` 19 | 20 | 21 | ## Deployment 22 | 23 | ``` 24 | yarn compile 25 | ``` 26 | 27 | ``` 28 | export TUBE_ID= 29 | export INIT_NONCE= 30 | export SAFE= 31 | 32 | yarn hardhat run scripts/000-deploy-base.ts 33 | ``` 34 | 35 | Add tokens 36 | edit ```scripts/ops/create-crosschain-token.ts``` then 37 | ``` 38 | yarn hardhat run scripts/ops/create-crosschain-token.ts 39 | ``` 40 | -------------------------------------------------------------------------------- /contracts/LPToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 6 | import "@openzeppelin/contracts/access/Ownable.sol"; 7 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 8 | import "./interfaces/ISwap.sol"; 9 | 10 | /** 11 | * @title Liquidity Provider Token 12 | * @notice This token is an ERC20 detailed token with added capability to be minted by the owner. 13 | * It is used to represent user's shares when providing liquidity to swap contracts. 14 | */ 15 | contract LPToken is ERC20Burnable, Ownable { 16 | using SafeMath for uint256; 17 | 18 | // Address of the swap contract that owns this LP token. When a user adds liquidity to the swap contract, 19 | // they receive a proportionate amount of this LPToken. 20 | ISwap public immutable swap; 21 | uint8 private _decimals; 22 | 23 | /** 24 | * @notice Deploys LPToken contract with given name, symbol, and decimals 25 | * @dev the caller of this constructor will become the owner of this contract 26 | * @param name_ name of this token 27 | * @param symbol_ symbol of this token 28 | * @param decimals_ number of decimals this token will be based on 29 | */ 30 | constructor( 31 | string memory name_, 32 | string memory symbol_, 33 | uint8 decimals_ 34 | ) ERC20(name_, symbol_) { 35 | _decimals = decimals_; 36 | swap = ISwap(_msgSender()); 37 | } 38 | 39 | function decimals() public view virtual override returns (uint8) { 40 | return _decimals; 41 | } 42 | 43 | /** 44 | * @notice Mints the given amount of LPToken to the recipient. 45 | * @dev only owner can call this mint function 46 | * @param recipient address of account to receive the tokens 47 | * @param amount amount of tokens to mint 48 | */ 49 | function mint(address recipient, uint256 amount) external onlyOwner { 50 | require(amount != 0, "amount == 0"); 51 | _mint(recipient, amount); 52 | } 53 | 54 | /** 55 | * @dev Overrides ERC20._beforeTokenTransfer() which get called on every transfers including 56 | * minting and burning. This ensures that swap.updateUserWithdrawFees are called everytime. 57 | */ 58 | function _beforeTokenTransfer( 59 | address from, 60 | address to, 61 | uint256 amount 62 | ) internal override(ERC20) { 63 | super._beforeTokenTransfer(from, to, amount); 64 | swap.updateUserWithdrawFee(to, amount); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /contracts/MathUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 6 | 7 | /** 8 | * @title MathUtils library 9 | * @notice A library to be used in conjunction with SafeMath. Contains functions for calculating 10 | * differences between two uint256. 11 | */ 12 | library MathUtils { 13 | /** 14 | * @notice Compares a and b and returns true if the difference between a and b 15 | * is less than 1 or equal to each other. 16 | * @param a uint256 to compare with 17 | * @param b uint256 to compare with 18 | * @return True if the difference between a and b is less than 1 or equal, 19 | * otherwise return false 20 | */ 21 | function within1(uint256 a, uint256 b) external pure returns (bool) { 22 | return (_difference(a, b) <= 1); 23 | } 24 | 25 | /** 26 | * @notice Calculates absolute difference between a and b 27 | * @param a uint256 to compare with 28 | * @param b uint256 to compare with 29 | * @return Difference between a and b 30 | */ 31 | function difference(uint256 a, uint256 b) external pure returns (uint256) { 32 | return _difference(a, b); 33 | } 34 | 35 | /** 36 | * @notice Calculates absolute difference between a and b 37 | * @param a uint256 to compare with 38 | * @param b uint256 to compare with 39 | * @return Difference between a and b 40 | */ 41 | function _difference(uint256 a, uint256 b) internal pure returns (uint256) { 42 | if (a > b) { 43 | return a - b; 44 | } 45 | return b - a; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /contracts/MockSafe.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 6 | 7 | contract MockSafe { 8 | using SafeERC20 for IERC20; 9 | address public safe; 10 | mapping(address => mapping(address => uint256)) public points; 11 | event DepositToSafe(address token, address recipient, uint256 amount); 12 | 13 | constructor(address _safe) { 14 | safe = _safe; 15 | } 16 | 17 | function deposit( 18 | IERC20 token, 19 | address recipient, 20 | uint256 amount 21 | ) public { 22 | require(amount >= 1000, "invalid amount"); 23 | require(token.balanceOf(address(this)) >= amount, "insufficient balance"); 24 | token.safeTransfer(safe, amount); 25 | points[address(token)][recipient] += amount; 26 | emit DepositToSafe(address(token), recipient, amount); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /contracts/MockToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 7 | 8 | contract MockToken is ERC20Burnable, Ownable { 9 | uint8 private _decimals; 10 | constructor( 11 | string memory name_, 12 | string memory symbol_, 13 | uint8 decimals_ 14 | ) ERC20(name_, symbol_) { 15 | _decimals = decimals_; 16 | } 17 | 18 | function mint(address recipient, uint256 amount) external onlyOwner { 19 | require(amount != 0, "amount == 0"); 20 | _mint(recipient, amount); 21 | } 22 | 23 | function decimals() public view virtual override returns (uint8) { 24 | return _decimals; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /contracts/MockTokenNFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; 7 | 8 | contract MockTokenNFT is ERC721Burnable, Ownable { 9 | constructor(string memory name_, string memory symbol_) public ERC721(name_, symbol_) {} 10 | 11 | function safeMint( 12 | address recipient, 13 | uint256 tokenID, 14 | bytes memory data 15 | ) external onlyOwner { 16 | _safeMint(recipient, tokenID, data); 17 | } 18 | } -------------------------------------------------------------------------------- /contracts/OwnerPausable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/security/Pausable.sol"; 7 | 8 | /** 9 | * @title OwnerPausable 10 | * @notice An ownable contract allows the owner to pause and unpause the 11 | * contract without a delay. 12 | * @dev Only methods using the provided modifiers will be paused. 13 | */ 14 | contract OwnerPausable is Ownable, Pausable { 15 | /** 16 | * @notice Pause the contract. Revert if already paused. 17 | */ 18 | function pause() external onlyOwner { 19 | Pausable._pause(); 20 | } 21 | 22 | /** 23 | * @notice Unpause the contract. Revert if already unpaused. 24 | */ 25 | function unpause() external onlyOwner { 26 | Pausable._unpause(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /contracts/Timelock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 6 | 7 | contract Timelock { 8 | using SafeMath for uint; 9 | 10 | event NewAdmin(address indexed newAdmin); 11 | event NewPendingAdmin(address indexed newPendingAdmin); 12 | event NewDelay(uint indexed newDelay); 13 | event CancelTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta); 14 | event ExecuteTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta); 15 | event QueueTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta); 16 | 17 | uint public constant GRACE_PERIOD = 15 days; 18 | uint public constant MINIMUM_DELAY = 1 days; 19 | uint public constant MAXIMUM_DELAY = 30 days; 20 | 21 | address public admin; 22 | address public pendingAdmin; 23 | uint public delay; 24 | bool public admin_initialized; 25 | 26 | mapping (bytes32 => bool) public queuedTransactions; 27 | 28 | constructor(address admin_, uint delay_) { 29 | require(delay_ >= MINIMUM_DELAY, "Timelock::constructor: Delay must exceed minimum delay."); 30 | require(delay_ <= MAXIMUM_DELAY, "Timelock::constructor: Delay must not exceed maximum delay."); 31 | 32 | admin = admin_; 33 | delay = delay_; 34 | admin_initialized = false; 35 | } 36 | 37 | receive() external payable { } 38 | 39 | function setDelay(uint delay_) public { 40 | require(msg.sender == address(this), "Timelock::setDelay: Call must come from Timelock."); 41 | require(delay_ >= MINIMUM_DELAY, "Timelock::setDelay: Delay must exceed minimum delay."); 42 | require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay."); 43 | delay = delay_; 44 | 45 | emit NewDelay(delay); 46 | } 47 | 48 | function acceptAdmin() public { 49 | require(msg.sender == pendingAdmin, "Timelock::acceptAdmin: Call must come from pendingAdmin."); 50 | admin = msg.sender; 51 | pendingAdmin = address(0); 52 | 53 | emit NewAdmin(admin); 54 | } 55 | 56 | function setPendingAdmin(address pendingAdmin_) public { 57 | if (admin_initialized) { 58 | require(msg.sender == address(this), "Timelock::setPendingAdmin: Call must come from Timelock."); 59 | } else { 60 | require(msg.sender == admin, "Timelock::setPendingAdmin: First call must come from admin."); 61 | admin_initialized = true; 62 | } 63 | pendingAdmin = pendingAdmin_; 64 | 65 | emit NewPendingAdmin(pendingAdmin); 66 | } 67 | 68 | function queueTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public returns (bytes32) { 69 | require(msg.sender == admin, "Timelock::queueTransaction: Call must come from admin."); 70 | require(eta >= getBlockTimestamp().add(delay), "Timelock::queueTransaction: Estimated execution block must satisfy delay."); 71 | 72 | bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); 73 | queuedTransactions[txHash] = true; 74 | 75 | emit QueueTransaction(txHash, target, value, signature, data, eta); 76 | return txHash; 77 | } 78 | 79 | function computeHash(address target, uint value, string memory signature, bytes memory data, uint eta) external pure returns (bytes32) { 80 | return keccak256(abi.encode(target, value, signature, data, eta)); 81 | } 82 | 83 | function cancelTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public { 84 | require(msg.sender == admin, "Timelock::cancelTransaction: Call must come from admin."); 85 | 86 | bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); 87 | queuedTransactions[txHash] = false; 88 | 89 | emit CancelTransaction(txHash, target, value, signature, data, eta); 90 | } 91 | 92 | function executeTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public payable returns (bytes memory) { 93 | require(msg.sender == admin, "Timelock::executeTransaction: Call must come from admin."); 94 | 95 | bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); 96 | require(queuedTransactions[txHash], "Timelock::executeTransaction: Transaction hasn't been queued."); 97 | require(getBlockTimestamp() >= eta, "Timelock::executeTransaction: Transaction hasn't surpassed time lock."); 98 | require(getBlockTimestamp() <= eta.add(GRACE_PERIOD), "Timelock::executeTransaction: Transaction is stale."); 99 | 100 | queuedTransactions[txHash] = false; 101 | 102 | bytes memory callData; 103 | 104 | if (bytes(signature).length == 0) { 105 | callData = data; 106 | } else { 107 | callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data); 108 | } 109 | 110 | (bool success, bytes memory returnData) = target.call{value: value}(callData); 111 | require(success, "Timelock::executeTransaction: Transaction execution reverted."); 112 | 113 | emit ExecuteTransaction(txHash, target, value, signature, data, eta); 114 | 115 | return returnData; 116 | } 117 | 118 | function getBlockTimestamp() internal view returns (uint) { 119 | return block.timestamp; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /contracts/WIOTX.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >= 0.8.0; 4 | 5 | contract WIOTX { 6 | string public name = "Wrapped IOTX"; 7 | string public symbol = "WIOTX"; 8 | uint8 public decimals = 18; 9 | 10 | event Approval(address indexed src, address indexed guy, uint256 wad); 11 | event Transfer(address indexed src, address indexed dst, uint256 wad); 12 | event Deposit(address indexed dst, uint256 wad); 13 | event Withdrawal(address indexed src, uint256 wad); 14 | 15 | mapping(address => uint256) public balanceOf; 16 | mapping(address => mapping(address => uint256)) public allowance; 17 | 18 | fallback() external payable { 19 | deposit(); 20 | } 21 | 22 | function deposit() public payable { 23 | balanceOf[msg.sender] += msg.value; 24 | emit Deposit(msg.sender, msg.value); 25 | } 26 | 27 | function withdraw(uint256 wad) public { 28 | require(balanceOf[msg.sender] >= wad); 29 | balanceOf[msg.sender] -= wad; 30 | payable(msg.sender).transfer(wad); 31 | emit Withdrawal(msg.sender, wad); 32 | } 33 | 34 | function totalSupply() public view returns (uint256) { 35 | return address(this).balance; 36 | } 37 | 38 | function approve(address guy, uint256 wad) public returns (bool) { 39 | allowance[msg.sender][guy] = wad; 40 | emit Approval(msg.sender, guy, wad); 41 | return true; 42 | } 43 | 44 | function transfer(address dst, uint256 wad) public returns (bool) { 45 | return transferFrom(msg.sender, dst, wad); 46 | } 47 | 48 | function transferFrom( 49 | address src, 50 | address dst, 51 | uint256 wad 52 | ) public returns (bool) { 53 | require(balanceOf[src] >= wad); 54 | 55 | if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) { 56 | require(allowance[src][msg.sender] >= wad); 57 | allowance[src][msg.sender] -= wad; 58 | } 59 | 60 | balanceOf[src] -= wad; 61 | balanceOf[dst] += wad; 62 | 63 | emit Transfer(src, dst, wad); 64 | 65 | return true; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /contracts/interfaces/IAllowlist.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | interface IAllowlist { 6 | function getPoolAccountLimit(address poolAddress) external view returns (uint256); 7 | 8 | function getPoolCap(address poolAddress) external view returns (uint256); 9 | 10 | function verifyAddress(address account, bytes32[] calldata merkleProof) external returns (bool); 11 | } 12 | -------------------------------------------------------------------------------- /contracts/interfaces/ISwap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | import "./IAllowlist.sol"; 7 | 8 | interface ISwap { 9 | // pool data view functions 10 | function getA() external view returns (uint256); 11 | 12 | function getAllowlist() external view returns (IAllowlist); 13 | 14 | function getToken(uint8 index) external view returns (IERC20); 15 | 16 | function getTokenIndex(address tokenAddress) external view returns (uint8); 17 | 18 | function getTokenBalance(uint8 index) external view returns (uint256); 19 | 20 | function getVirtualPrice() external view returns (uint256); 21 | 22 | function isGuarded() external view returns (bool); 23 | 24 | // min return calculation functions 25 | function calculateSwap( 26 | uint8 tokenIndexFrom, 27 | uint8 tokenIndexTo, 28 | uint256 dx 29 | ) external view returns (uint256); 30 | 31 | function calculateTokenAmount(uint256[] calldata amounts, bool deposit) external view returns (uint256); 32 | 33 | function calculateRemoveLiquidity(uint256 amount) external view returns (uint256[] memory); 34 | 35 | function calculateRemoveLiquidityOneToken(uint256 tokenAmount, uint8 tokenIndex) 36 | external 37 | view 38 | returns (uint256 availableTokenAmount); 39 | 40 | // state modifying functions 41 | function initialize( 42 | IERC20[] memory pooledTokens, 43 | uint8[] memory decimals, 44 | string memory lpTokenName, 45 | string memory lpTokenSymbol, 46 | uint256 a, 47 | uint256 fee, 48 | uint256 adminFee, 49 | uint256 withdrawFee 50 | ) external; 51 | 52 | function swap( 53 | uint8 tokenIndexFrom, 54 | uint8 tokenIndexTo, 55 | uint256 dx, 56 | uint256 minDy, 57 | uint256 deadline 58 | ) external returns (uint256); 59 | 60 | function addLiquidity( 61 | uint256[] calldata amounts, 62 | uint256 minToMint, 63 | uint256 deadline 64 | ) external returns (uint256); 65 | 66 | function removeLiquidity( 67 | uint256 amount, 68 | uint256[] calldata minAmounts, 69 | uint256 deadline 70 | ) external returns (uint256[] memory); 71 | 72 | function removeLiquidityOneToken( 73 | uint256 tokenAmount, 74 | uint8 tokenIndex, 75 | uint256 minAmount, 76 | uint256 deadline 77 | ) external returns (uint256); 78 | 79 | function removeLiquidityImbalance( 80 | uint256[] calldata amounts, 81 | uint256 maxBurnAmount, 82 | uint256 deadline 83 | ) external returns (uint256); 84 | 85 | // withdraw fee update function 86 | function updateUserWithdrawFee(address recipient, uint256 transferAmount) external; 87 | } 88 | -------------------------------------------------------------------------------- /contracts/v0.1/AssetRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma experimental ABIEncoderV2; 4 | pragma solidity >=0.8.0; 5 | 6 | import "@openzeppelin/contracts/access/Ownable.sol"; 7 | 8 | contract AssetRegistry is Ownable { 9 | event NewOriginalAsset(uint256 indexed tubeID, address indexed asset, uint256 indexed id); 10 | event AssetAddedOnTube(uint256 indexed id, uint256 indexed tubeID, address asset); 11 | event AssetRemovedOnTube(uint256 indexed id, uint256 indexed tubeID, address asset); 12 | event AssetActivated(uint256 indexed id, uint256 indexed tubID); 13 | event AssetDeactivated(uint256 indexed id, uint256 indexed tubeID); 14 | event TubeActivated(uint256 indexed tubID); 15 | event TubeDeactivated(uint256 indexed tubeID); 16 | event OperatorGranted(address indexed operator); 17 | event OperatorRevoked(address indexed operator); 18 | 19 | struct Asset { 20 | uint256 tubeID; 21 | address asset; 22 | bool active; 23 | } 24 | 25 | Asset[] private originalAssets; 26 | // tubeID + asset => assetID 27 | mapping(uint256 => mapping(address => uint256)) private originalAssetIDs; 28 | // assetID + shadow tubeID => shadow asset 29 | mapping(uint256 => mapping(uint256 => Asset)) private shadowAssets; 30 | // shadow tubeID + shadow asset => assetID 31 | mapping(uint256 => mapping(address => uint256)) private shadowAssetIDs; 32 | // tubes which are banned 33 | mapping(uint256 => bool) public bannedTubeIDs; 34 | 35 | mapping(address => bool) public operators; 36 | 37 | modifier onlyOperator() { 38 | require(operators[msg.sender], "no permission"); 39 | _; 40 | } 41 | 42 | // assetID returns the asset id of given tube id and asset address 43 | function assetID(uint256 _tubeID, address _asset) public view returns (uint256) { 44 | uint256 id = originalAssetIDs[_tubeID][_asset]; 45 | if (id == 0) { 46 | id = shadowAssetIDs[_tubeID][_asset]; 47 | } 48 | return id; 49 | } 50 | 51 | function originalAssetByID(uint256 _assetID) public view returns (Asset memory) { 52 | require(_assetID > 0 && _assetID <= originalAssets.length, "invalid asset id"); 53 | return originalAssets[_assetID - 1]; 54 | } 55 | 56 | function numOfAssets() public view returns (uint256) { 57 | return originalAssets.length; 58 | } 59 | 60 | function assetOnTubeByID(uint256 _assetID, uint256 _tubeID) public view returns (Asset memory) { 61 | Asset memory originalAsset = originalAssetByID(_assetID); 62 | if (originalAsset.tubeID == _tubeID) { 63 | return originalAsset; 64 | } 65 | return shadowAssets[_assetID][_tubeID]; 66 | } 67 | 68 | function assetOnTube( 69 | uint256 _srcTubeID, 70 | address _srcAsset, 71 | uint256 _dstTubeID 72 | ) public view returns (Asset memory) { 73 | return assetOnTubeByID(assetID(_srcTubeID, _srcAsset), _dstTubeID); 74 | } 75 | 76 | function addOriginalAsset(uint256 _tubeID, address _asset) public onlyOperator returns (uint256) { 77 | require(_tubeID > 0 && _asset != address(0), "invalid parameter"); 78 | uint256 id = assetID(_tubeID, _asset); 79 | if (id == 0) { 80 | originalAssets.push(Asset(_tubeID, _asset, true)); 81 | id = originalAssets.length; 82 | originalAssetIDs[_tubeID][_asset] = id; 83 | emit NewOriginalAsset(_tubeID, _asset, id); 84 | } 85 | return id; 86 | } 87 | 88 | function addAssetOnTube( 89 | uint256 _assetID, 90 | uint256 _tubeID, 91 | address _asset 92 | ) public onlyOperator { 93 | require( 94 | _tubeID > 0 && _asset != address(0) && _assetID > 0 && _assetID <= originalAssets.length, 95 | "invalid parameter" 96 | ); 97 | require(assetID(_tubeID, _asset) == 0, "invalid asset"); 98 | require(shadowAssets[_assetID][_tubeID].asset == address(0), "invalid asset"); 99 | shadowAssets[_assetID][_tubeID] = Asset(_tubeID, _asset, true); 100 | shadowAssetIDs[_tubeID][_asset] = _assetID; 101 | emit AssetAddedOnTube(_assetID, _tubeID, _asset); 102 | } 103 | 104 | function removeAssetOnTube( 105 | uint256 _assetID, 106 | uint256 _tubeID 107 | ) public onlyOperator { 108 | address asset = shadowAssets[_assetID][_tubeID].asset; 109 | require(asset != address(0), "not exist"); 110 | delete shadowAssetIDs[_tubeID][asset]; 111 | delete shadowAssets[_assetID][_tubeID]; 112 | emit AssetRemovedOnTube(_assetID, _tubeID, asset); 113 | } 114 | 115 | function activateAsset(uint256 _assetID, uint256 _tubeID) public onlyOperator { 116 | require(_assetID > 0 && _assetID <= originalAssets.length, "invalid asset id"); 117 | Asset storage oa = originalAssets[_assetID - 1]; 118 | if (_tubeID == 0 || oa.tubeID == _tubeID) { 119 | if (oa.active == false) { 120 | oa.active = true; 121 | emit AssetActivated(_assetID, oa.tubeID); 122 | } 123 | } else { 124 | Asset storage sa = shadowAssets[_assetID][_tubeID]; 125 | if (sa.asset != address(0) && sa.active == false) { 126 | sa.active = true; 127 | emit AssetActivated(_assetID, _tubeID); 128 | } 129 | } 130 | } 131 | 132 | function deactivateAsset(uint256 _assetID, uint256 _tubeID) public onlyOperator { 133 | require(_assetID > 0 && _assetID <= originalAssets.length, "invalid asset id"); 134 | Asset storage oa = originalAssets[_assetID - 1]; 135 | if (_tubeID == 0 || oa.tubeID == _tubeID) { 136 | if (oa.active == true) { 137 | oa.active = false; 138 | emit AssetDeactivated(_assetID, oa.tubeID); 139 | } 140 | } else { 141 | Asset storage sa = shadowAssets[_assetID][_tubeID]; 142 | if (sa.asset != address(0) && sa.active == true) { 143 | sa.active = false; 144 | emit AssetDeactivated(_assetID, _tubeID); 145 | } 146 | } 147 | } 148 | 149 | function activateTube(uint256 _tubeID) public onlyOperator { 150 | if (bannedTubeIDs[_tubeID]) { 151 | bannedTubeIDs[_tubeID] = false; 152 | emit TubeDeactivated(_tubeID); 153 | } 154 | } 155 | 156 | function deactivateTube(uint256 _tubeID) public onlyOperator { 157 | if (!bannedTubeIDs[_tubeID]) { 158 | bannedTubeIDs[_tubeID] = true; 159 | emit TubeActivated(_tubeID); 160 | } 161 | } 162 | 163 | function grant(address _account) public onlyOwner { 164 | if (!operators[_account]) { 165 | operators[_account] = true; 166 | emit OperatorGranted(_account); 167 | } 168 | } 169 | 170 | function revoke(address _account) public onlyOwner { 171 | if (operators[_account]) { 172 | operators[_account] = false; 173 | emit OperatorRevoked(_account); 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /contracts/v0.1/AssetUpperBound.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | 7 | contract AssetUpperBound is Ownable { 8 | event UpperBoundSet(uint256 indexed assetID, uint256 upperBound); 9 | 10 | // asset => upper bound 11 | mapping(uint256 => uint256) private _upperBounds; 12 | 13 | function setUpperBound(uint256 _assetID, uint256 _upperBound) public onlyOwner { 14 | _upperBounds[_assetID] = _upperBound; 15 | 16 | emit UpperBoundSet(_assetID, _upperBound); 17 | } 18 | 19 | function getUpperBound(uint256 _assetID) public view returns (uint256) { 20 | return _upperBounds[_assetID]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /contracts/v0.1/CrosschainCoinRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 6 | import "./CrosschainERC20.sol"; 7 | 8 | interface WrappedCoin { 9 | function deposit() external payable; 10 | 11 | function withdraw(uint256) external; 12 | } 13 | 14 | contract CrosschainCoinRouter { 15 | using SafeERC20 for ERC20; 16 | 17 | WrappedCoin public wrappedCoin; 18 | CrosschainERC20 public cerc20; 19 | 20 | constructor(CrosschainERC20 _cerc20) { 21 | ERC20 ct = _cerc20.coToken(); 22 | cerc20 = _cerc20; 23 | ct.safeApprove(address(cerc20), type(uint256).max); 24 | wrappedCoin = WrappedCoin(address(ct)); 25 | } 26 | 27 | receive() external payable {} 28 | 29 | function resetAllowance() public { 30 | ERC20 wc = ERC20(address(wrappedCoin)); 31 | uint256 allowance = wc.allowance(address(this), address(cerc20)); 32 | if (allowance != type(uint256).max) { 33 | wc.safeIncreaseAllowance(address(cerc20), type(uint256).max - allowance); 34 | } 35 | } 36 | 37 | function swapCoinForCrosschainCoin(uint256 _amount) public payable { 38 | require(msg.value == _amount, "incorrect amount value"); 39 | wrappedCoin.deposit{value: _amount}(); 40 | cerc20.depositTo(msg.sender, _amount); 41 | } 42 | 43 | function swapCrosschainCoinForCoin(uint256 _amount) public { 44 | ERC20(cerc20).safeTransferFrom(msg.sender, address(this), _amount); 45 | cerc20.withdraw(_amount); 46 | wrappedCoin.withdraw(_amount); 47 | payable(msg.sender).transfer(_amount); 48 | } 49 | 50 | function swapWrappedCoinForCrosschainCoin(uint256 _amount) public { 51 | ERC20(address(wrappedCoin)).safeTransferFrom(msg.sender, address(this), _amount); 52 | cerc20.depositTo(msg.sender, _amount); 53 | } 54 | 55 | function swapCrosschainCoinForWrappedCoin(uint256 _amount) public { 56 | ERC20(cerc20).safeTransferFrom(msg.sender, address(this), _amount); 57 | cerc20.withdrawTo(msg.sender, _amount); 58 | } 59 | 60 | function swapCoinForWrappedCoin(uint256 _amount) public payable { 61 | require(msg.value == _amount, "incorrect amount value"); 62 | wrappedCoin.deposit{value: _amount}(); 63 | ERC20(address(wrappedCoin)).safeTransfer(msg.sender, _amount); 64 | } 65 | 66 | function swapWrappedCoinForCoin(uint256 _amount) public { 67 | ERC20(address(wrappedCoin)).safeTransferFrom(msg.sender, address(this), _amount); 68 | wrappedCoin.withdraw(_amount); 69 | payable(msg.sender).transfer(_amount); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /contracts/v0.1/CrosschainERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 7 | 8 | contract CrosschainERC20 is ERC20Burnable { 9 | using SafeERC20 for ERC20; 10 | 11 | event MinterSet(address indexed minter); 12 | 13 | modifier onlyMinter() { 14 | require(minter == msg.sender, "not the minter"); 15 | _; 16 | } 17 | 18 | ERC20 public coToken; 19 | address public minter; 20 | uint8 private decimals_; 21 | 22 | constructor( 23 | ERC20 _coToken, 24 | address _minter, 25 | string memory _name, 26 | string memory _symbol, 27 | uint8 _decimals 28 | ) ERC20(_name, _symbol) { 29 | coToken = _coToken; 30 | minter = _minter; 31 | decimals_ = _decimals; 32 | emit MinterSet(_minter); 33 | } 34 | 35 | function decimals() public view virtual override returns (uint8) { 36 | return decimals_; 37 | } 38 | 39 | function transferMintership(address _newMinter) public onlyMinter { 40 | minter = _newMinter; 41 | emit MinterSet(_newMinter); 42 | } 43 | 44 | function deposit(uint256 _amount) public { 45 | depositTo(msg.sender, _amount); 46 | } 47 | 48 | function depositTo(address _to, uint256 _amount) public { 49 | require(address(coToken) != address(0), "no co-token"); 50 | coToken.safeTransferFrom(msg.sender, address(this), _amount); 51 | _mint(_to, _amount); 52 | } 53 | 54 | function withdraw(uint256 _amount) public { 55 | withdrawTo(msg.sender, _amount); 56 | } 57 | 58 | function withdrawTo(address _to, uint256 _amount) public { 59 | require(address(coToken) != address(0), "no co-token"); 60 | require(_amount != 0, "amount is 0"); 61 | _burn(msg.sender, _amount); 62 | coToken.safeTransfer(_to, _amount); 63 | } 64 | 65 | function mint(address _to, uint256 _amount) public onlyMinter returns (bool) { 66 | require(_amount != 0, "amount is 0"); 67 | _mint(_to, _amount); 68 | return true; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /contracts/v0.1/CrosschainERC20Factory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "./CrosschainERC20.sol"; 7 | 8 | contract CrosschainERC20Factory is Ownable { 9 | event NewCrosschainERC20( 10 | address indexed token, 11 | address indexed coToken, 12 | address lord, 13 | string name, 14 | string symbol, 15 | uint8 decimals 16 | ); 17 | address public lord; 18 | 19 | constructor(address _lord) { 20 | lord = _lord; 21 | } 22 | 23 | function createForeignToken( 24 | string memory _name, 25 | string memory _symbol, 26 | uint8 _decimals 27 | ) public onlyOwner returns (CrosschainERC20) { 28 | CrosschainERC20 cc = new CrosschainERC20(ERC20(address(0)), lord, _name, _symbol, _decimals); 29 | emit NewCrosschainERC20(address(cc), address(0), lord, _name, _symbol, _decimals); 30 | 31 | return cc; 32 | } 33 | 34 | function createLocalToken( 35 | ERC20 _coToken, 36 | string memory _name, 37 | string memory _symbol, 38 | uint8 _decimals 39 | ) public onlyOwner returns (CrosschainERC20) { 40 | require(address(_coToken) != address(0), "invalid paramter"); 41 | CrosschainERC20 cc = new CrosschainERC20(_coToken, lord, _name, _symbol, _decimals); 42 | emit NewCrosschainERC20(address(cc), address(_coToken), lord, _name, _symbol, _decimals); 43 | return cc; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /contracts/v0.1/CrosschainERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; 6 | 7 | contract CrosschainERC721 is ERC721Burnable { 8 | event MinterSet(address indexed minter); 9 | 10 | modifier onlyMinter() { 11 | require(minter == msg.sender, "not the minter"); 12 | _; 13 | } 14 | 15 | ERC721 public coToken; 16 | address public minter; 17 | 18 | constructor( 19 | ERC721 _coToken, 20 | address _minter, 21 | string memory _name, 22 | string memory _symbol 23 | ) ERC721(_name, _symbol) { 24 | coToken = _coToken; 25 | minter = _minter; 26 | emit MinterSet(_minter); 27 | } 28 | 29 | function transferMintership(address _newMinter) public onlyMinter { 30 | minter = _newMinter; 31 | emit MinterSet(_newMinter); 32 | } 33 | 34 | function deposit(uint256 _id) public { 35 | depositTo(msg.sender, _id); 36 | } 37 | 38 | function depositTo(address _to, uint256 _id) public { 39 | require(address(coToken) != address(0), "no co-token"); 40 | coToken.safeTransferFrom(msg.sender, address(this), _id); 41 | _mint(_to, _id); 42 | } 43 | 44 | function withdraw(uint256 _id) public { 45 | withdrawTo(msg.sender, _id); 46 | } 47 | 48 | function withdrawTo(address _to, uint256 _id) public { 49 | require(address(coToken) != address(0), "no co-token"); 50 | require(_isApprovedOrOwner(msg.sender, _id), "not owner nor approved"); 51 | _burn(_id); 52 | coToken.safeTransferFrom(address(this), _to, _id); 53 | } 54 | 55 | function safeMint( 56 | address _to, 57 | uint256 _id, 58 | bytes memory _data 59 | ) public onlyMinter { 60 | _safeMint(_to, _id, _data); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /contracts/v0.1/Ledger.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "./Owned.sol"; 6 | 7 | contract Ledger is Owned { 8 | mapping(bytes32 => uint256) public records; 9 | 10 | function record(bytes32 id) public onlyOwner { 11 | require(records[id] == 0, "already in ledger"); 12 | records[id] = block.number; 13 | } 14 | 15 | function get(bytes32 id) public view returns (uint256) { 16 | return records[id]; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contracts/v0.1/Lord.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "./Owned.sol"; 6 | import "@openzeppelin/contracts/utils/Address.sol"; 7 | 8 | interface IToken { 9 | function mint(address recipient, uint256 amount) external; 10 | 11 | function burn(uint256 amount) external; 12 | 13 | function burnFrom(address owner, uint256 amount) external; 14 | 15 | function transferFrom( 16 | address sender, 17 | address recipient, 18 | uint256 amount 19 | ) external; 20 | } 21 | 22 | interface IERC721Mintable { 23 | function safeMint( 24 | address recipient, 25 | uint256 tokenID, 26 | bytes memory data 27 | ) external; 28 | 29 | function mint(address recipient, uint256 tokenID) external; 30 | 31 | function burn(uint256 tokenID) external; 32 | } 33 | 34 | interface IAllowlist { 35 | function isAllowed(address) external view returns (bool); 36 | } 37 | 38 | interface IMinter { 39 | function mint( 40 | address _token, 41 | address _recipient, 42 | uint256 _amount 43 | ) external returns (bool); 44 | 45 | function transferOwnership(address newOwner) external; 46 | 47 | function owner() external view returns (address); 48 | } 49 | 50 | contract Lord is Owned { 51 | using Address for address; 52 | 53 | IAllowlist public standardTokenList; 54 | IMinter public tokenSafe; 55 | IAllowlist public proxyTokenList; 56 | IMinter public minterPool; 57 | 58 | constructor( 59 | IAllowlist _standardTokenList, 60 | IMinter _tokenSafe, 61 | IAllowlist _proxyTokenList, 62 | IMinter _minterPool 63 | ) public Owned() { 64 | standardTokenList = _standardTokenList; 65 | tokenSafe = _tokenSafe; 66 | proxyTokenList = _proxyTokenList; 67 | minterPool = _minterPool; 68 | } 69 | 70 | function burn( 71 | address _token, 72 | address _sender, 73 | uint256 _amount 74 | ) public onlyOwner { 75 | if (address(standardTokenList) != address(0) && standardTokenList.isAllowed(_token)) { 76 | // transfer token to standardTokenList 77 | _callOptionalReturn( 78 | _token, 79 | abi.encodeWithSelector(IToken(_token).transferFrom.selector, _sender, tokenSafe, _amount) 80 | ); 81 | return; 82 | } 83 | if (address(proxyTokenList) != address(0) && proxyTokenList.isAllowed(_token)) { 84 | _callOptionalReturn( 85 | _token, 86 | abi.encodeWithSelector(IToken(_token).transferFrom.selector, _sender, address(this), _amount) 87 | ); 88 | _callOptionalReturn(_token, abi.encodeWithSelector(IToken(_token).burn.selector, _amount)); 89 | return; 90 | } 91 | _callOptionalReturn(_token, abi.encodeWithSelector(IToken(_token).burnFrom.selector, _sender, _amount)); 92 | } 93 | 94 | function mint( 95 | address _token, 96 | address _recipient, 97 | uint256 _amount 98 | ) public onlyOwner { 99 | if (address(standardTokenList) != address(0) && standardTokenList.isAllowed(_token)) { 100 | require(tokenSafe.mint(_token, _recipient, _amount), "token safe mint failed"); 101 | return; 102 | } 103 | if (address(proxyTokenList) != address(0) && proxyTokenList.isAllowed(_token)) { 104 | require(minterPool.mint(_token, _recipient, _amount), "proxy token mint failed"); 105 | } 106 | _callOptionalReturn(_token, abi.encodeWithSelector(IToken(_token).mint.selector, _recipient, _amount)); 107 | } 108 | 109 | function burnNFT( 110 | address _token, 111 | uint256 _tokenID 112 | ) public onlyOwner { 113 | IERC721Mintable(_token).burn(_tokenID); 114 | } 115 | 116 | function mintNFT( 117 | address _token, 118 | uint256 _tokenID, 119 | address _recipient, 120 | bytes memory _data 121 | ) public onlyOwner { 122 | IERC721Mintable(_token).safeMint(_recipient, _tokenID, _data); 123 | } 124 | 125 | function upgrade(address _newLord) public onlyOwner { 126 | if (minterPool.owner() == address(this)) { 127 | _callOptionalReturn( 128 | address(minterPool), 129 | abi.encodeWithSelector(minterPool.transferOwnership.selector, _newLord) 130 | ); 131 | } 132 | if (tokenSafe.owner() == address(this)) { 133 | _callOptionalReturn( 134 | address(tokenSafe), 135 | abi.encodeWithSelector(tokenSafe.transferOwnership.selector, _newLord) 136 | ); 137 | } 138 | } 139 | 140 | function _callOptionalReturn(address addr, bytes memory data) private { 141 | bytes memory returndata = addr.functionCall(data, "SafeERC20: low-level call failed"); 142 | if (returndata.length > 0) { 143 | // solhint-disable-next-line max-line-length 144 | require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /contracts/v0.1/Owned.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | abstract contract Owned { 6 | event CandidateOwnerNominated(address candidate); 7 | event OwnershipTransferred(address owner); 8 | 9 | address public owner; 10 | address public candidateOwner; 11 | 12 | modifier onlyOwner { 13 | require(msg.sender == owner); 14 | _; 15 | } 16 | 17 | constructor() { 18 | owner = msg.sender; 19 | emit OwnershipTransferred(msg.sender); 20 | } 21 | 22 | function transferOwnership(address _newOwner) public onlyOwner { 23 | candidateOwner = _newOwner; 24 | emit CandidateOwnerNominated(_newOwner); 25 | } 26 | 27 | function acceptOwnership() public { 28 | require(msg.sender == candidateOwner, "not candidate owner"); 29 | owner = msg.sender; 30 | candidateOwner = address(0); 31 | emit OwnershipTransferred(msg.sender); 32 | } 33 | } -------------------------------------------------------------------------------- /contracts/v0.1/TestimonyDAO.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | contract TestimonyDAO { 6 | event Testimony(address indexed validator, bytes32 indexed key, bytes testimony); 7 | 8 | function addTestimony(bytes32 key, bytes calldata value) public { 9 | emit Testimony(msg.sender, key, value); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /contracts/v0.1/Tube.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 7 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 8 | import "@openzeppelin/contracts/security/Pausable.sol"; 9 | import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; 10 | 11 | interface ILedger { 12 | function owner() external view returns (address); 13 | function transferOwnership(address _newOwner) external; 14 | function acceptOwnership() external; 15 | function get(bytes32 _key) external view returns (uint256); 16 | function record(bytes32 _key) external; 17 | } 18 | 19 | interface ILord { 20 | function owner() external view returns (address); 21 | function transferOwnership(address _newOwner) external; 22 | function acceptOwnership() external; 23 | function burn(address _token, address _owner, uint256 _amount) external; 24 | function burnNFT(address _token, uint256 _amount) external; 25 | function mint(address _token, address _recipient, uint256 _amount) external; 26 | function mintNFT(address _token, uint256 _tokenID, address _recipient, bytes calldata _data) external; 27 | } 28 | 29 | interface IVerifier { 30 | function verify(bytes32 _key, bytes memory _signatures) 31 | external 32 | view 33 | returns (bool isValid_, address[] memory validators_); 34 | } 35 | 36 | contract Tube is Ownable, Pausable, ReentrancyGuard { 37 | using SafeERC20 for IERC20; 38 | 39 | event Settled(bytes32 indexed key, address[] validators, bool success); 40 | 41 | event Receipt( 42 | uint256 indexed tubeID, 43 | address indexed token, 44 | uint256 indexed txIdx, 45 | address sender, 46 | address recipient, 47 | uint256 amount, 48 | bytes data, 49 | uint256 fee 50 | ); 51 | 52 | event NFTReceipt( 53 | uint256 indexed tubeID, 54 | address indexed token, 55 | uint256 indexed tokenID, 56 | uint256 txIdx, 57 | address sender, 58 | address recipient, 59 | bytes data, 60 | uint256 fee 61 | ); 62 | 63 | uint256 public tubeID; 64 | ILedger public ledger; 65 | ILord public lord; 66 | IVerifier public verifier; 67 | IERC20 public tubeToken; 68 | address public safe; 69 | mapping(uint256 => mapping(address => uint256)) public counts; 70 | mapping(uint256 => uint256) public fees; 71 | 72 | constructor( 73 | uint256 _tubeID, 74 | ILedger _ledger, 75 | ILord _lord, 76 | IVerifier _verifier, 77 | IERC20 _tubeToken, 78 | address _safe 79 | ) ReentrancyGuard() { 80 | tubeID = _tubeID; 81 | ledger = _ledger; 82 | lord = _lord; 83 | verifier = _verifier; 84 | tubeToken = _tubeToken; 85 | safe = _safe; 86 | } 87 | 88 | function upgrade(address _newTube) public whenPaused onlyOwner { 89 | if (ledger.owner() == address(this)) { 90 | ledger.transferOwnership(_newTube); 91 | } 92 | if (lord.owner() == address(this)) { 93 | lord.transferOwnership(_newTube); 94 | } 95 | } 96 | 97 | function acceptOwnerships() public whenPaused onlyOwner { 98 | if (ledger.owner() != address(this)) { 99 | ledger.acceptOwnership(); 100 | } 101 | if (lord.owner() != address(this)) { 102 | lord.acceptOwnership(); 103 | } 104 | } 105 | 106 | function count(uint256 _tubeID, address _token) public view returns (uint256) { 107 | return counts[_tubeID][_token]; 108 | } 109 | 110 | function pause() public onlyOwner { 111 | _pause(); 112 | } 113 | 114 | function unpause() public onlyOwner { 115 | _unpause(); 116 | } 117 | 118 | function setFee(uint256 _tubeID, uint256 _fee) public whenPaused onlyOwner { 119 | fees[_tubeID] = _fee; 120 | } 121 | 122 | function depositTo( 123 | uint256 _tubeID, 124 | address _token, 125 | address _to, 126 | uint256 _amount, 127 | bytes memory _data 128 | ) public nonReentrant whenNotPaused { 129 | require(_to != address(0), "invalid recipient"); 130 | require(_amount > 0, "invalid amount"); 131 | uint256 fee = fees[_tubeID]; 132 | if (fee > 0) { 133 | tubeToken.safeTransferFrom(msg.sender, safe, fee); 134 | } 135 | lord.burn(_token, msg.sender, _amount); 136 | uint256 txIdx = ++counts[_tubeID][_token]; 137 | emit Receipt(_tubeID, _token, txIdx, msg.sender, _to, _amount, _data, fee); 138 | } 139 | 140 | function depositNFTTo( 141 | uint256 _tubeID, 142 | address _token, 143 | uint256 _tokenID, 144 | address _to, 145 | bytes memory _data 146 | ) public nonReentrant whenNotPaused { 147 | require(_to != address(0), "invalid recipient"); 148 | uint256 fee = fees[_tubeID]; 149 | if (fee > 0) { 150 | tubeToken.safeTransferFrom(msg.sender, safe, fee); 151 | } 152 | lord.burnNFT(_token, _tokenID); 153 | uint256 txIdx = ++counts[_tubeID][_token]; 154 | emit NFTReceipt(_tubeID, _token, _tokenID, txIdx, msg.sender, _to, _data, fee); 155 | } 156 | 157 | function deposit( 158 | uint256 _tubeID, 159 | address _token, 160 | uint256 _amount, 161 | bytes memory _data 162 | ) public { 163 | depositTo(_tubeID, _token, msg.sender, _amount, _data); 164 | } 165 | 166 | function depositNFT( 167 | uint256 _tubeID, 168 | address _token, 169 | uint256 _tokenID, 170 | bytes memory _data 171 | ) public { 172 | depositNFTTo(_tubeID, _token, _tokenID, msg.sender, _data); 173 | } 174 | 175 | function genKey( 176 | uint256 _srcTubeID, 177 | uint256 _txIdx, 178 | address _token, 179 | address _recipient, 180 | uint256 _amount, 181 | bytes memory _data 182 | ) public view returns (bytes32) { 183 | return keccak256(abi.encodePacked(_srcTubeID, _txIdx, tubeID, _token, _recipient, _amount, _data)); 184 | } 185 | 186 | function genKeyForNFT( 187 | uint256 _srcTubeID, 188 | uint256 _txIdx, 189 | address _token, 190 | uint256 _tokenID, 191 | address _recipient, 192 | bytes memory _data 193 | ) public view returns (bytes32) { 194 | return keccak256(abi.encodePacked(_srcTubeID, _txIdx, tubeID, _token, _tokenID, _recipient, _data)); 195 | } 196 | 197 | function concatKeys(bytes32[] memory keys) public pure returns (bytes32) { 198 | return keccak256(abi.encodePacked(keys)); 199 | } 200 | 201 | function isSettled(bytes32 key) public view returns (bool) { 202 | return ledger.get(key) != 0; 203 | } 204 | 205 | function withdraw( 206 | uint256 _srcTubeID, 207 | uint256 _txIdx, 208 | address _token, 209 | address _recipient, 210 | uint256 _amount, 211 | bytes memory _data, 212 | bytes memory _signatures 213 | ) public nonReentrant whenNotPaused { 214 | require(_amount != 0, "amount is 0"); 215 | require(_recipient != address(0), "invalid recipient"); 216 | require(_signatures.length % 65 == 0, "invalid signature length"); 217 | bytes32 key = genKey(_srcTubeID, _txIdx, _token, _recipient, _amount, _data); 218 | ledger.record(key); 219 | (bool isValid, address[] memory signers) = verifier.verify(key, _signatures); 220 | require(isValid, "insufficient validators"); 221 | lord.mint(_token, _recipient, _amount); 222 | bool success = true; 223 | if (_data.length > 0 && _recipient != address(lord) && _recipient != address(ledger) && _recipient != address(tubeToken)) { 224 | (success, ) = _recipient.call(_data); 225 | } 226 | emit Settled(key, signers, success); 227 | } 228 | 229 | function withdrawNFT( 230 | uint256 _srcTubeID, 231 | uint256 _txIdx, 232 | address _token, 233 | uint256 _tokenID, 234 | address _recipient, 235 | bytes memory _data, 236 | bytes memory _signatures 237 | ) public nonReentrant whenNotPaused { 238 | require(_recipient != address(0), "invalid recipient"); 239 | require(_signatures.length % 65 == 0, "invalid signature length"); 240 | bytes32 key = genKeyForNFT(_srcTubeID, _txIdx, _token, _tokenID, _recipient, _data); 241 | ledger.record(key); 242 | (bool isValid, address[] memory signers) = verifier.verify(key, _signatures); 243 | require(isValid, "insufficient validators"); 244 | // TODO: shall use onERC721Received or similar to "withdraw" 245 | lord.mintNFT(_token, _tokenID, _recipient, _data); 246 | emit Settled(key, signers, true); 247 | } 248 | 249 | function withdrawCoin(address payable _to) external onlyOwner { 250 | _to.transfer(address(this).balance); 251 | } 252 | 253 | function withdrawToken(address _to, IERC20 _token) external onlyOwner { 254 | uint256 balance = _token.balanceOf(address(this)); 255 | if (balance > 0) { 256 | _token.safeTransfer(_to, balance); 257 | } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /contracts/v0.1/TubeRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 7 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 8 | 9 | interface ITube { 10 | function depositTo( 11 | uint256 _tubeID, 12 | address _token, 13 | address _to, 14 | uint256 _amount, 15 | bytes memory _data 16 | ) external; 17 | 18 | function depositNFTTo( 19 | uint256 _tubeID, 20 | address _token, 21 | uint256 _tokenID, 22 | address _to, 23 | bytes memory _data 24 | ) external; 25 | 26 | function fees(uint256 _tubeID) external view returns (uint256); 27 | 28 | function lord() external view returns (address); 29 | 30 | function tubeToken() external view returns (IERC20); 31 | } 32 | 33 | contract TubeRouter is Ownable { 34 | using SafeERC20 for IERC20; 35 | event RelayFeeReceipt(address user, uint256 amount); 36 | struct RelayFee { 37 | uint256 fee; 38 | bool exists; 39 | } 40 | mapping(uint256 => RelayFee) private relayFees; 41 | address private lord; 42 | ITube public tube; 43 | 44 | constructor(ITube _tube) { 45 | tube = _tube; 46 | lord = _tube.lord(); 47 | } 48 | 49 | function setRelayFee(uint256 _tubeID, uint256 _fee) public onlyOwner { 50 | if (_fee == 0) { 51 | relayFees[_tubeID].exists = false; 52 | } else { 53 | relayFees[_tubeID] = RelayFee(_fee, true); 54 | } 55 | } 56 | 57 | function relayFee(uint256 _tubeID) public view returns (uint256) { 58 | require(relayFees[_tubeID].exists, "not supported"); 59 | return relayFees[_tubeID].fee; 60 | } 61 | 62 | function depositTo( 63 | uint256 _tubeID, 64 | address _token, 65 | address _recipient, 66 | uint256 _amount, 67 | bytes memory _data 68 | ) public payable { 69 | uint256 fee = relayFee(_tubeID); 70 | require(msg.value >= fee, "insufficient relay fee"); 71 | uint256 tubeFee = tube.fees(_tubeID); 72 | if (tubeFee > 0) { 73 | IERC20 tubeToken = tube.tubeToken(); 74 | tubeToken.safeTransferFrom(msg.sender, address(this), tubeFee); 75 | tubeToken.safeApprove(address(tube), _amount); 76 | } 77 | IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount); 78 | IERC20(_token).safeApprove(lord, _amount); 79 | tube.depositTo(_tubeID, _token, _recipient, _amount, _data); 80 | emit RelayFeeReceipt(msg.sender, msg.value); 81 | } 82 | 83 | function depositNFTTo( 84 | uint256 _tubeID, 85 | address _token, 86 | uint256 _tokenID, 87 | address _recipient, 88 | bytes memory _data 89 | ) public payable { 90 | uint256 fee = relayFee(_tubeID); 91 | require(msg.value >= fee, "insufficient relay fee"); 92 | IERC721(_token).safeTransferFrom(msg.sender, address(this), _tokenID); 93 | IERC721(_token).approve(address(tube), _tokenID); 94 | tube.depositNFTTo(_tubeID, _token, _tokenID, _recipient, _data); 95 | } 96 | 97 | function withdrawCoin(address payable _to) external onlyOwner { 98 | _to.transfer(address(this).balance); 99 | } 100 | 101 | function withdrawToken(address _to, IERC20 _token) external onlyOwner { 102 | uint256 balance = _token.balanceOf(address(this)); 103 | if (balance > 0) { 104 | _token.safeTransfer(_to, balance); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /contracts/v0.1/TubeToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | 7 | contract TubeToken is ERC20 { 8 | constructor() ERC20("Tube Token", "TT") { 9 | _mint(msg.sender, 10_000_000_000 * 10**18); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /contracts/v0.1/ValidatorRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | contract ValidatorRegistry { 6 | struct File { 7 | address validator; 8 | uint256 genre; 9 | string uri; 10 | } 11 | event Registration(address indexed validator, uint256 indexed genre, string uri); 12 | 13 | mapping(address => File) public files; 14 | 15 | function register(uint256 _genre, string calldata _uri) public { 16 | files[msg.sender] = File(msg.sender, _genre, _uri); 17 | emit Registration(msg.sender, _genre, _uri); 18 | } 19 | 20 | function getFile(address _validator) public view returns (uint256, string memory) { 21 | File storage r = files[_validator]; 22 | return (r.genre, r.uri); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/v0.1/Verifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | 7 | contract Verifier is Ownable { 8 | event ValidatorAdded(address indexed validator); 9 | event ValidatorRemoved(address indexed validator); 10 | 11 | uint8 public constant VALIDATOR_LIMIT = 50; 12 | 13 | address[] public validators; 14 | mapping(address => uint8) private validatorIndexes; 15 | 16 | function size() public view returns (uint256) { 17 | return validators.length; 18 | } 19 | 20 | function get(uint256 offset, uint8 limit) public view returns (uint256 count_, address[] memory validators_) { 21 | count_ = validators.length; 22 | validators_ = new address[](limit); 23 | for (uint256 i = 0; i < limit; i++) { 24 | if (offset + i >= validators.length) { 25 | break; 26 | } 27 | validators_[i] = validators[offset + i]; 28 | } 29 | } 30 | 31 | function addAll(address[] memory _validators) public onlyOwner { 32 | require(validators.length + _validators.length < VALIDATOR_LIMIT, "hit max validator limit"); 33 | address validator; 34 | for (uint256 i = 0; i < _validators.length; i++) { 35 | validator = _validators[i]; 36 | require(validator != address(0), "invalid validator"); 37 | if (validatorIndexes[validator] != 0) { 38 | continue; 39 | } 40 | validators.push(validator); 41 | validatorIndexes[validator] = uint8(validators.length); 42 | emit ValidatorAdded(validator); 43 | } 44 | } 45 | 46 | function removeAll(address[] memory _validators) public onlyOwner { 47 | address validator; 48 | address last; 49 | uint8 index; 50 | for (uint256 i = 0; i < _validators.length; i++) { 51 | validator = _validators[i]; 52 | index = validatorIndexes[validator]; 53 | if (index == 0) { 54 | continue; 55 | } 56 | last = validators[validators.length - 1]; 57 | validators[index - 1] = last; 58 | validatorIndexes[last] = index; 59 | validators.pop(); 60 | delete validatorIndexes[validator]; 61 | emit ValidatorRemoved(validator); 62 | } 63 | } 64 | 65 | function verify(bytes32 _key, bytes memory _signatures) 66 | public 67 | view 68 | returns (bool isValid_, address[] memory validators_) 69 | { 70 | uint256 numOfSignatures = _signatures.length / 65; 71 | bool[VALIDATOR_LIMIT] memory seen; 72 | address validator; 73 | uint8 index; 74 | validators_ = new address[](numOfSignatures); 75 | for (uint256 i = 0; i < numOfSignatures; i++) { 76 | validator = recover(_key, _signatures, i * 65); 77 | index = validatorIndexes[validator]; 78 | require(index != 0, "invalid validator"); 79 | require(!seen[index], "duplicate validator"); 80 | seen[index] = true; 81 | validators_[i] = validator; 82 | } 83 | isValid_ = validators_.length * 3 > validators.length * 2; 84 | } 85 | 86 | function recover( 87 | bytes32 hash, 88 | bytes memory signature, 89 | uint256 offset 90 | ) internal pure returns (address) { 91 | bytes32 r; 92 | bytes32 s; 93 | uint8 v; 94 | 95 | // Divide the signature in r, s and v variables with inline assembly. 96 | 97 | // solium-disable-next-line security/no-inline-assembly 98 | assembly { 99 | r := mload(add(signature, add(offset, 0x20))) 100 | s := mload(add(signature, add(offset, 0x40))) 101 | v := byte(0, mload(add(signature, add(offset, 0x60)))) 102 | } 103 | 104 | // Version of signature should be 27 or 28, but 0 and 1 are also possible versions 105 | if (v < 27) { 106 | v += 27; 107 | } 108 | 109 | // If the version is correct return the signer address 110 | if (v != 27 && v != 28) { 111 | return (address(0)); 112 | } 113 | // solium-disable-next-line arg-overflow 114 | return ecrecover(hash, v, r, s); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /contracts/v0.2/AssetRegistryV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | 7 | contract AssetRegistryV2 is Ownable { 8 | event NewAsset(uint256 indexed assetID, uint256 indexed tubeID, address indexed assetAddress); 9 | event AssetSetOnTube(uint256 indexed assetID, uint256 indexed tubeID, address indexed assetAddress); 10 | event AssetRemovedOnTube(uint256 indexed assetID, uint256 indexed tubeID, address indexed assetAddress); 11 | event AssetActivated(uint256 indexed id); 12 | event AssetDeactivated(uint256 indexed id); 13 | event TubeActivated(uint256 indexed id); 14 | event TubeDeactivated(uint256 indexed id); 15 | event AssetOnTubeActivated(uint256 indexed assetID, uint256 indexed tubeID, address indexed assetAddress); 16 | event AssetOnTubeDeactivated(uint256 indexed assetID, uint256 indexed tubeID, address indexed assetAddress); 17 | event OperatorGranted(address indexed operator); 18 | event OperatorRevoked(address indexed operator); 19 | 20 | struct Asset { 21 | address addr; 22 | bool active; 23 | } 24 | 25 | mapping(uint256 => bool) public activeAssetIDs; 26 | 27 | mapping(uint256 => bool) public activeTubeIDs; 28 | 29 | // tube ID + asset address => asset ID 30 | mapping(uint256 => mapping(address => uint256)) private tubeAndAssetToIDs; 31 | // asset ID + tube ID => asset address 32 | mapping(uint256 => mapping(uint256 => Asset)) private assets; 33 | 34 | uint256 private nextAssetID; 35 | 36 | mapping(address => bool) public operators; 37 | 38 | modifier onlyOperator() { 39 | require(operators[msg.sender], "no permission"); 40 | _; 41 | } 42 | 43 | modifier isValidAssetID(uint256 _assetID) { 44 | require(_assetID > 0 && _assetID < nextAssetID, "invalid asset id"); 45 | _; 46 | } 47 | 48 | modifier isValidTubeID(uint256 _tubeID) { 49 | require(_tubeID > 0, "invalid tube id"); 50 | _; 51 | } 52 | 53 | constructor() Ownable() { 54 | nextAssetID = 1; 55 | } 56 | 57 | /////////////////////////////////// 58 | // Public Functions 59 | /////////////////////////////////// 60 | 61 | function isActive(uint256 _assetID, uint256 _tubeID) public view returns (bool) { 62 | if (!isActiveAssetID(_assetID) || !isActiveTubeID(_tubeID)) { 63 | return false; 64 | } 65 | return assets[_assetID][_tubeID].active; 66 | } 67 | 68 | function isActiveAssetID(uint256 _id) public view isValidAssetID(_id) returns (bool) { 69 | return activeAssetIDs[_id]; 70 | } 71 | 72 | function isActiveTubeID(uint256 _id) public view isValidTubeID(_id) returns (bool) { 73 | return activeTubeIDs[_id]; 74 | } 75 | 76 | // assetID returns the asset id of given tube id and asset address 77 | function assetID(uint256 _tubeID, address _assetAddr) public view isValidTubeID(_tubeID) returns (uint256) { 78 | return tubeAndAssetToIDs[_tubeID][_assetAddr]; 79 | } 80 | 81 | function assetAddress(uint256 _assetID, uint256 _tubeID) public view isValidAssetID(_assetID) isValidTubeID(_tubeID) returns (address) { 82 | return assets[_assetID][_tubeID].addr; 83 | } 84 | 85 | function numOfAssets() public view returns (uint256) { 86 | return nextAssetID - 1; 87 | } 88 | 89 | /////////////////////////////////// 90 | // Operator Functions 91 | /////////////////////////////////// 92 | 93 | function newAsset(uint256 _tubeID, address _assetAddr) public onlyOperator isValidTubeID(_tubeID) { 94 | require(_assetAddr != address(0), "invalid asset address"); 95 | require(assetID(_tubeID, _assetAddr) == 0, "duplicate asset"); 96 | uint256 id = nextAssetID; 97 | tubeAndAssetToIDs[_tubeID][_assetAddr] = id; 98 | assets[id][_tubeID] = Asset(_assetAddr, false); 99 | nextAssetID++; 100 | emit NewAsset(id, _tubeID, _assetAddr); 101 | } 102 | 103 | function setAssetOnTube( 104 | uint256 _assetID, 105 | uint256 _tubeID, 106 | address _assetAddr 107 | ) public onlyOperator isValidAssetID(_assetID) isValidTubeID(_tubeID) { 108 | require(assetID(_tubeID, _assetAddr) == 0 && _assetAddr != address(0), "invalid asset"); 109 | require(assetAddress(_assetID, _tubeID) == address(0), "invalid operation"); 110 | tubeAndAssetToIDs[_tubeID][_assetAddr] = _assetID; 111 | assets[_assetID][_tubeID] = Asset(_assetAddr, false); 112 | emit AssetSetOnTube(_assetID, _tubeID, _assetAddr); 113 | } 114 | 115 | function removeAssetOnTube( 116 | uint256 _assetID, 117 | uint256 _tubeID 118 | ) public onlyOperator { 119 | address assetAddr = assetAddress(_assetID, _tubeID); 120 | require(assetAddr != address(0), "not exist"); 121 | delete tubeAndAssetToIDs[_tubeID][assetAddr]; 122 | delete assets[_assetID][_tubeID]; 123 | emit AssetRemovedOnTube(_assetID, _tubeID, assetAddr); 124 | } 125 | 126 | function activateAsset(uint256 _id) external onlyOperator isValidAssetID(_id) { 127 | require(activeAssetIDs[_id] == false, "already activated"); 128 | activeAssetIDs[_id] = true; 129 | emit AssetActivated(_id); 130 | } 131 | 132 | function deactivateAsset(uint256 _id) external onlyOperator isValidAssetID(_id) { 133 | require(activeAssetIDs[_id], "already deactivated"); 134 | activeAssetIDs[_id] = false; 135 | emit AssetDeactivated(_id); 136 | } 137 | 138 | function activateTube(uint256 _id) external onlyOperator isValidTubeID(_id) { 139 | require(activeTubeIDs[_id] == false, "already activated"); 140 | activeTubeIDs[_id] = true; 141 | emit TubeActivated(_id); 142 | } 143 | 144 | function deactivateTube(uint256 _id) external onlyOperator isValidTubeID(_id) { 145 | require(activeTubeIDs[_id], "already deactivated"); 146 | activeTubeIDs[_id] = false; 147 | emit TubeDeactivated(_id); 148 | } 149 | 150 | function activateAssetOnTube(uint256 _assetID, uint256 _tubeID) external onlyOperator isValidAssetID(_assetID) isValidTubeID(_tubeID) { 151 | Asset storage asset = assets[_assetID][_tubeID]; 152 | require(asset.addr != address(0), "asset not registered"); 153 | require(asset.active == false, "already activated"); 154 | asset.active = true; 155 | emit AssetOnTubeActivated(_assetID, _tubeID, asset.addr); 156 | } 157 | 158 | function deactivateAssetOnTube(uint256 _assetID, uint256 _tubeID) external onlyOperator isValidAssetID(_assetID) isValidTubeID(_tubeID) { 159 | Asset storage asset = assets[_assetID][_tubeID]; 160 | require(asset.addr != address(0), "asset not registered"); 161 | require(asset.active, "already activated"); 162 | asset.active = false; 163 | emit AssetOnTubeDeactivated(_assetID, _tubeID, asset.addr); 164 | } 165 | 166 | /////////////////////////////////// 167 | // Owner Functions 168 | /////////////////////////////////// 169 | 170 | function grant(address _account) public onlyOwner { 171 | require(!operators[_account], "already an operator"); 172 | operators[_account] = true; 173 | emit OperatorGranted(_account); 174 | } 175 | 176 | function revoke(address _account) public onlyOwner { 177 | require(operators[_account], "not an operator"); 178 | operators[_account] = false; 179 | emit OperatorRevoked(_account); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /contracts/v0.2/CrosschainERC20FactoryV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/utils/Address.sol"; 7 | import "./CrosschainERC20V2.sol"; 8 | import "./CrosschainERC20V2Pair.sol"; 9 | 10 | contract CrosschainERC20FactoryV2 is Ownable { 11 | using Address for address; 12 | 13 | event NewCrosschainERC20( 14 | address indexed token, 15 | string name, 16 | string symbol, 17 | uint8 decimals 18 | ); 19 | event NewCrosschainERC20Pair( 20 | address indexed crosschainToken, 21 | address indexed token, 22 | address indexed pair 23 | ); 24 | event MinterDAOSet(address indexed dao); 25 | 26 | address public minterDAO; 27 | 28 | constructor(address _minterDAO) { 29 | minterDAO = _minterDAO; 30 | emit MinterDAOSet(_minterDAO); 31 | } 32 | 33 | function setMinterDAO(address _minterDAO) external onlyOwner { 34 | minterDAO = _minterDAO; 35 | emit MinterDAOSet(_minterDAO); 36 | } 37 | 38 | function createCrosschainERC20( 39 | string memory _name, 40 | string memory _symbol, 41 | uint8 _decimals 42 | ) external onlyOwner returns (address) { 43 | CrosschainERC20V2 cc = new CrosschainERC20V2(minterDAO, _name, _symbol, _decimals); 44 | emit NewCrosschainERC20(address(cc), _name, _symbol, _decimals); 45 | 46 | return address(cc); 47 | } 48 | 49 | function createCrosschainERC20Pair( 50 | address _crosschainToken, 51 | uint8 _crosschainTokenDecimals, 52 | address _token, 53 | uint8 _tokenDecimals 54 | ) external onlyOwner returns (address) { 55 | CrosschainERC20V2Pair wrapper = new CrosschainERC20V2Pair(_crosschainToken, _crosschainTokenDecimals, _token, _tokenDecimals, owner()); 56 | emit NewCrosschainERC20Pair(_crosschainToken, _token, address(wrapper)); 57 | 58 | // TODO add to minter dao? need owner! 59 | return address(wrapper); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /contracts/v0.2/CrosschainERC20V2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 7 | 8 | interface IMinterDAO { 9 | function isMinter(address account, address token) external view returns (bool); 10 | } 11 | 12 | contract CrosschainERC20V2 is ERC20Burnable { 13 | using SafeERC20 for IERC20; 14 | 15 | uint8 private decimals_; 16 | IMinterDAO public minterDAO; 17 | 18 | constructor( 19 | address _minterDAO, 20 | string memory _name, 21 | string memory _symbol, 22 | uint8 _decimals 23 | ) ERC20(_name, _symbol) { 24 | minterDAO = IMinterDAO(_minterDAO); 25 | decimals_ = _decimals; 26 | } 27 | 28 | function decimals() public view virtual override returns (uint8) { 29 | return decimals_; 30 | } 31 | 32 | function mint(address _to, uint256 _amount) public returns (bool) { 33 | require(minterDAO.isMinter(msg.sender, address(this)), "not the minter"); 34 | require(_amount != 0, "amount is 0"); 35 | _mint(_to, _amount); 36 | return true; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /contracts/v0.2/CrosschainERC20V2Pair.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 7 | 8 | interface IERC20Mintable { 9 | function mint(address recipient, uint256 amount) external; 10 | 11 | function burnFrom(address account, uint256 amount) external; 12 | } 13 | 14 | contract CrosschainERC20V2Pair is Ownable { 15 | using SafeERC20 for IERC20; 16 | enum ScaleType{ SAME, UP, DOWN } 17 | event CreditIncreased(uint256 amount); 18 | event CreditReduced(uint256 amount); 19 | 20 | IERC20Mintable public crosschainToken; 21 | IERC20 public token; 22 | uint256 public immutable scale; 23 | ScaleType public immutable scaleType; 24 | uint256 public totalTokenAmount; 25 | uint256 public remainingCredit; 26 | 27 | constructor(address _crosschainToken, uint8 _crosschainTokenDecimals, address _token, uint8 _tokenDecimals, address _owner) { 28 | crosschainToken = IERC20Mintable(_crosschainToken); 29 | token = IERC20(_token); 30 | ScaleType st = ScaleType.SAME; 31 | uint256 s = 1; 32 | if (_tokenDecimals > _crosschainTokenDecimals) { 33 | st = ScaleType.DOWN; 34 | s = 10 ** (_tokenDecimals - _crosschainTokenDecimals); 35 | } 36 | if (_crosschainTokenDecimals > _tokenDecimals) { 37 | st = ScaleType.UP; 38 | s = 10 ** (_crosschainTokenDecimals - _tokenDecimals); 39 | } 40 | scaleType = st; 41 | scale = s; 42 | _transferOwnership(_owner); 43 | } 44 | 45 | function calculateDepositValues(uint256 _amount) public view returns (uint256, uint256) { 46 | uint256 mintAmount = _amount; 47 | if (scaleType == ScaleType.UP) { 48 | mintAmount = _amount * scale; 49 | } else if (scaleType == ScaleType.DOWN) { 50 | mintAmount = _amount / scale; 51 | _amount = mintAmount * scale; 52 | } 53 | return (_amount, mintAmount); 54 | } 55 | 56 | function calculateWithdrawValues(uint256 _amount) public view returns (uint256, uint256) { 57 | uint256 transferAmount = _amount; 58 | if (scaleType == ScaleType.UP) { 59 | transferAmount = _amount / scale; 60 | _amount = transferAmount * scale; 61 | } else if (scaleType == ScaleType.DOWN) { 62 | transferAmount = _amount * scale; 63 | } 64 | return (_amount, transferAmount); 65 | } 66 | 67 | function _deposit(address _sender, uint256 _depositAmount, address _recipient, uint256 _mintAmount) internal { 68 | require(_depositAmount != 0 && _mintAmount != 0, "invalid amount"); 69 | token.safeTransferFrom(_sender, address(this), _depositAmount); 70 | totalTokenAmount += _depositAmount; 71 | remainingCredit -= _mintAmount; 72 | crosschainToken.mint(_recipient, _mintAmount); 73 | } 74 | 75 | function deposit(uint256 _amount) external returns (uint256 inAmount_, uint256 outAmount_) { 76 | return depositTo(msg.sender, _amount); 77 | } 78 | 79 | function depositTo(address _to, uint256 _amount) public returns (uint256 inAmount_, uint256 outAmount_) { 80 | (inAmount_, outAmount_) = calculateDepositValues(_amount); 81 | _deposit(msg.sender, inAmount_, _to, outAmount_); 82 | } 83 | 84 | function depositNoRounding(uint256 _amount) external returns (uint256 inAmount_, uint256 outAmount_) { 85 | return depositToNoRounding(msg.sender, _amount); 86 | } 87 | 88 | function depositToNoRounding(address _to, uint256 _amount) public returns (uint256 inAmount_, uint256 outAmount_) { 89 | (inAmount_, outAmount_) = calculateDepositValues(_amount); 90 | require(inAmount_ == _amount, "no rounding"); 91 | _deposit(msg.sender, inAmount_, _to, outAmount_); 92 | } 93 | 94 | function _withdraw(address _sender, uint256 _burnAmount, address _recipient, uint256 _transferAmount) internal { 95 | require(_burnAmount != 0 && _transferAmount != 0, "invalid amount"); 96 | crosschainToken.burnFrom(_sender, _burnAmount); 97 | token.safeTransfer(_recipient, _transferAmount); 98 | totalTokenAmount -= _transferAmount; 99 | remainingCredit += _burnAmount; 100 | } 101 | 102 | function withdraw(uint256 _amount) external returns (uint256 inAmount_, uint256 outAmount_) { 103 | return withdrawTo(msg.sender, _amount); 104 | } 105 | 106 | function withdrawTo(address _to, uint256 _amount) public returns (uint256 inAmount_, uint256 outAmount_) { 107 | (inAmount_, outAmount_) = calculateWithdrawValues(_amount); 108 | _withdraw(msg.sender, inAmount_, _to, outAmount_); 109 | } 110 | 111 | function withdrawNoRounding(uint256 _amount) external returns (uint256 inAmount_, uint256 outAmount_) { 112 | return withdrawToNoRounding(msg.sender, _amount); 113 | } 114 | 115 | function withdrawToNoRounding(address _to, uint256 _amount) public returns (uint256 inAmount_, uint256 outAmount_) { 116 | (inAmount_, outAmount_) = calculateWithdrawValues(_amount); 117 | require(inAmount_ == _amount, "no rounding"); 118 | _withdraw(msg.sender, inAmount_, _to, outAmount_); 119 | } 120 | 121 | function increaseCredit(uint256 _amount) external onlyOwner { 122 | remainingCredit += _amount; 123 | emit CreditIncreased(_amount); 124 | } 125 | 126 | function reduceCredit(uint256 _amount) external onlyOwner { 127 | remainingCredit -= _amount; 128 | emit CreditReduced(_amount); 129 | } 130 | 131 | function adhocWithdraw(address _token, uint256 _amount) external onlyOwner { 132 | require(_token != address(token) || _amount < token.balanceOf(address(this)) - totalTokenAmount, "invalid amount"); 133 | IERC20(_token).safeTransfer(msg.sender, _amount); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /contracts/v0.2/ERC20Tube.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "./EmergencyOperator.sol"; 6 | import "@openzeppelin/contracts/access/Ownable.sol"; 7 | import "@openzeppelin/contracts/utils/Address.sol"; 8 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 9 | import "@openzeppelin/contracts/security/Pausable.sol"; 10 | import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; 11 | 12 | interface ILedger { 13 | function get(bytes32 _key) external view returns (uint256); 14 | function record(bytes32 _key) external; 15 | } 16 | 17 | interface ILord { 18 | function mint(address _token, address _recipient, uint256 _amount) external; 19 | } 20 | 21 | interface IBurnableERC20 { 22 | function burnFrom(address _owner, uint256 _amount) external; 23 | } 24 | 25 | interface IVerifier { 26 | function verify(bytes32 _key, bytes memory _signatures) 27 | external 28 | view 29 | returns (bool isValid_, address[] memory validators_); 30 | } 31 | 32 | contract ERC20Tube is Ownable, Pausable, ReentrancyGuard { 33 | using SafeERC20 for IERC20; 34 | 35 | event LordUpdated(address indexed lord); 36 | event SafeUpdated(address indexed safe); 37 | event TubeInfoUpdated(uint256 tubeID, uint256 feeRate, bool enabled); 38 | event Settled(bytes32 indexed key, address[] validators); 39 | event Receipt( 40 | uint256 indexed nonce, 41 | address sender, 42 | address indexed token, 43 | uint256 amount, 44 | uint256 indexed targetTubeID, 45 | address recipient, 46 | uint256 fee 47 | ); 48 | struct TubeInfo { 49 | uint256 rate; 50 | bool enabled; 51 | } 52 | 53 | EmergencyOperator public emergencyOperator; 54 | uint256 public tubeID; 55 | ILedger public ledger; 56 | ILord public lord; 57 | IVerifier public verifier; 58 | address public safe; 59 | uint256 public nonce; 60 | mapping(uint256 => TubeInfo) private tubeInfos; 61 | 62 | constructor( 63 | uint256 _tubeID, 64 | ILedger _ledger, 65 | ILord _lord, 66 | IVerifier _verifier, 67 | address _safe, 68 | uint256 _initNonce, 69 | address _emergencyOperator 70 | ) ReentrancyGuard() { 71 | tubeID = _tubeID; 72 | ledger = _ledger; 73 | lord = _lord; 74 | emit LordUpdated(address(_lord)); 75 | verifier = _verifier; 76 | safe = _safe; 77 | emit SafeUpdated(address(_safe)); 78 | nonce = _initNonce; 79 | emergencyOperator = EmergencyOperator(_emergencyOperator); 80 | } 81 | 82 | function destinationTubeInfo(uint256 _tubeID) public view returns (TubeInfo memory) { 83 | return tubeInfos[_tubeID]; 84 | } 85 | 86 | function depositTo( 87 | address _token, 88 | uint256 _amount, 89 | uint256 _targetTubeID, 90 | address _to 91 | ) public nonReentrant whenNotPaused { 92 | require(_to != address(0), "invalid recipient"); 93 | require(_amount > 0, "invalid amount"); 94 | // TODO: a whitelist of token? 95 | TubeInfo memory dst = tubeInfos[_targetTubeID]; 96 | require(dst.enabled, "invalid destination"); 97 | uint256 fee = 0; 98 | if (dst.rate > 0) { 99 | fee = _amount * dst.rate / 10000; 100 | if (fee > 0) { 101 | _amount -= fee; 102 | IERC20(_token).safeTransferFrom(msg.sender, safe, fee); 103 | } 104 | } 105 | IBurnableERC20(_token).burnFrom(msg.sender, _amount); 106 | emit Receipt(nonce++, msg.sender, _token, _amount, _targetTubeID, _to, fee); 107 | } 108 | 109 | function deposit( 110 | address _token, 111 | uint256 _amount, 112 | uint256 _targetTubeID 113 | ) public { 114 | depositTo(_token, _amount, _targetTubeID, msg.sender); 115 | } 116 | 117 | function genKey( 118 | uint256 _srcTubeID, 119 | uint256 _nonce, 120 | address _token, 121 | address _recipient, 122 | uint256 _amount 123 | ) public view returns (bytes32) { 124 | return keccak256(abi.encodePacked(_srcTubeID, _nonce, tubeID, _token, _amount, _recipient)); 125 | } 126 | 127 | function isSettled(bytes32 key) public view returns (bool) { 128 | return ledger.get(key) != 0; 129 | } 130 | 131 | function withdraw( 132 | uint256 _srcTubeID, 133 | uint256 _nonce, 134 | address _token, 135 | address _recipient, 136 | uint256 _amount, 137 | bytes memory _signatures 138 | ) public nonReentrant whenNotPaused { 139 | require(_amount != 0, "amount is 0"); 140 | require(_recipient != address(0), "invalid recipient"); 141 | require(_signatures.length % 65 == 0, "invalid signature length"); 142 | bytes32 key = genKey(_srcTubeID, _nonce, _token, _recipient, _amount); 143 | ledger.record(key); 144 | (bool isValid, address[] memory signers) = verifier.verify(key, _signatures); 145 | require(isValid, "insufficient validators"); 146 | lord.mint(_token, _recipient, _amount); 147 | emit Settled(key, signers); 148 | } 149 | 150 | function setDestinationTube(uint256 _tubeID, uint256 _feeRate, bool _enabled) public onlyOwner { 151 | require(_feeRate <= 10000, "invalid fee rate"); 152 | tubeInfos[_tubeID] = TubeInfo(_feeRate, _enabled); 153 | emit TubeInfoUpdated(_tubeID, _feeRate, _enabled); 154 | } 155 | 156 | function withdrawCoin(address payable _to) external onlyOwner { 157 | Address.sendValue(_to, address(this).balance); 158 | } 159 | 160 | function withdrawToken(address _to, IERC20 _token) external onlyOwner { 161 | uint256 balance = _token.balanceOf(address(this)); 162 | if (balance > 0) { 163 | _token.safeTransfer(_to, balance); 164 | } 165 | } 166 | 167 | function setLord(ILord _lord) external onlyOwner { 168 | lord = _lord; 169 | emit LordUpdated(address(_lord)); 170 | } 171 | 172 | function setSafe(address _safe) external onlyOwner { 173 | safe = _safe; 174 | emit SafeUpdated(_safe); 175 | } 176 | 177 | function pause() public { 178 | require(emergencyOperator.isEmergencyOperator(msg.sender), "no permission"); 179 | _pause(); 180 | } 181 | 182 | function unpause() public { 183 | require(emergencyOperator.isEmergencyOperator(msg.sender), "no permission"); 184 | _unpause(); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /contracts/v0.2/ERC20TubeRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 7 | import "@openzeppelin/contracts/utils/Address.sol"; 8 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 9 | import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; 10 | 11 | interface ITube { 12 | function depositTo( 13 | address _token, 14 | uint256 _amount, 15 | uint256 _targetTubeID, 16 | address _to 17 | ) external; 18 | 19 | function fees(uint256 _tubeID) external view returns (uint256); 20 | 21 | function tubeToken() external view returns (IERC20); 22 | } 23 | 24 | interface ICrosschainERC20V2Pair { 25 | function calculateDepositValues(uint256 _amount) external view returns (uint256, uint256); 26 | function deposit(uint256 _amount) external returns (uint256, uint256); 27 | function token() external view returns (IERC20); 28 | function crosschainToken() external view returns (IERC20); 29 | } 30 | 31 | contract ERC20TubeRouter is Ownable, ReentrancyGuard { 32 | using SafeERC20 for IERC20; 33 | event RelayFeeReceipt(address indexed user, address indexed token, uint256 indexed targetTubeID, uint256 amount); 34 | 35 | struct Setting { 36 | bool exists; 37 | uint256 fee; 38 | } 39 | mapping(uint256 => Setting) public settings; 40 | address payable public safe; 41 | ITube public tube; 42 | 43 | constructor(ITube _tube, address payable _safe) { 44 | tube = _tube; 45 | safe = _safe; 46 | } 47 | 48 | function setRelayFee(uint256 _tubeID, bool _active, uint256 _fee) external onlyOwner { 49 | settings[_tubeID] = Setting(_active, _fee); 50 | } 51 | 52 | function setSafe(address payable _safe) external onlyOwner { 53 | safe = _safe; 54 | } 55 | 56 | function depositToWithToken( 57 | address _crosschainERC20Pair, 58 | uint256 _amount, 59 | uint256 _tubeID, 60 | address _recipient 61 | ) external payable nonReentrant { 62 | Setting memory setting = settings[_tubeID]; 63 | require(setting.exists, "destination is inactive"); 64 | if (setting.fee > 0) { 65 | require(msg.value >= setting.fee, "insufficient relay fee"); 66 | safe.transfer(msg.value); 67 | } 68 | 69 | ICrosschainERC20V2Pair pair = ICrosschainERC20V2Pair(_crosschainERC20Pair); 70 | IERC20 token = pair.token(); 71 | require(address(token) != address(0), "invalid token"); 72 | (uint256 chargeAmount, uint256 mintAmount) = pair.calculateDepositValues(_amount); 73 | token.safeTransferFrom(msg.sender, address(this), chargeAmount); 74 | token.safeApprove(_crosschainERC20Pair, chargeAmount); 75 | (uint256 inAmount, uint256 outAmount) = pair.deposit(chargeAmount); 76 | require(inAmount == chargeAmount && outAmount == mintAmount, "invalid status"); 77 | 78 | IERC20 crosschainToken = pair.crosschainToken(); 79 | crosschainToken.safeApprove(address(tube), mintAmount); 80 | tube.depositTo(address(crosschainToken), mintAmount, _tubeID, _recipient); 81 | emit RelayFeeReceipt(msg.sender, address(crosschainToken), _tubeID, setting.fee); 82 | } 83 | 84 | function depositTo( 85 | address _crosschainToken, 86 | uint256 _amount, 87 | uint256 _tubeID, 88 | address _recipient 89 | ) external payable nonReentrant { 90 | Setting memory setting = settings[_tubeID]; 91 | require(setting.exists, "destination is inactive"); 92 | if (setting.fee > 0) { 93 | require(msg.value >= setting.fee, "insufficient relay fee"); 94 | safe.transfer(msg.value); 95 | } 96 | 97 | IERC20(_crosschainToken).safeTransferFrom(msg.sender, address(this), _amount); 98 | IERC20(_crosschainToken).safeApprove(address(tube), _amount); 99 | tube.depositTo(_crosschainToken, _amount, _tubeID, _recipient); 100 | emit RelayFeeReceipt(msg.sender, _crosschainToken, _tubeID, setting.fee); 101 | } 102 | 103 | function withdrawCoin(address payable _to) external onlyOwner { 104 | Address.sendValue(_to, address(this).balance); 105 | } 106 | 107 | function withdrawToken(address _to, IERC20 _token) external onlyOwner { 108 | uint256 balance = _token.balanceOf(address(this)); 109 | if (balance > 0) { 110 | _token.safeTransfer(_to, balance); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /contracts/v0.2/EmergencyOperator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "./OwnedUpgradeable.sol"; 6 | 7 | contract EmergencyOperator is OwnedUpgradeable { 8 | mapping(address => bool) private operators; 9 | 10 | event EmergencyOperatorAdded(address indexed operator); 11 | event EmergencyOperatorRemoved(address indexed operator); 12 | 13 | function initialize() public initializer { 14 | __Owned_init(); 15 | } 16 | 17 | modifier onlyEmergencyOperator() { 18 | require(isEmergencyOperator(msg.sender), "caller is not emergency operator"); 19 | _; 20 | } 21 | 22 | function addEmergencyOperator(address _newOperator) external onlyOwner { 23 | require(!operators[_newOperator], "already an operator"); 24 | operators[_newOperator] = true; 25 | emit EmergencyOperatorAdded(_newOperator); 26 | } 27 | 28 | function removeEmergencyOperator(address _newOperator) external onlyOwner { 29 | require(operators[_newOperator], "not an operator"); 30 | operators[_newOperator] = false; 31 | emit EmergencyOperatorRemoved(_newOperator); 32 | } 33 | 34 | function isEmergencyOperator(address _operator) public view returns (bool) { 35 | return operators[_operator]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /contracts/v0.2/LedgerV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | 7 | contract LedgerV2 is Ownable { 8 | event OperatorAdded(address indexed operator); 9 | event OperatorRemoved(address indexed operator); 10 | 11 | mapping(address => bool) public operators; 12 | mapping(bytes32 => uint256) public records; 13 | 14 | function addOperator(address operator) public onlyOwner { 15 | require(!operators[operator], "already an operator"); 16 | operators[operator] = true; 17 | emit OperatorAdded(operator); 18 | } 19 | 20 | function removeOperator(address operator) public onlyOwner { 21 | require(operators[operator], "not an operator"); 22 | operators[operator] = false; 23 | emit OperatorRemoved(operator); 24 | } 25 | 26 | function record(bytes32 id) public { 27 | require(operators[msg.sender], "invalid operator"); 28 | require(records[id] == 0, "duplicate record"); 29 | records[id] = block.number; 30 | } 31 | 32 | function get(bytes32 id) public view returns (uint256) { 33 | return records[id]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /contracts/v0.2/LordV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 6 | import "@openzeppelin/contracts/utils/Address.sol"; 7 | 8 | import "./OwnedUpgradeable.sol"; 9 | 10 | interface IERC20Mintable { 11 | function mint(address recipient, uint256 amount) external; 12 | } 13 | 14 | contract LordV2 is Initializable, OwnedUpgradeable { 15 | event OperatorAdded(address indexed operator); 16 | event OperatorRemoved(address indexed operator); 17 | 18 | mapping(address => bool) private operators; 19 | 20 | function initialize() public initializer { 21 | __Owned_init(); 22 | } 23 | 24 | function addOperator(address _operator) public onlyOwner { 25 | require(!operators[_operator], "already an operator"); 26 | operators[_operator] = true; 27 | emit OperatorAdded(_operator); 28 | } 29 | 30 | function removeOperator(address _operator) public onlyOwner { 31 | require(operators[_operator], "not an operator"); 32 | operators[_operator] = false; 33 | emit OperatorRemoved(_operator); 34 | } 35 | 36 | function isOperator(address _operator) public view returns (bool) { 37 | return operators[_operator]; 38 | } 39 | 40 | function mint( 41 | IERC20Mintable _token, 42 | address _recipient, 43 | uint256 _amount 44 | ) public { 45 | require(isOperator(msg.sender), "invalid operator"); 46 | _token.mint(_recipient, _amount); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /contracts/v0.2/MinterDAO.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "./EmergencyOperator.sol"; 6 | import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 7 | import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; 8 | 9 | contract MinterDAO is OwnableUpgradeable, PausableUpgradeable { 10 | event NewLord(address indexed lord); 11 | event MinterAdded(address indexed minter, address indexed token); 12 | event MinterRemoved(address indexed minter, address indexed token); 13 | 14 | EmergencyOperator public emergencyOperator; 15 | address public lord; 16 | mapping(address => mapping(address => bool)) private minters; 17 | 18 | function initialize(address _lord, address _emergencyOperator) public initializer { 19 | __Ownable_init(); 20 | __Pausable_init(); 21 | lord = _lord; 22 | emergencyOperator = EmergencyOperator(_emergencyOperator); 23 | emit NewLord(_lord); 24 | } 25 | 26 | function addMinter(address _minter, address _token) external onlyOwner { 27 | require(_minter != address(0), "invalid minter address"); 28 | require(_token != address(0), "invalid token address"); 29 | require(!minters[_minter][_token], "already a minter"); 30 | minters[_minter][_token] = true; 31 | emit MinterAdded(_minter, _token); 32 | } 33 | 34 | function removeMinter(address _minter, address _token) external onlyOwner { 35 | require(minters[_minter][_token], "not a minter"); 36 | minters[_minter][_token] = false; 37 | emit MinterRemoved(_minter, _token); 38 | } 39 | 40 | function isMinter(address _account, address _token) external view whenNotPaused returns (bool) { 41 | return _account == lord || minters[_account][_token]; 42 | } 43 | 44 | function pause() external { 45 | require(emergencyOperator.isEmergencyOperator(msg.sender), "no permission"); 46 | _pause(); 47 | } 48 | 49 | function unpause() external { 50 | require(emergencyOperator.isEmergencyOperator(msg.sender), "no permission"); 51 | _unpause(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /contracts/v0.2/OwnedUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 6 | 7 | abstract contract OwnedUpgradeable is Initializable { 8 | event CandidateOwnerNominated(address candidate); 9 | event OwnershipTransferred(address owner); 10 | 11 | address public owner; 12 | address public candidateOwner; 13 | 14 | modifier onlyOwner { 15 | require(msg.sender == owner, "caller is not the owner"); 16 | _; 17 | } 18 | 19 | function __Owned_init() internal onlyInitializing { 20 | owner = msg.sender; 21 | emit OwnershipTransferred(msg.sender); 22 | } 23 | 24 | function transferOwnership(address _newOwner) public onlyOwner { 25 | require(_newOwner != address(0), "invalid owner address"); 26 | candidateOwner = _newOwner; 27 | emit CandidateOwnerNominated(_newOwner); 28 | } 29 | 30 | function acceptOwnership() public { 31 | require(msg.sender == candidateOwner, "not candidate owner"); 32 | owner = msg.sender; 33 | candidateOwner = address(0); 34 | emit OwnershipTransferred(msg.sender); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /contracts/v0.2/VerifierV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "./EmergencyOperator.sol"; 6 | import "@openzeppelin/contracts/access/Ownable.sol"; 7 | import "@openzeppelin/contracts/security/Pausable.sol"; 8 | 9 | contract VerifierV2 is Ownable, Pausable { 10 | event ValidatorAdded(address indexed validator); 11 | event ValidatorRemoved(address indexed validator); 12 | 13 | uint8 public constant VALIDATOR_LIMIT = 50; 14 | 15 | EmergencyOperator public emergencyOperator; 16 | address[] public validators; 17 | mapping(address => uint8) private validatorIndexes; 18 | 19 | constructor(address _emergencyOperator) { 20 | emergencyOperator = EmergencyOperator(_emergencyOperator); 21 | } 22 | 23 | function pause() external { 24 | require(emergencyOperator.isEmergencyOperator(msg.sender), "no permission"); 25 | _pause(); 26 | } 27 | 28 | function unpause() external { 29 | require(emergencyOperator.isEmergencyOperator(msg.sender), "no permission"); 30 | _unpause(); 31 | } 32 | 33 | function size() public view returns (uint256) { 34 | return validators.length; 35 | } 36 | 37 | function get(uint256 offset, uint8 limit) public view returns (uint256 count_, address[] memory validators_) { 38 | count_ = validators.length; 39 | validators_ = new address[](limit); 40 | for (uint256 i = 0; i < limit; i++) { 41 | if (offset + i >= validators.length) { 42 | break; 43 | } 44 | validators_[i] = validators[offset + i]; 45 | } 46 | } 47 | 48 | function addAll(address[] memory _validators) public onlyOwner { 49 | require(validators.length + _validators.length < VALIDATOR_LIMIT, "hit max validator limit"); 50 | address validator; 51 | for (uint256 i = 0; i < _validators.length; i++) { 52 | validator = _validators[i]; 53 | require(validator != address(0), "invalid validator"); 54 | if (validatorIndexes[validator] != 0) { 55 | continue; 56 | } 57 | validators.push(validator); 58 | validatorIndexes[validator] = uint8(validators.length); 59 | emit ValidatorAdded(validator); 60 | } 61 | } 62 | 63 | function removeAll(address[] memory _validators) public onlyOwner { 64 | address validator; 65 | address last; 66 | uint8 index; 67 | for (uint256 i = 0; i < _validators.length; i++) { 68 | validator = _validators[i]; 69 | index = validatorIndexes[validator]; 70 | if (index == 0) { 71 | continue; 72 | } 73 | last = validators[validators.length - 1]; 74 | validators[index - 1] = last; 75 | validatorIndexes[last] = index; 76 | validators.pop(); 77 | delete validatorIndexes[validator]; 78 | emit ValidatorRemoved(validator); 79 | } 80 | } 81 | 82 | function verify(bytes32 _key, bytes memory _signatures) 83 | public 84 | view 85 | whenNotPaused 86 | returns (bool isValid_, address[] memory validators_) 87 | { 88 | uint256 numOfSignatures = _signatures.length / 65; 89 | bool[VALIDATOR_LIMIT] memory seen; 90 | address validator; 91 | uint8 index; 92 | validators_ = new address[](numOfSignatures); 93 | for (uint256 i = 0; i < numOfSignatures; i++) { 94 | validator = recover(_key, _signatures, i * 65); 95 | index = validatorIndexes[validator]; 96 | require(index != 0, "invalid validator"); 97 | require(!seen[index], "duplicate validator"); 98 | seen[index] = true; 99 | validators_[i] = validator; 100 | } 101 | isValid_ = validators_.length * 3 > validators.length * 2; 102 | } 103 | 104 | function recover( 105 | bytes32 hash, 106 | bytes memory signature, 107 | uint256 offset 108 | ) internal pure returns (address) { 109 | bytes32 r; 110 | bytes32 s; 111 | uint8 v; 112 | 113 | // Divide the signature in r, s and v variables with inline assembly. 114 | 115 | // solium-disable-next-line security/no-inline-assembly 116 | assembly { 117 | r := mload(add(signature, add(offset, 0x20))) 118 | s := mload(add(signature, add(offset, 0x40))) 119 | v := byte(0, mload(add(signature, add(offset, 0x60)))) 120 | } 121 | 122 | // Version of signature should be 27 or 28, but 0 and 1 are also possible versions 123 | if (v < 27) { 124 | v += 27; 125 | } 126 | 127 | // If the version is correct return the signer address 128 | if (v != 27 && v != 28) { 129 | return (address(0)); 130 | } 131 | // solium-disable-next-line arg-overflow 132 | return ecrecover(hash, v, r, s); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /deployments/bsc.json: -------------------------------------------------------------------------------- 1 | { 2 | "emergencyOperator": "0x3664f368dE9F5f04cD3165F4cFE04c8e4099ddBa", 3 | "lord": "0xF9E4Dff1d01E44a44a64Df5450F513fbD19326C2", 4 | "ledger": "0x47AC439e3cC41fD261BC96bAf4311a266883772a", 5 | "verifier": "0x077ad1bF8E10d2d4eBeD0265E125588d48142c4E", 6 | "tube": "0x10eA161Ab39aAcb58b7c00e87aB141d9AbCB0B70", 7 | "minterDAO": "0xC7d50D0aF7F19C016FE6469Eef73803E7Be0741f", 8 | "crosschainERC20Factory": "0x5465b93088eD24b81e31FA76256d5b4A64447295", 9 | "erc20TubeRouter": "0xE97720A1F0e5534a1474ca4D58b0E41FC1072541", 10 | "crosschainToken": { 11 | "cUSDT": "0xfEC51632aF0CF8075e6F391b5F7dC33E28B375C4", 12 | "cUSDC": "0x100Cb68fdEA6Fd2D1C3EC29C06Dd35A63f29547A", 13 | "cBUSD": "0x8D9EFF68052e8F9Ec89179D1402eB57Dbd6d5048" 14 | }, 15 | "pairs": { 16 | "cUSDT": [ 17 | "0x6f427e3Ce9b0E37314A4E2dbb70A125C185D43a5" 18 | ], 19 | "cUSDC": [ 20 | "0x32Cd8B9c114a1A61aD3DCB107802240d2fC9878c" 21 | ], 22 | "cBUSD": [ 23 | "0x8ffc6C03441A2E810950E175C96915bB58221742" 24 | ] 25 | } 26 | } -------------------------------------------------------------------------------- /deployments/iotex.json: -------------------------------------------------------------------------------- 1 | { 2 | "emergencyOperator": "0x82FFbd3166709D2BEC737FEDe7A5c53C12474D79", 3 | "lord": "0xA077a6F3042B89480b8A308d4A5923d2C99Bd147", 4 | "ledger": "0x5F3176290E5d7517689E62b4576E22B78d65ffb3", 5 | "verifier": "0xc0D539311BfABaef585880eA8C33E8C0F46EA40F", 6 | "tube": "0x9E3A18F6FB6a89aD7dC09f496CD497bCcb93D13B", 7 | "minterDAO": "0xC7E0792aDDEc37cC37B0Fd96DD6C5be35cC1cc15", 8 | "crosschainERC20Factory": "0xe57EE736cCD6fE54BC4bd81D9a2BA4AEfCdC3a9F", 9 | "erc20TubeRouter": "0x3D839085F06e79fA5CCf2FbCBBd30747d96fCfC8", 10 | "crosschainToken": { 11 | "cUSDT": "0x32492aC61580e7E42317579D43aB7C921C9406c4", 12 | "cUSDC": "0xC7c88394202C3CCe3d54557Ea60DDfDbB4a943Ac", 13 | "cBUSD": "0x144220970AC713b32398D82db2F7947AD2eB01e2" 14 | }, 15 | "pairs": { 16 | "cUSDT": [ 17 | "0x96AC081785D9a88Fd714c689C70124aC78cb5445", 18 | "0xcE46ebAe7a3Bef3F0c9Cc3Bd4a6D674fF844A489", 19 | "0x3B3E17F66e34E261cD9b8ADb33c46e2492b2288a" 20 | ], 21 | "cUSDC": [ 22 | "0x0b2c5b215232Fcb7E079d886dB4964784da4236a", 23 | "0x588f9d97653Bb946A9eAFd8980c4c0f29f2d9dd3", 24 | "0x4E98851C3A6D5684e8Ea3c1489a3836d1E9E660a" 25 | ], 26 | "cBUSD": [ 27 | "0x7D43e49562Eb9982e9804F2CC05E62961907DCea", 28 | "0x05B0c01c1aA5F3a18D8EF151d1bB82AD8CC5A217" 29 | ] 30 | } 31 | } -------------------------------------------------------------------------------- /deployments/iotex.registry.json: -------------------------------------------------------------------------------- 1 | { 2 | "assetRegistry": "0xF0BDBEd6Eb1b4A701C5C0573C103A7677Fb08ed9", 3 | "validatorRegistry": "0x7Fba91f90c5666d887254b39765192f0DaBB4977" 4 | } -------------------------------------------------------------------------------- /deployments/iotex_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "emergencyOperator": "0x8bD20A77190D8f5Ed1dA34e225F010941556A5CB", 3 | "lord": "0xad180Ae4A08a28DED2d76FAa9828e8361dda33Dc", 4 | "ledger": "0x0B8273C30aB33A7caB8ffBf76ca70cD004C35A48", 5 | "verifier": "0x43a52606d1930558d2D3D232E58f4bE35148B663", 6 | "tube": "0xAce2D45673D17D1852583B57B12f2eb2C16b111D", 7 | "minterDAO": "0x54e73abC43F43F11F620BF8389b4E95FD8A8d617", 8 | "crosschainERC20Factory": "0x7D97098397a67B0d3EA25804C2FC0aD80c59962F", 9 | "erc20TubeRouter": "0x36D13A012a8870b3944bE52B5acF18D6D84A0351", 10 | "crosschainToken": { 11 | "cUSDT": "0xD2ca972B579efe29a5501859E5f1420a672565bd" 12 | }, 13 | "pairs": { 14 | "cUSDT": [ 15 | "0x45B90EA2B8A9A964D8567ADAad15aDF874fD26c1", 16 | "0x209a1945b9a995842bE2a099e2CFb6E6b3C4d3F4" 17 | ] 18 | } 19 | } -------------------------------------------------------------------------------- /deployments/iotex_test.registry.json: -------------------------------------------------------------------------------- 1 | { 2 | "assetRegistry": "0x00c869bD06A4aF76a8529724D864e1dcA2B8A518", 3 | "validatorRegistry": "0xFC7868A2006488C2A1eb27f5f0c35659d303F47d" 4 | } -------------------------------------------------------------------------------- /deployments/kovan.json: -------------------------------------------------------------------------------- 1 | { 2 | "emergencyOperator": "0xC60227810C66c191B814D487C811ef1101a874Da", 3 | "lord": "0x5DdbebA0190F17aC5475d100Fcc6623de5eb8E9A", 4 | "ledger": "0x3F1D54244a7b30F538Ca23Be333B7887cD896350", 5 | "verifier": "0x05d06EeDd025f6907fEa50a2E665DdB53E137b12", 6 | "tube": "0x550bDE475bE37AdE7C710321DD2ae523Db2F2f4c", 7 | "minterDAO": "0xbF277F75c12a6aECBFEf3AFD615B3C786B2c5e89", 8 | "crosschainERC20Factory": "0xA57A9Df012954371998B2f5B71d26f3344e023c1", 9 | "erc20TubeRouter": "0x8468464E9b34b1C14a2466390d1e89E8a84b5cAF", 10 | "crosschainToken": { 11 | "cUSDT": "0x7F0cCc21EeF43236496697f1Cdc5b6270adC45bd" 12 | }, 13 | "pairs": { 14 | "cUSDT": [ 15 | "0x7e9A2fbd0D879452E64Bcd0E3f1158CBf51b3EaF" 16 | ] 17 | } 18 | } -------------------------------------------------------------------------------- /deployments/mainnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "emergencyOperator": "0x02554E65A1E4200c0df23C3a449a416803a69A53", 3 | "lord": "0xbeACd46feE2977b55849e4608927752fCcaf490B", 4 | "ledger": "0x646FF95a708a2901e52847a0dF0DF1C66d8B4350", 5 | "verifier": "0x3f49f6745c5933bd61A6D101EB1998A445C5Ef67", 6 | "tube": "0xFEc911eECDB790602f93ed37d5f99bdB0e3C608A", 7 | "minterDAO": "0x9EF7C039D6bffA8b49594A126c71b620448423cc", 8 | "crosschainERC20Factory": "0x838fc8EFe2e1C512EC3dDA55d496C8A9E373842E", 9 | "erc20TubeRouter": "0xF7653111F7480ED46D1F9f419188BCaFcfc94a6f", 10 | "crosschainToken": { 11 | "cIOTX": "0x2298B0129208CA20ecD3b94335BCcb0817449e55" 12 | } 13 | } -------------------------------------------------------------------------------- /deployments/polygon.json: -------------------------------------------------------------------------------- 1 | { 2 | "emergencyOperator": "0xA34Df71026b5979A4815DC5F6C8d82c39bb6c75B", 3 | "lord": "0xCE41af82dF30894a82Eef771743e75FA9B57cF58", 4 | "ledger": "0x9d11C74079D9ebBf0283Bd7bbebA8c0525a73EC0", 5 | "verifier": "0xC008725Da7f3A9E46BE5Ab6f9F46Dd769E8448F9", 6 | "tube": "0x8e278a7eAAB13965300277a952E8950847CeC6fb", 7 | "minterDAO": "0x63e509f7e8037DAe79332bB48239E4e700fE7A28", 8 | "crosschainERC20Factory": "0xe13b64B273c8161281F5DB4387E3C981C5FBC3a1", 9 | "erc20TubeRouter": "0x6DA45C956A0A9e0a4598Bb7b41Ca793004102E27", 10 | "crosschainToken": { 11 | "cUSDT": "0x81B1995286A5778bEA691f6A77552CA462CCCbe9", 12 | "cUSDC": "0x7AaB7e65348d1B22496ff67bF8680358C3684128" 13 | }, 14 | "pairs": { 15 | "cUSDT": [ 16 | "0x286cdB2d692c0fB767Cd942F4e9b36118886e297" 17 | ], 18 | "cUSDC": [ 19 | "0x4B31369B2d5ecbeAd16B78264CdE5D9d77E4A26c" 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import "@typechain/hardhat" 2 | import "@nomiclabs/hardhat-ethers" 3 | import "@nomiclabs/hardhat-waffle" 4 | import "@nomiclabs/hardhat-etherscan" 5 | import "@openzeppelin/hardhat-upgrades" 6 | import "solidity-coverage" 7 | import "hardhat-gas-reporter" 8 | import "hardhat-contract-sizer" 9 | import "dotenv/config" 10 | 11 | const MAINNET_RPC_URL = "https://rpc.ankr.com/eth" 12 | const FORKING_BLOCK_NUMBER = process.env.FORKING_BLOCK_NUMBER || "0" 13 | const PRIVATE_KEY = process.env.PRIVATE_KEY 14 | const REPORT_GAS = process.env.REPORT_GAS || false 15 | 16 | const accounts = [ 17 | process.env.PRIVATE_KEY || "0x0000000000000000000000000000000000000000000000000000000000000000", 18 | ] 19 | 20 | export default { 21 | namedAccounts: { 22 | deployer: { 23 | default: 0, 24 | }, 25 | }, 26 | defaultNetwork: "hardhat", 27 | networks: { 28 | hardhat: { 29 | forking: { 30 | url: MAINNET_RPC_URL, 31 | blockNumber: Number(FORKING_BLOCK_NUMBER), 32 | enabled: false, 33 | }, 34 | }, 35 | kovan: { 36 | url: `https://kovan.infura.io/v3/${process.env.INFURA_PROJECT_ID}`, 37 | accounts, 38 | }, 39 | mainnet: { 40 | url: MAINNET_RPC_URL, 41 | accounts: accounts, 42 | saveDeployments: true, 43 | chainId: 1, 44 | }, 45 | iotex: { 46 | url: 'https://babel-api.mainnet.iotex.io', 47 | accounts: accounts, 48 | chainId: 4689, 49 | }, 50 | iotex_test: { 51 | url: 'https://babel-api.testnet.iotex.io', 52 | accounts: accounts, 53 | chainId: 4690, 54 | }, 55 | avax_test: { 56 | url: 'https://api.avax-test.network/ext/bc/C/rpc', 57 | accounts: accounts, 58 | chainId: 43113, 59 | }, 60 | bsc: { 61 | url: 'https://bsc-dataseed.binance.org', 62 | accounts: accounts, 63 | chainId: 56, 64 | }, 65 | polygon: { 66 | url: 'https://polygon-rpc.com/', 67 | accounts: accounts, 68 | chainId: 137, 69 | }, 70 | }, 71 | solidity: { 72 | compilers: [{ 73 | version: "0.7.6", 74 | settings: { 75 | optimizer: { 76 | enabled: true, 77 | runs: 800, 78 | }, 79 | metadata: { 80 | bytecodeHash: "none", 81 | }, 82 | }, 83 | }, { 84 | version: "0.8.7", 85 | settings: { 86 | optimizer: { 87 | enabled: true, 88 | runs: 800, 89 | }, 90 | metadata: { 91 | bytecodeHash: "none", 92 | }, 93 | }, 94 | }] 95 | }, 96 | mocha: { 97 | timeout: 200000, 98 | }, 99 | paths: { 100 | artifacts: "artifacts", 101 | cache: "cache", 102 | deploy: "deploy", 103 | deployments: "deployments", 104 | imports: "imports", 105 | sources: "contracts", 106 | tests: "test", 107 | }, 108 | typechain: { 109 | outDir: "types", 110 | }, 111 | etherscan: { 112 | apiKey: `${process.env.ETHERSCAN_API_KEY}`, 113 | customChains: [{ 114 | network: "iotex", 115 | chainId: 4689, 116 | urls: { 117 | apiURL: "https://iotexscout.io/api", 118 | browserURL: "https://iotexscout.io" 119 | } 120 | }, { 121 | network: "iotex_test", 122 | chainId: 4690, 123 | urls: { 124 | apiURL: "https://testnet.iotexscout.io/api", 125 | browserURL: "https://testnet.iotexscout.io" 126 | } 127 | }] 128 | }, 129 | gasReporter: { 130 | enabled: REPORT_GAS, 131 | currency: "USD", 132 | outputFile: "gas-report.txt", 133 | noColors: true, 134 | }, 135 | contractSizer: { 136 | runOnCompile: false, 137 | only: ["ExampleToken"], 138 | }, 139 | } 140 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crosschained-contracts", 3 | "version": "0.1.0", 4 | "license": "MIT", 5 | "devDependencies": { 6 | "@nomiclabs/hardhat-ethers": "^2.0.5", 7 | "@nomiclabs/hardhat-etherscan": "^3.0.0", 8 | "@nomiclabs/hardhat-waffle": "^2.0.1", 9 | "@openzeppelin/hardhat-upgrades": "^1.17.0", 10 | "@typechain/ethers-v5": "^9.0.0", 11 | "@typechain/hardhat": "^5.0.0", 12 | "@types/chai": "^4.3.0", 13 | "@types/dotenv": "^8.2.0", 14 | "@types/mocha": "^9.1.0", 15 | "@types/node": "^17.0.21", 16 | "chai": "^4.3.6", 17 | "dotenv": "^10.0.0", 18 | "ethereum-private-key-to-address": "^0.0.7", 19 | "ethereum-waffle": "^3.4.0", 20 | "ethers": "^5.6.2", 21 | "hardhat": "^2.9.0", 22 | "hardhat-contract-sizer": "^2.4.0", 23 | "hardhat-gas-reporter": "^1.0.7", 24 | "prettier": "^2.4.1", 25 | "prettier-plugin-solidity": "^1.0.0-beta.19", 26 | "rimraf": "^3.0.2", 27 | "solhint": "^3.3.6", 28 | "solidity-coverage": "^0.7.13", 29 | "ts-node": "^10.7.0", 30 | "typechain": "^7.0.1", 31 | "typescript": "^4.6.2" 32 | }, 33 | "scripts": { 34 | "clean": "rimraf ./artifacts/ ./cache/", 35 | "precompile": "yarn clean", 36 | "compile": "hardhat compile", 37 | "test": "hardhat test", 38 | "lint": "solhint 'contracts/*.sol'", 39 | "lint:fix": "solhint 'contracts/**/*.sol' --fix", 40 | "format": "prettier --write .", 41 | "coverage": "hardhat coverage --solcoverjs ./.solcover.js", 42 | "fuzzing": "docker run -it --rm -v $PWD:/src trailofbits/eth-security-toolbox" 43 | }, 44 | "dependencies": { 45 | "@openzeppelin/contracts": "^4.5.0", 46 | "@openzeppelin/contracts-upgradeable": "^4.5.2" 47 | }, 48 | "mocha": { 49 | "timeout": 10000000 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /scripts/000-deploy-base.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { ethers, network, upgrades } from "hardhat" 3 | 4 | import { EmergencyOperator } from "../types/EmergencyOperator" 5 | import { LedgerV2 } from "../types/LedgerV2" 6 | import { LordV2 } from "../types/LordV2" 7 | import { MinterDAO } from "../types/MinterDAO" 8 | 9 | async function main() { 10 | const [deployer] = await ethers.getSigners() 11 | 12 | const deployment = {} 13 | const EmergencyOperatorFactory = await ethers.getContractFactory("EmergencyOperator") 14 | const emergencyOperator = await upgrades.deployProxy(EmergencyOperatorFactory) as EmergencyOperator 15 | await emergencyOperator.deployed() 16 | console.log("Emergency Operator deployed to:", emergencyOperator.address) 17 | deployment["emergencyOperator"] = emergencyOperator.address 18 | 19 | const LordV2Factory = await ethers.getContractFactory("LordV2") 20 | const lordV2 = await upgrades.deployProxy(LordV2Factory, []) as LordV2; 21 | await lordV2.deployed() 22 | console.log("LordV2 deployed to:", lordV2.address) 23 | deployment["lord"] = lordV2.address 24 | 25 | const LedgerV2Factory = await ethers.getContractFactory("LedgerV2") 26 | const ledgerV2 = await LedgerV2Factory.deploy() as LedgerV2 27 | await ledgerV2.deployed(); 28 | console.log("LedgerV2 deployed to:", ledgerV2.address) 29 | deployment["ledger"] = ledgerV2.address 30 | 31 | const Verifier = await ethers.getContractFactory("VerifierV2") 32 | const verifier = await Verifier.deploy(emergencyOperator.address) 33 | await verifier.deployed(); 34 | console.log("Verifier deployed to:", verifier.address) 35 | deployment["verifier"] = verifier.address 36 | 37 | const ERC20Tube = await ethers.getContractFactory("ERC20Tube") 38 | const tube = await ERC20Tube.deploy( 39 | process.env.TUBE_ID, // tubeID 40 | ledgerV2.address, // ledger 41 | lordV2.address, // lord 42 | verifier.address, // verifier 43 | process.env.SAFE, // safe 44 | 0, // initNonce 45 | emergencyOperator.address, // emergency operator address 46 | ) 47 | await tube.deployed() 48 | console.log("ERC20Tube deployed to:", tube.address) 49 | deployment["tube"] = tube.address 50 | 51 | // add operator 52 | let tx = await ledgerV2.addOperator(tube.address) 53 | await tx.wait() 54 | tx = await lordV2.addOperator(tube.address) 55 | await tx.wait() 56 | 57 | const MinterDAOFactory = await ethers.getContractFactory("MinterDAO") 58 | const minterDAO = await upgrades.deployProxy(MinterDAOFactory, [ 59 | lordV2.address, 60 | emergencyOperator.address 61 | ]) as MinterDAO; 62 | await minterDAO.deployed() 63 | console.log("MinterDAO deployed to:", lordV2.address) 64 | deployment["minterDAO"] = minterDAO.address 65 | 66 | const CrosschainERC20FactoryV2 = await ethers.getContractFactory("CrosschainERC20FactoryV2") 67 | const cTokenFactory = await CrosschainERC20FactoryV2.deploy(minterDAO.address) 68 | await cTokenFactory.deployed() 69 | console.log("CrosschainERC20FactoryV2 deployed to:", cTokenFactory.address) 70 | deployment["crosschainERC20Factory"] = cTokenFactory.address 71 | 72 | const ERC20TubeRouter = await ethers.getContractFactory("ERC20TubeRouter") 73 | // use deployer's address as default safe address 74 | const router = await ERC20TubeRouter.deploy(tube.address, deployer.address) 75 | await router.deployed(); 76 | console.log("ERC20TubeRouter deployed to:", router.address) 77 | deployment["erc20TubeRouter"] = router.address 78 | 79 | if(!fs.existsSync("./deployments")) { 80 | fs.mkdirSync("./deployments") 81 | } 82 | fs.writeFileSync(`./deployments/${network.name}.json`, JSON.stringify(deployment, null, 4)) 83 | } 84 | 85 | main() 86 | .then(() => process.exit(0)) 87 | .catch((error) => { 88 | console.error(error) 89 | process.exit(1) 90 | }) 91 | -------------------------------------------------------------------------------- /scripts/001-deploy-registrys.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { ethers, network } from "hardhat" 3 | 4 | async function main() { 5 | const [deployer] = await ethers.getSigners() 6 | 7 | const deployment = {} 8 | 9 | const AssetRegistryV2 = await ethers.getContractFactory("AssetRegistryV2") 10 | const assetRegistryV2 = await AssetRegistryV2.deploy() 11 | await assetRegistryV2.deployed() 12 | console.log("AssetRegistryV2 deployed to:", assetRegistryV2.address) 13 | deployment["assetRegistry"] = assetRegistryV2.address 14 | 15 | const ValidatorRegistry = await ethers.getContractFactory("ValidatorRegistry") 16 | const validatorRegistry = await ValidatorRegistry.deploy() 17 | console.log("ValidatorRegistry deployed to:", validatorRegistry.address) 18 | deployment["validatorRegistry"] = validatorRegistry.address 19 | 20 | if(!fs.existsSync("./deployments")) { 21 | fs.mkdirSync("./deployments") 22 | } 23 | fs.writeFileSync(`./deployments/${network.name}.registry.json`, JSON.stringify(deployment, null, 4)) 24 | } 25 | 26 | main() 27 | .then(() => process.exit(0)) 28 | .catch((error) => { 29 | console.error(error) 30 | process.exit(1) 31 | }) 32 | -------------------------------------------------------------------------------- /scripts/002-upgrade-lord.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs" 2 | import { ethers, upgrades, network } from "hardhat" 3 | 4 | async function main() { 5 | const deployments = JSON.parse(fs.readFileSync(`./deployments/${network.name}.json`).toString()) 6 | 7 | const LordV2 = await ethers.getContractFactory("LordV2"); 8 | const lord = await upgrades.upgradeProxy(deployments.lord, LordV2) 9 | console.log("lord upgraded"); 10 | } 11 | 12 | main(); -------------------------------------------------------------------------------- /scripts/ops/add-assets.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { ethers, network } from "hardhat" 3 | import { AssetRegistryV2 } from "../../types/AssetRegistryV2" 4 | 5 | async function main() { 6 | const [deployer] = await ethers.getSigners() 7 | const deployments = JSON.parse(fs.readFileSync(`./deployments/${network.name}.registry.json`).toString()) 8 | 9 | const registry = await ethers.getContractAt("AssetRegistryV2", deployments.assetRegistry) as AssetRegistryV2 10 | 11 | const tubes = [10002, 10003] 12 | const assets = [{ 13 | id: 1, 14 | name: "cUSDT", 15 | addresses: [{ 16 | tube: 10002, 17 | address: "0x32492aC61580e7E42317579D43aB7C921C9406c4" 18 | }, { 19 | tube: 10003, 20 | address: "0xfEC51632aF0CF8075e6F391b5F7dC33E28B375C4" 21 | }] 22 | },{ 23 | id: 2, 24 | name: "cUSDC", 25 | addresses: [{ 26 | tube: 10002, 27 | address: "0xC7c88394202C3CCe3d54557Ea60DDfDbB4a943Ac" 28 | }, { 29 | tube: 10003, 30 | address: "0x100Cb68fdEA6Fd2D1C3EC29C06Dd35A63f29547A" 31 | }] 32 | },{ 33 | id: 3, 34 | name: "cBUSD", 35 | addresses: [{ 36 | tube: 10002, 37 | address: "0x144220970AC713b32398D82db2F7947AD2eB01e2" 38 | }, { 39 | tube: 10003, 40 | address: "0x8D9EFF68052e8F9Ec89179D1402eB57Dbd6d5048" 41 | }] 42 | },] 43 | 44 | 45 | // add self as operator 46 | console.log(`Add admin as operator ...`) 47 | let tx = await registry.grant(deployer.address) 48 | await tx.wait() 49 | 50 | console.log(`Activate tube ...`) 51 | for (let i = 0; i < tubes.length; i++) { 52 | const tube = tubes[i]; 53 | const tx = await registry.activateTube(tube) 54 | await tx.wait() 55 | } 56 | 57 | for (let i = 0; i < assets.length; i++) { 58 | const asset = assets[i]; 59 | console.log(`New asset ${asset.name} at ${asset.addresses[0].tube} ...`) 60 | let tx = await registry.newAsset(asset.addresses[0].tube, asset.addresses[0].address) 61 | await tx.wait() 62 | tx = await registry.activateAssetOnTube(asset.id, asset.addresses[0].tube) 63 | await tx.wait() 64 | for (let j = 1; j < asset.addresses.length; j++) { 65 | const address = asset.addresses[j]; 66 | console.log(`Set ${asset.name} to ${address.tube} ...`) 67 | let tx = await registry.setAssetOnTube(asset.id, address.tube, address.address) 68 | await tx.wait() 69 | tx = await registry.activateAssetOnTube(asset.id, address.tube) 70 | await tx.wait() 71 | } 72 | 73 | console.log(`Activate asset ${asset.name}`) 74 | tx = await registry.activateAsset(asset.id) 75 | await tx.wait() 76 | } 77 | 78 | console.log(`Register assets completed`) 79 | } 80 | 81 | main() 82 | .then(() => process.exit(0)) 83 | .catch((error) => { 84 | console.error(error) 85 | process.exit(1) 86 | }) 87 | -------------------------------------------------------------------------------- /scripts/ops/config-add-destination-tube.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { ethers, network } from "hardhat" 3 | import { ERC20Tube } from "../../types/ERC20Tube"; 4 | 5 | async function main() { 6 | const deployments = JSON.parse(fs.readFileSync(`./deployments/${network.name}.json`).toString()) 7 | 8 | const tube = await ethers.getContractAt("ERC20Tube", deployments.tube) as ERC20Tube 9 | 10 | console.log("add desination tube...") 11 | const tx = await tube.setDestinationTube( 12 | 10002, // destination tube id 13 | 0, // fee rate 14 | true // enable 15 | ) 16 | await tx.wait() 17 | console.log("add desination tube successful.") 18 | } 19 | 20 | main() 21 | .then(() => process.exit(0)) 22 | .catch((error) => { 23 | console.error(error) 24 | process.exit(1) 25 | }) 26 | -------------------------------------------------------------------------------- /scripts/ops/config-router-fee.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { ethers, network } from "hardhat" 3 | import { ERC20TubeRouter } from "../../types/ERC20TubeRouter"; 4 | 5 | async function main() { 6 | const deployments = JSON.parse(fs.readFileSync(`./deployments/${network.name}.json`).toString()) 7 | 8 | const router = await ethers.getContractAt("ERC20TubeRouter", deployments.erc20TubeRouter) as ERC20TubeRouter 9 | 10 | console.log("add router fee...") 11 | const tx = await router.setRelayFee( 12 | 10002, // tube id 13 | true, // active 14 | 0 // fee 15 | ) 16 | await tx.wait() 17 | console.log("add router fee successful.") 18 | } 19 | 20 | main() 21 | .then(() => process.exit(0)) 22 | .catch((error) => { 23 | console.error(error) 24 | process.exit(1) 25 | }) 26 | -------------------------------------------------------------------------------- /scripts/ops/create-crosschain-token-pair.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { ethers, network } from "hardhat" 3 | import { CrosschainERC20FactoryV2 } from "../../types/CrosschainERC20FactoryV2"; 4 | import { MinterDAO } from "../../types/MinterDAO"; 5 | 6 | async function main() { 7 | const deployments = JSON.parse(fs.readFileSync(`./deployments/${network.name}.json`).toString()) 8 | 9 | const CrosschainERC20FactoryV2Factory = await ethers.getContractFactory("CrosschainERC20FactoryV2") 10 | const factory = CrosschainERC20FactoryV2Factory.attach(deployments.crosschainERC20Factory) as CrosschainERC20FactoryV2 11 | 12 | const tokenName = "cUSDT" 13 | if (!deployments.crosschainToken[tokenName]) { 14 | console.log("please create cToken first") 15 | return 16 | } 17 | const createTx = await factory.createCrosschainERC20Pair( 18 | deployments.crosschainToken[tokenName], // cUSDT 19 | 6, 20 | "0x2407031BE476aCbc60C9269093B8Aa6B082AcFa3", // USDT_b 21 | 18, 22 | ) 23 | const receipt = await createTx.wait() 24 | if (receipt.status === 1) { 25 | const log = CrosschainERC20FactoryV2Factory.interface.parseLog(receipt.logs[2]) 26 | if(!deployments.pairs) { 27 | deployments.pairs = {} 28 | } 29 | if(!deployments.pairs[tokenName]) { 30 | deployments.pairs[tokenName] = [] 31 | } 32 | deployments.pairs[tokenName].push(log.args.pair) 33 | console.log(`create crosschain token pair deployed at ${log.args.pair}`) 34 | 35 | const skipAddMinter = process.env.SKIP_ADD_MINTER || false 36 | if (!skipAddMinter) { 37 | console.log('add pair as token minter to MinterDAO') 38 | const minterdao = await ethers.getContractAt("MinterDAO", deployments.minterDAO) as MinterDAO 39 | const tx = await minterdao.addMinter(log.args.pair, deployments.crosschainToken[tokenName]) 40 | await tx.wait() 41 | } 42 | } else { 43 | console.log("create crosschain token pair fail") 44 | } 45 | fs.writeFileSync(`./deployments/${network.name}.json`, JSON.stringify(deployments, null, 4)) 46 | } 47 | 48 | main() 49 | .then(() => process.exit(0)) 50 | .catch((error) => { 51 | console.error(error) 52 | process.exit(1) 53 | }) 54 | -------------------------------------------------------------------------------- /scripts/ops/create-crosschain-token.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { ethers, network, upgrades } from "hardhat" 3 | import { CrosschainERC20FactoryV2 } from "../../types/CrosschainERC20FactoryV2"; 4 | 5 | async function main() { 6 | const [deployer] = await ethers.getSigners() 7 | 8 | const deployments = JSON.parse(fs.readFileSync(`./deployments/${network.name}.json`).toString()) 9 | 10 | const CrosschainERC20FactoryV2Factory = await ethers.getContractFactory("CrosschainERC20FactoryV2") 11 | const factory = CrosschainERC20FactoryV2Factory.attach(deployments.crosschainERC20Factory) as CrosschainERC20FactoryV2 12 | 13 | const tokenSymbol = "cBUSD"; 14 | const tokenName = "Crosschain BUSD"; 15 | const decimal = 18; 16 | 17 | const createTx = await factory.createCrosschainERC20( 18 | tokenName, 19 | tokenSymbol, 20 | decimal 21 | ) 22 | const receipt = await createTx.wait() 23 | if (receipt.status === 1) { 24 | const log = CrosschainERC20FactoryV2Factory.interface.parseLog(receipt.logs[0]) 25 | if(!deployments.crosschainToken) { 26 | deployments.crosschainToken = {} 27 | } 28 | deployments.crosschainToken[tokenSymbol] = log.args.token 29 | console.log(`create crosschain token deployed at ${log.args.token}`) 30 | } else { 31 | console.log("create crosschain token fail") 32 | } 33 | fs.writeFileSync(`./deployments/${network.name}.json`, JSON.stringify(deployments, null, 4)) 34 | } 35 | 36 | main() 37 | .then(() => process.exit(0)) 38 | .catch((error) => { 39 | console.error(error) 40 | process.exit(1) 41 | }) 42 | -------------------------------------------------------------------------------- /scripts/ops/credit-pair-increase.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { ethers, network } from "hardhat" 3 | import { CrosschainERC20V2Pair } from "../../types/CrosschainERC20V2Pair" 4 | 5 | async function main() { 6 | const pairAddr = process.env.PAIR 7 | const credit = process.env.CREDIT 8 | 9 | const pair = await ethers.getContractAt("CrosschainERC20V2Pair", pairAddr!) as CrosschainERC20V2Pair 10 | 11 | console.log(`Increase ${credit} credit for ${pairAddr} ...`) 12 | const tx = await pair.increaseCredit(credit!) 13 | await tx.wait() 14 | } 15 | 16 | main() 17 | .then(() => process.exit(0)) 18 | .catch((error) => { 19 | console.error(error) 20 | process.exit(1) 21 | }) 22 | -------------------------------------------------------------------------------- /scripts/ops/credit-pair-reduce.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { ethers, network } from "hardhat" 3 | import { CrosschainERC20V2Pair } from "../../types/CrosschainERC20V2Pair" 4 | 5 | async function main() { 6 | const pairAddr = process.env.PAIR 7 | const credit = process.env.CREDIT 8 | 9 | const pair = await ethers.getContractAt("CrosschainERC20V2Pair", pairAddr!) as CrosschainERC20V2Pair 10 | 11 | console.log(`Reduce ${credit} credit for ${pairAddr} ...`) 12 | const tx = await pair.increaseCredit(credit!) 13 | await tx.wait() 14 | } 15 | 16 | main() 17 | .then(() => process.exit(0)) 18 | .catch((error) => { 19 | console.error(error) 20 | process.exit(1) 21 | }) 22 | -------------------------------------------------------------------------------- /scripts/ops/deploy-timelock.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs" 2 | import { ethers, network } from "hardhat" 3 | import { CrosschainERC20FactoryV2 } from "../../types/CrosschainERC20FactoryV2" 4 | 5 | async function main() { 6 | const deployments = JSON.parse(fs.readFileSync(`./deployments/${network.name}.json`).toString()) 7 | 8 | const [deployer] = await ethers.getSigners() 9 | const factory = await ethers.getContractFactory("Timelock") 10 | 11 | const timelock = await factory.deploy( 12 | deployer.address, // admin 13 | 86400 // delay: one day 14 | ) 15 | await timelock.deployed() 16 | 17 | console.log("Timelock deployed to:", timelock.address) 18 | deployments["timelock"] = timelock.address 19 | 20 | const crosschainERC20FactoryV2 = await ethers.getContractAt( 21 | "CrosschainERC20FactoryV2", deployments.crosschainERC20Factory 22 | ) as CrosschainERC20FactoryV2 23 | let tx = await crosschainERC20FactoryV2.transferOwnership(timelock.address) 24 | await tx.wait() 25 | console.log(`Set CrosschainERC20FactoryV2 owner to ${timelock.address}`) 26 | 27 | fs.writeFileSync(`./deployments/${network.name}.json`, JSON.stringify(deployments, null, 4)) 28 | } 29 | 30 | main() 31 | .then(() => process.exit(0)) 32 | .catch((error) => { 33 | console.error(error) 34 | process.exit(1) 35 | }) 36 | -------------------------------------------------------------------------------- /scripts/ops/remove-crosschain-token-pair.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { ethers, network } from "hardhat" 3 | import { CrosschainERC20FactoryV2 } from "../../types/CrosschainERC20FactoryV2"; 4 | import { MinterDAO } from "../../types/MinterDAO"; 5 | 6 | async function main() { 7 | const deployments = JSON.parse(fs.readFileSync(`./deployments/${network.name}.json`).toString()) 8 | 9 | const CrosschainERC20FactoryV2Factory = await ethers.getContractFactory("CrosschainERC20FactoryV2") 10 | const factory = CrosschainERC20FactoryV2Factory.attach(deployments.crosschainERC20Factory) as CrosschainERC20FactoryV2 11 | 12 | const tokenName = "cUSDT" 13 | if (!deployments.crosschainToken[tokenName]) { 14 | console.log(`cToken ${tokenName} doesn't exists`) 15 | return 16 | } 17 | if(!deployments.pairs[tokenName] || deployments.pairs[tokenName].length === 0) { 18 | console.log(`no pairs for ${tokenName} found`) 19 | return 20 | } 21 | 22 | const minterdao = await ethers.getContractAt("MinterDAO", deployments.minterDAO) as MinterDAO 23 | for (let i = 0; i < deployments.pairs[tokenName].length; i++) { 24 | const pair = deployments.pairs[tokenName][i]; 25 | console.log(`remove ${pair} ...`) 26 | const tx = await minterdao.removeMinter(pair, deployments.crosschainToken[tokenName]) 27 | await tx.wait() 28 | } 29 | deployments.pairs[tokenName] = [] 30 | 31 | fs.writeFileSync(`./deployments/${network.name}.json`, JSON.stringify(deployments, null, 4)) 32 | } 33 | 34 | main() 35 | .then(() => process.exit(0)) 36 | .catch((error) => { 37 | console.error(error) 38 | process.exit(1) 39 | }) 40 | -------------------------------------------------------------------------------- /scripts/ops/temp-deploy-factory.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { ethers, network, upgrades } from "hardhat" 3 | 4 | async function main() { 5 | const [deployer] = await ethers.getSigners() 6 | const deployments = JSON.parse(fs.readFileSync(`./deployments/${network.name}.json`).toString()) 7 | 8 | const CrosschainERC20FactoryV2 = await ethers.getContractFactory("CrosschainERC20FactoryV2") 9 | const cTokenFactory = await CrosschainERC20FactoryV2.deploy(deployments.minterDAO) 10 | await cTokenFactory.deployed() 11 | console.log("CrosschainERC20FactoryV2 deployed to:", cTokenFactory.address) 12 | deployments["crosschainERC20Factory"] = cTokenFactory.address 13 | 14 | fs.writeFileSync(`./deployments/${network.name}.json`, JSON.stringify(deployments, null, 4)) 15 | } 16 | 17 | main() 18 | .then(() => process.exit(0)) 19 | .catch((error) => { 20 | console.error(error) 21 | process.exit(1) 22 | }) 23 | -------------------------------------------------------------------------------- /scripts/ops/temp-deploy-mock-token.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { ethers, network, upgrades } from "hardhat" 3 | 4 | async function main() { 5 | const [deployer] = await ethers.getSigners() 6 | 7 | const deployment = {} 8 | 9 | const MockToken = await ethers.getContractFactory("MockToken") 10 | let token = await MockToken.deploy("BUSD", "BUSD", 18) 11 | await token.deployed(); 12 | console.log("BUSD deployed to:", token.address) 13 | token = await MockToken.deploy("USDT", "USDT", 6) 14 | await token.deployed(); 15 | console.log("USDT deployed to:", token.address) 16 | token = await MockToken.deploy("USDC", "USDC", 6) 17 | await token.deployed(); 18 | console.log("USDC deployed to:", token.address) 19 | } 20 | 21 | main() 22 | .then(() => process.exit(0)) 23 | .catch((error) => { 24 | console.error(error) 25 | process.exit(1) 26 | }) 27 | -------------------------------------------------------------------------------- /scripts/ops/temp-deploy-registry.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { ethers, network, upgrades } from "hardhat" 3 | 4 | async function main() { 5 | const [deployer] = await ethers.getSigners() 6 | const deployments = JSON.parse(fs.readFileSync(`./deployments/${network.name}.json`).toString()) 7 | 8 | // const AssetRegistryV2 = await ethers.getContractFactory("AssetRegistryV2") 9 | // const assetRegistryV2 = await AssetRegistryV2.deploy() 10 | // await assetRegistryV2.deployed() 11 | // console.log("AssetRegistryV2 deployed to:", assetRegistryV2.address) 12 | // deployments["assetRegistry"] = assetRegistryV2.address 13 | 14 | const ValidatorRegistry = await ethers.getContractFactory("ValidatorRegistry") 15 | const validatorRegistry = await ValidatorRegistry.deploy() 16 | console.log("ValidatorRegistry deployed to:", validatorRegistry.address) 17 | deployments["validatorRegistry"] = validatorRegistry.address 18 | 19 | fs.writeFileSync(`./deployments/${network.name}.json`, JSON.stringify(deployments, null, 4)) 20 | } 21 | 22 | main() 23 | .then(() => process.exit(0)) 24 | .catch((error) => { 25 | console.error(error) 26 | process.exit(1) 27 | }) 28 | -------------------------------------------------------------------------------- /scripts/ops/temp-deploy-router.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { ethers, network, upgrades } from "hardhat" 3 | 4 | async function main() { 5 | const [deployer] = await ethers.getSigners() 6 | const deployments = JSON.parse(fs.readFileSync(`./deployments/${network.name}.json`).toString()) 7 | 8 | const ERC20TubeRouter = await ethers.getContractFactory("ERC20TubeRouter") 9 | // use deployer's address as default safe address 10 | const router = await ERC20TubeRouter.deploy(deployments.tube, deployer.address) 11 | await router.deployed(); 12 | console.log("ERC20TubeRouter deployed to:", router.address) 13 | deployments["erc20TubeRouter"] = router.address 14 | 15 | fs.writeFileSync(`./deployments/${network.name}.json`, JSON.stringify(deployments, null, 4)) 16 | } 17 | 18 | main() 19 | .then(() => process.exit(0)) 20 | .catch((error) => { 21 | console.error(error) 22 | process.exit(1) 23 | }) 24 | -------------------------------------------------------------------------------- /scripts/ops/temp-deploy-tube.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { ethers, network, upgrades } from "hardhat" 3 | import { LedgerV2 } from "../../types/LedgerV2"; 4 | import { LordV2 } from "../../types/LordV2"; 5 | 6 | async function main() { 7 | const [deployer] = await ethers.getSigners() 8 | const deployments = JSON.parse(fs.readFileSync(`./deployments/${network.name}.json`).toString()) 9 | 10 | const Verifier = await ethers.getContractFactory("VerifierV2") 11 | const verifier = await Verifier.deploy() 12 | await verifier.deployed(); 13 | console.log("Verifier deployed to:", verifier.address) 14 | deployments["verifier"] = verifier.address 15 | 16 | const ERC20Tube = await ethers.getContractFactory("ERC20Tube") 17 | const tube = await ERC20Tube.deploy( 18 | process.env.TUBE_ID, // tubeID 19 | deployments.ledger, // ledger 20 | deployments.lord, // lord 21 | deployments.verifier, // verifier 22 | process.env.SAFE, // safe 23 | process.env.INIT_NONCE // initNonce 24 | ) 25 | await tube.deployed(); 26 | console.log("ERC20Tube deployed to:", tube.address) 27 | deployments["tube"] = tube.address 28 | 29 | // add operator 30 | const LedgerV2Factory = await ethers.getContractFactory("LedgerV2") 31 | const ledgerV2 = LedgerV2Factory.attach(deployments.ledger) as LedgerV2 32 | await ledgerV2.addOperator(tube.address) 33 | // add minter 34 | const LordV2Factory = await ethers.getContractFactory("LordV2") 35 | const lordV2 = LordV2Factory.attach(deployments.lord) as LordV2; 36 | await lordV2.addMinter(tube.address) 37 | 38 | const ERC20TubeRouter = await ethers.getContractFactory("ERC20TubeRouter") 39 | const router = await ERC20TubeRouter.deploy(tube.address) 40 | await router.deployed(); 41 | console.log("ERC20TubeRouter deployed to:", router.address) 42 | deployments["erc20TubeRouter"] = router.address 43 | 44 | fs.writeFileSync(`./deployments/${network.name}.json`, JSON.stringify(deployments, null, 4)) 45 | } 46 | 47 | main() 48 | .then(() => process.exit(0)) 49 | .catch((error) => { 50 | console.error(error) 51 | process.exit(1) 52 | }) 53 | -------------------------------------------------------------------------------- /scripts/utils/signer.ts: -------------------------------------------------------------------------------- 1 | import { ethers, network } from "hardhat" 2 | 3 | async function main() { 4 | const [deployer] = await ethers.getSigners() 5 | 6 | const srcTubeId = process.env.SRC_TUBE_ID || "0" 7 | const nonce = process.env.NONCE || "1" 8 | const token = process.env.TOKEN || "0x70be56907d3f8dc1eda0a6f860f3a3d4b4162796" 9 | const recipient = process.env.RECIPIENT || "0x8896780a7912829781f70344Ab93E589dDdb2930" 10 | const tubeId = process.env.TUBE_ID || "0" 11 | const amount = process.env.AMOUNT || ethers.utils.parseEther("1").toString() 12 | 13 | const key = ethers.utils.solidityKeccak256( 14 | ["uint256", "uint256", "uint256", "address", "uint256", "address"], 15 | [srcTubeId, nonce, tubeId, token, amount, recipient] 16 | ) 17 | 18 | const privateKey = process.env.PRIVATE_KEY 19 | const wallet = new ethers.Wallet(privateKey!) 20 | const signature = ethers.utils.joinSignature(wallet._signingKey().signDigest(key)) 21 | console.log(signature) 22 | } 23 | 24 | main() 25 | .then(() => process.exit(0)) 26 | .catch((error) => { 27 | console.error(error) 28 | process.exit(1) 29 | }) 30 | -------------------------------------------------------------------------------- /scripts/verify/verify.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { ethers, run, network } from "hardhat" 3 | 4 | import { ERC20TubeRouter } from "../../types/ERC20TubeRouter"; 5 | 6 | async function verify(params: any) { 7 | try { 8 | await run("verify:verify", params) 9 | } catch (error) { 10 | // @ts-ignore 11 | console.log(`contract ${params.contract} fail: ${error.message}`) 12 | } 13 | } 14 | 15 | async function main() { 16 | const deployments = JSON.parse(fs.readFileSync(`./deployments/${network.name}.json`).toString()) 17 | 18 | const emergencyOperatorImplement = `0x${(await ethers.provider.getStorageAt( 19 | deployments.emergencyOperator, 20 | "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc" 21 | )).substring(26)}` 22 | await verify({ 23 | contract: "contracts/v0.2/EmergencyOperator.sol:EmergencyOperator", 24 | address: emergencyOperatorImplement, 25 | }) 26 | const lordImplement = `0x${(await ethers.provider.getStorageAt( 27 | deployments.lord, 28 | "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc" 29 | )).substring(26)}` 30 | await verify({ 31 | contract: "contracts/v0.2/LordV2.sol:LordV2", 32 | address: lordImplement, 33 | }) 34 | await verify({ 35 | contract: "contracts/v0.2/LedgerV2.sol:LedgerV2", 36 | address: deployments.ledger, 37 | }) 38 | await verify({ 39 | contract: "contracts/v0.2/VerifierV2.sol:VerifierV2", 40 | address: deployments.verifier, 41 | constructorArguments: [ 42 | deployments.emergencyOperator 43 | ], 44 | }) 45 | await verify({ 46 | contract: "contracts/v0.2/ERC20Tube.sol:ERC20Tube", 47 | address: deployments.tube, 48 | constructorArguments: [ 49 | process.env.TUBE_ID, 50 | deployments.ledger, 51 | deployments.lord, 52 | deployments.verifier, 53 | process.env.SAFE, 54 | 0, 55 | deployments.emergencyOperator 56 | ], 57 | }) 58 | const minterDAOImplement = `0x${(await ethers.provider.getStorageAt( 59 | deployments.minterDAO, 60 | "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc" 61 | )).substring(26)}` 62 | await verify({ 63 | contract: "contracts/v0.2/MinterDAO.sol:MinterDAO", 64 | address: minterDAOImplement, 65 | }) 66 | await verify({ 67 | contract: "contracts/v0.2/CrosschainERC20FactoryV2.sol:CrosschainERC20FactoryV2", 68 | address: deployments.crosschainERC20Factory, 69 | constructorArguments: [ 70 | deployments.minterDAO 71 | ], 72 | }) 73 | 74 | const router = await ethers.getContractAt( 75 | "ERC20TubeRouter", deployments.erc20TubeRouter 76 | ) as ERC20TubeRouter 77 | await verify({ 78 | contract: "contracts/v0.2/ERC20TubeRouter.sol:ERC20TubeRouter", 79 | address: deployments.erc20TubeRouter, 80 | constructorArguments: [ 81 | deployments.tube, 82 | await router.owner() 83 | ], 84 | }) 85 | 86 | // await verify({ 87 | // contract: "contracts/v0.2/CrosschainERC20V2.sol:CrosschainERC20V2", 88 | // address: "0xfEC51632aF0CF8075e6F391b5F7dC33E28B375C4", 89 | // constructorArguments: [ 90 | // deployments.minterDAO, 91 | // "Crosschain USDT", 92 | // "cUSDT", 93 | // 6 94 | // ], 95 | // }) 96 | // await verify({ 97 | // contract: "contracts/v0.2/CrosschainERC20V2Pair.sol:CrosschainERC20V2Pair", 98 | // address: "0xA151F8fe931fd6ae8541d7e614485A18e292f666", 99 | // constructorArguments: [ 100 | // "0xfEC51632aF0CF8075e6F391b5F7dC33E28B375C4", 101 | // 6, 102 | // "0x55d398326f99059fF775485246999027B3197955", 103 | // 18, 104 | // "0xa8683aadd56a60d9bcf9e0f57a65ff53333bae7e" 105 | // ], 106 | // }) 107 | } 108 | 109 | main() 110 | .then(() => process.exit(0)) 111 | .catch((error) => { 112 | console.error(error) 113 | process.exit(1) 114 | }) 115 | -------------------------------------------------------------------------------- /test/asset_registry.test.ts: -------------------------------------------------------------------------------- 1 | import _, { flatMap } from "lodash" 2 | import { ethers } from "hardhat" 3 | import { BigNumber } from "ethers" 4 | import { expect } from "chai" 5 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers" 6 | import { AssetRegistryV2 } from "../types/AssetRegistryV2" 7 | import { MockToken } from "../types/MockToken" 8 | 9 | describe("asset registry unit test", () => { 10 | let registry: AssetRegistryV2; 11 | let asset1: MockToken; 12 | let asset2: MockToken; 13 | let owner: SignerWithAddress; 14 | let hacker: SignerWithAddress; 15 | let operator: SignerWithAddress; 16 | let operator2: SignerWithAddress; 17 | 18 | beforeEach(async () => { 19 | [owner, hacker, operator, operator2] = await ethers.getSigners(); 20 | const RegistryContract = await ethers.getContractFactory("AssetRegistryV2"); 21 | registry = await RegistryContract.connect(owner).deploy() as AssetRegistryV2; 22 | await registry.deployed(); 23 | const MockToken = await ethers.getContractFactory("MockToken") 24 | asset1 = await MockToken.deploy("asset1", "symbol", 6) as MockToken 25 | await asset1.deployed() 26 | asset2 = await MockToken.deploy("asset2", "symbol", 6) as MockToken 27 | await asset2.deployed() 28 | }); 29 | describe("owner functions", () => { 30 | beforeEach(async () => { 31 | expect(await registry.operators(operator.address)).to.equal(false); 32 | }); 33 | describe("grant operator permission", () => { 34 | it("not owner", async () => { 35 | await expect(registry.connect(hacker).grant(operator.address)).to.be.revertedWith("caller is not the owner"); 36 | expect(await registry.operators(operator.address)).to.equal(false); 37 | }); 38 | describe("grant success", async () => { 39 | beforeEach(async () => { 40 | let tx = await registry.grant(operator.address); 41 | await tx.wait(); 42 | expect(await registry.operators(operator.address)).to.equal(true); 43 | }); 44 | it("not owner", async () => { 45 | await expect(registry.connect(hacker).revoke(operator.address)).to.be.revertedWith("caller is not the owner"); 46 | expect(await registry.operators(operator.address)).to.equal(true); 47 | }); 48 | it("revoke success", async () => { 49 | let tx = await registry.revoke(operator.address); 50 | await tx.wait(); 51 | expect(await registry.operators(operator.address)).to.equal(false); 52 | }); 53 | }); 54 | }); 55 | }); 56 | describe("operator functions", () => { 57 | beforeEach(async () => { 58 | let tx = await registry.grant(operator.address); 59 | await tx.wait(); 60 | expect(await registry.numOfAssets()).to.equal(0); 61 | }); 62 | it("not operator", async () => { 63 | await expect(registry.connect(hacker).newAsset(0, asset1.address)).to.be.revertedWith("no permission"); 64 | await expect(registry.connect(hacker).setAssetOnTube(0, 0, asset1.address)).to.be.revertedWith("no permission"); 65 | await expect(registry.connect(hacker).removeAssetOnTube(0, 0)).to.be.revertedWith("no permission"); 66 | await expect(registry.connect(hacker).activateAsset(0)).to.be.revertedWith("no permission"); 67 | await expect(registry.connect(hacker).deactivateAsset(0)).to.be.revertedWith("no permission"); 68 | await expect(registry.connect(hacker).activateTube(0)).to.be.revertedWith("no permission"); 69 | await expect(registry.connect(hacker).deactivateTube(0)).to.be.revertedWith("no permission"); 70 | await expect(registry.connect(hacker).activateAssetOnTube(0, 0)).to.be.revertedWith("no permission"); 71 | await expect(registry.connect(hacker).deactivateAssetOnTube(0, 0)).to.be.revertedWith("no permission"); 72 | }); 73 | describe("add new asset", () => { 74 | let tubeIDs = [1, 2, 3]; 75 | beforeEach(async () => { 76 | expect(await registry.numOfAssets()).to.equal(0); 77 | }); 78 | it("invalid parameters", async () => { 79 | await expect(registry.connect(operator).newAsset(0, asset1.address)).to.be.revertedWith("invalid tube id"); 80 | await expect(registry.connect(operator).newAsset(tubeIDs[0], ethers.constants.AddressZero)).to.be.revertedWith("invalid asset address"); 81 | }); 82 | describe("add asset 1", () => { 83 | beforeEach(async () => { 84 | expect(await registry.assetID(tubeIDs[0], asset1.address)).to.equal(0); 85 | expect(await registry.assetID(tubeIDs[1], asset1.address)).to.equal(0); 86 | expect(await registry.assetID(tubeIDs[2], asset2.address)).to.equal(0); 87 | let tx = await registry.connect(operator).newAsset(tubeIDs[0], asset1.address); 88 | let receipt = await tx.wait(); 89 | let event = _.find(receipt.events, (e: any) => e.event == "NewAsset"); 90 | expect(await registry.numOfAssets()).to.equal(1); 91 | expect(await registry.assetID(tubeIDs[0], asset1.address)).to.equal(event.args.assetID); 92 | }); 93 | it("duplicate asset", async () => { 94 | await expect(registry.connect(operator).newAsset(tubeIDs[0], asset1.address)).to.be.revertedWith("duplicate asset"); 95 | expect(await registry.numOfAssets()).to.equal(1); 96 | }); 97 | it("add asset 2", async () => { 98 | let tx = await registry.connect(operator).newAsset(tubeIDs[2], asset2.address); 99 | let receipt = await tx.wait(); 100 | let event = _.find(receipt.events, (e: any) => e.event == "NewAsset") 101 | expect(await registry.assetID(tubeIDs[2], asset2.address)).to.equal(event.args.assetID); 102 | expect(await registry.numOfAssets()).to.equal(2); 103 | }); 104 | describe("add asset 1 on tube 2", () => { 105 | const assetID = BigNumber.from(1); 106 | beforeEach(async () => { 107 | let tx = await registry.connect(operator).setAssetOnTube(assetID, tubeIDs[1], asset2.address); 108 | let receipt = await tx.wait(); 109 | let event = _.find(receipt.events, (e: any) => e.event == "AssetSetOnTube"); 110 | expect(await registry.assetID(tubeIDs[1], asset2.address)).to.equal(assetID); 111 | }); 112 | it("remove asset 1 on tube 2", async () => { 113 | let tx = await registry.connect(operator).removeAssetOnTube(assetID, tubeIDs[1]); 114 | let receipt = await tx.wait(); 115 | let event = _.find(receipt.events, (e: any) => e.event == "AssetRemovedOnTube"); 116 | expect(await registry.assetID(tubeIDs[1], asset2.address)).to.equal(0); 117 | }); 118 | it("activate asset 1", async () => { 119 | expect(await registry.isActive(assetID, tubeIDs[0])).to.equal(false); 120 | expect(await registry.isActive(assetID, tubeIDs[1])).to.equal(false); 121 | let tx = await registry.connect(operator).activateAsset(assetID); 122 | let receipt = await tx.wait(); 123 | let event = _.find(receipt.events, (e: any) => e.event == "AssetActivated"); 124 | expect(event.args.id).to.equal(assetID); 125 | }); 126 | }); 127 | }); 128 | }); 129 | }); 130 | describe("public functions", () => { 131 | 132 | }); 133 | }); 134 | -------------------------------------------------------------------------------- /test/crosschain_coin_router.test.ts: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import { ethers } from "hardhat"; 3 | import { expect } from "chai"; 4 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 5 | 6 | import { WIOTX } from "../types/WIOTX"; 7 | import { CrosschainERC20 } from "../types/CrosschainERC20"; 8 | import { CrosschainCoinRouter } from "../types/CrosschainCoinRouter"; 9 | 10 | const privateKeyToAddress = require("ethereum-private-key-to-address"); 11 | 12 | describe("router test", function () { 13 | const amount = 123456789; 14 | let wrappedCoin: WIOTX; 15 | let cerc20: CrosschainERC20; 16 | let router: CrosschainCoinRouter; 17 | 18 | let holder1: SignerWithAddress; 19 | let holder2: SignerWithAddress; 20 | let holder3: SignerWithAddress; 21 | let attacker: SignerWithAddress; 22 | 23 | beforeEach(async function () { 24 | [holder1, holder2, holder3, attacker] = await ethers.getSigners(); 25 | 26 | const WIOTX = await ethers.getContractFactory("WIOTX"); 27 | wrappedCoin = await WIOTX.deploy() as WIOTX; 28 | 29 | const crosschainERC20 = await ethers.getContractFactory("CrosschainERC20"); 30 | cerc20 = await crosschainERC20.deploy( 31 | wrappedCoin.address, 32 | ethers.constants.AddressZero, 33 | "crosschain-iotx", 34 | "ciotx", 35 | 18) as CrosschainERC20; 36 | 37 | const crosschainCoinRouter = await ethers.getContractFactory("CrosschainCoinRouter"); 38 | router = await crosschainCoinRouter.deploy(cerc20.address) as CrosschainCoinRouter; 39 | await wrappedCoin.connect(holder1).approve(router.address, "1000000000000000000000000000"); 40 | await cerc20.connect(holder1).approve(router.address, "1000000000000000000000000000"); 41 | }) 42 | 43 | it("iotx->wiotx->ciotx->iotx", async function () { 44 | expect(await wrappedCoin.balanceOf(holder1.address)).to.equal(0); 45 | await router.connect(holder1).swapCoinForWrappedCoin(amount, {value: amount}); 46 | expect(await wrappedCoin.balanceOf(holder1.address)).to.equal(amount); 47 | expect(await cerc20.balanceOf(holder1.address)).to.equal(0); 48 | await router.connect(holder1).swapWrappedCoinForCrosschainCoin(amount); 49 | expect(await wrappedCoin.balanceOf(holder1.address)).to.equal(0); 50 | expect(await cerc20.balanceOf(holder1.address)).to.equal(amount); 51 | await router.connect(holder1).swapCrosschainCoinForCoin(amount); 52 | expect(await cerc20.balanceOf(holder1.address)).to.equal(0); 53 | }); 54 | 55 | it("iotx<->ciotx->wiotx->iotx", async function () { 56 | expect(await cerc20.balanceOf(holder1.address)).to.equal(0); 57 | await router.connect(holder1).swapCoinForCrosschainCoin(amount, {value: amount}); 58 | expect(await cerc20.balanceOf(holder1.address)).to.equal(amount); 59 | expect(await wrappedCoin.balanceOf(holder1.address)).to.equal(0); 60 | await router.connect(holder1).swapCrosschainCoinForWrappedCoin(amount); 61 | expect(await cerc20.balanceOf(holder1.address)).to.equal(0); 62 | expect(await wrappedCoin.balanceOf(holder1.address)).to.equal(amount); 63 | await router.connect(holder1).swapWrappedCoinForCoin(amount); 64 | expect(await wrappedCoin.balanceOf(holder1.address)).to.equal(0); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /test/crosschain_erc20_factory_v2.test.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat" 2 | import { expect } from "chai" 3 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers" 4 | import { CrosschainERC20V2 } from "../types/CrosschainERC20V2" 5 | import { CrosschainERC20V2Pair } from "../types/CrosschainERC20V2Pair" 6 | import { CrosschainERC20FactoryV2 } from "../types/CrosschainERC20FactoryV2" 7 | import { ContractFactory } from "ethers" 8 | 9 | describe("crosschain erc20 factory v2", () => { 10 | let deployer: SignerWithAddress 11 | let minter: SignerWithAddress 12 | let tokenFactory: ContractFactory 13 | let pairFactory: ContractFactory 14 | let factory: CrosschainERC20FactoryV2 15 | 16 | beforeEach(async function () { 17 | [deployer, minter] = await ethers.getSigners() 18 | tokenFactory = await ethers.getContractFactory("CrosschainERC20V2") 19 | pairFactory = await ethers.getContractFactory("CrosschainERC20V2Pair") 20 | const factoryFactory = await ethers.getContractFactory("CrosschainERC20FactoryV2") 21 | factory = await factoryFactory.deploy(minter.address) as CrosschainERC20FactoryV2 22 | }) 23 | 24 | it("checkout create token", async () => { 25 | const createTokenTx = await factory.createCrosschainERC20("Crosschain Test Token", "cTEST", 8) 26 | const { events } = await createTokenTx.wait() 27 | 28 | const cToken = tokenFactory.attach(events![0].args!.token) as CrosschainERC20V2 29 | 30 | expect(await cToken.name()).to.equal("Crosschain Test Token"); 31 | expect(await cToken.symbol()).to.equal("cTEST"); 32 | expect(await cToken.decimals()).to.equal(8); 33 | expect(await cToken.minterDAO()).to.equals(minter.address) 34 | }) 35 | 36 | it("check create crosschain ERC20 pair",async () => { 37 | const token = await (await ethers.getContractFactory("MockToken")).deploy( 38 | "Test Token", 39 | "TEST", 40 | 8 41 | ) 42 | const createcTokenTx = await factory.createCrosschainERC20("Crosschain Test Token", "cTEST", 8) 43 | const { events } = await createcTokenTx.wait() 44 | const cTokenAddress = events![0].args!.token 45 | 46 | const createPairTx = await factory.createCrosschainERC20Pair( 47 | cTokenAddress, 48 | 8, 49 | token.address, 50 | 8 51 | ) 52 | const receipt = await createPairTx.wait() 53 | 54 | const pair = pairFactory.attach(receipt.events![2].args!.pair) as CrosschainERC20V2Pair 55 | expect(await pair.token()).to.equal(token.address) 56 | expect(await pair.crosschainToken()).to.equal(cTokenAddress) 57 | expect(await pair.scale()).to.equals(1) 58 | expect(await pair.scaleType()).to.equals(0) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /test/crosschain_erc20_pair.test.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat" 2 | import { expect } from "chai" 3 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers" 4 | import { CrosschainERC20V2 } from "../types/CrosschainERC20V2" 5 | import { CrosschainERC20V2Pair } from "../types/CrosschainERC20V2Pair" 6 | import { MockToken } from "../types/MockToken" 7 | import { MinterDAO } from "../types/MinterDAO" 8 | import { ContractFactory } from "ethers" 9 | 10 | 11 | describe("crosschain erc20 pair tests", () => { 12 | let cTokenFactory: ContractFactory 13 | let pairFactory: ContractFactory 14 | 15 | let minter: MinterDAO 16 | let cToken: CrosschainERC20V2 17 | let pair: CrosschainERC20V2Pair 18 | let token: MockToken 19 | 20 | let owner: SignerWithAddress 21 | let holder1: SignerWithAddress 22 | let holder2: SignerWithAddress 23 | let holder3: SignerWithAddress 24 | let attacker: SignerWithAddress 25 | 26 | beforeEach(async () => { 27 | [owner, holder1, holder2, holder3, attacker] = await ethers.getSigners() 28 | 29 | cTokenFactory = await ethers.getContractFactory("CrosschainERC20V2") 30 | pairFactory = await ethers.getContractFactory("CrosschainERC20V2Pair") 31 | 32 | const minerDaoFactory = await ethers.getContractFactory("MinterDAO") 33 | minter = await minerDaoFactory.deploy() as MinterDAO 34 | await minter.initialize(ethers.constants.AddressZero, ethers.constants.AddressZero) 35 | 36 | const tokenFactory = await ethers.getContractFactory("MockToken") 37 | token = await tokenFactory.deploy("Test Token", "Test", 8) as MockToken 38 | 39 | await token.mint(holder1.address, 100000000000) 40 | await token.mint(holder2.address, 100000000000) 41 | await token.mint(holder3.address, 100000000000) 42 | }) 43 | 44 | describe("deposit & withdraw with same decimal", () => { 45 | beforeEach(async () => { 46 | cToken = await cTokenFactory.deploy( 47 | minter.address, 48 | "Crosschain Test Token", 49 | "cTest", 50 | (await token.decimals()) 51 | ) as CrosschainERC20V2 52 | 53 | pair = await pairFactory.deploy( 54 | cToken.address, 55 | (await cToken.decimals()), 56 | token.address, 57 | (await token.decimals()), 58 | owner.address, 59 | ) as CrosschainERC20V2Pair 60 | 61 | await token.connect(holder1).approve(pair.address, 100000000000) 62 | await token.connect(holder2).approve(pair.address, 100000000000) 63 | await token.connect(holder3).approve(pair.address, 100000000000) 64 | await token.connect(attacker).approve(pair.address, 100000000000) 65 | 66 | await cToken.connect(holder1).approve(pair.address, 100000000000) 67 | await cToken.connect(holder2).approve(pair.address, 100000000000) 68 | await cToken.connect(holder3).approve(pair.address, 100000000000) 69 | await cToken.connect(attacker).approve(pair.address, 100000000000) 70 | await pair.connect(owner).increaseCredit(200000000000) 71 | expect(await pair.remainingCredit()).to.equals(200000000000) 72 | }) 73 | 74 | it("no minter", async () => { 75 | expect(1).to.equals(await pair.scale()) 76 | expect(0).to.equals(await pair.scaleType()) 77 | expect(0).to.equals(await cToken.totalSupply()) 78 | 79 | await expect(pair.connect(holder1).deposit(100000000)).to.be.revertedWith("not the minter") 80 | expect(0).to.equals(await cToken.totalSupply()) 81 | }) 82 | 83 | it("deposit", async () => { 84 | await minter.connect(owner).addMinter(pair.address, cToken.address) 85 | 86 | expect(0).to.equals(await cToken.totalSupply()) 87 | await pair.connect(holder1).deposit(100000000) 88 | 89 | expect(100000000).to.equals(await cToken.totalSupply()) 90 | expect(100000000).to.equals(await cToken.balanceOf(holder1.address)) 91 | expect(99900000000).to.equals(await token.balanceOf(holder1.address)) 92 | expect(100000000).to.equals(await token.balanceOf(pair.address)) 93 | 94 | await pair.connect(holder2).deposit(200000000) 95 | 96 | expect(300000000).to.equals(await cToken.totalSupply()) 97 | expect(200000000).to.equals(await cToken.balanceOf(holder2.address)) 98 | expect(99900000000).to.equals(await token.balanceOf(holder1.address)) 99 | expect(99800000000).to.equals(await token.balanceOf(holder2.address)) 100 | expect(300000000).to.equals(await token.balanceOf(pair.address)) 101 | 102 | await pair.connect(holder3).depositTo(holder1.address, 300000000) 103 | expect(0).to.equals(await cToken.balanceOf(holder3.address)) 104 | expect(400000000).to.equals(await cToken.balanceOf(holder1.address)) 105 | expect(600000000).to.equals(await cToken.totalSupply()) 106 | 107 | await pair.connect(holder1).withdraw(100000000) 108 | expect(300000000).to.equals(await cToken.balanceOf(holder1.address)) 109 | expect(500000000).to.equals(await cToken.totalSupply()) 110 | expect(100000000000).to.equals(await token.balanceOf(holder1.address)) 111 | 112 | await pair.connect(holder1).withdrawTo(holder2.address, 100000000) 113 | expect(200000000).to.equals(await cToken.balanceOf(holder1.address)) 114 | expect(400000000).to.equals(await cToken.totalSupply()) 115 | expect(100000000000).to.equals(await token.balanceOf(holder1.address)) 116 | expect(99900000000).to.equals(await token.balanceOf(holder2.address)) 117 | }) 118 | 119 | it("no rounding deposit", async () => { 120 | await minter.connect(owner).addMinter(pair.address, cToken.address) 121 | 122 | expect(0).to.equals(await cToken.totalSupply()) 123 | await pair.connect(holder1).depositNoRounding(100000000) 124 | 125 | expect(100000000).to.equals(await cToken.totalSupply()) 126 | expect(100000000).to.equals(await cToken.balanceOf(holder1.address)) 127 | expect(99900000000).to.equals(await token.balanceOf(holder1.address)) 128 | expect(100000000).to.equals(await token.balanceOf(pair.address)) 129 | 130 | await pair.connect(holder2).depositNoRounding(200000000) 131 | 132 | expect(300000000).to.equals(await cToken.totalSupply()) 133 | expect(200000000).to.equals(await cToken.balanceOf(holder2.address)) 134 | expect(99900000000).to.equals(await token.balanceOf(holder1.address)) 135 | expect(99800000000).to.equals(await token.balanceOf(holder2.address)) 136 | expect(300000000).to.equals(await token.balanceOf(pair.address)) 137 | 138 | await pair.connect(holder3).depositToNoRounding(holder1.address, 300000000) 139 | expect(0).to.equals(await cToken.balanceOf(holder3.address)) 140 | expect(400000000).to.equals(await cToken.balanceOf(holder1.address)) 141 | expect(600000000).to.equals(await cToken.totalSupply()) 142 | 143 | await pair.connect(holder1).withdrawNoRounding(100000000) 144 | expect(300000000).to.equals(await cToken.balanceOf(holder1.address)) 145 | expect(500000000).to.equals(await cToken.totalSupply()) 146 | expect(100000000000).to.equals(await token.balanceOf(holder1.address)) 147 | 148 | await pair.connect(holder1).withdrawToNoRounding(holder2.address, 100000000) 149 | expect(200000000).to.equals(await cToken.balanceOf(holder1.address)) 150 | expect(400000000).to.equals(await cToken.totalSupply()) 151 | expect(100000000000).to.equals(await token.balanceOf(holder1.address)) 152 | expect(99900000000).to.equals(await token.balanceOf(holder2.address)) 153 | }) 154 | }) 155 | 156 | describe("deposit & withdraw with down decimal", () => { 157 | beforeEach(async () => { 158 | cToken = await cTokenFactory.deploy( 159 | minter.address, 160 | "Crosschain Test Token", 161 | "cTest", 162 | 6 163 | ) as CrosschainERC20V2 164 | 165 | pair = await pairFactory.deploy( 166 | cToken.address, 167 | (await cToken.decimals()), 168 | token.address, 169 | (await token.decimals()), 170 | owner.address, 171 | ) as CrosschainERC20V2Pair 172 | 173 | await token.connect(holder1).approve(pair.address, 100000000000) 174 | await token.connect(holder2).approve(pair.address, 100000000000) 175 | await token.connect(holder3).approve(pair.address, 100000000000) 176 | await token.connect(attacker).approve(pair.address, 100000000000) 177 | 178 | await cToken.connect(holder1).approve(pair.address, 100000000000) 179 | await cToken.connect(holder2).approve(pair.address, 100000000000) 180 | await cToken.connect(holder3).approve(pair.address, 100000000000) 181 | await cToken.connect(attacker).approve(pair.address, 100000000000) 182 | 183 | await minter.connect(owner).addMinter(pair.address, cToken.address) 184 | await pair.connect(owner).increaseCredit(200000000000) 185 | expect(await pair.remainingCredit()).to.equals(200000000000) 186 | }) 187 | 188 | describe("credit", () => { 189 | it("no permission", async() => { 190 | await expect(pair.connect(attacker).increaseCredit(100)).to.be.revertedWith("Ownable: caller is not the owner") 191 | expect(await pair.remainingCredit()).to.equals(200000000000) 192 | await expect(pair.connect(attacker).reduceCredit(100)).to.be.revertedWith("Ownable: caller is not the owner") 193 | expect(await pair.remainingCredit()).to.equals(200000000000) 194 | }) 195 | it("failed to reduce credit", async() => { 196 | await expect(pair.connect(owner).reduceCredit(200000000001)).to.be.reverted 197 | expect(await pair.remainingCredit()).to.equals(200000000000) 198 | }) 199 | it("reduce credit", async () => { 200 | await pair.connect(owner).reduceCredit(100000000000) 201 | expect(await pair.remainingCredit()).to.equals(100000000000) 202 | }) 203 | }) 204 | 205 | it("check basic", async () => { 206 | expect(100).to.equals(await pair.scale()) 207 | expect(2).to.equals(await pair.scaleType()) 208 | expect(0).to.equals(await cToken.totalSupply()) 209 | }) 210 | 211 | it("deposit", async () => { 212 | expect(0).to.equals(await cToken.totalSupply()) 213 | await pair.connect(holder1).deposit(100000000) 214 | 215 | expect(1000000).to.equals(await cToken.totalSupply()) 216 | expect(1000000).to.equals(await cToken.balanceOf(holder1.address)) 217 | expect(99900000000).to.equals(await token.balanceOf(holder1.address)) 218 | expect(100000000).to.equals(await token.balanceOf(pair.address)) 219 | 220 | await pair.connect(holder2).deposit(200000000) 221 | 222 | expect(3000000).to.equals(await cToken.totalSupply()) 223 | expect(2000000).to.equals(await cToken.balanceOf(holder2.address)) 224 | expect(99900000000).to.equals(await token.balanceOf(holder1.address)) 225 | expect(99800000000).to.equals(await token.balanceOf(holder2.address)) 226 | expect(300000000).to.equals(await token.balanceOf(pair.address)) 227 | 228 | await pair.connect(holder3).depositTo(holder1.address, 300000099) 229 | expect(0).to.equals(await cToken.balanceOf(holder3.address)) 230 | expect(4000000).to.equals(await cToken.balanceOf(holder1.address)) 231 | expect(6000000).to.equals(await cToken.totalSupply()) 232 | expect(99700000000).to.equals(await token.balanceOf(holder3.address)) 233 | expect(600000000).to.equals(await token.balanceOf(pair.address)) 234 | 235 | await pair.connect(holder1).withdraw(1000000) 236 | expect(3000000).to.equals(await cToken.balanceOf(holder1.address)) 237 | expect(5000000).to.equals(await cToken.totalSupply()) 238 | expect(100000000000).to.equals(await token.balanceOf(holder1.address)) 239 | 240 | await pair.connect(holder1).withdrawTo(holder2.address, 1000000) 241 | expect(2000000).to.equals(await cToken.balanceOf(holder1.address)) 242 | expect(4000000).to.equals(await cToken.totalSupply()) 243 | expect(100000000000).to.equals(await token.balanceOf(holder1.address)) 244 | expect(99900000000).to.equals(await token.balanceOf(holder2.address)) 245 | 246 | await expect(pair.connect(holder1).depositNoRounding(100000001)).to.be.revertedWith("no rounding") 247 | 248 | await pair.connect(holder1).withdrawNoRounding(1000000) 249 | expect(1000000).to.equals(await cToken.balanceOf(holder1.address)) 250 | expect(3000000).to.equals(await cToken.totalSupply()) 251 | }) 252 | }) 253 | 254 | describe("deposit & withdraw with up decimal", () => { 255 | beforeEach(async () => { 256 | cToken = await cTokenFactory.deploy( 257 | minter.address, 258 | "Crosschain Test Token", 259 | "cTest", 260 | 10 261 | ) as CrosschainERC20V2 262 | 263 | pair = await pairFactory.deploy( 264 | cToken.address, 265 | (await cToken.decimals()), 266 | token.address, 267 | (await token.decimals()), 268 | owner.address, 269 | ) as CrosschainERC20V2Pair 270 | 271 | await token.connect(holder1).approve(pair.address, 100000000000) 272 | await token.connect(holder2).approve(pair.address, 100000000000) 273 | await token.connect(holder3).approve(pair.address, 100000000000) 274 | await token.connect(attacker).approve(pair.address, 100000000000) 275 | 276 | await cToken.connect(holder1).approve(pair.address, 10000000000000) 277 | await cToken.connect(holder2).approve(pair.address, 10000000000000) 278 | await cToken.connect(holder3).approve(pair.address, 10000000000000) 279 | await cToken.connect(attacker).approve(pair.address, 10000000000000) 280 | 281 | await minter.connect(owner).addMinter(pair.address, cToken.address) 282 | await pair.connect(owner).increaseCredit(200000000000) 283 | expect(await pair.remainingCredit()).to.equals(200000000000) 284 | }) 285 | 286 | it("check basic", async () => { 287 | expect(100).to.equals(await pair.scale()) 288 | expect(1).to.equals(await pair.scaleType()) 289 | expect(0).to.equals(await cToken.totalSupply()) 290 | }) 291 | 292 | it("deposit", async () => { 293 | expect(0).to.equals(await cToken.totalSupply()) 294 | await pair.connect(holder1).deposit(100000000) 295 | 296 | expect(10000000000).to.equals(await cToken.totalSupply()) 297 | expect(10000000000).to.equals(await cToken.balanceOf(holder1.address)) 298 | expect(99900000000).to.equals(await token.balanceOf(holder1.address)) 299 | expect(100000000).to.equals(await token.balanceOf(pair.address)) 300 | 301 | await pair.connect(holder2).deposit(200000000) 302 | 303 | expect(30000000000).to.equals(await cToken.totalSupply()) 304 | expect(20000000000).to.equals(await cToken.balanceOf(holder2.address)) 305 | expect(99900000000).to.equals(await token.balanceOf(holder1.address)) 306 | expect(99800000000).to.equals(await token.balanceOf(holder2.address)) 307 | expect(300000000).to.equals(await token.balanceOf(pair.address)) 308 | 309 | await pair.connect(holder3).depositTo(holder1.address, 300000000) 310 | expect(0).to.equals(await cToken.balanceOf(holder3.address)) 311 | expect(40000000000).to.equals(await cToken.balanceOf(holder1.address)) 312 | expect(60000000000).to.equals(await cToken.totalSupply()) 313 | 314 | await pair.connect(holder1).withdraw(10000000000) 315 | expect(30000000000).to.equals(await cToken.balanceOf(holder1.address)) 316 | expect(50000000000).to.equals(await cToken.totalSupply()) 317 | expect(100000000000).to.equals(await token.balanceOf(holder1.address)) 318 | 319 | await pair.connect(holder1).withdrawTo(holder2.address, 10000000000) 320 | expect(20000000000).to.equals(await cToken.balanceOf(holder1.address)) 321 | expect(40000000000).to.equals(await cToken.totalSupply()) 322 | expect(100000000000).to.equals(await token.balanceOf(holder1.address)) 323 | expect(99900000000).to.equals(await token.balanceOf(holder2.address)) 324 | 325 | await pair.connect(holder1).withdraw(10000000099) 326 | expect(10000000000).to.equals(await cToken.balanceOf(holder1.address)) 327 | expect(30000000000).to.equals(await cToken.totalSupply()) 328 | 329 | await expect(pair.connect(holder1).withdrawNoRounding(9999999901)).to.be.revertedWith("no rounding") 330 | }) 331 | }) 332 | }) -------------------------------------------------------------------------------- /test/ledger_v2.test.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat" 2 | import { expect } from "chai" 3 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers" 4 | import { LedgerV2 } from "../types/LedgerV2"; 5 | 6 | describe("ledger v2 tests", () => { 7 | let ledger: LedgerV2 8 | 9 | let owner: SignerWithAddress 10 | let operator: SignerWithAddress 11 | const TEST_ID = "0x0000000000000000000000000000000000000000000000000000000000000001" 12 | 13 | beforeEach(async function () { 14 | [owner, operator] = await ethers.getSigners() 15 | 16 | const factory = await ethers.getContractFactory("LedgerV2") 17 | ledger = await factory.deploy() as LedgerV2 18 | }) 19 | 20 | it("check record", async () => { 21 | await expect( 22 | ledger.connect(operator).record(TEST_ID) 23 | ).to.be.revertedWith("invalid operator") 24 | 25 | await expect( 26 | ledger.connect(operator).addOperator(operator.address) 27 | ).to.be.revertedWith("Ownable: caller is not the owner") 28 | 29 | await expect( 30 | ledger.connect(owner).removeOperator(operator.address) 31 | ).to.be.revertedWith("not an operator") 32 | await ledger.connect(owner).addOperator(operator.address) 33 | await expect( 34 | ledger.connect(owner).addOperator(operator.address) 35 | ).to.be.revertedWith("already an operator") 36 | 37 | expect(0).to.equals(await ledger.get(TEST_ID)) 38 | 39 | ledger.connect(operator).record(TEST_ID) 40 | await expect( 41 | ledger.connect(operator).record(TEST_ID) 42 | ).to.be.revertedWith("duplicate record") 43 | 44 | await ledger.connect(owner).removeOperator(operator.address) 45 | await expect( 46 | ledger.connect(operator).record("0x0000000000000000000000000000000000000000000000000000000000000002") 47 | ).to.be.revertedWith("invalid operator") 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /test/lord.test.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers" 2 | import { expect } from "chai" 3 | import { ethers } from "hardhat" 4 | import { Lord } from "../types/Lord" 5 | import { MockToken } from "../types/MockToken" 6 | import { MockTokenNFT } from "../types/MockTokenNFT" 7 | 8 | const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" 9 | 10 | describe("lord test", function () { 11 | const tokenID = 123456789 12 | let coToken: MockToken 13 | let coTokenNFT: MockTokenNFT 14 | let lord: Lord 15 | 16 | let owner: SignerWithAddress 17 | let holder1: SignerWithAddress 18 | let holder2: SignerWithAddress 19 | let holder3: SignerWithAddress 20 | let attacker: SignerWithAddress 21 | 22 | beforeEach(async function () { 23 | [owner, holder1, holder2, holder3, attacker] = await ethers.getSigners() 24 | 25 | const Lord = await ethers.getContractFactory("Lord") 26 | lord = (await Lord.connect(owner).deploy( 27 | ZERO_ADDRESS, 28 | ZERO_ADDRESS, 29 | ZERO_ADDRESS, 30 | ZERO_ADDRESS 31 | )) as Lord 32 | await lord.connect(owner).deployed() 33 | 34 | const MockToken = await ethers.getContractFactory("MockToken") 35 | coToken = (await MockToken.deploy("name", "symbol", 6)) as MockToken 36 | await coToken.deployed() 37 | await coToken.transferOwnership(lord.address) 38 | await coToken.connect(holder2).approve(lord.address, 1000000) 39 | 40 | const MockTokenNFT = await ethers.getContractFactory("MockTokenNFT") 41 | coTokenNFT = (await MockTokenNFT.deploy("name", "symbol")) as MockTokenNFT 42 | await coTokenNFT.deployed() 43 | await coTokenNFT.transferOwnership(lord.address) 44 | 45 | coTokenNFT.connect(holder3).setApprovalForAll(lord.address, true) 46 | }) 47 | 48 | it("mint & burn token", async function () { 49 | expect(await coToken.balanceOf(holder2.address)).to.be.equal(0) 50 | await lord.connect(owner).mint(coToken.address, holder2.address, 1000) 51 | expect(await coToken.balanceOf(holder2.address)).to.be.equal(1000) 52 | 53 | await lord.connect(owner).burn(coToken.address, holder2.address, 200) 54 | expect(await coToken.balanceOf(holder2.address)).to.equal(800) 55 | }) 56 | 57 | it("mint & burn tokenNFT", async function () { 58 | expect(await coTokenNFT.balanceOf(holder3.address)).to.equal(0) 59 | await lord.connect(owner).mintNFT(coTokenNFT.address, tokenID, holder3.address, "0x") 60 | expect(await coTokenNFT.balanceOf(holder3.address)).to.equal(1) 61 | 62 | await coTokenNFT.connect(holder3).approve(lord.address, tokenID) 63 | 64 | await lord.connect(owner).burnNFT(coTokenNFT.address, tokenID) 65 | expect(await coTokenNFT.balanceOf(holder3.address)).to.equal(0) 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /test/lord_v2.test.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat" 2 | import { expect } from "chai" 3 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers" 4 | import { LordV2 } from "../types/LordV2"; 5 | import { MockToken } from "../types/MockToken"; 6 | 7 | describe("lord v2 tests", () => { 8 | let lord: LordV2 9 | let token: MockToken 10 | 11 | let owner: SignerWithAddress 12 | let operator: SignerWithAddress 13 | const ACCOUNT = "0x0000000000000000000000000000000000000001" 14 | 15 | beforeEach(async function () { 16 | [owner, operator] = await ethers.getSigners() 17 | 18 | const tokenFactory = await ethers.getContractFactory("MockToken") 19 | token = await tokenFactory.deploy( 20 | "Test Token", 21 | "TEST", 22 | 8 23 | ) as MockToken 24 | 25 | const factory = await ethers.getContractFactory("LordV2") 26 | lord = await factory.deploy() as LordV2 27 | await lord.initialize() 28 | 29 | await token.transferOwnership(lord.address) 30 | }) 31 | 32 | it("check mint", async () => { 33 | await expect( 34 | lord.connect(operator).mint(token.address, ACCOUNT, 1) 35 | ).to.be.revertedWith("invalid operator") 36 | 37 | await expect( 38 | lord.connect(operator).addOperator(operator.address) 39 | ).to.be.revertedWith("caller is not the owner") 40 | 41 | await expect( 42 | lord.connect(owner).removeOperator(operator.address) 43 | ).to.be.revertedWith("not an operator") 44 | await lord.connect(owner).addOperator(operator.address) 45 | await expect( 46 | lord.connect(owner).addOperator(operator.address) 47 | ).to.be.revertedWith("already an operator") 48 | 49 | expect(0).to.equals(await token.balanceOf(ACCOUNT)) 50 | await lord.connect(operator).mint(token.address, ACCOUNT, 1) 51 | expect(1).to.equals(await token.balanceOf(ACCOUNT)) 52 | 53 | await lord.connect(owner).removeOperator(operator.address) 54 | await expect( 55 | lord.connect(operator).mint(token.address, ACCOUNT, 1) 56 | ).to.be.revertedWith("invalid operator") 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /test/minter_dao.test.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat" 2 | import { expect } from "chai" 3 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers" 4 | import { EmergencyOperator } from "../types/EmergencyOperator" 5 | import { MinterDAO } from "../types/MinterDAO" 6 | 7 | describe("minter dao tests", () => { 8 | let minterDAO: MinterDAO 9 | let emergencyOperatorContract: EmergencyOperator 10 | 11 | let owner: SignerWithAddress 12 | let lord: SignerWithAddress 13 | let emergencyOperator: SignerWithAddress 14 | let pair: SignerWithAddress 15 | let attacker: SignerWithAddress 16 | 17 | beforeEach(async () => { 18 | [owner, lord, emergencyOperator, pair, attacker] = await ethers.getSigners() 19 | const emergencyOperatorFactory = await ethers.getContractFactory("EmergencyOperator") 20 | emergencyOperatorContract = await emergencyOperatorFactory.deploy() as EmergencyOperator 21 | await emergencyOperatorContract.initialize() 22 | await emergencyOperatorContract.addEmergencyOperator(emergencyOperator.address) 23 | 24 | const minterDAOFactory = await ethers.getContractFactory("MinterDAO") 25 | minterDAO = await minterDAOFactory.deploy() as MinterDAO 26 | 27 | await minterDAO.initialize(lord.address, emergencyOperatorContract.address) 28 | }) 29 | 30 | it("check lord", async () => { 31 | expect(await minterDAO.isMinter(lord.address, "0x0000000000000000000000000000000000000001")).to.be.true 32 | 33 | await expect(minterDAO.connect(attacker).pause()).to.be.revertedWith("no permission") 34 | await minterDAO.connect(emergencyOperator).pause() 35 | await expect(minterDAO.isMinter(lord.address, "0x0000000000000000000000000000000000000001")).to.be.reverted 36 | }) 37 | 38 | it("check pair", async () => { 39 | expect(await minterDAO.isMinter(pair.address, "0x0000000000000000000000000000000000000001")).to.be.false 40 | 41 | await minterDAO.connect(owner).addMinter(pair.address, "0x0000000000000000000000000000000000000001") 42 | await expect( 43 | minterDAO.connect(owner).addMinter(pair.address, "0x0000000000000000000000000000000000000001") 44 | ).to.be.revertedWith("already a minter") 45 | 46 | expect(await minterDAO.isMinter(pair.address, "0x0000000000000000000000000000000000000001")).to.be.true 47 | 48 | await expect(minterDAO.connect(attacker).pause()).to.be.revertedWith("no permission") 49 | await minterDAO.connect(emergencyOperator).pause() 50 | await expect(minterDAO.isMinter(pair.address, "0x0000000000000000000000000000000000000001")).to.be.reverted 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /test/validator_registry.test.ts: -------------------------------------------------------------------------------- 1 | import _ from "lodash" 2 | import { ethers } from "hardhat" 3 | import { expect } from "chai" 4 | import { Contract } from "@ethersproject/contracts" 5 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers" 6 | import { ecsign, toBuffer, setLengthLeft } from "ethereumjs-util" 7 | 8 | const privateKeyToAddress = require("ethereum-private-key-to-address") 9 | 10 | // TODO: add unit test for validator registry -------------------------------------------------------------------------------- /test/verifier_v2.test.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat" 2 | import { expect } from "chai" 3 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers" 4 | import { EmergencyOperator } from "../types/EmergencyOperator"; 5 | import { VerifierV2 } from "../types/VerifierV2"; 6 | 7 | describe("verifier v2 tests", () => { 8 | let verifier: VerifierV2 9 | let emergencyOperator: EmergencyOperator 10 | 11 | let owner: SignerWithAddress 12 | let operator: SignerWithAddress 13 | 14 | beforeEach(async function () { 15 | [owner, operator] = await ethers.getSigners() 16 | 17 | const emergencyOperatorFactory = await ethers.getContractFactory("EmergencyOperator") 18 | emergencyOperator = await emergencyOperatorFactory.deploy() as EmergencyOperator 19 | await emergencyOperator.initialize() 20 | 21 | const verifierFactory = await ethers.getContractFactory("VerifierV2") 22 | verifier = await verifierFactory.deploy(emergencyOperator.address) as VerifierV2 23 | }) 24 | 25 | it("check emergency pause", async () => { 26 | expect(owner.address).to.equals(await verifier.owner()) 27 | 28 | await expect(verifier.connect(operator).pause()).to.be.revertedWith("no permission") 29 | 30 | await emergencyOperator.connect(owner).addEmergencyOperator(operator.address) 31 | 32 | await verifier.connect(operator).pause() 33 | 34 | expect(await verifier.paused()).to.be.true 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "outDir": "dist", 8 | "resolveJsonModule": true, 9 | "declaration": true, 10 | "sourceMap": true, 11 | "noImplicitAny": false 12 | }, 13 | "exclude": ["dist", "node_modules"], 14 | "include": ["./test", "./src", "./scripts"], 15 | "files": ["./hardhat.config.ts"] 16 | } 17 | --------------------------------------------------------------------------------