Transaction Hash:
Block:
21947814 at Feb-28-2025 10:24:23 PM +UTC
Transaction Fee:
0.000323921941597464 ETH
$0.78
Gas Used:
100,803 Gas / 3.213415688 Gwei
Emitted Events:
80 |
TransparentUpgradeableProxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x0000000000000000000000005d22045daceab03b158031ecb7d9d06fad24609b, 0x0000000000000000000000003a7aaf9ef3a9ca1f2a9c8f993a97042459482124, 0000000000000000000000000000000000000000000001dddba0e168bd034400 )
|
81 |
Proxy.0xb7477a7b93b2addc5272bbd7ad0986ef1c0d0bd265f26c3dc4bbe42727c2ac0c( 0xb7477a7b93b2addc5272bbd7ad0986ef1c0d0bd265f26c3dc4bbe42727c2ac0c, 0000000000000000000000003a7aaf9ef3a9ca1f2a9c8f993a97042459482124, 00171b628737e245c05870d0c44347e1506ad899427ff651c2dc2d1cbb347b07, 0000000000000000000000000000000000000000000001dddba0e168bd034400, 000000000000000000000000000000000000000000000000000000cd3d08ca99, 0000000000000000000000003a7aaf9ef3a9ca1f2a9c8f993a97042459482124 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x3A7AAf9e...459482124 |
0.978647425339862908 Eth
Nonce: 33
|
0.978323503398265444 Eth
Nonce: 34
| 0.000323921941597464 | ||
0x4838B106...B0BAD5f97
Miner
| (Titan Builder) | 14.910195092130529848 Eth | 14.910447099630529848 Eth | 0.0002520075 | |
0x5d22045D...Fad24609b | (Rhino.fi: Bridge) | ||||
0x9ee91F9f...b9dfd8599 |
Execution Trace
Proxy.441a3e70( )
StarkExchange.441a3e70( )
TokensAndRamping.withdraw( ownerKey=333857454360539460641057648466950877856529916196, assetType=40826485956188234318082755377178565859657088016697035626568149089176025863 )
TransparentUpgradeableProxy.70a08231( )
-
StMATIC.balanceOf( account=0x5d22045DAcEAB03B158031eCB7D9d06Fad24609b ) => ( 31966847229280000000000 )
-
TransparentUpgradeableProxy.a9059cbb( )
-
StMATIC.transfer( to=0x3A7AAf9ef3A9ca1F2a9C8F993A97042459482124, amount=8814922820090000000000 ) => ( True )
-
TransparentUpgradeableProxy.70a08231( )
-
StMATIC.balanceOf( account=0x5d22045DAcEAB03B158031eCB7D9d06Fad24609b ) => ( 23151924409190000000000 )
-
File 1 of 5: Proxy
File 2 of 5: TransparentUpgradeableProxy
File 3 of 5: StarkExchange
File 4 of 5: TokensAndRamping
File 5 of 5: StMATIC
{"Common.sol":{"content":"/*\n Copyright 2019,2020 StarkWare Industries Ltd.\n\n Licensed under the Apache License, Version 2.0 (the \"License\").\n You may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n https://www.starkware.co/open-source-license/\n\n Unless required by applicable law or agreed to in writing,\n software distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions\n and limitations under the License.\n*/\npragma solidity ^0.5.2;\n\n/*\n Common Utility libraries.\n 1. Addresses (extending address).\n*/\nlibrary Addresses {\n function isContract(address account) internal view returns (bool) {\n uint256 size;\n // solium-disable-next-line security/no-inline-assembly\n assembly {\n size := extcodesize(account)\n }\n return size \u003e 0;\n }\n}\n"},"Governance.sol":{"content":"/*\n Copyright 2019,2020 StarkWare Industries Ltd.\n\n Licensed under the Apache License, Version 2.0 (the \"License\").\n You may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n https://www.starkware.co/open-source-license/\n\n Unless required by applicable law or agreed to in writing,\n software distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions\n and limitations under the License.\n*/\npragma solidity ^0.5.2;\n\nimport \"GovernanceStorage.sol\";\nimport \"MGovernance.sol\";\n\n/*\n Implements Generic Governance, applicable for both proxy and main contract, and possibly others.\n Notes:\n 1. This class is virtual (getGovernanceTag is not implemented).\n 2. The use of the same function names by both the Proxy and a delegated implementation\n is not possible since calling the implementation functions is done via the default function\n of the Proxy. For this reason, for example, the implementation of MainContract (MainGovernance)\n exposes mainIsGovernor, which calls the internal isGovernor method.\n*/\ncontract Governance is GovernanceStorage, MGovernance {\n event LogNominatedGovernor(address nominatedGovernor);\n event LogNewGovernorAccepted(address acceptedGovernor);\n event LogRemovedGovernor(address removedGovernor);\n event LogNominationCancelled();\n\n address internal constant ZERO_ADDRESS = address(0x0);\n\n /*\n Returns a string which uniquely identifies the type of the governance mechanism.\n */\n function getGovernanceTag()\n internal\n view\n returns (string memory);\n\n /*\n Returns the GovernanceInfoStruct associated with the governance tag.\n */\n function contractGovernanceInfo()\n internal\n view\n returns (GovernanceInfoStruct storage) {\n string memory tag = getGovernanceTag();\n GovernanceInfoStruct storage gub = governanceInfo[tag];\n require(gub.initialized == true, \"NOT_INITIALIZED\");\n return gub;\n }\n\n function initGovernance()\n internal\n {\n string memory tag = getGovernanceTag();\n GovernanceInfoStruct storage gub = governanceInfo[tag];\n require(gub.initialized == false, \"ALREADY_INITIALIZED\");\n gub.initialized = true; // to ensure addGovernor() won\u0027t fail.\n // Add the initial governer.\n addGovernor(msg.sender);\n }\n\n modifier onlyGovernance()\n {\n require(isGovernor(msg.sender), \"ONLY_GOVERNANCE\");\n _;\n }\n\n function isGovernor(address testGovernor)\n internal view\n returns (bool addressIsGovernor){\n GovernanceInfoStruct storage gub = contractGovernanceInfo();\n addressIsGovernor = gub.effectiveGovernors[testGovernor];\n }\n\n /*\n Cancels the nomination of a governor condidate.\n */\n function cancelNomination() internal onlyGovernance() {\n GovernanceInfoStruct storage gub = contractGovernanceInfo();\n gub.candidateGovernor = ZERO_ADDRESS;\n emit LogNominationCancelled();\n }\n\n function nominateNewGovernor(address newGovernor) internal onlyGovernance() {\n GovernanceInfoStruct storage gub = contractGovernanceInfo();\n require(isGovernor(newGovernor) == false, \"ALREADY_GOVERNOR\");\n gub.candidateGovernor = newGovernor;\n emit LogNominatedGovernor(newGovernor);\n }\n\n /*\n The addGovernor is called in two cases:\n 1. by acceptGovernance when a new governor accepts its role.\n 2. by initGovernance to add the initial governor.\n The difference is that the init path skips the nominate step\n that would fail because of the onlyGovernance modifier.\n */\n function addGovernor(address newGovernor) private {\n require(isGovernor(newGovernor) == false, \"ALREADY_GOVERNOR\");\n GovernanceInfoStruct storage gub = contractGovernanceInfo();\n gub.effectiveGovernors[newGovernor] = true;\n }\n\n function acceptGovernance()\n internal\n {\n // The new governor was proposed as a candidate by the current governor.\n GovernanceInfoStruct storage gub = contractGovernanceInfo();\n require(msg.sender == gub.candidateGovernor, \"ONLY_CANDIDATE_GOVERNOR\");\n\n // Update state.\n addGovernor(gub.candidateGovernor);\n gub.candidateGovernor = ZERO_ADDRESS;\n\n // Send a notification about the change of governor.\n emit LogNewGovernorAccepted(msg.sender);\n }\n\n /*\n Remove a governor from office.\n */\n function removeGovernor(address governorForRemoval) internal onlyGovernance() {\n require(msg.sender != governorForRemoval, \"GOVERNOR_SELF_REMOVE\");\n GovernanceInfoStruct storage gub = contractGovernanceInfo();\n require (isGovernor(governorForRemoval), \"NOT_GOVERNOR\");\n gub.effectiveGovernors[governorForRemoval] = false;\n emit LogRemovedGovernor(governorForRemoval);\n }\n}\n"},"GovernanceStorage.sol":{"content":"/*\n Copyright 2019,2020 StarkWare Industries Ltd.\n\n Licensed under the Apache License, Version 2.0 (the \"License\").\n You may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n https://www.starkware.co/open-source-license/\n\n Unless required by applicable law or agreed to in writing,\n software distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions\n and limitations under the License.\n*/\npragma solidity ^0.5.2;\n\n/*\n Holds the governance slots for ALL entities, including proxy and the main contract.\n*/\ncontract GovernanceStorage {\n\n struct GovernanceInfoStruct {\n mapping (address =\u003e bool) effectiveGovernors;\n address candidateGovernor;\n bool initialized;\n }\n\n // A map from a Governor tag to its own GovernanceInfoStruct.\n mapping (string =\u003e GovernanceInfoStruct) internal governanceInfo;\n}\n"},"MGovernance.sol":{"content":"/*\n Copyright 2019,2020 StarkWare Industries Ltd.\n\n Licensed under the Apache License, Version 2.0 (the \"License\").\n You may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n https://www.starkware.co/open-source-license/\n\n Unless required by applicable law or agreed to in writing,\n software distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions\n and limitations under the License.\n*/\npragma solidity ^0.5.2;\n\ncontract MGovernance {\n /*\n Allows calling the function only by a Governor.\n */\n modifier onlyGovernance()\n {\n // Pure modifier declarations are not supported. Instead we provide\n // a dummy definition.\n revert(\"UNIMPLEMENTED\");\n _;\n }\n}\n"},"Proxy.sol":{"content":"/*\n Copyright 2019,2020 StarkWare Industries Ltd.\n\n Licensed under the Apache License, Version 2.0 (the \"License\").\n You may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n https://www.starkware.co/open-source-license/\n\n Unless required by applicable law or agreed to in writing,\n software distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions\n and limitations under the License.\n*/\npragma solidity ^0.5.2;\n\nimport \"ProxyGovernance.sol\";\nimport \"ProxyStorage.sol\";\nimport \"Common.sol\";\n\n/**\n The Proxy contract implements delegation of calls to other contracts (`implementations`), with\n proper forwarding of return values and revert reasons. This pattern allows retaining the contract\n storage while replacing implementation code.\n\n The following operations are supported by the proxy contract:\n\n - :sol:func:`addImplementation`: Defines a new implementation, the data with which it should be initialized and whether this will be the last version of implementation.\n - :sol:func:`upgradeTo`: Once an implementation is added, the governor may upgrade to that implementation only after a safety time period has passed (time lock), the current implementation is not the last version and the implementation is not frozen (see :sol:mod:`FullWithdrawals`).\n - :sol:func:`removeImplementation`: Any announced implementation may be removed. Removing an implementation is especially important once it has been used for an upgrade in order to avoid an additional unwanted revert to an older version.\n\n The only entity allowed to perform the above operations is the proxy governor\n (see :sol:mod:`ProxyGovernance`).\n\n Every implementation is required to have an `initialize` function that replaces the constructor\n of a normal contract. Furthermore, the only parameter of this function is an array of bytes\n (`data`) which may be decoded arbitrarily by the `initialize` function. It is up to the\n implementation to ensure that this function cannot be run more than once if so desired.\n\n When an implementation is added (:sol:func:`addImplementation`) the initialization `data` is also\n announced, allowing users of the contract to analyze the full effect of an upgrade to the new\n implementation. During an :sol:func:`upgradeTo`, the `data` is provided again and only if it is\n identical to the announced `data` is the upgrade performed by pointing the proxy to the new\n implementation and calling its `initialize` function with this `data`.\n\n It is the responsibility of the implementation not to overwrite any storage belonging to the\n proxy (`ProxyStorage`). In addition, upon upgrade, the new implementation is assumed to be\n backward compatible with previous implementations with respect to the storage used until that\n point.\n*/\ncontract Proxy is ProxyStorage, ProxyGovernance {\n\n // Emitted when the active implementation is replaced.\n event Upgraded(address indexed implementation);\n\n // Emitted when an implementation is submitted as an upgrade candidate and a time lock\n // is activated.\n event ImplementationAdded(address indexed implementation, bytes initializer, bool finalize);\n\n // Emitted when an implementation is removed from the list of upgrade candidates.\n event ImplementationRemoved(address indexed implementation);\n\n // Emitted when the implementation is finalized.\n event FinalizedImplementation(address indexed implementation);\n\n // Storage slot with the address of the current implementation.\n // The address of the slot is keccak256(\"StarkWare2019.implemntation-slot\").\n // We need to keep this variable stored outside of the commonly used space,\n // so that it\u0027s not overrun by the logical implementaiton (the proxied contract).\n bytes32 internal constant IMPLEMENTATION_SLOT =\n 0x177667240aeeea7e35eabe3a35e18306f336219e1386f7710a6bf8783f761b24;\n\n // This storage slot stores the finalization flag.\n // Once the value stored in this slot is set to non-zero\n // the proxy blocks implementation upgrades.\n // The current implementation is then referred to as Finalized.\n // Web3.solidityKeccak([\u0027string\u0027], [\"StarkWare2019.finalization-flag-slot\"]).\n bytes32 internal constant FINALIZED_STATE_SLOT =\n 0x7d433c6f837e8f93009937c466c82efbb5ba621fae36886d0cac433c5d0aa7d2;\n uint256 public constant UPGRADE_ACTIVATION_DELAY = 28 days;\n\n using Addresses for address;\n\n constructor ( )\n public\n {\n initGovernance();\n }\n\n /*\n Returns true if the implementation is frozen.\n If the implementation was not assigned yet, returns false.\n */\n function implementationIsFrozen() private returns (bool) {\n address _implementation = implementation();\n\n // We can\u0027t call low level implementation before it\u0027s assigned. (i.e. ZERO).\n if (_implementation == ZERO_ADDRESS) {\n return false;\n }\n // solium-disable-next-line security/no-low-level-calls\n (bool success, bytes memory returndata) = _implementation.delegatecall(\n abi.encodeWithSignature(\"isFrozen()\"));\n require(success, string(returndata));\n return abi.decode(returndata, (bool));\n }\n\n /*\n This method blocks delegation to initialize().\n Only upgradeTo should be able to delegate call to initialize().\n */\n function initialize(bytes calldata /*data*/)\n external pure\n {\n revert(\"CANNOT_CALL_INITIALIZE\");\n }\n\n modifier notFinalized()\n {\n require(isNotFinalized(), \"IMPLEMENTATION_FINALIZED\");\n _;\n }\n\n /*\n Forbids calling the function if the implementation is frozen.\n This modifier relies on the lower level (logical contract) implementation of isFrozen().\n */\n modifier notFrozen()\n {\n require(implementationIsFrozen() == false, \"STATE_IS_FROZEN\");\n _;\n }\n\n /*\n Contract\u0027s default function. Delegates execution to the implementation contract.\n It returns back to the external caller whatever the implementation delegated code returns.\n */\n function () external payable {\n address _implementation = implementation();\n require (_implementation != ZERO_ADDRESS, \"MISSING_IMPLEMENTATION\");\n\n // solium-disable-next-line security/no-inline-assembly\n assembly {\n // Copy msg.data. We take full control of memory in this inline assembly\n // block because it will not return to Solidity code. We overwrite the\n // Solidity scratch pad at memory position 0.\n calldatacopy(0, 0, calldatasize)\n\n // Call the implementation.\n // out and outsize are 0 for now, as we don\u0027t know the out size yet.\n let result := delegatecall(gas, _implementation, 0, calldatasize, 0, 0)\n\n // Copy the returned data.\n returndatacopy(0, 0, returndatasize)\n\n switch result\n // delegatecall returns 0 on error.\n case 0 { revert(0, returndatasize) }\n default { return(0, returndatasize) }\n }\n }\n\n /*\n Returns the address of the current implementation.\n */\n function implementation() public view returns (address _implementation) {\n bytes32 slot = IMPLEMENTATION_SLOT;\n // solium-disable-next-line security/no-inline-assembly\n assembly {\n _implementation := sload(slot)\n }\n }\n\n /*\n Sets the implementation address of the proxy.\n */\n function setImplementation(address newImplementation) private {\n bytes32 slot = IMPLEMENTATION_SLOT;\n // solium-disable-next-line security/no-inline-assembly\n assembly {\n sstore(slot, newImplementation)\n }\n }\n\n /*\n Returns true if the contract is not in the finalized state.\n */\n function isNotFinalized() public view returns (bool notFinal) {\n bytes32 slot = FINALIZED_STATE_SLOT;\n uint256 slotValue;\n // solium-disable-next-line security/no-inline-assembly\n assembly {\n slotValue := sload(slot)\n }\n notFinal = (slotValue == 0);\n }\n\n /*\n Marks the current implementation as finalized.\n */\n function setFinalizedFlag() private {\n bytes32 slot = FINALIZED_STATE_SLOT;\n // solium-disable-next-line security/no-inline-assembly\n assembly {\n sstore(slot, 0x1)\n }\n }\n\n /*\n Introduce an implementation and its initialization vector,\n and start the time-lock before it can be upgraded to.\n addImplementation is not blocked when frozen or finalized.\n (upgradeTo API is blocked when finalized or frozen).\n */\n function addImplementation(address newImplementation, bytes calldata data, bool finalize)\n external onlyGovernance {\n require(newImplementation.isContract(), \"ADDRESS_NOT_CONTRACT\");\n\n bytes32 init_hash = keccak256(abi.encode(data, finalize));\n initializationHash[newImplementation] = init_hash;\n\n // solium-disable-next-line security/no-block-members\n uint256 activation_time = now + UPGRADE_ACTIVATION_DELAY;\n\n // First implementation should not have time-lock.\n if (implementation() == ZERO_ADDRESS) {\n // solium-disable-next-line security/no-block-members\n activation_time = now;\n }\n\n enabledTime[newImplementation] = activation_time;\n emit ImplementationAdded(newImplementation, data, finalize);\n }\n\n /*\n Removes a candidate implementation.\n Note that it is possible to remove the current implementation. Doing so doesn\u0027t affect the\n current implementation, but rather revokes it as a future candidate.\n */\n function removeImplementation(address newImplementation)\n external onlyGovernance {\n\n // If we have initializer, we set the hash of it.\n uint256 activation_time = enabledTime[newImplementation];\n\n require(activation_time \u003e 0, \"ADDRESS_NOT_UPGRADE_CANDIDATE\");\n\n enabledTime[newImplementation] = 0;\n\n initializationHash[newImplementation] = 0;\n emit ImplementationRemoved(newImplementation);\n }\n\n /*\n Upgrades the proxy to a new implementation, with its initialization.\n to upgrade successfully, implementation must have been added time-lock agreeably\n before, and the init vector must be identical ot the one submitted before.\n\n Upon assignment of new implementation address,\n its initialize will be called with the inititalizing vector (even if empty).\n Therefore, the implementatin MUST must have such a method.\n */\n function upgradeTo(address newImplementation, bytes calldata data, bool finalize)\n external payable onlyGovernance notFinalized notFrozen {\n uint256 activation_time = enabledTime[newImplementation];\n\n require(activation_time \u003e 0, \"ADDRESS_NOT_UPGRADE_CANDIDATE\");\n // solium-disable-next-line security/no-block-members\n require(activation_time \u003c= now, \"UPGRADE_NOT_ENABLED_YET\");\n\n bytes32 init_vector_hash = initializationHash[newImplementation];\n require(init_vector_hash == keccak256(abi.encode(data, finalize)), \"CHANGED_INITIALIZER\");\n setImplementation(newImplementation);\n\n // solium-disable-next-line security/no-low-level-calls\n (bool success, bytes memory returndata) = newImplementation.delegatecall(\n abi.encodeWithSelector(this.initialize.selector, data));\n require(success, string(returndata));\n\n // Verify that the new implementation is not frozen post initialization.\n (success, returndata) = newImplementation.delegatecall(\n abi.encodeWithSignature(\"isFrozen()\"));\n require(success, \"CALL_TO_ISFROZEN_REVERTED\");\n require(abi.decode(returndata, (bool)) == false, \"NEW_IMPLEMENTATION_FROZEN\");\n\n if (finalize == true) {\n setFinalizedFlag();\n emit FinalizedImplementation(newImplementation);\n }\n\n emit Upgraded(newImplementation);\n }\n}\n"},"ProxyGovernance.sol":{"content":"/*\n Copyright 2019,2020 StarkWare Industries Ltd.\n\n Licensed under the Apache License, Version 2.0 (the \"License\").\n You may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n https://www.starkware.co/open-source-license/\n\n Unless required by applicable law or agreed to in writing,\n software distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions\n and limitations under the License.\n*/\npragma solidity ^0.5.2;\n\nimport \"Governance.sol\";\n\n/**\n The Proxy contract is governed by one or more Governors of which the initial one is the\n deployer of the contract.\n\n A governor has the sole authority to perform the following operations:\n\n 1. Nominate additional governors (:sol:func:`proxyNominateNewGovernor`)\n 2. Remove other governors (:sol:func:`proxyRemoveGovernor`)\n 3. Add new `implementations` (proxied contracts)\n 4. Remove (new or old) `implementations`\n 5. Update `implementations` after a timelock allows it\n\n Adding governors is performed in a two step procedure:\n\n 1. First, an existing governor nominates a new governor (:sol:func:`proxyNominateNewGovernor`)\n 2. Then, the new governor must accept governance to become a governor (:sol:func:`proxyAcceptGovernance`)\n\n This two step procedure ensures that a governor public key cannot be nominated unless there is an\n entity that has the corresponding private key. This is intended to prevent errors in the addition\n process.\n\n The governor private key should typically be held in a secure cold wallet or managed via a\n multi-sig contract.\n*/\n/*\n Implements Governance for the proxy contract.\n It is a thin wrapper to the Governance contract,\n which is needed so that it can have non-colliding function names,\n and a specific tag (key) to allow unique state storage.\n*/\ncontract ProxyGovernance is Governance {\n\n // The tag is the string key that is used in the Governance storage mapping.\n string public constant PROXY_GOVERNANCE_TAG = \"StarkEx.Proxy.2019.GovernorsInformation\";\n\n function getGovernanceTag()\n internal\n view\n returns (string memory tag) {\n tag = PROXY_GOVERNANCE_TAG;\n }\n\n function proxyIsGovernor(address testGovernor) external view returns (bool) {\n return isGovernor(testGovernor);\n }\n\n function proxyNominateNewGovernor(address newGovernor) external {\n nominateNewGovernor(newGovernor);\n }\n\n function proxyRemoveGovernor(address governorForRemoval) external {\n removeGovernor(governorForRemoval);\n }\n\n function proxyAcceptGovernance()\n external\n {\n acceptGovernance();\n }\n\n function proxyCancelNomination() external {\n cancelNomination();\n }\n}\n"},"ProxyStorage.sol":{"content":"/*\n Copyright 2019,2020 StarkWare Industries Ltd.\n\n Licensed under the Apache License, Version 2.0 (the \"License\").\n You may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n https://www.starkware.co/open-source-license/\n\n Unless required by applicable law or agreed to in writing,\n software distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions\n and limitations under the License.\n*/\npragma solidity ^0.5.2;\n\nimport \"GovernanceStorage.sol\";\n\n/*\n Holds the Proxy-specific state variables.\n This contract is inherited by the GovernanceStorage (and indirectly by MainStorage)\n to prevent collision hazard.\n*/\ncontract ProxyStorage is GovernanceStorage {\n\n // Stores the hash of the initialization vector of the added implementation.\n // Upon upgradeTo the implementation, the initialization vector is verified\n // to be identical to the one submitted when adding the implementaion.\n mapping (address =\u003e bytes32) internal initializationHash;\n\n // The time after which we can switch to the implementation.\n mapping (address =\u003e uint256) internal enabledTime;\n\n // A central storage of the flags whether implementation has been initialized.\n // Note - it can be used flexibly enough to accomodate multiple level of initialization\n // (i.e. using different key salting schemes for different initialization levels).\n mapping (bytes32 =\u003e bool) internal initialized;\n}\n"}}
File 2 of 5: TransparentUpgradeableProxy
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "../ERC1967/ERC1967Proxy.sol"; /** * @dev This contract implements a proxy that is upgradeable by an admin. * * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector * clashing], which can potentially be used in an attack, this contract uses the * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two * things that go hand in hand: * * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if * that call matches one of the admin functions exposed by the proxy itself. * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the * implementation. If the admin tries to call a function on the implementation it will fail with an error that says * "admin cannot fallback to proxy target". * * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due * to sudden errors when trying to call a function from the proxy implementation. * * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way, * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy. */ contract TransparentUpgradeableProxy is ERC1967Proxy { /** * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}. */ constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) { assert(_ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1)); _changeAdmin(admin_); } /** * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin. */ modifier ifAdmin() { if (msg.sender == _getAdmin()) { _; } else { _fallback(); } } /** * @dev Returns the current admin. * * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}. * * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` */ function admin() external ifAdmin returns (address admin_) { admin_ = _getAdmin(); } /** * @dev Returns the current implementation. * * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}. * * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` */ function implementation() external ifAdmin returns (address implementation_) { implementation_ = _implementation(); } /** * @dev Changes the admin of the proxy. * * Emits an {AdminChanged} event. * * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}. */ function changeAdmin(address newAdmin) external virtual ifAdmin { _changeAdmin(newAdmin); } /** * @dev Upgrade the implementation of the proxy. * * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}. */ function upgradeTo(address newImplementation) external ifAdmin { _upgradeToAndCall(newImplementation, bytes(""), false); } /** * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the * proxied contract. * * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}. */ function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin { _upgradeToAndCall(newImplementation, data, true); } /** * @dev Returns the current admin. */ function _admin() internal view virtual returns (address) { return _getAdmin(); } /** * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}. */ function _beforeFallback() internal virtual override { require(msg.sender != _getAdmin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target"); super._beforeFallback(); } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "../Proxy.sol"; import "./ERC1967Upgrade.sol"; /** * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an * implementation address that can be changed. This address is stored in storage in the location specified by * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the * implementation behind the proxy. */ contract ERC1967Proxy is Proxy, ERC1967Upgrade { /** * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`. * * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded * function call, and allows initializating the storage of the proxy like a Solidity constructor. */ constructor(address _logic, bytes memory _data) payable { assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)); _upgradeToAndCall(_logic, _data, false); } /** * @dev Returns the current implementation address. */ function _implementation() internal view virtual override returns (address impl) { return ERC1967Upgrade._getImplementation(); } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to * be specified by overriding the virtual {_implementation} function. * * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a * different contract through the {_delegate} function. * * The success and return data of the delegated call will be returned back to the caller of the proxy. */ abstract contract Proxy { /** * @dev Delegates the current call to `implementation`. * * This function does not return to its internall call site, it will return directly to the external caller. */ function _delegate(address implementation) internal virtual { // solhint-disable-next-line no-inline-assembly assembly { // Copy msg.data. We take full control of memory in this inline assembly // block because it will not return to Solidity code. We overwrite the // Solidity scratch pad at memory position 0. calldatacopy(0, 0, calldatasize()) // Call the implementation. // out and outsize are 0 because we don't know the size yet. let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) // Copy the returned data. returndatacopy(0, 0, returndatasize()) switch result // delegatecall returns 0 on error. case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } /** * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function * and {_fallback} should delegate. */ function _implementation() internal view virtual returns (address); /** * @dev Delegates the current call to the address returned by `_implementation()`. * * This function does not return to its internall call site, it will return directly to the external caller. */ function _fallback() internal virtual { _beforeFallback(); _delegate(_implementation()); } /** * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other * function in the contract matches the call data. */ fallback () external payable virtual { _fallback(); } /** * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data * is empty. */ receive () external payable virtual { _fallback(); } /** * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback` * call, or as part of the Solidity `fallback` or `receive` functions. * * If overriden should call `super._beforeFallback()`. */ function _beforeFallback() internal virtual { } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.2; import "../beacon/IBeacon.sol"; import "../../utils/Address.sol"; import "../../utils/StorageSlot.sol"; /** * @dev This abstract contract provides getters and event emitting update functions for * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots. * * _Available since v4.1._ * * @custom:oz-upgrades-unsafe-allow delegatecall */ abstract contract ERC1967Upgrade { // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1 bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143; /** * @dev Storage slot with the address of the current implementation. * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is * validated in the constructor. */ bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; /** * @dev Emitted when the implementation is upgraded. */ event Upgraded(address indexed implementation); /** * @dev Returns the current implementation address. */ function _getImplementation() internal view returns (address) { return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; } /** * @dev Stores a new address in the EIP1967 implementation slot. */ function _setImplementation(address newImplementation) private { require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; } /** * @dev Perform implementation upgrade * * Emits an {Upgraded} event. */ function _upgradeTo(address newImplementation) internal { _setImplementation(newImplementation); emit Upgraded(newImplementation); } /** * @dev Perform implementation upgrade with additional setup call. * * Emits an {Upgraded} event. */ function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal { _setImplementation(newImplementation); emit Upgraded(newImplementation); if (data.length > 0 || forceCall) { Address.functionDelegateCall(newImplementation, data); } } /** * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call. * * Emits an {Upgraded} event. */ function _upgradeToAndCallSecure(address newImplementation, bytes memory data, bool forceCall) internal { address oldImplementation = _getImplementation(); // Initial upgrade and setup call _setImplementation(newImplementation); if (data.length > 0 || forceCall) { Address.functionDelegateCall(newImplementation, data); } // Perform rollback test if not already in progress StorageSlot.BooleanSlot storage rollbackTesting = StorageSlot.getBooleanSlot(_ROLLBACK_SLOT); if (!rollbackTesting.value) { // Trigger rollback using upgradeTo from the new implementation rollbackTesting.value = true; Address.functionDelegateCall( newImplementation, abi.encodeWithSignature( "upgradeTo(address)", oldImplementation ) ); rollbackTesting.value = false; // Check rollback was effective require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades"); // Finally reset to the new implementation and log the upgrade _setImplementation(newImplementation); emit Upgraded(newImplementation); } } /** * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that). * * Emits a {BeaconUpgraded} event. */ function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal { _setBeacon(newBeacon); emit BeaconUpgraded(newBeacon); if (data.length > 0 || forceCall) { Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data); } } /** * @dev Storage slot with the admin of the contract. * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is * validated in the constructor. */ bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; /** * @dev Emitted when the admin account has changed. */ event AdminChanged(address previousAdmin, address newAdmin); /** * @dev Returns the current admin. */ function _getAdmin() internal view returns (address) { return StorageSlot.getAddressSlot(_ADMIN_SLOT).value; } /** * @dev Stores a new address in the EIP1967 admin slot. */ function _setAdmin(address newAdmin) private { require(newAdmin != address(0), "ERC1967: new admin is the zero address"); StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin; } /** * @dev Changes the admin of the proxy. * * Emits an {AdminChanged} event. */ function _changeAdmin(address newAdmin) internal { emit AdminChanged(_getAdmin(), newAdmin); _setAdmin(newAdmin); } /** * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy. * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor. */ bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50; /** * @dev Emitted when the beacon is upgraded. */ event BeaconUpgraded(address indexed beacon); /** * @dev Returns the current beacon. */ function _getBeacon() internal view returns (address) { return StorageSlot.getAddressSlot(_BEACON_SLOT).value; } /** * @dev Stores a new beacon in the EIP1967 beacon slot. */ function _setBeacon(address newBeacon) private { require( Address.isContract(newBeacon), "ERC1967: new beacon is not a contract" ); require( Address.isContract(IBeacon(newBeacon).implementation()), "ERC1967: beacon implementation is not a contract" ); StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon; } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev This is the interface that {BeaconProxy} expects of its beacon. */ interface IBeacon { /** * @dev Must return an address that can be used as a delegate call target. * * {BeaconProxy} will check that this address is a contract. */ function implementation() external view returns (address); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Collection of functions related to the address type */ library Address { /** * @dev Returns true if `account` is a contract. * * [IMPORTANT] * ==== * It is unsafe to assume that an address for which this function returns * false is an externally-owned account (EOA) and not a contract. * * Among others, `isContract` will return false for the following * types of addresses: * * - an externally-owned account * - a contract in construction * - an address where a contract will be created * - an address where a contract lived, but was destroyed * ==== */ function isContract(address account) internal view returns (bool) { // This method relies on extcodesize, which returns 0 for contracts in // construction, since the code is only stored at the end of the // constructor execution. uint256 size; // solhint-disable-next-line no-inline-assembly assembly { size := extcodesize(account) } return size > 0; } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); // solhint-disable-next-line avoid-low-level-calls, avoid-call-value (bool success, ) = recipient.call{ value: amount }(""); require(success, "Address: unable to send value, recipient may have reverted"); } /** * @dev Performs a Solidity function call using a low level `call`. A * plain`call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason, it is bubbled up by this * function (like regular Solidity function calls). * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCall(target, data, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); require(isContract(target), "Address: call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.call{ value: value }(data); return _verifyCallResult(success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { return functionStaticCall(target, data, "Address: low-level static call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) { require(isContract(target), "Address: static call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.staticcall(data); return _verifyCallResult(success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { return functionDelegateCall(target, data, "Address: low-level delegate call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { require(isContract(target), "Address: delegate call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.delegatecall(data); return _verifyCallResult(success, returndata, errorMessage); } function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) { if (success) { return returndata; } else { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly // solhint-disable-next-line no-inline-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Library for reading and writing primitive types to specific storage slots. * * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. * This library helps with reading and writing to such slots without the need for inline assembly. * * The functions in this library return Slot structs that contain a `value` member that can be used to read or write. * * Example usage to set ERC1967 implementation slot: * ``` * contract ERC1967 { * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; * * function _getImplementation() internal view returns (address) { * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; * } * * function _setImplementation(address newImplementation) internal { * require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; * } * } * ``` * * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._ */ library StorageSlot { struct AddressSlot { address value; } struct BooleanSlot { bool value; } struct Bytes32Slot { bytes32 value; } struct Uint256Slot { uint256 value; } /** * @dev Returns an `AddressSlot` with member `value` located at `slot`. */ function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { assembly { r.slot := slot } } /** * @dev Returns an `BooleanSlot` with member `value` located at `slot`. */ function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { assembly { r.slot := slot } } /** * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. */ function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { assembly { r.slot := slot } } /** * @dev Returns an `Uint256Slot` with member `value` located at `slot`. */ function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { assembly { r.slot := slot } } }
File 3 of 5: StarkExchange
/* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; /* This contract provides means to block direct call of an external function. A derived contract (e.g. MainDispatcherBase) should decorate sensitive functions with the notCalledDirectly modifier, thereby preventing it from being called directly, and allowing only calling using delegate_call. */ abstract contract BlockDirectCall { address immutable this_; constructor() internal { this_ = address(this); } modifier notCalledDirectly() { require(this_ != address(this), "DIRECT_CALL_DISALLOWED"); _; } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; /* Common Utility librarries. I. Addresses (extending address). */ library Addresses { function isContract(address account) internal view returns (bool) { uint256 size; assembly { size := extcodesize(account) } return size > 0; } function performEthTransfer(address recipient, uint256 amount) internal { (bool success, ) = recipient.call{value: amount}(""); // NOLINT: low-level-calls. require(success, "ETH_TRANSFER_FAILED"); } /* Safe wrapper around ERC20/ERC721 calls. This is required because many deployed ERC20 contracts don't return a value. See https://github.com/ethereum/solidity/issues/4116. */ function safeTokenContractCall(address tokenAddress, bytes memory callData) internal { require(isContract(tokenAddress), "BAD_TOKEN_ADDRESS"); // NOLINTNEXTLINE: low-level-calls. (bool success, bytes memory returndata) = tokenAddress.call(callData); require(success, string(returndata)); if (returndata.length > 0) { require(abi.decode(returndata, (bool)), "TOKEN_OPERATION_FAILED"); } } /* Validates that the passed contract address is of a real contract, and that its id hash (as infered fromn identify()) matched the expected one. */ function validateContractId(address contractAddress, bytes32 expectedIdHash) internal { require(isContract(contractAddress), "ADDRESS_NOT_CONTRACT"); (bool success, bytes memory returndata) = contractAddress.call( // NOLINT: low-level-calls. abi.encodeWithSignature("identify()") ); require(success, "FAILED_TO_IDENTIFY_CONTRACT"); string memory realContractId = abi.decode(returndata, (string)); require( keccak256(abi.encodePacked(realContractId)) == expectedIdHash, "UNEXPECTED_CONTRACT_IDENTIFIER" ); } } /* II. StarkExTypes - Common data types. */ library StarkExTypes { // Structure representing a list of verifiers (validity/availability). // A statement is valid only if all the verifiers in the list agree on it. // Adding a verifier to the list is immediate - this is used for fast resolution of // any soundness issues. // Removing from the list is time-locked, to ensure that any user of the system // not content with the announced removal has ample time to leave the system before it is // removed. struct ApprovalChainData { address[] list; // Represents the time after which the verifier with the given address can be removed. // Removal of the verifier with address A is allowed only in the case the value // of unlockedForRemovalTime[A] != 0 and unlockedForRemovalTime[A] < (current time). mapping(address => uint256) unlockedForRemovalTime; } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../interfaces/MGovernance.sol"; /* Holds the governance slots for ALL entities, including proxy and the main contract. */ contract GovernanceStorage { // A map from a Governor tag to its own GovernanceInfoStruct. mapping(string => GovernanceInfoStruct) internal governanceInfo; //NOLINT uninitialized-state. } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; /* Interface for a generic dispatcher to use, which the concrete dispatcher must implement. It contains the functions that are specific to the concrete dispatcher instance. The interface is implemented as contract, because interface implies all methods external. */ abstract contract IDispatcherBase { function getSubContract(bytes4 selector) public view virtual returns (address); function setSubContractAddress(uint256 index, address subContract) internal virtual; function getNumSubcontracts() internal pure virtual returns (uint256); function validateSubContractIndex(uint256 index, address subContract) internal pure virtual; /* Ensures initializer can be called. Reverts otherwise. */ function initializationSentinel() internal view virtual; } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; interface Identity { /* Allows a caller, typically another contract, to ensure that the provided address is of the expected type and version. */ function identify() external pure returns (string memory); } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; struct GovernanceInfoStruct { mapping(address => bool) effectiveGovernors; address candidateGovernor; bool initialized; } abstract contract MGovernance { function _isGovernor(address testGovernor) internal view virtual returns (bool); /* Allows calling the function only by a Governor. */ modifier onlyGovernance() { require(_isGovernor(msg.sender), "ONLY_GOVERNANCE"); _; } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../components/MainStorage.sol"; import "./MainDispatcherBase.sol"; abstract contract MainDispatcher is MainStorage, MainDispatcherBase { uint256 constant SUBCONTRACT_BITS = 4; function magicSalt() internal pure virtual returns (uint256); function handlerMapSection(uint256 section) internal pure virtual returns (uint256); function expectedIdByIndex(uint256 index) internal pure virtual returns (string memory id); function validateSubContractIndex(uint256 index, address subContract) internal pure override { string memory id = SubContractor(subContract).identify(); bytes32 hashed_expected_id = keccak256(abi.encodePacked(expectedIdByIndex(index))); require( hashed_expected_id == keccak256(abi.encodePacked(id)), "MISPLACED_INDEX_OR_BAD_CONTRACT_ID" ); // Gets the list of critical selectors from the sub-contract and checks that the selector // is mapped to that sub-contract. bytes4[] memory selectorsToValidate = SubContractor(subContract).validatedSelectors(); for (uint256 i = 0; i < selectorsToValidate.length; i++) { require( getSubContractIndex(selectorsToValidate[i]) == index, "INCONSISTENT_DISPATCHER_MAP" ); } } function handlingContractId(bytes4 selector) external pure virtual returns (string memory id) { uint256 index = getSubContractIndex(selector); return expectedIdByIndex(index); } /* Returns the index in subContracts where the address of the sub-contract implementing the function with the queried selector is held. Note: The nature of the sub-contracts handler map is such that all the required selectors are mapped. However, other selectors, such that are not implemented in any subcontract, may also return a sub-contract address. This behavior is by-design, and not a problem. */ function getSubContractIndex(bytes4 selector) internal pure returns (uint256) { uint256 location = 0xFF & uint256(keccak256(abi.encodePacked(selector, magicSalt()))); uint256 offset = (SUBCONTRACT_BITS * location) % 256; // We have 64 locations in each register, hence the >> 6 (i.e. location // 64). return (handlerMapSection(location >> 6) >> offset) & 0xF; } /* Returns the address of the sub-contract that would be delegated to handle a call with the queried selector. (see note above). */ function getSubContract(bytes4 selector) public view override returns (address) { return subContracts[getSubContractIndex(selector)]; } function setSubContractAddress(uint256 index, address subContractAddress) internal override { subContracts[index] = subContractAddress; } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "./SubContractor.sol"; import "./IDispatcherBase.sol"; import "../interfaces/BlockDirectCall.sol"; import "../libraries/Common.sol"; abstract contract MainDispatcherBase is IDispatcherBase, BlockDirectCall { using Addresses for address; /* This entry point serves only transactions with empty calldata. (i.e. pure value transfer tx). We don't expect to receive such, thus block them. */ receive() external payable { revert("CONTRACT_NOT_EXPECTED_TO_RECEIVE"); } fallback() external payable { address subContractAddress = getSubContract(msg.sig); require(subContractAddress != address(0x0), "NO_CONTRACT_FOR_FUNCTION"); assembly { // Copy msg.data. We take full control of memory in this inline assembly // block because it will not return to Solidity code. We overwrite the // Solidity scratch pad at memory position 0. calldatacopy(0, 0, calldatasize()) // Call the implementation. // out and outsize are 0 for now, as we don"t know the out size yet. let result := delegatecall(gas(), subContractAddress, 0, calldatasize(), 0, 0) // Copy the returned data. returndatacopy(0, 0, returndatasize()) switch result // delegatecall returns 0 on error. case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } /* 1. Extract subcontracts. 2. Verify correct sub-contract initializer size. 3. Extract sub-contract initializer data. 4. Call sub-contract initializer. The init data bytes passed to initialize are structed as following: I. N slots (uin256 size) addresses of the deployed sub-contracts. II. An address of an external initialization contract (optional, or ZERO_ADDRESS). III. (Up to) N bytes sections of the sub-contracts initializers. If already initialized (i.e. upgrade) we expect the init data to be consistent with this. and if a different size of init data is expected when upgrading, the initializerSize should reflect this. If an external initializer contract is not used, ZERO_ADDRESS is passed in its slot. If the external initializer contract is used, all the remaining init data is passed to it, and internal initialization will not occur. External Initialization Contract -------------------------------- External Initialization Contract (EIC) is a hook for custom initialization. Typically in an upgrade flow, the expected initialization contains only the addresses of the sub-contracts. Normal initialization of the sub-contracts is such that is not needed in an upgrade, and actually may be very dangerous, as changing of state on a working system may corrupt it. In the event that some state initialization is required, the EIC is a hook that allows this. It may be deployed and called specifically for this purpose. The address of the EIC must be provided (if at all) when a new implementation is added to a Proxy contract (as part of the initialization vector). Hence, it is considered part of the code open to reviewers prior to a time-locked upgrade. When a custom initialization is performed using an EIC, the main dispatcher initialize extracts and stores the sub-contracts addresses, and then yields to the EIC, skipping the rest of its initialization code. Flow of MainDispatcher initialize --------------------------------- 1. Extraction and assignment of subcontracts addresses Main dispatcher expects a valid and consistent set of addresses in the passed data. It validates that, extracts the addresses from the data, and validates that the addresses are of the expected type and order. Then those addresses are stored. 2. Extraction of EIC address The address of the EIC is extracted from the data. External Initializer Contract is optional. ZERO_ADDRESS indicates it is not used. 3a. EIC is used Dispatcher calls the EIC initialize function with the remaining data. Note - In this option 3b is not performed. 3b. EIC is not used If there is additional initialization data then: I. Sentitenl function is called to permit subcontracts initialization. II. Dispatcher loops through the subcontracts and for each one it extracts the initializing data and passes it to the subcontract's initialize function. */ function initialize(bytes calldata data) external virtual notCalledDirectly { // Number of sub-contracts. uint256 nSubContracts = getNumSubcontracts(); // We support currently 4 bits per contract, i.e. 16, reserving 00 leads to 15. require(nSubContracts <= 15, "TOO_MANY_SUB_CONTRACTS"); // Sum of subcontract initializers. Aggregated for verification near the end. uint256 totalInitSizes = 0; // Offset (within data) of sub-contract initializer vector. // Just past the sub-contract+eic addresses. uint256 initDataContractsOffset = 32 * (nSubContracts + 1); // Init data MUST include addresses for all sub-contracts + EIC. require(data.length >= initDataContractsOffset, "SUB_CONTRACTS_NOT_PROVIDED"); // Size of passed data, excluding sub-contract addresses. uint256 additionalDataSize = data.length - initDataContractsOffset; // Extract & update contract addresses. for (uint256 nContract = 1; nContract <= nSubContracts; nContract++) { // Extract sub-contract address. address contractAddress = abi.decode( data[32 * (nContract - 1):32 * nContract], (address) ); validateSubContractIndex(nContract, contractAddress); // Contracts are indexed from 1 and 0 is not in use here. setSubContractAddress(nContract, contractAddress); } // Check if we have an external initializer contract. address externalInitializerAddr = abi.decode( data[initDataContractsOffset - 32:initDataContractsOffset], (address) ); // 3(a). Yield to EIC initialization. if (externalInitializerAddr != address(0x0)) { callExternalInitializer(externalInitializerAddr, data[initDataContractsOffset:]); return; } // 3(b). Subcontracts initialization. // I. If no init data passed besides sub-contracts, return. if (additionalDataSize == 0) { return; } // Just to be on the safe side. assert(externalInitializerAddr == address(0x0)); // II. Gate further initialization. initializationSentinel(); // III. Loops through the subcontracts, extracts their data and calls their initializer. for (uint256 nContract = 1; nContract <= nSubContracts; nContract++) { // Extract sub-contract address. address contractAddress = abi.decode( data[32 * (nContract - 1):32 * nContract], (address) ); // The initializerSize is called via delegatecall, so that it can relate to the state, // and not only to the new contract code. (e.g. return 0 if state-intialized else 192). // NOLINTNEXTLINE: controlled-delegatecall low-level-calls calls-loop. (bool success, bytes memory returndata) = contractAddress.delegatecall( abi.encodeWithSelector(SubContractor(contractAddress).initializerSize.selector) ); require(success, string(returndata)); uint256 initSize = abi.decode(returndata, (uint256)); require(initSize <= additionalDataSize, "INVALID_INITIALIZER_SIZE"); require(totalInitSizes + initSize <= additionalDataSize, "INVALID_INITIALIZER_SIZE"); if (initSize == 0) { continue; } // Call sub-contract initializer. // NOLINTNEXTLINE: controlled-delegatecall calls-loop. (success, returndata) = contractAddress.delegatecall( abi.encodeWithSelector( this.initialize.selector, data[initDataContractsOffset:initDataContractsOffset + initSize] ) ); require(success, string(returndata)); totalInitSizes += initSize; initDataContractsOffset += initSize; } require(additionalDataSize == totalInitSizes, "MISMATCHING_INIT_DATA_SIZE"); } function callExternalInitializer(address externalInitializerAddr, bytes calldata extInitData) private { require(externalInitializerAddr.isContract(), "NOT_A_CONTRACT"); // NOLINTNEXTLINE: low-level-calls, controlled-delegatecall. (bool success, bytes memory returndata) = externalInitializerAddr.delegatecall( abi.encodeWithSelector(this.initialize.selector, extInitData) ); require(success, string(returndata)); require(returndata.length == 0, string(returndata)); } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../upgrade/ProxyStorage.sol"; import "../libraries/Common.sol"; /* Holds ALL the main contract state (storage) variables. */ contract MainStorage is ProxyStorage { uint256 internal constant LAYOUT_LENGTH = 2**64; address escapeVerifierAddress; // NOLINT: constable-states. // Global dex-frozen flag. bool stateFrozen; // NOLINT: constable-states. // Time when unFreeze can be successfully called (UNFREEZE_DELAY after freeze). uint256 unFreezeTime; // NOLINT: constable-states. // Pending deposits. // A map STARK key => asset id => vault id => quantized amount. mapping(uint256 => mapping(uint256 => mapping(uint256 => uint256))) pendingDeposits; // Cancellation requests. // A map STARK key => asset id => vault id => request timestamp. mapping(uint256 => mapping(uint256 => mapping(uint256 => uint256))) cancellationRequests; // Pending withdrawals. // A map STARK key => asset id => quantized amount. mapping(uint256 => mapping(uint256 => uint256)) pendingWithdrawals; // vault_id => escape used boolean. mapping(uint256 => bool) escapesUsed; // Number of escapes that were performed when frozen. uint256 escapesUsedCount; // NOLINT: constable-states. // NOTE: fullWithdrawalRequests is deprecated, and replaced by forcedActionRequests. // NOLINTNEXTLINE naming-convention. mapping(uint256 => mapping(uint256 => uint256)) fullWithdrawalRequests_DEPRECATED; // State sequence number. uint256 sequenceNumber; // NOLINT: constable-states uninitialized-state. // Validium Vaults Tree Root & Height. uint256 validiumVaultRoot; // NOLINT: constable-states uninitialized-state. uint256 validiumTreeHeight; // NOLINT: constable-states uninitialized-state. // Order Tree Root & Height. uint256 orderRoot; // NOLINT: constable-states uninitialized-state. uint256 orderTreeHeight; // NOLINT: constable-states uninitialized-state. // True if and only if the address is allowed to add tokens. mapping(address => bool) tokenAdmins; // This mapping is no longer in use, remains for backwards compatibility. mapping(address => bool) userAdmins_DEPRECATED; // NOLINT: naming-convention. // True if and only if the address is an operator (allowed to update state). mapping(address => bool) operators; // NOLINT: uninitialized-state. // Mapping of contract ID to asset data. mapping(uint256 => bytes) assetTypeToAssetInfo; // NOLINT: uninitialized-state. // Mapping of registered contract IDs. mapping(uint256 => bool) registeredAssetType; // NOLINT: uninitialized-state. // Mapping from contract ID to quantum. mapping(uint256 => uint256) assetTypeToQuantum; // NOLINT: uninitialized-state. // This mapping is no longer in use, remains for backwards compatibility. mapping(address => uint256) starkKeys_DEPRECATED; // NOLINT: naming-convention. // Mapping from STARK public key to the Ethereum public key of its owner. mapping(uint256 => address) ethKeys; // NOLINT: uninitialized-state. // Timelocked state transition and availability verification chain. StarkExTypes.ApprovalChainData verifiersChain; StarkExTypes.ApprovalChainData availabilityVerifiersChain; // Batch id of last accepted proof. uint256 lastBatchId; // NOLINT: constable-states uninitialized-state. // Mapping between sub-contract index to sub-contract address. mapping(uint256 => address) subContracts; // NOLINT: uninitialized-state. mapping(uint256 => bool) permissiveAssetType_DEPRECATED; // NOLINT: naming-convention. // ---- END OF MAIN STORAGE AS DEPLOYED IN STARKEX2.0 ---- // Onchain-data version configured for the system. uint256 onchainDataVersion_DEPRECATED; // NOLINT: naming-convention constable-states. // Counter of forced action request in block. The key is the block number. mapping(uint256 => uint256) forcedRequestsInBlock; // ForcedAction requests: actionHash => requestTime. mapping(bytes32 => uint256) forcedActionRequests; // Mapping for timelocked actions. // A actionKey => activation time. mapping(bytes32 => uint256) actionsTimeLock; // Append only list of requested forced action hashes. bytes32[] actionHashList; // ---- END OF MAIN STORAGE AS DEPLOYED IN STARKEX3.0 ---- // ---- END OF MAIN STORAGE AS DEPLOYED IN STARKEX4.0 ---- // Rollup Vaults Tree Root & Height. uint256 rollupVaultRoot; // NOLINT: constable-states uninitialized-state. uint256 rollupTreeHeight; // NOLINT: constable-states uninitialized-state. uint256 globalConfigCode; // NOLINT: constable-states uninitialized-state. // Mapping of owner keys that are blocked from withdrawals. mapping(uint256 => uint256) internal blockListed; // Reserved storage space for Extensibility. // Every added MUST be added above the end gap, and the __endGap size must be reduced // accordingly. // NOLINTNEXTLINE: naming-convention. uint256[LAYOUT_LENGTH - 41] private __endGap; // __endGap complements layout to LAYOUT_LENGTH. } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../components/GovernanceStorage.sol"; /* Holds the Proxy-specific state variables. This contract is inherited by the GovernanceStorage (and indirectly by MainStorage) to prevent collision hazard. */ contract ProxyStorage is GovernanceStorage { // NOLINTNEXTLINE: naming-convention uninitialized-state. mapping(address => bytes32) internal initializationHash_DEPRECATED; // The time after which we can switch to the implementation. // Hash(implementation, data, finalize) => time. mapping(bytes32 => uint256) internal enabledTime; // A central storage of the flags whether implementation has been initialized. // Note - it can be used flexibly enough to accommodate multiple levels of initialization // (i.e. using different key salting schemes for different initialization levels). mapping(bytes32 => bool) internal initialized; } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../interfaces/MainDispatcher.sol"; contract StarkExchange is MainDispatcher { string public constant VERSION = "4.5.3-blc"; // Salt for a 8 bit unique spread of all relevant selectors. Pre-caclulated. // ---------- The following code was auto-generated. PLEASE DO NOT EDIT. ---------- uint256 constant MAGIC_SALT = 4259003; uint256 constant IDX_MAP_0 = 0x1030220010120000001021030000002223022000021220125001010000021300; uint256 constant IDX_MAP_1 = 0x2023105020000205000002451020200305120020000000000002230015000201; uint256 constant IDX_MAP_2 = 0x1000000030000003000002000000020000500100500230025010222000010; uint256 constant IDX_MAP_3 = 0x420002010300345030000000005010021020004000000050002052300060001; // ---------- End of auto-generated code. ---------- function getNumSubcontracts() internal pure override returns (uint256) { return 6; } function magicSalt() internal pure override returns (uint256) { return MAGIC_SALT; } function handlerMapSection(uint256 section) internal pure override returns (uint256) { if (section == 0) { return IDX_MAP_0; } else if (section == 1) { return IDX_MAP_1; } else if (section == 2) { return IDX_MAP_2; } else if (section == 3) { return IDX_MAP_3; } revert("BAD_IDX_MAP_SECTION"); } function expectedIdByIndex(uint256 index) internal pure override returns (string memory id) { if (index == 1) { id = "StarkWare_AllVerifiers_2022_2"; } else if (index == 2) { id = "StarkWare_TokensAndRamping_2024_4"; } else if (index == 3) { id = "StarkWare_StarkExState_2022_5"; } else if (index == 4) { id = "StarkWare_ForcedActions_2022_3"; } else if (index == 5) { id = "StarkWare_OnchainVaults_2022_2"; } else if (index == 6) { id = "StarkWare_ProxyUtils_2022_2"; } else { revert("UNEXPECTED_INDEX"); } } function initializationSentinel() internal view override { string memory REVERT_MSG = "INITIALIZATION_BLOCKED"; // This initializer sets roots etc. It must not be applied twice. // I.e. it can run only when the state is still empty. require(validiumVaultRoot == 0, REVERT_MSG); require(validiumTreeHeight == 0, REVERT_MSG); require(rollupVaultRoot == 0, REVERT_MSG); require(rollupTreeHeight == 0, REVERT_MSG); require(orderRoot == 0, REVERT_MSG); require(orderTreeHeight == 0, REVERT_MSG); } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "./Identity.sol"; interface SubContractor is Identity { function initialize(bytes calldata data) external; function initializerSize() external view returns (uint256); /* Returns an array with selectors for validation. These selectors are the critical ones for maintaining self custody and anti censorship. During the upgrade process, as part of the sub-contract validation, the MainDispatcher validates that the selectos are mapped to the correct sub-contract. */ function validatedSelectors() external pure returns (bytes4[] memory); }
File 4 of 5: TokensAndRamping
/* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../libraries/LibConstants.sol"; import "../interfaces/MAcceptModifications.sol"; import "../interfaces/MTokenQuantization.sol"; import "../components/MainStorage.sol"; /* Interface containing actions a verifier can invoke on the state. The contract containing the state should implement these and verify correctness. */ abstract contract AcceptModifications is MainStorage, LibConstants, MAcceptModifications, MTokenQuantization { event LogWithdrawalAllowed( uint256 ownerKey, uint256 assetType, uint256 nonQuantizedAmount, uint256 quantizedAmount ); event LogNftWithdrawalAllowed(uint256 ownerKey, uint256 assetId); event LogAssetWithdrawalAllowed(uint256 ownerKey, uint256 assetId, uint256 quantizedAmount); event LogMintableWithdrawalAllowed(uint256 ownerKey, uint256 assetId, uint256 quantizedAmount); /* Transfers funds from the on-chain deposit area to the off-chain area. Implemented in the Deposits contracts. */ function acceptDeposit( uint256 ownerKey, uint256 vaultId, uint256 assetId, uint256 quantizedAmount ) internal virtual override { // Fetch deposit. require( pendingDeposits[ownerKey][assetId][vaultId] >= quantizedAmount, "DEPOSIT_INSUFFICIENT" ); // Subtract accepted quantized amount. pendingDeposits[ownerKey][assetId][vaultId] -= quantizedAmount; } /* Transfers funds from the off-chain area to the on-chain withdrawal area. */ function allowWithdrawal( uint256 ownerKey, uint256 assetId, uint256 quantizedAmount ) internal override { // Fetch withdrawal. uint256 withdrawal = pendingWithdrawals[ownerKey][assetId]; // Add accepted quantized amount. withdrawal += quantizedAmount; require(withdrawal >= quantizedAmount, "WITHDRAWAL_OVERFLOW"); // Store withdrawal. pendingWithdrawals[ownerKey][assetId] = withdrawal; // Log event. uint256 presumedAssetType = assetId; if (registeredAssetType[presumedAssetType]) { emit LogWithdrawalAllowed( ownerKey, presumedAssetType, fromQuantized(presumedAssetType, quantizedAmount), quantizedAmount ); } else if (assetId == ((assetId & MASK_240) | MINTABLE_ASSET_ID_FLAG)) { emit LogMintableWithdrawalAllowed(ownerKey, assetId, quantizedAmount); } else { // Default case is Non-Mintable ERC721 or ERC1155 asset id. // In ERC721 and ERC1155 cases, assetId is not the assetType. require(assetId == assetId & MASK_250, "INVALID_ASSET_ID"); // If withdrawal amount is 1, the asset could be either NFT or SFT. In that case, both // NFT and general events will be emitted so that the listened for event is captured. // When withdrawal is greater than 1, it must be SFT and only one event will be emitted. if (withdrawal <= 1) { emit LogNftWithdrawalAllowed(ownerKey, assetId); } emit LogAssetWithdrawalAllowed(ownerKey, assetId, quantizedAmount); } } // Verifier authorizes withdrawal. function acceptWithdrawal( uint256 ownerKey, uint256 assetId, uint256 quantizedAmount ) internal virtual override { allowWithdrawal(ownerKey, assetId, quantizedAmount); } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../components/MainStorage.sol"; import "../libraries/LibConstants.sol"; /* Calculation action hash for the various forced actions in a generic manner. */ contract ActionHash is MainStorage, LibConstants { function getActionHash(string memory actionName, bytes memory packedActionParameters) internal pure returns (bytes32 actionHash) { actionHash = keccak256(abi.encodePacked(actionName, packedActionParameters)); } function setActionHash(bytes32 actionHash, bool premiumCost) internal { // The rate of forced trade requests is restricted. // First restriction is by capping the number of requests in a block. // User can override this cap by requesting with a permium flag set, // in this case, the gas cost is high (~1M) but no "technical" limit is set. // However, the high gas cost creates an obvious limitation due to the block gas limit. if (premiumCost) { for (uint256 i = 0; i < 21129; i++) {} } else { require( forcedRequestsInBlock[block.number] < MAX_FORCED_ACTIONS_REQS_PER_BLOCK, "MAX_REQUESTS_PER_BLOCK_REACHED" ); forcedRequestsInBlock[block.number] += 1; } forcedActionRequests[actionHash] = block.timestamp; actionHashList.push(actionHash); } function getActionCount() external view returns (uint256) { return actionHashList.length; } function getActionHashByIndex(uint256 actionIndex) external view returns (bytes32) { require(actionIndex < actionHashList.length, "ACTION_INDEX_TOO_HIGH"); return actionHashList[actionIndex]; } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../components/MainStorage.sol"; import "../interfaces/MBlocklist.sol"; abstract contract Blocklist is MainStorage, MBlocklist { uint256 constant CLEARANCE_DELAY = 2 weeks; // NOLINTNEXTLINE: external-function. function isBlockListed(uint256 ownerKey) public view override returns (bool) { return blockListed[ownerKey] > 0; } function readyForClearance(uint256 ownerKey) public view override returns (bool) { return isBlockListed(ownerKey) && blockListed[ownerKey] <= block.timestamp; } function addToBlockedList(uint256 ownerKey) public override onlyBlockAdmin { if (!isBlockListed(ownerKey)) { emit BlockPlaced(ownerKey); } blockListed[ownerKey] = block.timestamp + CLEARANCE_DELAY; } function removeFromBlockedList(uint256 ownerKey) public override onlyBlockAdmin { if (isBlockListed(ownerKey)) { emit BlockReleased(ownerKey); } blockListed[ownerKey] = 0; } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; /* Common Utility librarries. I. Addresses (extending address). */ library Addresses { function isContract(address account) internal view returns (bool) { uint256 size; assembly { size := extcodesize(account) } return size > 0; } function performEthTransfer(address recipient, uint256 amount) internal { (bool success, ) = recipient.call{value: amount}(""); // NOLINT: low-level-calls. require(success, "ETH_TRANSFER_FAILED"); } /* Safe wrapper around ERC20/ERC721 calls. This is required because many deployed ERC20 contracts don't return a value. See https://github.com/ethereum/solidity/issues/4116. */ function safeTokenContractCall(address tokenAddress, bytes memory callData) internal { require(isContract(tokenAddress), "BAD_TOKEN_ADDRESS"); // NOLINTNEXTLINE: low-level-calls. (bool success, bytes memory returndata) = tokenAddress.call(callData); require(success, string(returndata)); if (returndata.length > 0) { require(abi.decode(returndata, (bool)), "TOKEN_OPERATION_FAILED"); } } /* Validates that the passed contract address is of a real contract, and that its id hash (as infered fromn identify()) matched the expected one. */ function validateContractId(address contractAddress, bytes32 expectedIdHash) internal { require(isContract(contractAddress), "ADDRESS_NOT_CONTRACT"); (bool success, bytes memory returndata) = contractAddress.call( // NOLINT: low-level-calls. abi.encodeWithSignature("identify()") ); require(success, "FAILED_TO_IDENTIFY_CONTRACT"); string memory realContractId = abi.decode(returndata, (string)); require( keccak256(abi.encodePacked(realContractId)) == expectedIdHash, "UNEXPECTED_CONTRACT_IDENTIFIER" ); } } /* II. StarkExTypes - Common data types. */ library StarkExTypes { // Structure representing a list of verifiers (validity/availability). // A statement is valid only if all the verifiers in the list agree on it. // Adding a verifier to the list is immediate - this is used for fast resolution of // any soundness issues. // Removing from the list is time-locked, to ensure that any user of the system // not content with the announced removal has ample time to leave the system before it is // removed. struct ApprovalChainData { address[] list; // Represents the time after which the verifier with the given address can be removed. // Removal of the verifier with address A is allowed only in the case the value // of unlockedForRemovalTime[A] != 0 and unlockedForRemovalTime[A] < (current time). mapping(address => uint256) unlockedForRemovalTime; } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../libraries/LibConstants.sol"; import "../interfaces/MAcceptModifications.sol"; import "../interfaces/MDeposits.sol"; import "../interfaces/MTokenQuantization.sol"; import "../interfaces/MTokenAssetData.sol"; import "../interfaces/MFreezable.sol"; import "../interfaces/MKeyGetters.sol"; import "../interfaces/MTokenTransfers.sol"; import "../components/MainStorage.sol"; /** For a user to perform a deposit to the contract two calls need to take place: 1. A call to an ERC20 contract, authorizing this contract to transfer funds on behalf of the user. 2. A call to :sol:func:`deposit` indicating the starkKey, amount, asset type and target vault ID to which to send the deposit. The amount should be quantized, according to the specific quantization defined for the asset type. The result of the operation, assuming all requirements are met, is that an amount of ERC20 tokens equaling the amount specified in the :sol:func:`deposit` call times the quantization factor is transferred on behalf of the user to the contract. In addition, the contract adds the funds to an accumulator of pending deposits for the provided user, asset ID and vault ID. Once a deposit is made, the exchange may include it in a proof which will result in addition of the amount(s) deposited to the off-chain vault with the specified ID. When the contract receives such valid proof, it deducts the transfered funds from the pending deposits for the specified Stark key, asset ID and vault ID. The exchange will not be able to move the deposited funds to the off-chain vault if the Stark key is not registered in the system. Until that point, the user may cancel the deposit by performing a time-locked cancel-deposit operation consisting of two calls: 1. A call to :sol:func:`depositCancel`, setting a timer to enable reclaiming the deposit. Until this timer expires the user cannot reclaim funds as the exchange may still be processing the deposit for inclusion in the off chain vault. 2. A call to :sol:func:`depositReclaim`, to perform the actual transfer of funds from the contract back to the ERC20 contract. This will only succeed if the timer set in the previous call has expired. The result should be the transfer of all funds not accounted for in proofs for off-chain inclusion, back to the user account on the ERC20 contract. Calling depositCancel and depositReclaim can only be done via an ethKey that is associated with that vault's starkKey. This is enforced by the contract. */ abstract contract Deposits is MainStorage, LibConstants, MAcceptModifications, MDeposits, MTokenQuantization, MTokenAssetData, MFreezable, MKeyGetters, MTokenTransfers { event LogDeposit( address depositorEthKey, uint256 starkKey, uint256 vaultId, uint256 assetType, uint256 nonQuantizedAmount, uint256 quantizedAmount ); event LogNftDeposit( address depositorEthKey, uint256 starkKey, uint256 vaultId, uint256 assetType, uint256 tokenId, uint256 assetId ); event LogDepositWithTokenId( address depositorEthKey, uint256 starkKey, uint256 vaultId, uint256 assetType, uint256 tokenId, uint256 assetId, uint256 nonQuantizedAmount, uint256 quantizedAmount ); event LogDepositCancel(uint256 starkKey, uint256 vaultId, uint256 assetId); event LogDepositCancelReclaimed( uint256 starkKey, uint256 vaultId, uint256 assetType, uint256 nonQuantizedAmount, uint256 quantizedAmount ); event LogDepositNftCancelReclaimed( uint256 starkKey, uint256 vaultId, uint256 assetType, uint256 tokenId, uint256 assetId ); event LogDepositWithTokenIdCancelReclaimed( uint256 starkKey, uint256 vaultId, uint256 assetType, uint256 tokenId, uint256 assetId, uint256 nonQuantizedAmount, uint256 quantizedAmount ); function getDepositBalance( uint256 starkKey, uint256 assetId, uint256 vaultId ) external view returns (uint256) { uint256 presumedAssetType = assetId; return fromQuantized(presumedAssetType, pendingDeposits[starkKey][assetId][vaultId]); } function getQuantizedDepositBalance( uint256 starkKey, uint256 assetId, uint256 vaultId ) external view returns (uint256) { return pendingDeposits[starkKey][assetId][vaultId]; } function depositNft( uint256 starkKey, uint256 assetType, uint256 vaultId, uint256 tokenId ) external notFrozen { require(isERC721(assetType), "NOT_ERC721_TOKEN"); depositWithTokenId(starkKey, assetType, tokenId, vaultId, 1); } function depositERC1155( uint256 starkKey, uint256 assetType, uint256 tokenId, uint256 vaultId, uint256 quantizedAmount ) external notFrozen { require(isERC1155(assetType), "NOT_ERC1155_TOKEN"); depositWithTokenId(starkKey, assetType, tokenId, vaultId, quantizedAmount); } function depositStateUpdate( uint256 starkKey, uint256 assetId, uint256 vaultId, uint256 quantizedAmount ) private returns (uint256) { // Checks for overflow and updates the pendingDeposits balance. uint256 vaultBalance = pendingDeposits[starkKey][assetId][vaultId]; vaultBalance += quantizedAmount; require(vaultBalance >= quantizedAmount, "DEPOSIT_OVERFLOW"); pendingDeposits[starkKey][assetId][vaultId] = vaultBalance; // Disable the cancellationRequest timeout when users deposit into their own account. if ( isMsgSenderKeyOwner(starkKey) && cancellationRequests[starkKey][assetId][vaultId] != 0 ) { delete cancellationRequests[starkKey][assetId][vaultId]; } // Returns the updated vault balance. return vaultBalance; } function depositWithTokenId( uint256 starkKey, uint256 assetType, uint256 tokenId, uint256 vaultId, uint256 quantizedAmount ) public notFrozen { // The vaultId is not validated but should be in the allowed range supported by the // exchange. If not, it will be ignored by the exchange and the starkKey owner may reclaim // the funds by using depositCancel + depositReclaim. require(isAssetTypeWithTokenId(assetType), "INVALID_ASSET_TYPE"); uint256 assetId = calculateAssetIdWithTokenId(assetType, tokenId); // Updates the pendingDeposits balance and clears cancellationRequests when applicable. uint256 newVaultBalance = depositStateUpdate(starkKey, assetId, vaultId, quantizedAmount); // No need to verify amount > 0, a deposit with amount = 0 can be used to undo cancellation. if (isERC721(assetType)) { require(newVaultBalance <= 1, "ILLEGAL_ERC721_AMOUNT"); emit LogNftDeposit(msg.sender, starkKey, vaultId, assetType, tokenId, assetId); } // Transfer the tokens to the Deposit contract. transferInWithTokenId(assetType, tokenId, quantizedAmount); // Log event. emit LogDepositWithTokenId( msg.sender, starkKey, vaultId, assetType, tokenId, assetId, fromQuantized(assetType, quantizedAmount), quantizedAmount ); } function getCancellationRequest( uint256 starkKey, uint256 assetId, uint256 vaultId ) external view returns (uint256 request) { request = cancellationRequests[starkKey][assetId][vaultId]; } function depositERC20( uint256 starkKey, uint256 assetType, uint256 vaultId, uint256 quantizedAmount ) public override { deposit(starkKey, assetType, vaultId, quantizedAmount); } // NOLINTNEXTLINE: locked-ether. function depositEth( uint256 starkKey, uint256 assetType, uint256 vaultId ) public payable override { require(isEther(assetType), "INVALID_ASSET_TYPE"); deposit(starkKey, assetType, vaultId, toQuantized(assetType, msg.value)); } function deposit( uint256 starkKey, uint256 assetType, uint256 vaultId, uint256 quantizedAmount ) public notFrozen { // The vaultId is not validated but should be in the allowed range supported by the // exchange. If not, it will be ignored by the exchange and the starkKey owner may reclaim // the funds by using depositCancel + depositReclaim. // No need to verify amount > 0, a deposit with amount = 0 can be used to undo cancellation. require(!isMintableAssetType(assetType), "MINTABLE_ASSET_TYPE"); require(isFungibleAssetType(assetType), "NON_FUNGIBLE_ASSET_TYPE"); uint256 assetId = assetType; // Updates the pendingDeposits balance and clears cancellationRequests when applicable. depositStateUpdate(starkKey, assetId, vaultId, quantizedAmount); // Transfer the tokens to the Deposit contract. transferIn(assetType, quantizedAmount); // Log event. emit LogDeposit( msg.sender, starkKey, vaultId, assetType, fromQuantized(assetType, quantizedAmount), quantizedAmount ); } function deposit( // NOLINT: locked-ether. uint256 starkKey, uint256 assetType, uint256 vaultId ) external payable { require(isEther(assetType), "INVALID_ASSET_TYPE"); deposit(starkKey, assetType, vaultId, toQuantized(assetType, msg.value)); } function depositCancel( uint256 starkKey, uint256 assetId, uint256 vaultId ) external onlyKeyOwner(starkKey) // No notFrozen modifier: This function can always be used, even when frozen. { // Allow cancellation only for real deposit that were placed. require(pendingDeposits[starkKey][assetId][vaultId] > 0, "DEPOSIT_DOESNT_EXIST"); // Start the timeout. cancellationRequests[starkKey][assetId][vaultId] = block.timestamp; // Log event. emit LogDepositCancel(starkKey, vaultId, assetId); } function clearCancelledDeposit( uint256 starkKey, uint256 assetId, uint256 vaultId ) private returns (uint256) { // Make sure enough time has passed. uint256 requestTime = cancellationRequests[starkKey][assetId][vaultId]; require(requestTime != 0, "DEPOSIT_NOT_CANCELED"); uint256 freeTime = requestTime + DEPOSIT_CANCEL_DELAY; assert(freeTime >= DEPOSIT_CANCEL_DELAY); require(block.timestamp >= freeTime, "DEPOSIT_LOCKED"); // NOLINT: timestamp. // Clear deposit. uint256 quantizedAmount = pendingDeposits[starkKey][assetId][vaultId]; delete pendingDeposits[starkKey][assetId][vaultId]; delete cancellationRequests[starkKey][assetId][vaultId]; // Return the cleared amount so it can be transferred back to the reclaimer. return quantizedAmount; } function depositReclaim( uint256 starkKey, uint256 assetType, uint256 vaultId ) external onlyKeyOwner(starkKey) // No notFrozen modifier: This function can always be used, even when frozen. { require(isFungibleAssetType(assetType), "NON_FUNGIBLE_ASSET_TYPE"); // Clear deposit and attain the cleared amount to be transferred out. uint256 assetId = assetType; uint256 quantizedAmount = clearCancelledDeposit(starkKey, assetId, vaultId); // Refund deposit. transferOut(msg.sender, assetType, quantizedAmount); // Log event. emit LogDepositCancelReclaimed( starkKey, vaultId, assetType, fromQuantized(assetType, quantizedAmount), quantizedAmount ); } function depositWithTokenIdReclaim( uint256 starkKey, uint256 assetType, uint256 tokenId, uint256 vaultId ) public onlyKeyOwner(starkKey) // No notFrozen modifier: This function can always be used, even when frozen. { require(isAssetTypeWithTokenId(assetType), "INVALID_ASSET_TYPE"); // Clear deposit and attain the cleared amount to be transferred out. uint256 assetId = calculateAssetIdWithTokenId(assetType, tokenId); uint256 quantizedAmount = clearCancelledDeposit(starkKey, assetId, vaultId); if (quantizedAmount > 0) { // Refund deposit. transferOutWithTokenId(msg.sender, assetType, tokenId, quantizedAmount); } // Log event. if (isERC721(assetType)) { emit LogDepositNftCancelReclaimed(starkKey, vaultId, assetType, tokenId, assetId); } emit LogDepositWithTokenIdCancelReclaimed( starkKey, vaultId, assetType, tokenId, assetId, fromQuantized(assetType, quantizedAmount), quantizedAmount ); } function depositNftReclaim( uint256 starkKey, uint256 assetType, uint256 vaultId, uint256 tokenId ) external onlyKeyOwner(starkKey) // No notFrozen modifier: This function can always be used, even when frozen. { depositWithTokenIdReclaim(starkKey, assetType, tokenId, vaultId); } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../tokens/ERC1155/IERC1155Receiver.sol"; /* ERC1155 token receiver interface. EIP-1155 requires any contract receiving ERC1155 tokens to implement IERC1155Receiver interface. By EIP: 1. safeTransferFrom API of ERC1155 shall call onERC1155Received on the receiving contract. 2. safeBatchTransferFrom API of ERC1155 to call onERC1155BatchReceived on the receiving contract. Have the receiving contract failed to respond as expected, the safe transfer functions shall be reverted. */ contract ERC1155Receiver is IERC1155Receiver { /** Handles the receipt of a single ERC1155 token type. @param `operator` The address which called `safeTransferFrom` function @param `from` The address which previously owned the token @param `id` The identifier of the token which is being transferred @param `value` the amount of token units being transferred @param `data` Additional data with no specified format Returns: When invoked by the receiving contract, satisfying the deposit pattern (i.e. operator == this) `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` is returned. In all other cases returns `bytes4(0)`, which should invoke an error of the form `ERC1155: ERC1155Receiver rejected tokens`. */ function onERC1155Received( address operator, address, // from uint256, // id uint256, // value bytes calldata // data ) external override returns (bytes4) { return (operator == address(this) ? this.onERC1155Received.selector : bytes4(0)); } /** Handles the receipt of multiple ERC1155 token types. @param `operator` The address which called `safeBatchTransferFrom` function @param `from` The address which previously owned the token @param `ids` The identifier of the token which is being transferred @param `values` the amount of token units being transferred @param `data` Additional data with no specified format Returns: When invoked by the receiving contract, satisfying the deposit pattern (i.e. operator == this) `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256,uint256,bytes)"))` is returned. In all other cases returns `bytes4(0)`, which should invoke an error of the form `ERC1155: ERC1155Receiver rejected tokens`. Note: a rejection value `bytes4(0)` is to be expected. Batch deposits are unsupported by StarkEx. */ function onERC1155BatchReceived( address operator, address, // from uint256[] calldata, // ids uint256[] calldata, // values bytes calldata // data ) external override returns (bytes4) { return (operator == address(this) ? this.onERC1155BatchReceived.selector : bytes4(0)); } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../tokens/ERC721/IERC721Receiver.sol"; /* ERC721 token receiver interface EIP-721 requires any contract receiving ERC721 tokens to implement IERC721Receiver interface. By EIP, safeTransferFrom API of ERC721 shall call onERC721Received on the receiving contract. Have the receiving contract failed to respond as expected, the safeTransferFrom shall be reverted. Params: `operator` The address which called `safeTransferFrom` function `from` The address which previously owned the token `tokenId` The NFT identifier which is being transferred `data` Additional data with no specified format Returns: When invoked by the main contract, following the deposit pattern: `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`, which indicates success. In all other cases: `bytes4(0)`, which should fail ERC721's safeTransferFrom. */ contract ERC721Receiver is IERC721Receiver { function onERC721Received( address operator, // The address which called `safeTransferFrom` function. address, // from - The address which previously owned the token. uint256, // tokenId - The NFT identifier which is being transferred. bytes calldata // data - Additional data with no specified format. ) external override returns (bytes4) { return (operator == address(this) ? this.onERC721Received.selector : bytes4(0)); } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../libraries/LibConstants.sol"; import "../interfaces/MFreezable.sol"; import "../interfaces/MGovernance.sol"; import "./MainStorage.sol"; /* Implements MFreezable. */ abstract contract Freezable is MainStorage, LibConstants, MGovernance, MFreezable { event LogFrozen(); event LogUnFrozen(); function isFrozen() public view override returns (bool) { return stateFrozen; } function validateFreezeRequest(uint256 requestTime) internal override { require(requestTime != 0, "FORCED_ACTION_UNREQUESTED"); // Verify timer on escape request. uint256 freezeTime = requestTime + FREEZE_GRACE_PERIOD; // Prevent wraparound. assert(freezeTime >= FREEZE_GRACE_PERIOD); require(block.timestamp >= freezeTime, "FORCED_ACTION_PENDING"); // NOLINT: timestamp. // Forced action requests placed before freeze, are no longer valid after the un-freeze. require(freezeTime > unFreezeTime, "REFREEZE_ATTEMPT"); } function freeze() internal override notFrozen { unFreezeTime = block.timestamp + UNFREEZE_DELAY; // Update state. stateFrozen = true; // Log event. emit LogFrozen(); } function unFreeze() external onlyFrozen onlyGovernance { require(block.timestamp >= unFreezeTime, "UNFREEZE_NOT_ALLOWED_YET"); // Update state. stateFrozen = false; // Increment roots to invalidate them, w/o losing information. validiumVaultRoot += 1; rollupVaultRoot += 1; orderRoot += 1; // Log event. emit LogUnFrozen(); } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../interfaces/MGovernance.sol"; /* Implements Generic Governance, applicable for both proxy and main contract, and possibly others. Notes: The use of the same function names by both the Proxy and a delegated implementation is not possible since calling the implementation functions is done via the default function of the Proxy. For this reason, for example, the implementation of MainContract (MainGovernance) exposes mainIsGovernor, which calls the internal _isGovernor method. */ abstract contract Governance is MGovernance { event LogNominatedGovernor(address nominatedGovernor); event LogNewGovernorAccepted(address acceptedGovernor); event LogRemovedGovernor(address removedGovernor); event LogNominationCancelled(); function getGovernanceInfo() internal view virtual returns (GovernanceInfoStruct storage); /* Current code intentionally prevents governance re-initialization. This may be a problem in an upgrade situation, in a case that the upgrade-to implementation performs an initialization (for real) and within that calls initGovernance(). Possible workarounds: 1. Clearing the governance info altogether by changing the MAIN_GOVERNANCE_INFO_TAG. This will remove existing main governance information. 2. Modify the require part in this function, so that it will exit quietly when trying to re-initialize (uncomment the lines below). */ function initGovernance() internal { GovernanceInfoStruct storage gub = getGovernanceInfo(); require(!gub.initialized, "ALREADY_INITIALIZED"); gub.initialized = true; // to ensure addGovernor() won't fail. // Add the initial governer. addGovernor(msg.sender); } function _isGovernor(address testGovernor) internal view override returns (bool) { GovernanceInfoStruct storage gub = getGovernanceInfo(); return gub.effectiveGovernors[testGovernor]; } /* Cancels the nomination of a governor candidate. */ function _cancelNomination() internal onlyGovernance { GovernanceInfoStruct storage gub = getGovernanceInfo(); gub.candidateGovernor = address(0x0); emit LogNominationCancelled(); } function _nominateNewGovernor(address newGovernor) internal onlyGovernance { GovernanceInfoStruct storage gub = getGovernanceInfo(); require(!_isGovernor(newGovernor), "ALREADY_GOVERNOR"); gub.candidateGovernor = newGovernor; emit LogNominatedGovernor(newGovernor); } /* The addGovernor is called in two cases: 1. by _acceptGovernance when a new governor accepts its role. 2. by initGovernance to add the initial governor. The difference is that the init path skips the nominate step that would fail because of the onlyGovernance modifier. */ function addGovernor(address newGovernor) private { require(!_isGovernor(newGovernor), "ALREADY_GOVERNOR"); GovernanceInfoStruct storage gub = getGovernanceInfo(); gub.effectiveGovernors[newGovernor] = true; } function _acceptGovernance() internal { // The new governor was proposed as a candidate by the current governor. GovernanceInfoStruct storage gub = getGovernanceInfo(); require(msg.sender == gub.candidateGovernor, "ONLY_CANDIDATE_GOVERNOR"); // Update state. addGovernor(gub.candidateGovernor); gub.candidateGovernor = address(0x0); // Send a notification about the change of governor. emit LogNewGovernorAccepted(msg.sender); } /* Remove a governor from office. */ function _removeGovernor(address governorForRemoval) internal onlyGovernance { require(msg.sender != governorForRemoval, "GOVERNOR_SELF_REMOVE"); GovernanceInfoStruct storage gub = getGovernanceInfo(); require(_isGovernor(governorForRemoval), "NOT_GOVERNOR"); gub.effectiveGovernors[governorForRemoval] = false; emit LogRemovedGovernor(governorForRemoval); } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../interfaces/MGovernance.sol"; /* Holds the governance slots for ALL entities, including proxy and the main contract. */ contract GovernanceStorage { // A map from a Governor tag to its own GovernanceInfoStruct. mapping(string => GovernanceInfoStruct) internal governanceInfo; //NOLINT uninitialized-state. } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: MIT. pragma solidity ^0.6.12; /** Required interface of an ERC1155 compliant contract, as defined in the https://eips.ethereum.org/EIPS/eip-1155[EIP]. _Available since v3.1. */ interface IERC1155 { /** Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`. */ event TransferSingle( address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value ); /** Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all transfers. */ event TransferBatch( address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values ); /** Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to `approved`. */ event ApprovalForAll(address indexed account, address indexed operator, bool approved); /** Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. If an {URI} event was emitted for `id`, the standard https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value returned by {IERC1155MetadataURI-uri}. */ event URI(string value, uint256 indexed id); /** Returns the amount of tokens of token type `id` owned by `account`. Requirements: - `account` cannot be the zero address. */ function balanceOf(address account, uint256 id) external view returns (uint256); /** Requirements: - `accounts` and `ids` must have the same length. */ function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) external view returns (uint256[] memory); /** Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`, Emits an {ApprovalForAll} event. Requirements: - `operator` cannot be the caller. */ function setApprovalForAll(address operator, bool approved) external; /** Returns true if `operator` is approved to transfer ``account``'s tokens. See {setApprovalForAll}. */ function isApprovedForAll(address account, address operator) external view returns (bool); /** Transfers `amount` tokens of token type `id` from `from` to `to`. Emits a {TransferSingle} event. Requirements: - `to` cannot be the zero address. - If the caller is not `from`, it must be have been approved to spend ``from``'s tokens via {setApprovalForAll}. - `from` must have a balance of tokens of type `id` of at least `amount`. - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the acceptance magic value. */ function safeTransferFrom( address from, address to, uint256 id, uint256 amount, bytes calldata data ) external; /** Emits a {TransferBatch} event. Requirements: - `ids` and `amounts` must have the same length. - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the acceptance magic value. */ function safeBatchTransferFrom( address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data ) external; } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; /** Note: The ERC-165 identifier for this interface is 0x4e2312e0. */ interface IERC1155Receiver { /** Handles the receipt of a single ERC1155 token type. @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeTransferFrom` after the balance has been updated. This function MUST return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` (i.e. 0xf23a6e61) if it accepts the transfer. This function MUST revert if it rejects the transfer. Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller. @param operator The address which initiated the transfer (i.e. msg.sender) @param from The address which previously owned the token @param id The ID of the token being transferred @param value The amount of tokens being transferred @param data Additional data with no specified format @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` . */ function onERC1155Received( address operator, address from, uint256 id, uint256 value, bytes calldata data ) external returns (bytes4); /** Handles the receipt of multiple ERC1155 token types. @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeBatchTransferFrom` after the balances have been updated. This function MUST return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` (i.e. 0xbc197c81) if it accepts the transfer(s). This function MUST revert if it rejects the transfer(s). Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller. @param operator The address which initiated the batch transfer (i.e. msg.sender) @param from The address which previously owned the token @param ids An array containing ids of each token being transferred (order and length must match values array) @param values An array containing amounts of each token being transferred (order and length must match ids array) @param data Additional data with no specified format @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` . */ function onERC1155BatchReceived( address operator, address from, uint256[] calldata ids, uint256[] calldata values, bytes calldata data ) external returns (bytes4); } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; /** Interface of the ERC20 standard as defined in the EIP. Does not include the optional functions; to access them see {ERC20Detailed}. */ interface IERC20 { function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); function transfer(address recipient, uint256 amount) external returns (bool); function allowance(address owner, address spender) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); function transferFrom( address sender, address recipient, uint256 amount ) external returns (bool); event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; interface IERC721Receiver { function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) external returns (bytes4); } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; interface Identity { /* Allows a caller, typically another contract, to ensure that the provided address is of the expected type and version. */ function identify() external pure returns (string memory); } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../components/MainStorage.sol"; import "../interfaces/MKeyGetters.sol"; /* Implements MKeyGetters. */ contract KeyGetters is MainStorage, MKeyGetters { uint256 internal constant MASK_ADDRESS = (1 << 160) - 1; /* Returns the Ethereum public key (address) that owns the given ownerKey. If the ownerKey size is within the range of an Ethereum address (i.e. < 2**160) it returns the owner key itself. If the ownerKey is larger than a potential eth address, the eth address for which the starkKey was registered is returned, and 0 if the starkKey is not registered. Note - prior to version 4.0 this function reverted on an unregistered starkKey. For a variant of this function that reverts on an unregistered starkKey, use strictGetEthKey. */ function getEthKey(uint256 ownerKey) public view override returns (address) { address registeredEth = ethKeys[ownerKey]; if (registeredEth != address(0x0)) { return registeredEth; } return ownerKey == (ownerKey & MASK_ADDRESS) ? address(ownerKey) : address(0x0); } /* Same as getEthKey, but fails when a stark key is not registered. */ function strictGetEthKey(uint256 ownerKey) internal view override returns (address ethKey) { ethKey = getEthKey(ownerKey); require(ethKey != address(0x0), "USER_UNREGISTERED"); } function isMsgSenderKeyOwner(uint256 ownerKey) internal view override returns (bool) { return msg.sender == getEthKey(ownerKey); } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; contract LibConstants { // Durations for time locked mechanisms (in seconds). // Note that it is known that miners can manipulate block timestamps // up to a deviation of a few seconds. // This mechanism should not be used for fine grained timing. // The time required to cancel a deposit, in the case the operator does not move the funds // to the off-chain storage. uint256 public constant DEPOSIT_CANCEL_DELAY = 2 days; // The time required to freeze the exchange, in the case the operator does not execute a // requested full withdrawal. uint256 public constant FREEZE_GRACE_PERIOD = 7 days; // The time after which the exchange may be unfrozen after it froze. This should be enough time // for users to perform escape hatches to get back their funds. uint256 public constant UNFREEZE_DELAY = 365 days; // Maximal number of verifiers which may co-exist. uint256 public constant MAX_VERIFIER_COUNT = uint256(64); // The time required to remove a verifier in case of a verifier upgrade. uint256 public constant VERIFIER_REMOVAL_DELAY = FREEZE_GRACE_PERIOD + (21 days); address constant ZERO_ADDRESS = address(0x0); uint256 constant K_MODULUS = 0x800000000000011000000000000000000000000000000000000000000000001; uint256 constant K_BETA = 0x6f21413efbe40de150e596d72f7a8c5609ad26c15c915c1f4cdfcb99cee9e89; uint256 internal constant MASK_250 = 0x03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; uint256 internal constant MASK_240 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; uint256 public constant MAX_FORCED_ACTIONS_REQS_PER_BLOCK = 10; uint256 constant QUANTUM_UPPER_BOUND = 2**128; uint256 internal constant MINTABLE_ASSET_ID_FLAG = 1 << 250; // The 64th bit (indexed 63, counting from 0) is a flag indicating a rollup vault id. uint256 constant ROLLUP_VAULTS_BIT = 63; } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; /* Interface containing actions a verifier can invoke on the state. The contract containing the state should implement these and verify correctness. */ abstract contract MAcceptModifications { function acceptDeposit( uint256 ownerKey, uint256 vaultId, uint256 assetId, uint256 quantizedAmount ) internal virtual; function allowWithdrawal( uint256 ownerKey, uint256 assetId, uint256 quantizedAmount ) internal virtual; function acceptWithdrawal( uint256 ownerKey, uint256 assetId, uint256 quantizedAmount ) internal virtual; } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; abstract contract MBlocklist { event BlockPlaced(uint256 indexed ownerKey); event BlockReleased(uint256 indexed ownerKey); event AdminForcedWithdrawal(uint256 indexed ownerKey, address indexed destination); address internal constant BLOCK_ADMIN = 0x520Cf70a2D0B3dfB7386A2Bc9F800321F62a5c3a; // NOLINTNEXTLINE: external-function. function isBlockListed(uint256 ownerKey) public view virtual returns (bool); // NOLINTNEXTLINE: external-function. function readyForClearance(uint256 ownerKey) public view virtual returns (bool); function addToBlockedList(uint256 ownerKey) external virtual; function removeFromBlockedList(uint256 ownerKey) external virtual; /** Reverts if called by a blocked account. */ modifier onlyNotBlocked(uint256 ownerKey) { require(!isBlockListed(ownerKey), "USER_BLOCK_LISTED"); _; } modifier onlyBlockAdmin() { require(msg.sender == BLOCK_ADMIN, "ONLY_BLOCK_ADMIN"); _; } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; abstract contract MDeposits { function depositERC20( // NOLINT external-function. uint256 starkKey, uint256 assetType, uint256 vaultId, uint256 quantizedAmount ) public virtual; function depositEth( // NOLINT external-function. uint256 starkKey, uint256 assetType, uint256 vaultId ) public payable virtual; } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; abstract contract MFreezable { /* Returns true if the exchange is frozen. */ function isFrozen() public view virtual returns (bool); // NOLINT: external-function. /* Forbids calling the function if the exchange is frozen. */ modifier notFrozen() { require(!isFrozen(), "STATE_IS_FROZEN"); _; } function validateFreezeRequest(uint256 requestTime) internal virtual; /* Allows calling the function only if the exchange is frozen. */ modifier onlyFrozen() { require(isFrozen(), "STATE_NOT_FROZEN"); _; } /* Freezes the exchange. */ function freeze() internal virtual; } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; struct GovernanceInfoStruct { mapping(address => bool) effectiveGovernors; address candidateGovernor; bool initialized; } abstract contract MGovernance { function _isGovernor(address testGovernor) internal view virtual returns (bool); /* Allows calling the function only by a Governor. */ modifier onlyGovernance() { require(_isGovernor(msg.sender), "ONLY_GOVERNANCE"); _; } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; abstract contract MKeyGetters { // NOLINTNEXTLINE: external-function. function getEthKey(uint256 ownerKey) public view virtual returns (address); function strictGetEthKey(uint256 ownerKey) internal view virtual returns (address); function isMsgSenderKeyOwner(uint256 ownerKey) internal view virtual returns (bool); /* Allows calling the function only if ownerKey is registered to msg.sender. */ modifier onlyKeyOwner(uint256 ownerKey) { // Require the calling user to own the stark key. require(msg.sender == strictGetEthKey(ownerKey), "MISMATCHING_STARK_ETH_KEYS"); _; } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; abstract contract MStarkExForcedActionState { function fullWithdrawActionHash(uint256 ownerKey, uint256 vaultId) internal pure virtual returns (bytes32); function clearFullWithdrawalRequest(uint256 ownerKey, uint256 vaultId) internal virtual; // NOLINTNEXTLINE: external-function. function getFullWithdrawalRequest(uint256 ownerKey, uint256 vaultId) public view virtual returns (uint256); function setFullWithdrawalRequest(uint256 ownerKey, uint256 vaultId) internal virtual; } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; abstract contract MTokenAssetData { // NOLINTNEXTLINE: external-function. function getAssetInfo(uint256 assetType) public view virtual returns (bytes memory); function isEther(uint256 assetType) internal view virtual returns (bool); function isERC20(uint256 assetType) internal view virtual returns (bool); function isERC721(uint256 assetType) internal view virtual returns (bool); function isERC1155(uint256 assetType) internal view virtual returns (bool); function isFungibleAssetType(uint256 assetType) internal view virtual returns (bool); function isMintableAssetType(uint256 assetType) internal view virtual returns (bool); function isAssetTypeWithTokenId(uint256 assetType) internal view virtual returns (bool); function extractContractAddress(uint256 assetType) internal view virtual returns (address); function verifyAssetInfo(bytes memory assetInfo) internal view virtual; function isNonFungibleAssetInfo(bytes memory assetInfo) internal pure virtual returns (bool); function calculateAssetIdWithTokenId(uint256 assetType, uint256 tokenId) public view virtual returns (uint256); function calculateMintableAssetId(uint256 assetType, bytes memory mintingBlob) public pure virtual returns (uint256); } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; abstract contract MTokenQuantization { function fromQuantized(uint256 presumedAssetType, uint256 quantizedAmount) internal view virtual returns (uint256 amount); // NOLINTNEXTLINE: external-function. function getQuantum(uint256 presumedAssetType) public view virtual returns (uint256 quantum); function toQuantized(uint256 presumedAssetType, uint256 amount) internal view virtual returns (uint256 quantizedAmount); } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; abstract contract MTokenTransfers { function transferIn(uint256 assetType, uint256 quantizedAmount) internal virtual; function transferInWithTokenId( uint256 assetType, uint256 tokenId, uint256 quantizedAmount ) internal virtual; function transferOut( address payable recipient, uint256 assetType, uint256 quantizedAmount ) internal virtual; function transferOutWithTokenId( address recipient, uint256 assetType, uint256 tokenId, uint256 quantizedAmount ) internal virtual; function transferOutMint( uint256 assetType, uint256 quantizedAmount, address recipient, bytes calldata mintingBlob ) internal virtual; } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "./Governance.sol"; import "./GovernanceStorage.sol"; /** The StarkEx contract is governed by one or more Governors of which the initial one is the deployer of the contract. A governor has the sole authority to perform the following operations: 1. Nominate additional governors (:sol:func:`mainNominateNewGovernor`) 2. Remove other governors (:sol:func:`mainRemoveGovernor`) 3. Add new :sol:mod:`Verifiers` and :sol:mod:`AvailabilityVerifiers` 4. Remove :sol:mod:`Verifiers` and :sol:mod:`AvailabilityVerifiers` after a timelock allows it 5. Nominate Operators (see :sol:mod:`Operator`) and Token Administrators (see :sol:mod:`TokenRegister`) Adding governors is performed in a two step procedure: 1. First, an existing governor nominates a new governor (:sol:func:`mainNominateNewGovernor`) 2. Then, the new governor must accept governance to become a governor (:sol:func:`mainAcceptGovernance`) This two step procedure ensures that a governor public key cannot be nominated unless there is an entity that has the corresponding private key. This is intended to prevent errors in the addition process. The governor private key should typically be held in a secure cold wallet. */ /* Implements Governance for the StarkDex main contract. The wrapper methods (e.g. mainIsGovernor wrapping _isGovernor) are needed to give the method unique names. Both Proxy and StarkExchange inherit from Governance. Thus, the logical contract method names must have unique names in order for the proxy to successfully delegate to them. */ contract MainGovernance is GovernanceStorage, Governance { // The tag is the sting key that is used in the Governance storage mapping. string public constant MAIN_GOVERNANCE_INFO_TAG = "StarkEx.Main.2019.GovernorsInformation"; /* Returns the GovernanceInfoStruct associated with the governance tag. */ function getGovernanceInfo() internal view override returns (GovernanceInfoStruct storage) { return governanceInfo[MAIN_GOVERNANCE_INFO_TAG]; } function mainIsGovernor(address testGovernor) external view returns (bool) { return _isGovernor(testGovernor); } function mainNominateNewGovernor(address newGovernor) external { _nominateNewGovernor(newGovernor); } function mainRemoveGovernor(address governorForRemoval) external { _removeGovernor(governorForRemoval); } function mainAcceptGovernance() external { _acceptGovernance(); } function mainCancelNomination() external { _cancelNomination(); } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../upgrade/ProxyStorage.sol"; import "../libraries/Common.sol"; /* Holds ALL the main contract state (storage) variables. */ contract MainStorage is ProxyStorage { uint256 internal constant LAYOUT_LENGTH = 2**64; address escapeVerifierAddress; // NOLINT: constable-states. // Global dex-frozen flag. bool stateFrozen; // NOLINT: constable-states. // Time when unFreeze can be successfully called (UNFREEZE_DELAY after freeze). uint256 unFreezeTime; // NOLINT: constable-states. // Pending deposits. // A map STARK key => asset id => vault id => quantized amount. mapping(uint256 => mapping(uint256 => mapping(uint256 => uint256))) pendingDeposits; // Cancellation requests. // A map STARK key => asset id => vault id => request timestamp. mapping(uint256 => mapping(uint256 => mapping(uint256 => uint256))) cancellationRequests; // Pending withdrawals. // A map STARK key => asset id => quantized amount. mapping(uint256 => mapping(uint256 => uint256)) pendingWithdrawals; // vault_id => escape used boolean. mapping(uint256 => bool) escapesUsed; // Number of escapes that were performed when frozen. uint256 escapesUsedCount; // NOLINT: constable-states. // NOTE: fullWithdrawalRequests is deprecated, and replaced by forcedActionRequests. // NOLINTNEXTLINE naming-convention. mapping(uint256 => mapping(uint256 => uint256)) fullWithdrawalRequests_DEPRECATED; // State sequence number. uint256 sequenceNumber; // NOLINT: constable-states uninitialized-state. // Validium Vaults Tree Root & Height. uint256 validiumVaultRoot; // NOLINT: constable-states uninitialized-state. uint256 validiumTreeHeight; // NOLINT: constable-states uninitialized-state. // Order Tree Root & Height. uint256 orderRoot; // NOLINT: constable-states uninitialized-state. uint256 orderTreeHeight; // NOLINT: constable-states uninitialized-state. // True if and only if the address is allowed to add tokens. mapping(address => bool) tokenAdmins; // This mapping is no longer in use, remains for backwards compatibility. mapping(address => bool) userAdmins_DEPRECATED; // NOLINT: naming-convention. // True if and only if the address is an operator (allowed to update state). mapping(address => bool) operators; // NOLINT: uninitialized-state. // Mapping of contract ID to asset data. mapping(uint256 => bytes) assetTypeToAssetInfo; // NOLINT: uninitialized-state. // Mapping of registered contract IDs. mapping(uint256 => bool) registeredAssetType; // NOLINT: uninitialized-state. // Mapping from contract ID to quantum. mapping(uint256 => uint256) assetTypeToQuantum; // NOLINT: uninitialized-state. // This mapping is no longer in use, remains for backwards compatibility. mapping(address => uint256) starkKeys_DEPRECATED; // NOLINT: naming-convention. // Mapping from STARK public key to the Ethereum public key of its owner. mapping(uint256 => address) ethKeys; // NOLINT: uninitialized-state. // Timelocked state transition and availability verification chain. StarkExTypes.ApprovalChainData verifiersChain; StarkExTypes.ApprovalChainData availabilityVerifiersChain; // Batch id of last accepted proof. uint256 lastBatchId; // NOLINT: constable-states uninitialized-state. // Mapping between sub-contract index to sub-contract address. mapping(uint256 => address) subContracts; // NOLINT: uninitialized-state. mapping(uint256 => bool) permissiveAssetType_DEPRECATED; // NOLINT: naming-convention. // ---- END OF MAIN STORAGE AS DEPLOYED IN STARKEX2.0 ---- // Onchain-data version configured for the system. uint256 onchainDataVersion_DEPRECATED; // NOLINT: naming-convention constable-states. // Counter of forced action request in block. The key is the block number. mapping(uint256 => uint256) forcedRequestsInBlock; // ForcedAction requests: actionHash => requestTime. mapping(bytes32 => uint256) forcedActionRequests; // Mapping for timelocked actions. // A actionKey => activation time. mapping(bytes32 => uint256) actionsTimeLock; // Append only list of requested forced action hashes. bytes32[] actionHashList; // ---- END OF MAIN STORAGE AS DEPLOYED IN STARKEX3.0 ---- // ---- END OF MAIN STORAGE AS DEPLOYED IN STARKEX4.0 ---- // Rollup Vaults Tree Root & Height. uint256 rollupVaultRoot; // NOLINT: constable-states uninitialized-state. uint256 rollupTreeHeight; // NOLINT: constable-states uninitialized-state. uint256 globalConfigCode; // NOLINT: constable-states uninitialized-state. // Mapping of owner keys that are blocked from withdrawals. mapping(uint256 => uint256) internal blockListed; // Reserved storage space for Extensibility. // Every added MUST be added above the end gap, and the __endGap size must be reduced // accordingly. // NOLINTNEXTLINE: naming-convention. uint256[LAYOUT_LENGTH - 41] private __endGap; // __endGap complements layout to LAYOUT_LENGTH. } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../components/GovernanceStorage.sol"; /* Holds the Proxy-specific state variables. This contract is inherited by the GovernanceStorage (and indirectly by MainStorage) to prevent collision hazard. */ contract ProxyStorage is GovernanceStorage { // NOLINTNEXTLINE: naming-convention uninitialized-state. mapping(address => bytes32) internal initializationHash_DEPRECATED; // The time after which we can switch to the implementation. // Hash(implementation, data, finalize) => time. mapping(bytes32 => uint256) internal enabledTime; // A central storage of the flags whether implementation has been initialized. // Note - it can be used flexibly enough to accommodate multiple levels of initialization // (i.e. using different key salting schemes for different initialization levels). mapping(bytes32 => bool) internal initialized; } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../components/StarkExStorage.sol"; import "../interfaces/MStarkExForcedActionState.sol"; import "../../components/ActionHash.sol"; /* StarkExchange specific action hashses. */ contract StarkExForcedActionState is StarkExStorage, ActionHash, MStarkExForcedActionState { function fullWithdrawActionHash(uint256 ownerKey, uint256 vaultId) internal pure override returns (bytes32) { return getActionHash("FULL_WITHDRAWAL", abi.encode(ownerKey, vaultId)); } /* Implemented in the FullWithdrawal contracts. */ function clearFullWithdrawalRequest(uint256 ownerKey, uint256 vaultId) internal virtual override { // Reset escape request. delete forcedActionRequests[fullWithdrawActionHash(ownerKey, vaultId)]; } function getFullWithdrawalRequest(uint256 ownerKey, uint256 vaultId) public view override returns (uint256) { // Return request value. Expect zero if the request doesn't exist or has been serviced, and // a non-zero value otherwise. return forcedActionRequests[fullWithdrawActionHash(ownerKey, vaultId)]; } function setFullWithdrawalRequest(uint256 ownerKey, uint256 vaultId) internal override { // FullWithdrawal is always at premium cost, hence the `true`. setActionHash(fullWithdrawActionHash(ownerKey, vaultId), true); } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../../components/MainStorage.sol"; /* Extends MainStorage, holds StarkEx App specific state (storage) variables. ALL State variables that are common to all applications, reside in MainStorage, whereas ALL the StarkEx app specific ones reside here. */ contract StarkExStorage is MainStorage { // Onchain vaults balances. // A map eth_address => asset_id => vault_id => quantized amount. mapping(address => mapping(uint256 => mapping(uint256 => uint256))) vaultsBalances; // Onchain vaults withdrawal lock time. // A map eth_address => asset_id => vault_id => lock expiration timestamp. mapping(address => mapping(uint256 => mapping(uint256 => uint256))) vaultsWithdrawalLocks; // Enforces the minimal balance requirement (as output by Cairo) on onchain vault updates. // When disabled, flash loans are enabled. bool strictVaultBalancePolicy; // NOLINT: constable-states, uninitialized-state. // The default time, in seconds, that an onchain vault is locked for withdrawal after a deposit. uint256 public defaultVaultWithdrawalLock; // NOLINT: constable-states. // Address of the message registry contract that is used to sign and verify L1 orders. address public orderRegistryAddress; // NOLINT: constable-states. // Reserved storage space for Extensibility. // Every added MUST be added above the end gap, and the __endGap size must be reduced // accordingly. // NOLINTNEXTLINE: naming-convention shadowing-abstract. uint256[LAYOUT_LENGTH - 5] private __endGap; // __endGap complements layout to LAYOUT_LENGTH. } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "./Identity.sol"; interface SubContractor is Identity { function initialize(bytes calldata data) external; function initializerSize() external view returns (uint256); /* Returns an array with selectors for validation. These selectors are the critical ones for maintaining self custody and anti censorship. During the upgrade process, as part of the sub-contract validation, the MainDispatcher validates that the selectos are mapped to the correct sub-contract. */ function validatedSelectors() external pure returns (bytes4[] memory); } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../components/MainStorage.sol"; import "../interfaces/MTokenAssetData.sol"; import "../libraries/Common.sol"; import "../libraries/LibConstants.sol"; contract TokenAssetData is MainStorage, LibConstants, MTokenAssetData { bytes4 internal constant ERC20_SELECTOR = bytes4(keccak256("ERC20Token(address)")); bytes4 internal constant ETH_SELECTOR = bytes4(keccak256("ETH()")); bytes4 internal constant ERC721_SELECTOR = bytes4(keccak256("ERC721Token(address,uint256)")); bytes4 internal constant ERC1155_SELECTOR = bytes4(keccak256("ERC1155Token(address,uint256)")); bytes4 internal constant MINTABLE_ERC20_SELECTOR = bytes4(keccak256("MintableERC20Token(address)")); bytes4 internal constant MINTABLE_ERC721_SELECTOR = bytes4(keccak256("MintableERC721Token(address,uint256)")); // The selector follows the 0x20 bytes assetInfo.length field. uint256 internal constant SELECTOR_OFFSET = 0x20; uint256 internal constant SELECTOR_SIZE = 4; uint256 internal constant TOKEN_CONTRACT_ADDRESS_OFFSET = SELECTOR_OFFSET + SELECTOR_SIZE; string internal constant NFT_ASSET_ID_PREFIX = "NFT:"; string internal constant NON_MINTABLE_PREFIX = "NON_MINTABLE:"; string internal constant MINTABLE_PREFIX = "MINTABLE:"; using Addresses for address; /* Extract the tokenSelector from assetInfo. Works like bytes4 tokenSelector = abi.decode(assetInfo, (bytes4)) but does not revert when assetInfo.length < SELECTOR_OFFSET. */ function extractTokenSelectorFromAssetInfo(bytes memory assetInfo) private pure returns (bytes4 selector) { assembly { selector := and( 0xffffffff00000000000000000000000000000000000000000000000000000000, mload(add(assetInfo, SELECTOR_OFFSET)) ) } } function getAssetInfo(uint256 assetType) public view override returns (bytes memory assetInfo) { // Verify that the registration is set and valid. require(registeredAssetType[assetType], "ASSET_TYPE_NOT_REGISTERED"); // Retrieve registration. assetInfo = assetTypeToAssetInfo[assetType]; } function extractTokenSelectorFromAssetType(uint256 assetType) private view returns (bytes4) { return extractTokenSelectorFromAssetInfo(getAssetInfo(assetType)); } function isEther(uint256 assetType) internal view override returns (bool) { return extractTokenSelectorFromAssetType(assetType) == ETH_SELECTOR; } function isERC20(uint256 assetType) internal view override returns (bool) { return extractTokenSelectorFromAssetType(assetType) == ERC20_SELECTOR; } function isERC721(uint256 assetType) internal view override returns (bool) { return extractTokenSelectorFromAssetType(assetType) == ERC721_SELECTOR; } function isERC1155(uint256 assetType) internal view override returns (bool) { return extractTokenSelectorFromAssetType(assetType) == ERC1155_SELECTOR; } function isFungibleAssetType(uint256 assetType) internal view override returns (bool) { bytes4 tokenSelector = extractTokenSelectorFromAssetType(assetType); return tokenSelector == ETH_SELECTOR || tokenSelector == ERC20_SELECTOR || tokenSelector == MINTABLE_ERC20_SELECTOR; } function isMintableAssetType(uint256 assetType) internal view override returns (bool) { bytes4 tokenSelector = extractTokenSelectorFromAssetType(assetType); return tokenSelector == MINTABLE_ERC20_SELECTOR || tokenSelector == MINTABLE_ERC721_SELECTOR; } function isAssetTypeWithTokenId(uint256 assetType) internal view override returns (bool) { bytes4 tokenSelector = extractTokenSelectorFromAssetType(assetType); return tokenSelector == ERC721_SELECTOR || tokenSelector == ERC1155_SELECTOR; } function isTokenSupported(bytes4 tokenSelector) private pure returns (bool) { return tokenSelector == ETH_SELECTOR || tokenSelector == ERC20_SELECTOR || tokenSelector == ERC721_SELECTOR || tokenSelector == MINTABLE_ERC20_SELECTOR || tokenSelector == MINTABLE_ERC721_SELECTOR || tokenSelector == ERC1155_SELECTOR; } function extractContractAddressFromAssetInfo(bytes memory assetInfo) private pure returns (address) { uint256 offset = TOKEN_CONTRACT_ADDRESS_OFFSET; uint256 res; assembly { res := mload(add(assetInfo, offset)) } return address(res); } function extractContractAddress(uint256 assetType) internal view override returns (address) { return extractContractAddressFromAssetInfo(getAssetInfo(assetType)); } function verifyAssetInfo(bytes memory assetInfo) internal view override { bytes4 tokenSelector = extractTokenSelectorFromAssetInfo(assetInfo); // Ensure the selector is of an asset type we know. require(isTokenSupported(tokenSelector), "UNSUPPORTED_TOKEN_TYPE"); if (tokenSelector == ETH_SELECTOR) { // Assset info for ETH assetType is only a selector, i.e. 4 bytes length. require(assetInfo.length == 4, "INVALID_ASSET_STRING"); } else { // Assset info for other asset types are a selector + uint256 concatanation. // We pass the address as a uint256 (zero padded), // thus its length is 0x04 + 0x20 = 0x24. require(assetInfo.length == 0x24, "INVALID_ASSET_STRING"); address tokenAddress = extractContractAddressFromAssetInfo(assetInfo); require(tokenAddress.isContract(), "BAD_TOKEN_ADDRESS"); } } function isNonFungibleAssetInfo(bytes memory assetInfo) internal pure override returns (bool) { bytes4 tokenSelector = extractTokenSelectorFromAssetInfo(assetInfo); return tokenSelector == ERC721_SELECTOR || tokenSelector == MINTABLE_ERC721_SELECTOR; } function calculateAssetIdWithTokenId(uint256 assetType, uint256 tokenId) public view override returns (uint256) { require(isAssetTypeWithTokenId(assetType), "ASSET_TYPE_DOES_NOT_TAKE_TOKEN_ID"); string memory prefix = isERC721(assetType) ? NFT_ASSET_ID_PREFIX : NON_MINTABLE_PREFIX; return uint256(keccak256(abi.encodePacked(prefix, assetType, tokenId))) & MASK_250; } function calculateMintableAssetId(uint256 assetType, bytes memory mintingBlob) public pure override returns (uint256 assetId) { uint256 blobHash = uint256(keccak256(mintingBlob)); assetId = (uint256(keccak256(abi.encodePacked(MINTABLE_PREFIX, assetType, blobHash))) & MASK_240) | MINTABLE_ASSET_ID_FLAG; } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../components/MainStorage.sol"; import "../interfaces/MTokenQuantization.sol"; contract TokenQuantization is MainStorage, MTokenQuantization { function fromQuantized(uint256 presumedAssetType, uint256 quantizedAmount) internal view override returns (uint256 amount) { uint256 quantum = getQuantum(presumedAssetType); amount = quantizedAmount * quantum; require(amount / quantum == quantizedAmount, "DEQUANTIZATION_OVERFLOW"); } function getQuantum(uint256 presumedAssetType) public view override returns (uint256 quantum) { if (!registeredAssetType[presumedAssetType]) { // Default quantization, for NFTs etc. quantum = 1; } else { // Retrieve registration. quantum = assetTypeToQuantum[presumedAssetType]; } } function toQuantized(uint256 presumedAssetType, uint256 amount) internal view override returns (uint256 quantizedAmount) { uint256 quantum = getQuantum(presumedAssetType); require(amount % quantum == 0, "INVALID_AMOUNT"); quantizedAmount = amount / quantum; } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../libraries/LibConstants.sol"; import "../interfaces/MGovernance.sol"; import "../interfaces/MTokenAssetData.sol"; import "../tokens/ERC20/IERC20.sol"; import "./MainStorage.sol"; /** Registration of a new token (:sol:func:`registerToken`) entails defining a new asset type within the system, and associating it with an `assetInfo` array of bytes and a quantization factor (`quantum`). The `assetInfo` is a byte array, with a size depending on the token. For ETH, assetInfo is 4 bytes long. For ERC20 tokens, it is 36 bytes long. For each token type, the following constant 4-byte hash is defined, called the `selector`: | `ETH_SELECTOR = bytes4(keccak256("ETH()"));` | `ERC20_SELECTOR = bytes4(keccak256("ERC20Token(address)"));` | `ERC721_SELECTOR = bytes4(keccak256("ERC721Token(address,uint256)"));` | `MINTABLE_ERC20_SELECTOR = bytes4(keccak256("MintableERC20Token(address)"));` | `MINTABLE_ERC721_SELECTOR = bytes4(keccak256("MintableERC721Token(address,uint256)"));` For each token type, `assetInfo` is defined as follows: The `quantum` quantization factor defines the multiplicative transformation from the native token denomination as a 256b unsigned integer to a 63b unsigned integer representation as used by the Stark exchange. Only amounts in the native representation that represent an integer number of quanta are allowed in the system. The asset type is restricted to be the result of a hash of the `assetInfo` and the `quantum` masked to 250 bits (to be less than the prime used) according to the following formula: | ``uint256 assetType = uint256(keccak256(abi.encodePacked(assetInfo, quantum))) &`` | ``0x03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;`` Once registered, tokens cannot be removed from the system, as their IDs may be used by off-chain accounts. New tokens may only be registered by a Token Administrator. A Token Administrator may be instantly appointed or removed by the contract Governor (see :sol:mod:`MainGovernance`). Typically, the Token Administrator's private key should be kept in a cold wallet. */ abstract contract TokenRegister is MainStorage, LibConstants, MGovernance, MTokenAssetData { event LogTokenRegistered(uint256 assetType, bytes assetInfo, uint256 quantum); event LogTokenAdminAdded(address tokenAdmin); event LogTokenAdminRemoved(address tokenAdmin); modifier onlyTokensAdmin() { require(isTokenAdmin(msg.sender), "ONLY_TOKENS_ADMIN"); _; } function isTokenAdmin(address testedAdmin) public view returns (bool) { return tokenAdmins[testedAdmin]; } function registerTokenAdmin(address newAdmin) external onlyGovernance { tokenAdmins[newAdmin] = true; emit LogTokenAdminAdded(newAdmin); } function unregisterTokenAdmin(address oldAdmin) external onlyGovernance { tokenAdmins[oldAdmin] = false; emit LogTokenAdminRemoved(oldAdmin); } function isAssetRegistered(uint256 assetType) public view returns (bool) { return registeredAssetType[assetType]; } /* Registers a new asset to the system. Once added, it can not be removed and there is a limited number of slots available. */ function registerToken( uint256 assetType, bytes calldata assetInfo, uint256 quantum ) public virtual onlyTokensAdmin { // Make sure it is not invalid or already registered. require(!isAssetRegistered(assetType), "ASSET_ALREADY_REGISTERED"); require(assetType < K_MODULUS, "INVALID_ASSET_TYPE"); require(quantum > 0, "INVALID_QUANTUM"); require(quantum < QUANTUM_UPPER_BOUND, "INVALID_QUANTUM"); // Require that the assetType is the hash of the assetInfo and quantum truncated to 250 bits. uint256 enforcedId = uint256(keccak256(abi.encodePacked(assetInfo, quantum))) & MASK_250; require(assetType == enforcedId, "INVALID_ASSET_TYPE"); verifyAssetInfo(assetInfo); // NFTs quantum must equal one. if (isNonFungibleAssetInfo(assetInfo)) { require(quantum == 1, "INVALID_NFT_QUANTUM"); } // Add token to the in-storage structures. registeredAssetType[assetType] = true; assetTypeToAssetInfo[assetType] = assetInfo; assetTypeToQuantum[assetType] = quantum; // Log the registration of a new token. emit LogTokenRegistered(assetType, assetInfo, quantum); } function registerToken(uint256 assetType, bytes calldata assetInfo) external virtual { registerToken(assetType, assetInfo, 1); } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../libraries/Common.sol"; import "../interfaces/MTokenTransfers.sol"; import "../interfaces/MTokenAssetData.sol"; import "../interfaces/MTokenQuantization.sol"; import "../tokens/ERC1155/IERC1155.sol"; import "../tokens/ERC20/IERC20.sol"; /* Implements various transferIn and transferOut functionalities. */ abstract contract TokenTransfers is MTokenQuantization, MTokenAssetData, MTokenTransfers { using Addresses for address; using Addresses for address payable; /* Transfers funds from msg.sender to the exchange. */ function transferIn(uint256 assetType, uint256 quantizedAmount) internal override { uint256 amount = fromQuantized(assetType, quantizedAmount); if (isERC20(assetType)) { if (quantizedAmount == 0) return; address tokenAddress = extractContractAddress(assetType); IERC20 token = IERC20(tokenAddress); uint256 exchangeBalanceBefore = token.balanceOf(address(this)); bytes memory callData = abi.encodeWithSelector( token.transferFrom.selector, msg.sender, address(this), amount ); tokenAddress.safeTokenContractCall(callData); uint256 exchangeBalanceAfter = token.balanceOf(address(this)); require(exchangeBalanceAfter >= exchangeBalanceBefore, "OVERFLOW"); // NOLINTNEXTLINE(incorrect-equality): strict equality needed. require( exchangeBalanceAfter == exchangeBalanceBefore + amount, "INCORRECT_AMOUNT_TRANSFERRED" ); } else if (isEther(assetType)) { require(msg.value == amount, "INCORRECT_DEPOSIT_AMOUNT"); } else { revert("UNSUPPORTED_TOKEN_TYPE"); } } /* Transfers non fungible and semi fungible tokens from a user to the exchange. */ function transferInWithTokenId( uint256 assetType, uint256 tokenId, uint256 quantizedAmount ) internal override { require(isAssetTypeWithTokenId(assetType), "FUNGIBLE_ASSET_TYPE"); if (isERC721(assetType)) { require(quantizedAmount == 1, "ILLEGAL_NFT_BALANCE"); transferInNft(assetType, tokenId); } else if (quantizedAmount > 0) { transferInSft(assetType, tokenId, quantizedAmount); } } function transferInNft(uint256 assetType, uint256 tokenId) private { require(isERC721(assetType), "NOT_ERC721_TOKEN"); address tokenAddress = extractContractAddress(assetType); tokenAddress.safeTokenContractCall( abi.encodeWithSignature( "safeTransferFrom(address,address,uint256)", msg.sender, address(this), tokenId ) ); } function transferInSft( uint256 assetType, uint256 tokenId, uint256 quantizedAmount ) private { require(isERC1155(assetType), "NOT_ERC1155_TOKEN"); if (quantizedAmount == 0) return; uint256 amount = fromQuantized(assetType, quantizedAmount); address tokenAddress = extractContractAddress(assetType); IERC1155 token = IERC1155(tokenAddress); uint256 exchangeBalanceBefore = token.balanceOf(address(this), tokenId); // Call an ERC1155 token transfer. tokenAddress.safeTokenContractCall( abi.encodeWithSelector( token.safeTransferFrom.selector, msg.sender, address(this), tokenId, amount, bytes("") ) ); uint256 exchangeBalanceAfter = token.balanceOf(address(this), tokenId); require(exchangeBalanceAfter >= exchangeBalanceBefore, "OVERFLOW"); // NOLINTNEXTLINE(incorrect-equality): strict equality needed. require( exchangeBalanceAfter == exchangeBalanceBefore + amount, "INCORRECT_AMOUNT_TRANSFERRED" ); } /* Transfers funds from the exchange to recipient. */ function transferOut( address payable recipient, uint256 assetType, uint256 quantizedAmount ) internal override { // Make sure we don't accidentally burn funds. require(recipient != address(0x0), "INVALID_RECIPIENT"); uint256 amount = fromQuantized(assetType, quantizedAmount); if (isERC20(assetType)) { if (quantizedAmount == 0) return; address tokenAddress = extractContractAddress(assetType); IERC20 token = IERC20(tokenAddress); uint256 exchangeBalanceBefore = token.balanceOf(address(this)); bytes memory callData = abi.encodeWithSelector( token.transfer.selector, recipient, amount ); tokenAddress.safeTokenContractCall(callData); uint256 exchangeBalanceAfter = token.balanceOf(address(this)); require(exchangeBalanceAfter <= exchangeBalanceBefore, "UNDERFLOW"); // NOLINTNEXTLINE(incorrect-equality): strict equality needed. require( exchangeBalanceAfter == exchangeBalanceBefore - amount, "INCORRECT_AMOUNT_TRANSFERRED" ); } else if (isEther(assetType)) { if (quantizedAmount == 0) return; recipient.performEthTransfer(amount); } else { revert("UNSUPPORTED_TOKEN_TYPE"); } } /* Transfers non fungible and semi fungible tokens from the exchange to recipient. */ function transferOutWithTokenId( address recipient, uint256 assetType, uint256 tokenId, uint256 quantizedAmount ) internal override { require(isAssetTypeWithTokenId(assetType), "FUNGIBLE_ASSET_TYPE"); if (isERC721(assetType)) { require(quantizedAmount == 1, "ILLEGAL_NFT_BALANCE"); transferOutNft(recipient, assetType, tokenId); } else if (quantizedAmount > 0) { transferOutSft(recipient, assetType, tokenId, quantizedAmount); } } /* Transfers NFT from the exchange to recipient. */ function transferOutNft( address recipient, uint256 assetType, uint256 tokenId ) private { // Make sure we don't accidentally burn funds. require(recipient != address(0x0), "INVALID_RECIPIENT"); require(isERC721(assetType), "NOT_ERC721_TOKEN"); address tokenAddress = extractContractAddress(assetType); tokenAddress.safeTokenContractCall( abi.encodeWithSignature( "safeTransferFrom(address,address,uint256)", address(this), recipient, tokenId ) ); } /* Transfers Semi Fungible Tokens from the exchange to recipient. */ function transferOutSft( address recipient, uint256 assetType, uint256 tokenId, uint256 quantizedAmount ) private { // Make sure we don't accidentally burn funds. require(recipient != address(0x0), "INVALID_RECIPIENT"); require(isERC1155(assetType), "NOT_ERC1155_TOKEN"); if (quantizedAmount == 0) return; uint256 amount = fromQuantized(assetType, quantizedAmount); address tokenAddress = extractContractAddress(assetType); IERC1155 token = IERC1155(tokenAddress); uint256 exchangeBalanceBefore = token.balanceOf(address(this), tokenId); // Call an ERC1155 token transfer. tokenAddress.safeTokenContractCall( abi.encodeWithSelector( token.safeTransferFrom.selector, address(this), recipient, tokenId, amount, bytes("") ) ); uint256 exchangeBalanceAfter = token.balanceOf(address(this), tokenId); require(exchangeBalanceAfter <= exchangeBalanceBefore, "UNDERFLOW"); // NOLINTNEXTLINE(incorrect-equality): strict equality needed. require( exchangeBalanceAfter == exchangeBalanceBefore - amount, "INCORRECT_AMOUNT_TRANSFERRED" ); } function transferOutMint( uint256 assetType, uint256 quantizedAmount, address recipient, bytes calldata mintingBlob ) internal override { // Make sure we don't accidentally burn funds. require(recipient != address(0x0), "INVALID_RECIPIENT"); require(isMintableAssetType(assetType), "NON_MINTABLE_ASSET_TYPE"); require(quantizedAmount > 0, "INVALID_MINT_AMOUNT"); uint256 amount = fromQuantized(assetType, quantizedAmount); address tokenAddress = extractContractAddress(assetType); tokenAddress.safeTokenContractCall( abi.encodeWithSignature( "mintFor(address,uint256,bytes)", recipient, amount, mintingBlob ) ); } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../interactions/StarkExForcedActionState.sol"; import "../../components/ERC721Receiver.sol"; import "../../components/ERC1155Receiver.sol"; import "../../components/Freezable.sol"; import "../../components/KeyGetters.sol"; import "../../components/TokenRegister.sol"; import "../../components/TokenTransfers.sol"; import "../../components/MainGovernance.sol"; import "../../interactions/AcceptModifications.sol"; import "../../interactions/Blocklist.sol"; import "../../interactions/Deposits.sol"; import "../../interactions/TokenAssetData.sol"; import "../../interactions/TokenQuantization.sol"; import "../../interactions/Withdrawals.sol"; import "../../interfaces/SubContractor.sol"; contract TokensAndRamping is ERC1155Receiver, ERC721Receiver, SubContractor, Freezable, MainGovernance, AcceptModifications, StarkExForcedActionState, TokenAssetData, TokenQuantization, TokenRegister, TokenTransfers, KeyGetters, Deposits, Withdrawals, Blocklist { function initialize( bytes calldata /* data */ ) external override { revert("NOT_IMPLEMENTED"); } function initializerSize() external view override returns (uint256) { return 0; } function validatedSelectors() external pure override returns (bytes4[] memory selectors) { uint256 len_ = 8; uint256 index_ = 0; selectors = new bytes4[](len_); selectors[index_++] = Deposits.depositCancel.selector; selectors[index_++] = Deposits.depositWithTokenIdReclaim.selector; selectors[index_++] = Deposits.depositReclaim.selector; selectors[index_++] = Withdrawals.withdraw.selector; selectors[index_++] = Withdrawals.withdrawAndMint.selector; selectors[index_++] = Withdrawals.withdrawWithTokenId.selector; selectors[index_++] = Withdrawals.adminForcedWithdraw.selector; selectors[index_++] = Blocklist.addToBlockedList.selector; require(index_ == len_, "INCORRECT_SELECTORS_ARRAY_LENGTH"); } function identify() external pure override returns (string memory) { return "StarkWare_TokensAndRamping_2024_4"; } } /* Copyright 2019-2024 StarkWare Industries Ltd. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.starkware.co/open-source-license/ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // SPDX-License-Identifier: Apache-2.0. pragma solidity ^0.6.12; import "../interfaces/MAcceptModifications.sol"; import "../interfaces/MBlocklist.sol"; import "../interfaces/MTokenQuantization.sol"; import "../interfaces/MTokenAssetData.sol"; import "../interfaces/MFreezable.sol"; import "../interfaces/MKeyGetters.sol"; import "../interfaces/MTokenTransfers.sol"; import "../components/MainStorage.sol"; /** For a user to perform a withdrawal operation from the Stark Exchange during normal operation two calls are required: 1. A call to an offchain exchange API, requesting a withdrawal from a user account (vault). 2. A call to the on-chain :sol:func:`withdraw` function to perform the actual withdrawal of funds transferring them to the users Eth or ERC20 account (depending on the token type). For simplicity, hereafter it is assumed that all tokens are ERC20 tokens but the text below applies to Eth in the same manner. In the first call mentioned above, anyone can call the API to request the withdrawal of an amount from a given vault. Following the request, the exchange may include the withdrawal in a STARK proof. The submission of a proof then results in the addition of the amount(s) withdrawn to an on-chain pending withdrawals account under the stark key of the vault owner and the appropriate asset ID. At the same time, this also implies that this amount is deducted from the off-chain vault. Once the amount to be withdrawn has been transfered to the on-chain pending withdrawals account, the user may perform the second call mentioned above to complete the transfer of funds from the Stark Exchange contract to the appropriate ERC20 account. Only a user holding the Eth key corresponding to the Stark Key of a pending withdrawals account may perform this operation. It is possible that for multiple withdrawal calls to the API, a single withdrawal call to the contract may retrieve all funds, as long as they are all for the same asset ID. The result of the operation, assuming all requirements are met, is that an amount of ERC20 tokens in the pending withdrawal account times the quantization factor is transferred to the ERC20 account of the user. A withdrawal request cannot be cancelled. Once funds reach the pending withdrawals account on-chain, they cannot be moved back into an off-chain vault before completion of the withdrawal to the ERC20 account of the user. In the event that the exchange reaches a frozen state the user may perform a withdrawal operation via an alternative flow, known as the "Escape" flow. In this flow, the API call above is replaced with an :sol:func:`escape` call to the on-chain contract (see :sol:mod:`Escapes`) proving the ownership of off-chain funds. If such proof is accepted, the user may proceed as above with the :sol:func:`withdraw` call to the contract to complete the operation. */ abstract contract Withdrawals is MainStorage, MAcceptModifications, MTokenQuantization, MTokenAssetData, MFreezable, MKeyGetters, MTokenTransfers, MBlocklist { event LogWithdrawalPerformed( uint256 ownerKey, uint256 assetType, uint256 nonQuantizedAmount, uint256 quantizedAmount, address recipient ); event LogNftWithdrawalPerformed( uint256 ownerKey, uint256 assetType, uint256 tokenId, uint256 assetId, address recipient ); event LogWithdrawalWithTokenIdPerformed( uint256 ownerKey, uint256 assetType, uint256 tokenId, uint256 assetId, uint256 nonQuantizedAmount, uint256 quantizedAmount, address recipient ); event LogMintWithdrawalPerformed( uint256 ownerKey, uint256 assetType, uint256 nonQuantizedAmount, uint256 quantizedAmount, uint256 assetId ); function getWithdrawalBalance(uint256 ownerKey, uint256 assetId) external view returns (uint256) { uint256 presumedAssetType = assetId; return fromQuantized(presumedAssetType, pendingWithdrawals[ownerKey][assetId]); } /* Moves funds from the pending withdrawal account to the owner address. Note: this function can be called by anyone. Can be called normally while frozen. */ function withdraw(uint256 ownerKey, uint256 assetType) external onlyNotBlocked(ownerKey) { address payable recipient = payable(strictGetEthKey(ownerKey)); _withdraw(ownerKey, assetType, recipient); } function adminForcedWithdraw( uint256 ownerKey, uint256 assetType, address payable destination ) external onlyBlockAdmin { require(readyForClearance(ownerKey), "CANNOT_ADMIN_FORCE_WITHDRAW"); emit AdminForcedWithdrawal(ownerKey, destination); _withdraw(ownerKey, assetType, destination); } function _withdraw( uint256 ownerKey, uint256 assetType, address payable recipient ) private { // address payable recipient = payable(strictGetEthKey(ownerKey)); require(!isMintableAssetType(assetType), "MINTABLE_ASSET_TYPE"); require(isFungibleAssetType(assetType), "NON_FUNGIBLE_ASSET_TYPE"); uint256 assetId = assetType; // Fetch and clear quantized amount. uint256 quantizedAmount = pendingWithdrawals[ownerKey][assetId]; pendingWithdrawals[ownerKey][assetId] = 0; // Transfer funds. transferOut(recipient, assetType, quantizedAmount); emit LogWithdrawalPerformed( ownerKey, assetType, fromQuantized(assetType, quantizedAmount), quantizedAmount, recipient ); } /* Allows withdrawal of tokens to their owner's account. Note: this function can be called by anyone. This function can be called normally while frozen. */ function withdrawWithTokenId( uint256 ownerKey, uint256 assetType, uint256 tokenId // No notFrozen modifier: This function can always be used, even when frozen. ) public onlyNotBlocked(ownerKey) { address recipient = strictGetEthKey(ownerKey); _withdrawWithTokenId(ownerKey, assetType, tokenId, recipient); } function adminForcedWithdrawWithTokenId( uint256 ownerKey, uint256 assetType, uint256 tokenId, address destination ) external onlyBlockAdmin { require(readyForClearance(ownerKey), "CANNOT_ADMIN_FORCE_WITHDRAW"); emit AdminForcedWithdrawal(ownerKey, destination); _withdrawWithTokenId(ownerKey, assetType, tokenId, destination); } function _withdrawWithTokenId( uint256 ownerKey, uint256 assetType, uint256 tokenId, address recipient ) private { require(isAssetTypeWithTokenId(assetType), "INVALID_ASSET_TYPE"); uint256 assetId = calculateAssetIdWithTokenId(assetType, tokenId); uint256 quantizedAmount = pendingWithdrawals[ownerKey][assetId]; pendingWithdrawals[ownerKey][assetId] = 0; // Transfer funds. transferOutWithTokenId(recipient, assetType, tokenId, quantizedAmount); if (isERC721(assetType)) { emit LogNftWithdrawalPerformed(ownerKey, assetType, tokenId, assetId, recipient); } emit LogWithdrawalWithTokenIdPerformed( ownerKey, assetType, tokenId, assetId, fromQuantized(assetType, quantizedAmount), quantizedAmount, recipient ); } /* Allows withdrawal of an NFT to its owner's account. Note: this function can be called by anyone. This function can be called normally while frozen. */ function withdrawNft( uint256 ownerKey, uint256 assetType, uint256 tokenId // No notFrozen modifier: This function can always be used, even when frozen. ) external { withdrawWithTokenId(ownerKey, assetType, tokenId); } function withdrawAndMint( uint256 ownerKey, uint256 assetType, bytes calldata mintingBlob ) external onlyNotBlocked(ownerKey) { address recipient = strictGetEthKey(ownerKey); _withdrawAndMint(ownerKey, assetType, mintingBlob, recipient); } function adminForcedWithdrawAndMint( uint256 ownerKey, uint256 assetType, bytes calldata mintingBlob, address destination ) external onlyBlockAdmin { require(readyForClearance(ownerKey), "CANNOT_ADMIN_FORCE_WITHDRAW"); emit AdminForcedWithdrawal(ownerKey, destination); _withdrawAndMint(ownerKey, assetType, mintingBlob, destination); } function _withdrawAndMint( uint256 ownerKey, uint256 assetType, bytes calldata mintingBlob, address recipient ) private { require(registeredAssetType[assetType], "INVALID_ASSET_TYPE"); require(isMintableAssetType(assetType), "NON_MINTABLE_ASSET_TYPE"); uint256 assetId = calculateMintableAssetId(assetType, mintingBlob); require(pendingWithdrawals[ownerKey][assetId] > 0, "NO_PENDING_WITHDRAWAL_BALANCE"); uint256 quantizedAmount = pendingWithdrawals[ownerKey][assetId]; pendingWithdrawals[ownerKey][assetId] = 0; // Transfer funds. transferOutMint(assetType, quantizedAmount, recipient, mintingBlob); emit LogMintWithdrawalPerformed( ownerKey, assetType, fromQuantized(assetType, quantizedAmount), quantizedAmount, assetId ); } }
File 5 of 5: StMATIC
// SPDX-FileCopyrightText: 2021 ShardLabs // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.7; import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; import "./interfaces/IValidatorShare.sol"; import "./interfaces/INodeOperatorRegistry.sol"; import "./interfaces/IStakeManager.sol"; import "./interfaces/IPoLidoNFT.sol"; import "./interfaces/IFxStateRootTunnel.sol"; import "./interfaces/IStMATIC.sol"; /// @title StMATIC /// @author 2021 ShardLabs. contract StMATIC is IStMATIC, ERC20Upgradeable, AccessControlUpgradeable, PausableUpgradeable { using SafeERC20Upgradeable for IERC20Upgradeable; /// @notice node operator registry interface. INodeOperatorRegistry public override nodeOperatorRegistry; /// @notice The fee distribution. FeeDistribution public override entityFees; /// @notice StakeManager interface. IStakeManager public override stakeManager; /// @notice LidoNFT interface. IPoLidoNFT public override poLidoNFT; /// @notice fxStateRootTunnel interface. IFxStateRootTunnel public override fxStateRootTunnel; /// @notice contract version. string public override version; /// @notice dao address. address public override dao; /// @notice insurance address. address public override insurance; /// @notice Matic ERC20 token. address public override token; /// @notice Matic ERC20 token address NOT USED IN V2. uint256 public override lastWithdrawnValidatorId; /// @notice total buffered Matic in the contract. uint256 public override totalBuffered; /// @notice delegation lower bound. uint256 public override delegationLowerBound; /// @notice reward distribution lower bound. uint256 public override rewardDistributionLowerBound; /// @notice reserved funds in Matic. uint256 public override reservedFunds; /// @notice submit threshold NOT USED in V2. uint256 public override submitThreshold; /// @notice submit handler NOT USED in V2. bool public override submitHandler; /// @notice token to WithdrawRequest mapping one-to-one. mapping(uint256 => RequestWithdraw) public override token2WithdrawRequest; /// @notice DAO Role. bytes32 public constant override DAO = keccak256("DAO"); bytes32 public constant override PAUSE_ROLE = keccak256("LIDO_PAUSE_OPERATOR"); bytes32 public constant override UNPAUSE_ROLE = keccak256("LIDO_UNPAUSE_OPERATOR"); /// @notice When an operator quit the system StMATIC contract withdraw the total delegated /// to it. The request is stored inside this array. RequestWithdraw[] public stMaticWithdrawRequest; /// @notice token to Array WithdrawRequest mapping one-to-many. mapping(uint256 => RequestWithdraw[]) public token2WithdrawRequests; /// @notice protocol fee. uint8 public override protocolFee; // @notice these state variable are used to mark entrance and exit form a contract function uint256 private constant _NOT_ENTERED = 1; uint256 private constant _ENTERED = 2; uint256 private _status; // @notice used to execute the recovery 1 time bool private recovered; /// @notice Prevents a contract from calling itself, directly or indirectly. modifier nonReentrant() { _nonReentrant(); _status = _ENTERED; _; _status = _NOT_ENTERED; } /// @param _nodeOperatorRegistry - Address of the node operator registry /// @param _token - Address of MATIC token on Ethereum Mainnet /// @param _dao - Address of the DAO /// @param _insurance - Address of the insurance /// @param _stakeManager - Address of the stake manager /// @param _poLidoNFT - Address of the stMATIC NFT /// @param _fxStateRootTunnel - Address of the FxStateRootTunnel function initialize( address _nodeOperatorRegistry, address _token, address _dao, address _insurance, address _stakeManager, address _poLidoNFT, address _fxStateRootTunnel ) external override initializer { __AccessControl_init_unchained(); __Pausable_init_unchained(); __ERC20_init_unchained("Staked MATIC", "stMATIC"); _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _grantRole(DAO, _dao); _grantRole(PAUSE_ROLE, msg.sender); _grantRole(UNPAUSE_ROLE, _dao); nodeOperatorRegistry = INodeOperatorRegistry(_nodeOperatorRegistry); stakeManager = IStakeManager(_stakeManager); poLidoNFT = IPoLidoNFT(_poLidoNFT); fxStateRootTunnel = IFxStateRootTunnel(_fxStateRootTunnel); dao = _dao; token = _token; insurance = _insurance; entityFees = FeeDistribution(25, 50, 25); } /// @notice Send funds to StMATIC contract and mints StMATIC to msg.sender /// @notice Requires that msg.sender has approved _amount of MATIC to this contract /// @param _amount - Amount of MATIC sent from msg.sender to this contract /// @param _referral - referral address. /// @return Amount of StMATIC shares generated function submit(uint256 _amount, address _referral) external override whenNotPaused nonReentrant returns (uint256) { _require(_amount > 0, "Invalid amount"); IERC20Upgradeable(token).safeTransferFrom( msg.sender, address(this), _amount ); ( uint256 amountToMint, uint256 totalShares, uint256 totalPooledMatic ) = convertMaticToStMatic(_amount); _require(amountToMint > 0, "Mint ZERO"); _mint(msg.sender, amountToMint); totalBuffered += _amount; _bridge(totalShares + amountToMint, totalPooledMatic + _amount); emit SubmitEvent(msg.sender, _amount, _referral); return amountToMint; } /// @notice Stores users request to withdraw into a RequestWithdraw struct /// @param _amount - Amount of StMATIC that is requested to withdraw /// @param _referral - referral address. /// @return NFT token id. function requestWithdraw(uint256 _amount, address _referral) external override whenNotPaused nonReentrant returns (uint256) { _require( _amount > 0 && balanceOf(msg.sender) >= _amount, "Invalid amount" ); uint256 tokenId; { uint256 totalPooledMatic = getTotalPooledMatic(); uint256 totalAmount2WithdrawInMatic = _convertStMaticToMatic( _amount, totalPooledMatic ); _require(totalAmount2WithdrawInMatic > 0, "Withdraw ZERO Matic"); ( INodeOperatorRegistry.ValidatorData[] memory activeNodeOperators, uint256 totalDelegated, uint256[] memory bigNodeOperatorIds, uint256[] memory smallNodeOperatorIds, uint256[] memory allowedAmountToRequestFromOperators, uint256 totalValidatorsToWithdrawFrom ) = nodeOperatorRegistry.getValidatorsRequestWithdraw(totalAmount2WithdrawInMatic); { uint256 totalBufferedMem = totalBuffered; uint256 reservedFundsMem = reservedFunds; uint256 localActiveBalance = totalBufferedMem > reservedFundsMem ? totalBufferedMem - reservedFundsMem : 0; uint256 liquidity = totalDelegated + localActiveBalance; _require( liquidity >= totalAmount2WithdrawInMatic, "Too much to withdraw" ); } // Added a scoop here to fix stack too deep error { uint256 currentAmount2WithdrawInMatic = totalAmount2WithdrawInMatic; tokenId = poLidoNFT.mint(msg.sender); if (totalDelegated != 0) { if (totalValidatorsToWithdrawFrom != 0) { currentAmount2WithdrawInMatic = _requestWithdrawBalanced( tokenId, activeNodeOperators, totalAmount2WithdrawInMatic, totalValidatorsToWithdrawFrom, totalDelegated, currentAmount2WithdrawInMatic ); } else { // request withdraw from big delegated validators currentAmount2WithdrawInMatic = _requestWithdrawUnbalanced( tokenId, activeNodeOperators, bigNodeOperatorIds, allowedAmountToRequestFromOperators, currentAmount2WithdrawInMatic ); // request withdraw from small delegated validators if (currentAmount2WithdrawInMatic != 0) { currentAmount2WithdrawInMatic = _requestWithdrawUnbalanced( tokenId, activeNodeOperators, smallNodeOperatorIds, allowedAmountToRequestFromOperators, currentAmount2WithdrawInMatic ); } } } if (totalAmount2WithdrawInMatic > totalDelegated) { IStakeManager stakeManagerMem = stakeManager; token2WithdrawRequests[tokenId].push( RequestWithdraw( currentAmount2WithdrawInMatic, 0, stakeManagerMem.epoch() + stakeManagerMem.withdrawalDelay(), address(0) ) ); reservedFunds += currentAmount2WithdrawInMatic; currentAmount2WithdrawInMatic = 0; } } _burn(msg.sender, _amount); _bridge(totalSupply(), totalPooledMatic - totalAmount2WithdrawInMatic); } emit RequestWithdrawEvent(msg.sender, _amount, _referral); return tokenId; } /// @notice Request withdraw when system is balanced function _requestWithdrawBalanced( uint256 tokenId, INodeOperatorRegistry.ValidatorData[] memory activeNodeOperators, uint256 totalAmount2WithdrawInMatic, uint256 totalValidatorsToWithdrawFrom, uint256 totalDelegated, uint256 currentAmount2WithdrawInMatic ) private returns (uint256) { uint256 totalAmount = min(totalDelegated, totalAmount2WithdrawInMatic); uint256 amount2WithdrawFromValidator = totalAmount / totalValidatorsToWithdrawFrom; for (uint256 idx = 0; idx < totalValidatorsToWithdrawFrom; idx++) { address validatorShare = activeNodeOperators[idx].validatorShare; _require( _calculateValidatorShares( validatorShare, amount2WithdrawFromValidator ) > 0, "ZERO shares to withdraw" ); currentAmount2WithdrawInMatic = _requestWithdraw( tokenId, validatorShare, amount2WithdrawFromValidator, currentAmount2WithdrawInMatic ); } return currentAmount2WithdrawInMatic; } /// @notice Request withdraw when system is unbalanced function _requestWithdrawUnbalanced( uint256 tokenId, INodeOperatorRegistry.ValidatorData[] memory activeNodeOperators, uint256[] memory nodeOperatorIds, uint256[] memory allowedAmountToRequestFromOperators, uint256 currentAmount2WithdrawInMatic ) private returns (uint256) { for (uint256 idx = 0; idx < nodeOperatorIds.length; idx++) { uint256 id = nodeOperatorIds[idx]; uint256 amountCanBeRequested = allowedAmountToRequestFromOperators[ id ]; if (amountCanBeRequested == 0) continue; uint256 amount2WithdrawFromValidator = min(amountCanBeRequested, currentAmount2WithdrawInMatic); address validatorShare = activeNodeOperators[id].validatorShare; _require( _calculateValidatorShares( validatorShare, amount2WithdrawFromValidator ) > 0, "ZERO shares to withdraw" ); currentAmount2WithdrawInMatic = _requestWithdraw( tokenId, validatorShare, amount2WithdrawFromValidator, currentAmount2WithdrawInMatic ); if (currentAmount2WithdrawInMatic == 0) break; } return currentAmount2WithdrawInMatic; } function _requestWithdraw( uint256 tokenId, address validatorShare, uint256 amount2WithdrawFromValidator, uint256 currentAmount2WithdrawInMatic ) private returns (uint256) { sellVoucher_new( validatorShare, amount2WithdrawFromValidator, type(uint256).max ); IStakeManager stakeManagerMem = stakeManager; token2WithdrawRequests[tokenId].push( RequestWithdraw( 0, IValidatorShare(validatorShare).unbondNonces(address(this)), stakeManagerMem.epoch() + stakeManagerMem.withdrawalDelay(), validatorShare ) ); currentAmount2WithdrawInMatic -= amount2WithdrawFromValidator; return currentAmount2WithdrawInMatic; } /// @notice This will be included in the cron job /// @notice Delegates tokens to validator share contract function delegate() external override whenNotPaused nonReentrant { uint256 ltotalBuffered = totalBuffered; uint256 lreservedFunds = reservedFunds; _require( ltotalBuffered > delegationLowerBound + lreservedFunds, "Amount to delegate lower than minimum" ); uint256 amountToDelegate = ltotalBuffered - lreservedFunds; ( INodeOperatorRegistry.ValidatorData[] memory delegatableNodeOperators, uint256[] memory operatorRatiosToDelegate, uint256 totalRatio ) = nodeOperatorRegistry.getValidatorsDelegationAmount( amountToDelegate ); uint256 totalDelegatableNodeOperators = delegatableNodeOperators.length; uint256 remainder; uint256 amountDelegated; address maticTokenAddress = token; address stakeManagerAddress = address(stakeManager); IERC20Upgradeable(maticTokenAddress).safeApprove(stakeManagerAddress, 0); IERC20Upgradeable(maticTokenAddress).safeApprove( stakeManagerAddress, amountToDelegate ); // If the total Ratio is equal to ZERO that means the system is balanced so we // distribute the buffered tokens equally between the validators uint256 amountToDelegatePerOperator = amountToDelegate / totalDelegatableNodeOperators; for (uint256 i = 0; i < totalDelegatableNodeOperators; i++) { if (totalRatio != 0) { if (operatorRatiosToDelegate[i] == 0) continue; amountToDelegatePerOperator = (operatorRatiosToDelegate[i] * amountToDelegate) / totalRatio; } address _validatorAddress = delegatableNodeOperators[i] .validatorShare; uint256 shares = _calculateValidatorShares( _validatorAddress, amountToDelegatePerOperator ); if (shares == 0) continue; buyVoucher(_validatorAddress, amountToDelegatePerOperator, 0); amountDelegated += amountToDelegatePerOperator; } remainder = amountToDelegate - amountDelegated; totalBuffered = remainder + lreservedFunds; emit DelegateEvent(amountDelegated, remainder); } /// @notice Claims tokens from validator share and sends them to the /// user if his request is in the userToWithdrawRequest /// @param _tokenId - Id of the token that wants to be claimed function claimTokens(uint256 _tokenId) external override whenNotPaused { _require( poLidoNFT.isApprovedOrOwner(msg.sender, _tokenId), "Not owner" ); if (token2WithdrawRequest[_tokenId].requestEpoch != 0) { _claimTokensV1(_tokenId); } else if (token2WithdrawRequests[_tokenId].length != 0) { _claimTokensV2(_tokenId); } else { revert("Invalid claim token"); } } /// @notice Claims tokens v2 function _claimTokensV2(uint256 _tokenId) private { RequestWithdraw[] memory usersRequest = token2WithdrawRequests[ _tokenId ]; _require( stakeManager.epoch() >= usersRequest[0].requestEpoch, "Not able to claim yet" ); poLidoNFT.burn(_tokenId); delete token2WithdrawRequests[_tokenId]; uint256 length = usersRequest.length; uint256 amountToClaim; address maticTokenAddress = token; uint256 balanceBeforeClaim = IERC20Upgradeable(maticTokenAddress).balanceOf( address(this) ); for (uint256 idx = 0; idx < length; idx++) { if (usersRequest[idx].validatorAddress != address(0)) { unstakeClaimTokens_new( usersRequest[idx].validatorAddress, usersRequest[idx].validatorNonce ); } else { uint256 _amountToClaim = usersRequest[idx] .amount2WithdrawFromStMATIC; reservedFunds -= _amountToClaim; totalBuffered -= _amountToClaim; amountToClaim += _amountToClaim; } } amountToClaim += IERC20Upgradeable(maticTokenAddress).balanceOf(address(this)) - balanceBeforeClaim; IERC20Upgradeable(maticTokenAddress).safeTransfer(msg.sender, amountToClaim); emit ClaimTokensEvent(msg.sender, _tokenId, amountToClaim, 0); } /// @notice Claims tokens v1 function _claimTokensV1(uint256 _tokenId) private { RequestWithdraw memory usersRequest = token2WithdrawRequest[_tokenId]; _require( stakeManager.epoch() >= usersRequest.requestEpoch, "Not able to claim yet" ); poLidoNFT.burn(_tokenId); delete token2WithdrawRequest[_tokenId]; uint256 amountToClaim; address maticTokenAddress = token; if (usersRequest.validatorAddress != address(0)) { uint256 balanceBeforeClaim = IERC20Upgradeable(maticTokenAddress).balanceOf( address(this) ); unstakeClaimTokens_new( usersRequest.validatorAddress, usersRequest.validatorNonce ); amountToClaim = IERC20Upgradeable(maticTokenAddress).balanceOf(address(this)) - balanceBeforeClaim; } else { amountToClaim = usersRequest.amount2WithdrawFromStMATIC; reservedFunds -= amountToClaim; totalBuffered -= amountToClaim; } IERC20Upgradeable(maticTokenAddress).safeTransfer(msg.sender, amountToClaim); emit ClaimTokensEvent(msg.sender, _tokenId, amountToClaim, 0); } /// @notice Distributes rewards claimed from validator shares based on fees defined /// in entityFee. function distributeRewards() external override whenNotPaused nonReentrant { INodeOperatorRegistry.ValidatorData[] memory operatorInfos = nodeOperatorRegistry.listDelegatedNodeOperators(); uint256 totalActiveOperatorInfos = operatorInfos.length; for (uint256 i = 0; i < totalActiveOperatorInfos; i++) { IValidatorShare validatorShare = IValidatorShare( operatorInfos[i].validatorShare ); uint256 stMaticReward = validatorShare.getLiquidRewards( address(this) ); uint256 rewardThreshold = validatorShare.minAmount(); if (stMaticReward > rewardThreshold) { validatorShare.withdrawRewards(); } } address maticTokenAddress = token; uint256 totalRewards = IERC20Upgradeable(maticTokenAddress).balanceOf( address(this) ) - totalBuffered; uint256 protocolRewards = totalRewards * protocolFee / 100; _require( protocolRewards > rewardDistributionLowerBound, "Amount to distribute lower than minimum" ); uint256 balanceBeforeDistribution = IERC20Upgradeable(maticTokenAddress).balanceOf( address(this) ); uint256 daoRewards = (protocolRewards * entityFees.dao) / 100; uint256 insuranceRewards = (protocolRewards * entityFees.insurance) / 100; uint256 operatorsRewards = (protocolRewards * entityFees.operators) / 100; uint256 operatorReward = operatorsRewards / totalActiveOperatorInfos; IERC20Upgradeable(maticTokenAddress).safeTransfer(dao, daoRewards); IERC20Upgradeable(maticTokenAddress).safeTransfer(insurance, insuranceRewards); for (uint256 i = 0; i < totalActiveOperatorInfos; i++) { IERC20Upgradeable(maticTokenAddress).safeTransfer( operatorInfos[i].rewardAddress, operatorReward ); } uint256 currentBalance = IERC20Upgradeable(maticTokenAddress).balanceOf( address(this) ); uint256 totalDistributed = balanceBeforeDistribution - currentBalance; // Add the remainder to totalBuffered totalBuffered = currentBalance; _bridge(totalSupply(), getTotalPooledMatic()); emit DistributeRewardsEvent(totalDistributed); } /// @notice Only NodeOperatorRegistry can call this function /// @notice Withdraws funds from stopped validator. /// @param _validatorShare - Address of the validator share that will be withdrawn function withdrawTotalDelegated(address _validatorShare) external override nonReentrant { _require( msg.sender == address(nodeOperatorRegistry), "Not a node operator" ); (uint256 stakedAmount, ) = getTotalStake( IValidatorShare(_validatorShare) ); // Check if the validator has enough shares. uint256 shares = _calculateValidatorShares( _validatorShare, stakedAmount ); if (shares == 0) { return; } _createWithdrawRequest(_validatorShare, stakedAmount); emit WithdrawTotalDelegatedEvent(_validatorShare, stakedAmount); } /// @notice Rebalane the system by request withdraw from the validators that contains /// more token delegated to them. function rebalanceDelegatedTokens() external override onlyRole(DAO) { uint256 amountToReDelegate = totalBuffered - reservedFunds + calculatePendingBufferedTokens(); ( INodeOperatorRegistry.ValidatorData[] memory nodeOperators, uint256[] memory operatorRatiosToRebalance, uint256 totalRatio, uint256 totalToWithdraw ) = nodeOperatorRegistry.getValidatorsRebalanceAmount( amountToReDelegate ); uint256 amountToWithdraw; address _validatorAddress; for (uint256 i = 0; i < nodeOperators.length; i++) { if (operatorRatiosToRebalance[i] == 0) continue; amountToWithdraw = (operatorRatiosToRebalance[i] * totalToWithdraw) / totalRatio; if (amountToWithdraw == 0) continue; _validatorAddress = nodeOperators[i].validatorShare; uint256 shares = _calculateValidatorShares( _validatorAddress, amountToWithdraw ); if (shares == 0) continue; _createWithdrawRequest( nodeOperators[i].validatorShare, amountToWithdraw ); } } function _createWithdrawRequest(address _validatorShare, uint256 amount) private { sellVoucher_new(_validatorShare, amount, type(uint256).max); IStakeManager stakeManagerMem = stakeManager; stMaticWithdrawRequest.push( RequestWithdraw( 0, IValidatorShare(_validatorShare).unbondNonces(address(this)), stakeManagerMem.epoch() + stakeManagerMem.withdrawalDelay(), _validatorShare ) ); } /// @notice calculate the total amount stored in stMaticWithdrawRequest array. /// @return pendingBufferedTokens the total pending amount for stMatic. function calculatePendingBufferedTokens() public view override returns (uint256 pendingBufferedTokens) { uint256 pendingWithdrawalLength = stMaticWithdrawRequest.length; for (uint256 i = 0; i < pendingWithdrawalLength; i++) { pendingBufferedTokens += _getMaticFromRequestData( stMaticWithdrawRequest[i] ); } return pendingBufferedTokens; } /// @notice Claims tokens from validator share and sends them to the StMATIC contract. function claimTokensFromValidatorToContract(uint256 _index) external override whenNotPaused nonReentrant { uint256 length = stMaticWithdrawRequest.length; _require(_index < length, "invalid index"); RequestWithdraw memory lidoRequest = stMaticWithdrawRequest[_index]; _require( stakeManager.epoch() >= lidoRequest.requestEpoch, "Not able to claim yet" ); address maticTokenAddress = token; uint256 balanceBeforeClaim = IERC20Upgradeable(maticTokenAddress).balanceOf( address(this) ); unstakeClaimTokens_new( lidoRequest.validatorAddress, lidoRequest.validatorNonce ); uint256 claimedAmount = IERC20Upgradeable(maticTokenAddress).balanceOf( address(this) ) - balanceBeforeClaim; totalBuffered += claimedAmount; if (_index != length - 1 && length != 1) { stMaticWithdrawRequest[_index] = stMaticWithdrawRequest[length - 1]; } stMaticWithdrawRequest.pop(); _bridge(totalSupply(), getTotalPooledMatic()); emit ClaimTotalDelegatedEvent( lidoRequest.validatorAddress, claimedAmount ); } /// @notice Pauses the contract function pause() external onlyRole(PAUSE_ROLE) { _pause(); } /// @notice Unpauses the contract function unpause() external onlyRole(UNPAUSE_ROLE) { _unpause(); } //////////////////////////////////////////////////////////// ///// /// ///// ***ValidatorShare API*** /// ///// /// //////////////////////////////////////////////////////////// /// @notice Returns the stMaticWithdrawRequest list function getTotalWithdrawRequest() public view returns (RequestWithdraw[] memory) { return stMaticWithdrawRequest; } /// @notice API for delegated buying vouchers from validatorShare /// @param _validatorShare - Address of validatorShare contract /// @param _amount - Amount of MATIC to use for buying vouchers /// @param _minSharesToMint - Minimum of shares that is bought with _amount of MATIC /// @return Actual amount of MATIC used to buy voucher, might differ from _amount because of _minSharesToMint function buyVoucher( address _validatorShare, uint256 _amount, uint256 _minSharesToMint ) private returns (uint256) { uint256 amountSpent = IValidatorShare(_validatorShare).buyVoucher( _amount, _minSharesToMint ); return amountSpent; } /// @notice API for delegated unstaking and claiming tokens from validatorShare /// @param _validatorShare - Address of validatorShare contract /// @param _unbondNonce - Unbond nonce function unstakeClaimTokens_new( address _validatorShare, uint256 _unbondNonce ) private { IValidatorShare(_validatorShare).unstakeClaimTokens_new(_unbondNonce); } /// @notice API for delegated selling vouchers from validatorShare /// @param _validatorShare - Address of validatorShare contract /// @param _claimAmount - Amount of MATIC to claim /// @param _maximumSharesToBurn - Maximum amount of shares to burn function sellVoucher_new( address _validatorShare, uint256 _claimAmount, uint256 _maximumSharesToBurn ) private { IValidatorShare(_validatorShare).sellVoucher_new( _claimAmount, _maximumSharesToBurn ); } /// @notice API for getting total stake of this contract from validatorShare /// @param _validatorShare - Address of validatorShare contract /// @return Total stake of this contract and MATIC -> share exchange rate function getTotalStake(IValidatorShare _validatorShare) public view override returns (uint256, uint256) { return _validatorShare.getTotalStake(address(this)); } /// @notice API for liquid rewards of this contract from validatorShare /// @param _validatorShare - Address of validatorShare contract /// @return Liquid rewards of this contract function getLiquidRewards(IValidatorShare _validatorShare) external view override returns (uint256) { return _validatorShare.getLiquidRewards(address(this)); } //////////////////////////////////////////////////////////// ///// /// ///// ***Helpers & Utilities*** /// ///// /// //////////////////////////////////////////////////////////// /// @notice Helper function for that returns total pooled MATIC /// @return Total pooled MATIC function getTotalStakeAcrossAllValidators() public view override returns (uint256) { uint256 totalStake; INodeOperatorRegistry.ValidatorData[] memory nodeOperators = nodeOperatorRegistry.listWithdrawNodeOperators(); for (uint256 i = 0; i < nodeOperators.length; i++) { (uint256 currValidatorShare, ) = getTotalStake( IValidatorShare(nodeOperators[i].validatorShare) ); totalStake += currValidatorShare; } return totalStake; } /// @notice Function that calculates total pooled Matic /// @return Total pooled Matic function getTotalPooledMatic() public view override returns (uint256) { uint256 totalStaked = getTotalStakeAcrossAllValidators(); return _getTotalPooledMatic(totalStaked); } function _getTotalPooledMatic(uint256 _totalStaked) private view returns (uint256) { return _totalStaked + totalBuffered + calculatePendingBufferedTokens() - reservedFunds; } /// @notice Function that converts arbitrary stMATIC to Matic /// @param _amountInStMatic - Amount of stMATIC to convert to Matic /// @return amountInMatic - Amount of Matic after conversion, /// @return totalStMaticAmount - Total StMatic in the contract, /// @return totalPooledMatic - Total Matic in the staking pool function convertStMaticToMatic(uint256 _amountInStMatic) external view override returns ( uint256 amountInMatic, uint256 totalStMaticAmount, uint256 totalPooledMatic ) { totalStMaticAmount = totalSupply(); uint256 totalPooledMATIC = getTotalPooledMatic(); return ( _convertStMaticToMatic(_amountInStMatic, totalPooledMATIC), totalStMaticAmount, totalPooledMATIC ); } /// @notice Function that converts arbitrary amount of stMatic to Matic /// @param _stMaticAmount - amount of stMatic to convert to Matic /// @return amountInMatic, totalStMaticAmount and totalPooledMatic function _convertStMaticToMatic( uint256 _stMaticAmount, uint256 _totalPooledMatic ) private view returns (uint256) { uint256 totalStMaticSupply = totalSupply(); totalStMaticSupply = totalStMaticSupply == 0 ? 1 : totalStMaticSupply; _totalPooledMatic = _totalPooledMatic == 0 ? 1 : _totalPooledMatic; uint256 amountInMatic = (_stMaticAmount * _totalPooledMatic) / totalStMaticSupply; return amountInMatic; } /// @notice Function that converts arbitrary Matic to stMATIC /// @param _amountInMatic - Amount of Matic to convert to stMatic /// @return amountInStMatic - Amount of Matic to converted to stMatic /// @return totalStMaticSupply - Total amount of StMatic in the contract /// @return totalPooledMatic - Total amount of Matic in the staking pool function convertMaticToStMatic(uint256 _amountInMatic) public view override returns ( uint256 amountInStMatic, uint256 totalStMaticSupply, uint256 totalPooledMatic ) { totalStMaticSupply = totalSupply(); totalPooledMatic = getTotalPooledMatic(); return ( _convertMaticToStMatic(_amountInMatic, totalPooledMatic), totalStMaticSupply, totalPooledMatic ); } function getToken2WithdrawRequests(uint256 _tokenId) external view returns (RequestWithdraw[] memory) { return token2WithdrawRequests[_tokenId]; } /// @notice Function that converts arbitrary amount of Matic to stMatic /// @param _maticAmount - Amount in Matic to convert to stMatic /// @return amountInStMatic , totalStMaticAmount and totalPooledMatic function _convertMaticToStMatic( uint256 _maticAmount, uint256 _totalPooledMatic ) private view returns (uint256) { uint256 totalStMaticSupply = totalSupply(); totalStMaticSupply = totalStMaticSupply == 0 ? 1 : totalStMaticSupply; _totalPooledMatic = _totalPooledMatic == 0 ? 1 : _totalPooledMatic; uint256 amountInStMatic = (_maticAmount * totalStMaticSupply) / _totalPooledMatic; return amountInStMatic; } //////////////////////////////////////////////////////////// ///// /// ///// ***Setters*** /// ///// /// //////////////////////////////////////////////////////////// /// @notice Function that sets entity fees /// @notice Callable only by dao /// @param _daoFee - DAO fee in % /// @param _operatorsFee - Operator fees in % /// @param _insuranceFee - Insurance fee in % function setFees( uint8 _daoFee, uint8 _operatorsFee, uint8 _insuranceFee ) external override onlyRole(DAO) { _require( _daoFee + _operatorsFee + _insuranceFee == 100, "sum(fee)!=100" ); entityFees.dao = _daoFee; entityFees.operators = _operatorsFee; entityFees.insurance = _insuranceFee; emit SetFees(_daoFee, _operatorsFee, _insuranceFee); } /// @notice Function that sets protocol fee /// @param _newProtocolFee new protocol fee function setProtocolFee(uint8 _newProtocolFee) external override onlyRole(DAO) { _require( _newProtocolFee > 0 && _newProtocolFee <= 100, "Invalid protcol fee" ); uint8 oldProtocolFee = protocolFee; protocolFee = _newProtocolFee; emit SetProtocolFee(oldProtocolFee, _newProtocolFee); } /// @notice Function that sets new dao address /// @notice Callable only by dao /// @param _newDAO - New dao address function setDaoAddress(address _newDAO) external override onlyRole(DAO) { address oldDAO = dao; dao = _newDAO; emit SetDaoAddress(oldDAO, _newDAO); } /// @notice Function that sets new insurance address /// @notice Callable only by dao /// @param _address - New insurance address function setInsuranceAddress(address _address) external override onlyRole(DAO) { insurance = _address; emit SetInsuranceAddress(_address); } /// @notice Function that sets new node operator address /// @notice Only callable by dao /// @param _address - New node operator address function setNodeOperatorRegistryAddress(address _address) external override onlyRole(DAO) { nodeOperatorRegistry = INodeOperatorRegistry(_address); emit SetNodeOperatorRegistryAddress(_address); } /// @notice Function that sets new lower bound for delegation /// @notice Only callable by dao /// @param _delegationLowerBound - New lower bound for delegation function setDelegationLowerBound(uint256 _delegationLowerBound) external override onlyRole(DAO) { delegationLowerBound = _delegationLowerBound; emit SetDelegationLowerBound(_delegationLowerBound); } /// @notice Function that sets new lower bound for rewards distribution /// @notice Only callable by dao /// @param _newRewardDistributionLowerBound - New lower bound for rewards distribution function setRewardDistributionLowerBound( uint256 _newRewardDistributionLowerBound ) external override onlyRole(DAO) { uint256 oldRewardDistributionLowerBound = rewardDistributionLowerBound; rewardDistributionLowerBound = _newRewardDistributionLowerBound; emit SetRewardDistributionLowerBound( oldRewardDistributionLowerBound, _newRewardDistributionLowerBound ); } /// @notice Function that sets the poLidoNFT address /// @param _newLidoNFT new poLidoNFT address function setPoLidoNFT(address _newLidoNFT) external override onlyRole(DAO) { address oldPoLidoNFT = address(poLidoNFT); poLidoNFT = IPoLidoNFT(_newLidoNFT); emit SetLidoNFT(oldPoLidoNFT, _newLidoNFT); } /// @notice Function that sets the fxStateRootTunnel address /// @param _newFxStateRootTunnel address of fxStateRootTunnel function setFxStateRootTunnel(address _newFxStateRootTunnel) external override onlyRole(DAO) { address oldFxStateRootTunnel = address(fxStateRootTunnel); fxStateRootTunnel = IFxStateRootTunnel(_newFxStateRootTunnel); emit SetFxStateRootTunnel(oldFxStateRootTunnel, _newFxStateRootTunnel); } /// @notice Function that sets the new version /// @param _newVersion - New version that will be set function setVersion(string calldata _newVersion) external override onlyRole(DAO) { emit Version(version, _newVersion); version = _newVersion; } /// @notice Function that retrieves the amount of matic that will be claimed from the NFT token /// @param _tokenId - Id of the PolidoNFT function getMaticFromTokenId(uint256 _tokenId) external view override returns (uint256) { if (token2WithdrawRequest[_tokenId].requestEpoch != 0) { return _getMaticFromRequestData(token2WithdrawRequest[_tokenId]); } else if (token2WithdrawRequests[_tokenId].length != 0) { RequestWithdraw[] memory requestsData = token2WithdrawRequests[ _tokenId ]; uint256 totalMatic; for (uint256 idx = 0; idx < requestsData.length; idx++) { totalMatic += _getMaticFromRequestData(requestsData[idx]); } return totalMatic; } return 0; } function _getMaticFromRequestData(RequestWithdraw memory requestData) private view returns (uint256) { if (requestData.validatorAddress == address(0)) { return requestData.amount2WithdrawFromStMATIC; } IValidatorShare validatorShare = IValidatorShare( requestData.validatorAddress ); uint256 exchangeRatePrecision = _getExchangeRatePrecision( validatorShare.validatorId() ); uint256 withdrawExchangeRate = validatorShare.withdrawExchangeRate(); IValidatorShare.DelegatorUnbond memory unbond = validatorShare .unbonds_new(address(this), requestData.validatorNonce); return (withdrawExchangeRate * unbond.shares) / exchangeRatePrecision; } function _nonReentrant() private view { _require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); } function _require(bool _condition, string memory _message) private pure { require(_condition, _message); } /// @dev get the exchange rate precision per validator. /// More details: https://github.com/maticnetwork/contracts/blob/v0.3.0-backport/contracts/staking/validatorShare/ValidatorShare.sol#L21 /// https://github.com/maticnetwork/contracts/blob/v0.3.0-backport/contracts/staking/validatorShare/ValidatorShare.sol#L87 function _getExchangeRatePrecision(uint256 _validatorId) private pure returns (uint256) { return _validatorId < 8 ? 100 : 10**29; } /// @dev calculate the number of shares to get when delegate an amount of Matic function _calculateValidatorShares( address _validatorAddress, uint256 _amountInMatic ) private view returns (uint256) { IValidatorShare validatorShare = IValidatorShare(_validatorAddress); uint256 exchangeRatePrecision = _getExchangeRatePrecision( validatorShare.validatorId() ); uint256 rate = validatorShare.exchangeRate(); return (_amountInMatic * exchangeRatePrecision) / rate; } /// @dev call fxStateRootTunnel to update L2. function _bridge(uint256 _totalSupply, uint256 _totalPooledMatic) private { fxStateRootTunnel.sendMessageToChild(abi.encode(_totalSupply, _totalPooledMatic)); } function min(uint256 _valueA, uint256 _valueB) private pure returns(uint256) { return _valueA > _valueB ? _valueB : _valueA; } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol) pragma solidity ^0.8.0; import "./IERC20Upgradeable.sol"; import "./extensions/IERC20MetadataUpgradeable.sol"; import "../../utils/ContextUpgradeable.sol"; import "../../proxy/utils/Initializable.sol"; /** * @dev Implementation of the {IERC20} interface. * * This implementation is agnostic to the way tokens are created. This means * that a supply mechanism has to be added in a derived contract using {_mint}. * For a generic mechanism see {ERC20PresetMinterPauser}. * * TIP: For a detailed writeup see our guide * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How * to implement supply mechanisms]. * * We have followed general OpenZeppelin Contracts guidelines: functions revert * instead returning `false` on failure. This behavior is nonetheless * conventional and does not conflict with the expectations of ERC20 * applications. * * Additionally, an {Approval} event is emitted on calls to {transferFrom}. * This allows applications to reconstruct the allowance for all accounts just * by listening to said events. Other implementations of the EIP may not emit * these events, as it isn't required by the specification. * * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} * functions have been added to mitigate the well-known issues around setting * allowances. See {IERC20-approve}. */ contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20Upgradeable, IERC20MetadataUpgradeable { mapping(address => uint256) private _balances; mapping(address => mapping(address => uint256)) private _allowances; uint256 private _totalSupply; string private _name; string private _symbol; /** * @dev Sets the values for {name} and {symbol}. * * The default value of {decimals} is 18. To select a different value for * {decimals} you should overload it. * * All two of these values are immutable: they can only be set once during * construction. */ function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing { __ERC20_init_unchained(name_, symbol_); } function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing { _name = name_; _symbol = symbol_; } /** * @dev Returns the name of the token. */ function name() public view virtual override returns (string memory) { return _name; } /** * @dev Returns the symbol of the token, usually a shorter version of the * name. */ function symbol() public view virtual override returns (string memory) { return _symbol; } /** * @dev Returns the number of decimals used to get its user representation. * For example, if `decimals` equals `2`, a balance of `505` tokens should * be displayed to a user as `5.05` (`505 / 10 ** 2`). * * Tokens usually opt for a value of 18, imitating the relationship between * Ether and Wei. This is the value {ERC20} uses, unless this function is * overridden; * * NOTE: This information is only used for _display_ purposes: it in * no way affects any of the arithmetic of the contract, including * {IERC20-balanceOf} and {IERC20-transfer}. */ function decimals() public view virtual override returns (uint8) { return 18; } /** * @dev See {IERC20-totalSupply}. */ function totalSupply() public view virtual override returns (uint256) { return _totalSupply; } /** * @dev See {IERC20-balanceOf}. */ function balanceOf(address account) public view virtual override returns (uint256) { return _balances[account]; } /** * @dev See {IERC20-transfer}. * * Requirements: * * - `to` cannot be the zero address. * - the caller must have a balance of at least `amount`. */ function transfer(address to, uint256 amount) public virtual override returns (bool) { address owner = _msgSender(); _transfer(owner, to, amount); return true; } /** * @dev See {IERC20-allowance}. */ function allowance(address owner, address spender) public view virtual override returns (uint256) { return _allowances[owner][spender]; } /** * @dev See {IERC20-approve}. * * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on * `transferFrom`. This is semantically equivalent to an infinite approval. * * Requirements: * * - `spender` cannot be the zero address. */ function approve(address spender, uint256 amount) public virtual override returns (bool) { address owner = _msgSender(); _approve(owner, spender, amount); return true; } /** * @dev See {IERC20-transferFrom}. * * Emits an {Approval} event indicating the updated allowance. This is not * required by the EIP. See the note at the beginning of {ERC20}. * * NOTE: Does not update the allowance if the current allowance * is the maximum `uint256`. * * Requirements: * * - `from` and `to` cannot be the zero address. * - `from` must have a balance of at least `amount`. * - the caller must have allowance for ``from``'s tokens of at least * `amount`. */ function transferFrom( address from, address to, uint256 amount ) public virtual override returns (bool) { address spender = _msgSender(); _spendAllowance(from, spender, amount); _transfer(from, to, amount); return true; } /** * @dev Atomically increases the allowance granted to `spender` by the caller. * * This is an alternative to {approve} that can be used as a mitigation for * problems described in {IERC20-approve}. * * Emits an {Approval} event indicating the updated allowance. * * Requirements: * * - `spender` cannot be the zero address. */ function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { address owner = _msgSender(); _approve(owner, spender, _allowances[owner][spender] + addedValue); return true; } /** * @dev Atomically decreases the allowance granted to `spender` by the caller. * * This is an alternative to {approve} that can be used as a mitigation for * problems described in {IERC20-approve}. * * Emits an {Approval} event indicating the updated allowance. * * Requirements: * * - `spender` cannot be the zero address. * - `spender` must have allowance for the caller of at least * `subtractedValue`. */ function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { address owner = _msgSender(); uint256 currentAllowance = _allowances[owner][spender]; require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); unchecked { _approve(owner, spender, currentAllowance - subtractedValue); } return true; } /** * @dev Moves `amount` of tokens from `sender` to `recipient`. * * This internal function is equivalent to {transfer}, and can be used to * e.g. implement automatic token fees, slashing mechanisms, etc. * * Emits a {Transfer} event. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `from` must have a balance of at least `amount`. */ function _transfer( address from, address to, uint256 amount ) internal virtual { require(from != address(0), "ERC20: transfer from the zero address"); require(to != address(0), "ERC20: transfer to the zero address"); _beforeTokenTransfer(from, to, amount); uint256 fromBalance = _balances[from]; require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); unchecked { _balances[from] = fromBalance - amount; } _balances[to] += amount; emit Transfer(from, to, amount); _afterTokenTransfer(from, to, amount); } /** @dev Creates `amount` tokens and assigns them to `account`, increasing * the total supply. * * Emits a {Transfer} event with `from` set to the zero address. * * Requirements: * * - `account` cannot be the zero address. */ function _mint(address account, uint256 amount) internal virtual { require(account != address(0), "ERC20: mint to the zero address"); _beforeTokenTransfer(address(0), account, amount); _totalSupply += amount; _balances[account] += amount; emit Transfer(address(0), account, amount); _afterTokenTransfer(address(0), account, amount); } /** * @dev Destroys `amount` tokens from `account`, reducing the * total supply. * * Emits a {Transfer} event with `to` set to the zero address. * * Requirements: * * - `account` cannot be the zero address. * - `account` must have at least `amount` tokens. */ function _burn(address account, uint256 amount) internal virtual { require(account != address(0), "ERC20: burn from the zero address"); _beforeTokenTransfer(account, address(0), amount); uint256 accountBalance = _balances[account]; require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); unchecked { _balances[account] = accountBalance - amount; } _totalSupply -= amount; emit Transfer(account, address(0), amount); _afterTokenTransfer(account, address(0), amount); } /** * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. * * This internal function is equivalent to `approve`, and can be used to * e.g. set automatic allowances for certain subsystems, etc. * * Emits an {Approval} event. * * Requirements: * * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. */ function _approve( address owner, address spender, uint256 amount ) internal virtual { require(owner != address(0), "ERC20: approve from the zero address"); require(spender != address(0), "ERC20: approve to the zero address"); _allowances[owner][spender] = amount; emit Approval(owner, spender, amount); } /** * @dev Spend `amount` form the allowance of `owner` toward `spender`. * * Does not update the allowance amount in case of infinite allowance. * Revert if not enough allowance is available. * * Might emit an {Approval} event. */ function _spendAllowance( address owner, address spender, uint256 amount ) internal virtual { uint256 currentAllowance = allowance(owner, spender); if (currentAllowance != type(uint256).max) { require(currentAllowance >= amount, "ERC20: insufficient allowance"); unchecked { _approve(owner, spender, currentAllowance - amount); } } } /** * @dev Hook that is called before any transfer of tokens. This includes * minting and burning. * * Calling conditions: * * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens * will be transferred to `to`. * - when `from` is zero, `amount` tokens will be minted for `to`. * - when `to` is zero, `amount` of ``from``'s tokens will be burned. * - `from` and `to` are never both zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _beforeTokenTransfer( address from, address to, uint256 amount ) internal virtual {} /** * @dev Hook that is called after any transfer of tokens. This includes * minting and burning. * * Calling conditions: * * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens * has been transferred to `to`. * - when `from` is zero, `amount` tokens have been minted for `to`. * - when `to` is zero, `amount` of ``from``'s tokens have been burned. * - `from` and `to` are never both zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _afterTokenTransfer( address from, address to, uint256 amount ) internal virtual {} /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[45] private __gap; } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControl.sol) pragma solidity ^0.8.0; import "./IAccessControlUpgradeable.sol"; import "../utils/ContextUpgradeable.sol"; import "../utils/StringsUpgradeable.sol"; import "../utils/introspection/ERC165Upgradeable.sol"; import "../proxy/utils/Initializable.sol"; /** * @dev Contract module that allows children to implement role-based access * control mechanisms. This is a lightweight version that doesn't allow enumerating role * members except through off-chain means by accessing the contract event logs. Some * applications may benefit from on-chain enumerability, for those cases see * {AccessControlEnumerable}. * * Roles are referred to by their `bytes32` identifier. These should be exposed * in the external API and be unique. The best way to achieve this is by * using `public constant` hash digests: * * ``` * bytes32 public constant MY_ROLE = keccak256("MY_ROLE"); * ``` * * Roles can be used to represent a set of permissions. To restrict access to a * function call, use {hasRole}: * * ``` * function foo() public { * require(hasRole(MY_ROLE, msg.sender)); * ... * } * ``` * * Roles can be granted and revoked dynamically via the {grantRole} and * {revokeRole} functions. Each role has an associated admin role, and only * accounts that have a role's admin role can call {grantRole} and {revokeRole}. * * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means * that only accounts with this role will be able to grant or revoke other * roles. More complex role relationships can be created by using * {_setRoleAdmin}. * * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to * grant and revoke this role. Extra precautions should be taken to secure * accounts that have been granted it. */ abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControlUpgradeable, ERC165Upgradeable { function __AccessControl_init() internal onlyInitializing { } function __AccessControl_init_unchained() internal onlyInitializing { } struct RoleData { mapping(address => bool) members; bytes32 adminRole; } mapping(bytes32 => RoleData) private _roles; bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; /** * @dev Modifier that checks that an account has a specific role. Reverts * with a standardized message including the required role. * * The format of the revert reason is given by the following regular expression: * * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/ * * _Available since v4.1._ */ modifier onlyRole(bytes32 role) { _checkRole(role, _msgSender()); _; } /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { return interfaceId == type(IAccessControlUpgradeable).interfaceId || super.supportsInterface(interfaceId); } /** * @dev Returns `true` if `account` has been granted `role`. */ function hasRole(bytes32 role, address account) public view virtual override returns (bool) { return _roles[role].members[account]; } /** * @dev Revert with a standard message if `account` is missing `role`. * * The format of the revert reason is given by the following regular expression: * * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/ */ function _checkRole(bytes32 role, address account) internal view virtual { if (!hasRole(role, account)) { revert( string( abi.encodePacked( "AccessControl: account ", StringsUpgradeable.toHexString(uint160(account), 20), " is missing role ", StringsUpgradeable.toHexString(uint256(role), 32) ) ) ); } } /** * @dev Returns the admin role that controls `role`. See {grantRole} and * {revokeRole}. * * To change a role's admin, use {_setRoleAdmin}. */ function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) { return _roles[role].adminRole; } /** * @dev Grants `role` to `account`. * * If `account` had not been already granted `role`, emits a {RoleGranted} * event. * * Requirements: * * - the caller must have ``role``'s admin role. */ function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) { _grantRole(role, account); } /** * @dev Revokes `role` from `account`. * * If `account` had been granted `role`, emits a {RoleRevoked} event. * * Requirements: * * - the caller must have ``role``'s admin role. */ function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) { _revokeRole(role, account); } /** * @dev Revokes `role` from the calling account. * * Roles are often managed via {grantRole} and {revokeRole}: this function's * purpose is to provide a mechanism for accounts to lose their privileges * if they are compromised (such as when a trusted device is misplaced). * * If the calling account had been revoked `role`, emits a {RoleRevoked} * event. * * Requirements: * * - the caller must be `account`. */ function renounceRole(bytes32 role, address account) public virtual override { require(account == _msgSender(), "AccessControl: can only renounce roles for self"); _revokeRole(role, account); } /** * @dev Grants `role` to `account`. * * If `account` had not been already granted `role`, emits a {RoleGranted} * event. Note that unlike {grantRole}, this function doesn't perform any * checks on the calling account. * * [WARNING] * ==== * This function should only be called from the constructor when setting * up the initial roles for the system. * * Using this function in any other way is effectively circumventing the admin * system imposed by {AccessControl}. * ==== * * NOTE: This function is deprecated in favor of {_grantRole}. */ function _setupRole(bytes32 role, address account) internal virtual { _grantRole(role, account); } /** * @dev Sets `adminRole` as ``role``'s admin role. * * Emits a {RoleAdminChanged} event. */ function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual { bytes32 previousAdminRole = getRoleAdmin(role); _roles[role].adminRole = adminRole; emit RoleAdminChanged(role, previousAdminRole, adminRole); } /** * @dev Grants `role` to `account`. * * Internal function without access restriction. */ function _grantRole(bytes32 role, address account) internal virtual { if (!hasRole(role, account)) { _roles[role].members[account] = true; emit RoleGranted(role, account, _msgSender()); } } /** * @dev Revokes `role` from `account`. * * Internal function without access restriction. */ function _revokeRole(bytes32 role, address account) internal virtual { if (hasRole(role, account)) { _roles[role].members[account] = false; emit RoleRevoked(role, account, _msgSender()); } } /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[49] private __gap; } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20Upgradeable { /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address to, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `from` to `to` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom( address from, address to, uint256 amount ) external returns (bool); /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol) pragma solidity ^0.8.0; import "../IERC20Upgradeable.sol"; import "../../../utils/AddressUpgradeable.sol"; /** * @title SafeERC20 * @dev Wrappers around ERC20 operations that throw on failure (when the token * contract returns false). Tokens that return no value (and instead revert or * throw on failure) are also supported, non-reverting calls are assumed to be * successful. * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20Upgradeable { using AddressUpgradeable for address; function safeTransfer( IERC20Upgradeable token, address to, uint256 value ) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); } function safeTransferFrom( IERC20Upgradeable token, address from, address to, uint256 value ) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } /** * @dev Deprecated. This function has issues similar to the ones found in * {IERC20-approve}, and its usage is discouraged. * * Whenever possible, use {safeIncreaseAllowance} and * {safeDecreaseAllowance} instead. */ function safeApprove( IERC20Upgradeable token, address spender, uint256 value ) internal { // safeApprove should only be called when setting an initial allowance, // or when resetting it to zero. To increase and decrease it, use // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' require( (value == 0) || (token.allowance(address(this), spender) == 0), "SafeERC20: approve from non-zero to non-zero allowance" ); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); } function safeIncreaseAllowance( IERC20Upgradeable token, address spender, uint256 value ) internal { uint256 newAllowance = token.allowance(address(this), spender) + value; _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } function safeDecreaseAllowance( IERC20Upgradeable token, address spender, uint256 value ) internal { unchecked { uint256 oldAllowance = token.allowance(address(this), spender); require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); uint256 newAllowance = oldAllowance - value; _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). */ function _callOptionalReturn(IERC20Upgradeable token, bytes memory data) private { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that // the target address contains contract code and also asserts for success in the low-level call. bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); if (returndata.length > 0) { // Return data is optional require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); } } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (security/Pausable.sol) pragma solidity ^0.8.0; import "../utils/ContextUpgradeable.sol"; import "../proxy/utils/Initializable.sol"; /** * @dev Contract module which allows children to implement an emergency stop * mechanism that can be triggered by an authorized account. * * This module is used through inheritance. It will make available the * modifiers `whenNotPaused` and `whenPaused`, which can be applied to * the functions of your contract. Note that they will not be pausable by * simply including this module, only once the modifiers are put in place. */ abstract contract PausableUpgradeable is Initializable, ContextUpgradeable { /** * @dev Emitted when the pause is triggered by `account`. */ event Paused(address account); /** * @dev Emitted when the pause is lifted by `account`. */ event Unpaused(address account); bool private _paused; /** * @dev Initializes the contract in unpaused state. */ function __Pausable_init() internal onlyInitializing { __Pausable_init_unchained(); } function __Pausable_init_unchained() internal onlyInitializing { _paused = false; } /** * @dev Returns true if the contract is paused, and false otherwise. */ function paused() public view virtual returns (bool) { return _paused; } /** * @dev Modifier to make a function callable only when the contract is not paused. * * Requirements: * * - The contract must not be paused. */ modifier whenNotPaused() { require(!paused(), "Pausable: paused"); _; } /** * @dev Modifier to make a function callable only when the contract is paused. * * Requirements: * * - The contract must be paused. */ modifier whenPaused() { require(paused(), "Pausable: not paused"); _; } /** * @dev Triggers stopped state. * * Requirements: * * - The contract must not be paused. */ function _pause() internal virtual whenNotPaused { _paused = true; emit Paused(_msgSender()); } /** * @dev Returns to normal state. * * Requirements: * * - The contract must be paused. */ function _unpause() internal virtual whenPaused { _paused = false; emit Unpaused(_msgSender()); } /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[49] private __gap; } // SPDX-FileCopyrightText: 2021 ShardLabs // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.7; /// @title Polygon validator share interface. /// @dev https://github.com/maticnetwork/contracts/blob/v0.3.0-backport/contracts/staking/validatorShare/ValidatorShare.sol /// @author 2021 ShardLabs interface IValidatorShare { struct DelegatorUnbond { uint256 shares; uint256 withdrawEpoch; } function unbondNonces(address _address) external view returns (uint256); function activeAmount() external view returns (uint256); function validatorId() external view returns (uint256); function withdrawExchangeRate() external view returns (uint256); function withdrawRewards() external; function unstakeClaimTokens() external; function minAmount() external view returns (uint256); function getLiquidRewards(address user) external view returns (uint256); function delegation() external view returns (bool); function updateDelegation(bool _delegation) external; function buyVoucher(uint256 _amount, uint256 _minSharesToMint) external returns (uint256); function sellVoucher_new(uint256 claimAmount, uint256 maximumSharesToBurn) external; function unstakeClaimTokens_new(uint256 unbondNonce) external; function unbonds_new(address _address, uint256 _unbondNonce) external view returns (DelegatorUnbond memory); function getTotalStake(address user) external view returns (uint256, uint256); function owner() external view returns (address); function restake() external returns (uint256, uint256); function unlock() external; function lock() external; function drain( address token, address payable destination, uint256 amount ) external; function slash(uint256 _amount) external; function migrateOut(address user, uint256 amount) external; function migrateIn(address user, uint256 amount) external; function exchangeRate() external view returns (uint256); } // SPDX-FileCopyrightText: 2021 ShardLabs // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.7; /// @title INodeOperatorRegistry /// @author 2021 ShardLabs /// @notice Node operator registry interface interface INodeOperatorRegistry { /// @notice Node Operator Registry Statuses /// StakeManager statuses: https://github.com/maticnetwork/contracts/blob/v0.3.0-backport/contracts/staking/stakeManager/StakeManagerStorage.sol#L13 /// ACTIVE: (validator.status == status.Active && validator.deactivationEpoch == 0) /// JAILED: (validator.status == status.Locked && validator.deactivationEpoch == 0) /// EJECTED: ((validator.status == status.Active || validator.status == status.Locked) && validator.deactivationEpoch != 0) /// UNSTAKED: (validator.status == status.Unstaked) enum NodeOperatorRegistryStatus { INACTIVE, ACTIVE, JAILED, EJECTED, UNSTAKED } /// @notice The full node operator struct. /// @param validatorId the validator id on stakeManager. /// @param commissionRate rate of each operator /// @param validatorShare the validator share address of the validator. /// @param rewardAddress the reward address. /// @param delegation delegation. /// @param status the status of the node operator in the stake manager. struct FullNodeOperatorRegistry { uint256 validatorId; uint256 commissionRate; address validatorShare; address rewardAddress; bool delegation; NodeOperatorRegistryStatus status; } /// @notice The node operator struct /// @param validatorShare the validator share address of the validator. /// @param rewardAddress the reward address. struct ValidatorData { address validatorShare; address rewardAddress; } /// @notice Add a new node operator to the system. /// ONLY DAO can execute this function. /// @param validatorId the validator id on stakeManager. /// @param rewardAddress the reward address. function addNodeOperator(uint256 validatorId, address rewardAddress) external; /// @notice Exit the node operator registry /// ONLY the owner of the node operator can call this function function exitNodeOperatorRegistry() external; /// @notice Remove a node operator from the system and withdraw total delegated tokens to it. /// ONLY DAO can execute this function. /// withdraw delegated tokens from it. /// @param validatorId the validator id on stakeManager. function removeNodeOperator(uint256 validatorId) external; /// @notice Remove a node operator from the system if it fails to meet certain conditions. /// 1. If the commission of the Node Operator is less than the standard commission. /// 2. If the Node Operator is either Unstaked or Ejected. /// @param validatorId the validator id on stakeManager. function removeInvalidNodeOperator(uint256 validatorId) external; /// @notice Set StMatic address. /// ONLY DAO can call this function /// @param newStMatic new stMatic address. function setStMaticAddress(address newStMatic) external; /// @notice Update reward address of a Node Operator. /// ONLY Operator owner can call this function /// @param newRewardAddress the new reward address. function setRewardAddress(address newRewardAddress) external; /// @notice set DISTANCETHRESHOLD /// ONLY DAO can call this function /// @param distanceThreshold the min rebalance threshold to include /// a validator in the delegation process. function setDistanceThreshold(uint256 distanceThreshold) external; /// @notice set MINREQUESTWITHDRAWRANGE /// ONLY DAO can call this function /// @param minRequestWithdrawRange the min request withdraw range. function setMinRequestWithdrawRange(uint8 minRequestWithdrawRange) external; /// @notice set MAXWITHDRAWPERCENTAGEPERREBALANCE /// ONLY DAO can call this function /// @param maxWithdrawPercentagePerRebalance the max withdraw percentage to /// withdraw from a validator per rebalance. function setMaxWithdrawPercentagePerRebalance( uint256 maxWithdrawPercentagePerRebalance ) external; /// @notice Allows to set new version. /// @param _newVersion new contract version. function setVersion(string memory _newVersion) external; /// @notice List all the ACTIVE operators on the stakeManager. /// @return activeNodeOperators a list of ACTIVE node operator. function listDelegatedNodeOperators() external view returns (ValidatorData[] memory); /// @notice List all the operators on the stakeManager that can be withdrawn from this includes ACTIVE, JAILED, and /// @notice UNSTAKED operators. /// @return nodeOperators a list of ACTIVE, JAILED or UNSTAKED node operator. function listWithdrawNodeOperators() external view returns (ValidatorData[] memory); /// @notice Calculate how total buffered should be delegated between the active validators, /// depending on if the system is balanced or not. If validators are in EJECTED or UNSTAKED /// status the function will revert. /// @param amountToDelegate The total that can be delegated. /// @return validators all active node operators. /// @return operatorRatiosToDelegate a list of operator's ratio used to calculate the amount to delegate per node. /// @return totalRatio the total ratio. If ZERO that means the system is balanced. /// It will be calculated if the system is not balanced. function getValidatorsDelegationAmount(uint256 amountToDelegate) external view returns ( ValidatorData[] memory validators, uint256[] memory operatorRatiosToDelegate, uint256 totalRatio ); /// @notice Calculate how the system could be rebalanced depending on the current /// buffered tokens. If validators are in EJECTED or UNSTAKED status the function will revert. /// If the system is balanced the function will revert. /// @notice Calculate the operator ratios to rebalance the system. /// @param totalBuffered The total amount buffered in stMatic. /// @return validators all active node operators. /// @return operatorRatiosToRebalance a list of operator's ratio used to calculate the amount to withdraw per node. /// @return totalRatio the total ratio. If ZERO that means the system is balanced. /// @return totalToWithdraw the total amount to withdraw. function getValidatorsRebalanceAmount(uint256 totalBuffered) external view returns ( ValidatorData[] memory validators, uint256[] memory operatorRatiosToRebalance, uint256 totalRatio, uint256 totalToWithdraw ); /// @notice Calculate the validators to request withdrawal from depending if the system is balalnced or not. /// @param _withdrawAmount The amount to withdraw. /// @return validators all node operators. /// @return totalDelegated total amount delegated. /// @return bigNodeOperatorIds stores the ids of node operators that amount delegated to it is greater than the average delegation. /// @return smallNodeOperatorIds stores the ids of node operators that amount delegated to it is less than the average delegation. /// @return operatorAmountCanBeRequested amount that can be requested from a spécific validator when the system is not balanced. /// @return totalValidatorToWithdrawFrom the number of validator to withdraw from when the system is balanced. function getValidatorsRequestWithdraw(uint256 _withdrawAmount) external view returns ( ValidatorData[] memory validators, uint256 totalDelegated, uint256[] memory bigNodeOperatorIds, uint256[] memory smallNodeOperatorIds, uint256[] memory operatorAmountCanBeRequested, uint256 totalValidatorToWithdrawFrom ); /// @notice Returns a node operator. /// @param validatorId the validator id on stakeManager. /// @return operatorStatus a node operator. function getNodeOperator(uint256 validatorId) external view returns (FullNodeOperatorRegistry memory operatorStatus); /// @notice Returns a node operator. /// @param rewardAddress the reward address. /// @return operatorStatus a node operator. function getNodeOperator(address rewardAddress) external view returns (FullNodeOperatorRegistry memory operatorStatus); /// @notice Returns a node operator status. /// @param validatorId is the id of the node operator. /// @return operatorStatus Returns a node operator status. function getNodeOperatorStatus(uint256 validatorId) external view returns (NodeOperatorRegistryStatus operatorStatus); /// @notice Return a list of all validator ids in the system. function getValidatorIds() external view returns (uint256[] memory); /// @notice Explain to an end user what this does /// @return isBalanced if the system is balanced or not. /// @return distanceThreshold the distance threshold /// @return minAmount min amount delegated to a validator. /// @return maxAmount max amount delegated to a validator. function getProtocolStats() external view returns ( bool isBalanced, uint256 distanceThreshold, uint256 minAmount, uint256 maxAmount ); /// @notice List all the node operator statuses in the system. /// @return inactiveNodeOperator the number of inactive operators. /// @return activeNodeOperator the number of active operators. /// @return jailedNodeOperator the number of jailed operators. /// @return ejectedNodeOperator the number of ejected operators. /// @return unstakedNodeOperator the number of unstaked operators. function getStats() external view returns ( uint256 inactiveNodeOperator, uint256 activeNodeOperator, uint256 jailedNodeOperator, uint256 ejectedNodeOperator, uint256 unstakedNodeOperator ); //////////////////////////////////////////////////////////// ///// /// ///// ***EVENTS*** /// ///// /// //////////////////////////////////////////////////////////// /// @notice Add Node Operator event /// @param validatorId validator id. /// @param rewardAddress reward address. event AddNodeOperator(uint256 validatorId, address rewardAddress); /// @notice Remove Node Operator event. /// @param validatorId validator id. /// @param rewardAddress reward address. event RemoveNodeOperator(uint256 validatorId, address rewardAddress); /// @notice Remove Invalid Node Operator event. /// @param validatorId validator id. /// @param rewardAddress reward address. event RemoveInvalidNodeOperator(uint256 validatorId, address rewardAddress); /// @notice Set StMatic address event. /// @param oldStMatic old stMatic address. /// @param newStMatic new stMatic address. event SetStMaticAddress(address oldStMatic, address newStMatic); /// @notice Set reward address event. /// @param validatorId the validator id. /// @param oldRewardAddress old reward address. /// @param newRewardAddress new reward address. event SetRewardAddress( uint256 validatorId, address oldRewardAddress, address newRewardAddress ); /// @notice Emit when the distance threshold is changed. /// @param oldDistanceThreshold the old distance threshold. /// @param newDistanceThreshold the new distance threshold. event SetDistanceThreshold( uint256 oldDistanceThreshold, uint256 newDistanceThreshold ); /// @notice Emit when the min request withdraw range is changed. /// @param oldMinRequestWithdrawRange the old min request withdraw range. /// @param newMinRequestWithdrawRange the new min request withdraw range. event SetMinRequestWithdrawRange( uint8 oldMinRequestWithdrawRange, uint8 newMinRequestWithdrawRange ); /// @notice Emit when the max withdraw percentage per rebalance is changed. /// @param oldMaxWithdrawPercentagePerRebalance the old max withdraw percentage per rebalance. /// @param newMaxWithdrawPercentagePerRebalance the new max withdraw percentage per rebalance. event SetMaxWithdrawPercentagePerRebalance( uint256 oldMaxWithdrawPercentagePerRebalance, uint256 newMaxWithdrawPercentagePerRebalance ); /// @notice Emit when set new version. /// @param oldVersion the old version. /// @param newVersion the new version. event SetVersion(string oldVersion, string newVersion); /// @notice Emit when the node operator exits the registry /// @param validatorId node operator id /// @param rewardAddress node operator reward address event ExitNodeOperator(uint256 validatorId, address rewardAddress); } // SPDX-FileCopyrightText: 2021 ShardLabs // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.7; /// @title polygon stake manager interface. /// @author 2021 ShardLabs interface IStakeManager { /// @dev Plygon stakeManager status and Validator struct /// https://github.com/maticnetwork/contracts/blob/v0.3.0-backport/contracts/staking/stakeManager/StakeManagerStorage.sol enum Status { Inactive, Active, Locked, Unstaked } struct Validator { uint256 amount; uint256 reward; uint256 activationEpoch; uint256 deactivationEpoch; uint256 jailTime; address signer; address contractAddress; Status status; uint256 commissionRate; uint256 lastCommissionUpdate; uint256 delegatorsReward; uint256 delegatedAmount; uint256 initialRewardPerStake; } /// @notice get the validator contract used for delegation. /// @param validatorId validator id. /// @return return the address of the validator contract. function getValidatorContract(uint256 validatorId) external view returns (address); /// @notice Transfers amount from delegator function delegationDeposit( uint256 validatorId, uint256 amount, address delegator ) external returns (bool); function epoch() external view returns (uint256); function validators(uint256 _index) external view returns (Validator memory); /// @notice Returns a withdrawal delay. function withdrawalDelay() external view returns (uint256); } // SPDX-FileCopyrightText: 2021 ShardLabs // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.7; import "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol"; /// @title PoLidoNFT interface. /// @author 2021 ShardLabs interface IPoLidoNFT is IERC721Upgradeable { /// @notice Mint a new Lido NFT for a _to address. /// @param _to owner of the NFT. /// @return tokenId returns the token id. function mint(address _to) external returns (uint256); /// @notice Burn a Lido NFT for a _to address. /// @param _tokenId the token id. function burn(uint256 _tokenId) external; /// @notice Check if the spender is the owner of the NFT or it was approved to it. /// @param _spender the spender address. /// @param _tokenId the token id. /// @return result return if the token is owned or approved to/by the spender. function isApprovedOrOwner(address _spender, uint256 _tokenId) external view returns (bool); /// @notice Set stMatic address. /// @param _stMATIC new stMatic address. function setStMATIC(address _stMATIC) external; /// @notice List all the tokens owned by an address. /// @param _owner the owner address. /// @return result return a list of token ids. function getOwnedTokens(address _owner) external view returns (uint256[] memory); /// @notice toggle pause/unpause the contract function togglePause() external; /// @notice Allows to set new version. /// @param _newVersion new contract version. function setVersion(string calldata _newVersion) external; } // SPDX-FileCopyrightText: 2021 ShardLabs // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.7; interface IFxStateRootTunnel { /// @notice send message to child /// @param _message message function sendMessageToChild(bytes memory _message) external; /// @notice Set stMatic address. /// @param _newStMATIC the new stMatic address. function setStMATIC(address _newStMATIC) external; } // SPDX-FileCopyrightText: 2021 ShardLabs // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.7; import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; import "./IValidatorShare.sol"; import "./INodeOperatorRegistry.sol"; import "./IStakeManager.sol"; import "./IPoLidoNFT.sol"; import "./IFxStateRootTunnel.sol"; /// @title StMATIC interface. /// @author 2021 ShardLabs interface IStMATIC is IERC20Upgradeable { /// @notice The request withdraw struct. /// @param amount2WithdrawFromStMATIC amount in Matic. /// @param validatorNonce validator nonce. /// @param requestEpoch request epoch. /// @param validatorAddress validator share address. struct RequestWithdraw { uint256 amount2WithdrawFromStMATIC; uint256 validatorNonce; uint256 requestEpoch; address validatorAddress; } /// @notice The fee distribution struct. /// @param dao dao fee. /// @param operators operators fee. /// @param insurance insurance fee. struct FeeDistribution { uint8 dao; uint8 operators; uint8 insurance; } /// @notice node operator registry interface. function nodeOperatorRegistry() external view returns (INodeOperatorRegistry); /// @notice The fee distribution. /// @return dao dao fee. /// @return operators operators fee. /// @return insurance insurance fee. function entityFees() external view returns ( uint8, uint8, uint8 ); /// @notice StakeManager interface. function stakeManager() external view returns (IStakeManager); /// @notice LidoNFT interface. function poLidoNFT() external view returns (IPoLidoNFT); /// @notice fxStateRootTunnel interface. function fxStateRootTunnel() external view returns (IFxStateRootTunnel); /// @notice contract version. function version() external view returns (string memory); /// @notice dao address. function dao() external view returns (address); /// @notice insurance address. function insurance() external view returns (address); /// @notice Matic ERC20 token. function token() external view returns (address); /// @notice Matic ERC20 token address NOT USED IN V2. function lastWithdrawnValidatorId() external view returns (uint256); /// @notice total buffered Matic in the contract. function totalBuffered() external view returns (uint256); /// @notice delegation lower bound. function delegationLowerBound() external view returns (uint256); /// @notice reward distribution lower bound. function rewardDistributionLowerBound() external view returns (uint256); /// @notice reserved funds in Matic. function reservedFunds() external view returns (uint256); /// @notice submit threshold NOT USED in V2. function submitThreshold() external view returns (uint256); /// @notice submit handler NOT USED in V2. function submitHandler() external view returns (bool); /// @notice token to WithdrawRequest mapping. function token2WithdrawRequest(uint256 _requestId) external view returns ( uint256, uint256, uint256, address ); /// @notice DAO Role. function DAO() external view returns (bytes32); /// @notice PAUSE_ROLE Role. function PAUSE_ROLE() external view returns (bytes32); /// @notice UNPAUSE_ROLE Role. function UNPAUSE_ROLE() external view returns (bytes32); /// @notice Protocol Fee. function protocolFee() external view returns (uint8); /// @param _nodeOperatorRegistry - Address of the node operator registry /// @param _token - Address of MATIC token on Ethereum Mainnet /// @param _dao - Address of the DAO /// @param _insurance - Address of the insurance /// @param _stakeManager - Address of the stake manager /// @param _poLidoNFT - Address of the stMATIC NFT /// @param _fxStateRootTunnel - Address of the FxStateRootTunnel function initialize( address _nodeOperatorRegistry, address _token, address _dao, address _insurance, address _stakeManager, address _poLidoNFT, address _fxStateRootTunnel ) external; /// @notice Send funds to StMATIC contract and mints StMATIC to msg.sender /// @notice Requires that msg.sender has approved _amount of MATIC to this contract /// @param _amount - Amount of MATIC sent from msg.sender to this contract /// @param _referral - referral address. /// @return Amount of StMATIC shares generated function submit(uint256 _amount, address _referral) external returns (uint256); /// @notice Stores users request to withdraw into a RequestWithdraw struct /// @param _amount - Amount of StMATIC that is requested to withdraw /// @param _referral - referral address. /// @return NFT token id. function requestWithdraw(uint256 _amount, address _referral) external returns (uint256); /// @notice This will be included in the cron job /// @notice Delegates tokens to validator share contract function delegate() external; /// @notice Claims tokens from validator share and sends them to the /// StMATIC contract /// @param _tokenId - Id of the token that is supposed to be claimed function claimTokens(uint256 _tokenId) external; /// @notice Distributes rewards claimed from validator shares based on fees defined /// in entityFee. function distributeRewards() external; /// @notice withdraw total delegated /// @param _validatorShare validator share address. function withdrawTotalDelegated(address _validatorShare) external; /// @notice Claims tokens from validator share and sends them to the /// StMATIC contract /// @param _tokenId - Id of the token that is supposed to be claimed function claimTokensFromValidatorToContract(uint256 _tokenId) external; /// @notice Rebalane the system by request withdraw from the validators that contains /// more token delegated to them. function rebalanceDelegatedTokens() external; /// @notice Helper function for that returns total pooled MATIC /// @return Total pooled MATIC function getTotalStake(IValidatorShare _validatorShare) external view returns (uint256, uint256); /// @notice API for liquid rewards of this contract from validatorShare /// @param _validatorShare - Address of validatorShare contract /// @return Liquid rewards of this contract function getLiquidRewards(IValidatorShare _validatorShare) external view returns (uint256); /// @notice Helper function for that returns total pooled MATIC /// @return Total pooled MATIC function getTotalStakeAcrossAllValidators() external view returns (uint256); /// @notice Function that calculates total pooled Matic /// @return Total pooled Matic function getTotalPooledMatic() external view returns (uint256); /// @notice get Matic from token id. /// @param _tokenId NFT token id. /// @return total the amount in Matic. function getMaticFromTokenId(uint256 _tokenId) external view returns (uint256); /// @notice calculate the total amount stored in all the NFTs owned by /// stMatic contract. /// @return pendingBufferedTokens the total pending amount for stMatic. function calculatePendingBufferedTokens() external view returns(uint256); /// @notice Function that converts arbitrary stMATIC to Matic /// @param _amountInStMatic - Amount of stMATIC to convert to Matic /// @return amountInMatic - Amount of Matic after conversion, /// @return totalStMaticAmount - Total StMatic in the contract, /// @return totalPooledMatic - Total Matic in the staking pool function convertStMaticToMatic(uint256 _amountInStMatic) external view returns ( uint256 amountInMatic, uint256 totalStMaticAmount, uint256 totalPooledMatic ); /// @notice Function that converts arbitrary Matic to stMATIC /// @param _amountInMatic - Amount of Matic to convert to stMatic /// @return amountInStMatic - Amount of Matic to converted to stMatic /// @return totalStMaticSupply - Total amount of StMatic in the contract /// @return totalPooledMatic - Total amount of Matic in the staking pool function convertMaticToStMatic(uint256 _amountInMatic) external view returns ( uint256 amountInStMatic, uint256 totalStMaticSupply, uint256 totalPooledMatic ); /// @notice Allows to set fees. /// @param _daoFee the new daoFee /// @param _operatorsFee the new operatorsFee /// @param _insuranceFee the new insuranceFee function setFees( uint8 _daoFee, uint8 _operatorsFee, uint8 _insuranceFee ) external; /// @notice Function that sets protocol fee /// @param _newProtocolFee - Insurance fee in % function setProtocolFee(uint8 _newProtocolFee) external; /// @notice Allows to set DaoAddress. /// @param _newDaoAddress new DaoAddress. function setDaoAddress(address _newDaoAddress) external; /// @notice Allows to set InsuranceAddress. /// @param _newInsuranceAddress new InsuranceAddress. function setInsuranceAddress(address _newInsuranceAddress) external; /// @notice Allows to set NodeOperatorRegistryAddress. /// @param _newNodeOperatorRegistry new NodeOperatorRegistryAddress. function setNodeOperatorRegistryAddress(address _newNodeOperatorRegistry) external; /// @notice Allows to set delegationLowerBound. /// @param _delegationLowerBound new delegationLowerBound. function setDelegationLowerBound(uint256 _delegationLowerBound) external; /// @notice Allows to set setRewardDistributionLowerBound. /// @param _rewardDistributionLowerBound new setRewardDistributionLowerBound. function setRewardDistributionLowerBound( uint256 _rewardDistributionLowerBound ) external; /// @notice Allows to set LidoNFT. /// @param _poLidoNFT new LidoNFT. function setPoLidoNFT(address _poLidoNFT) external; /// @notice Allows to set fxStateRootTunnel. /// @param _fxStateRootTunnel new fxStateRootTunnel. function setFxStateRootTunnel(address _fxStateRootTunnel) external; /// @notice Allows to set new version. /// @param _newVersion new contract version. function setVersion(string calldata _newVersion) external; //////////////////////////////////////////////////////////// ///// /// ///// ***EVENTS*** /// ///// /// //////////////////////////////////////////////////////////// /// @notice Emit when submit. /// @param _from msg.sender. /// @param _amount amount. /// @param _referral - referral address. event SubmitEvent(address indexed _from, uint256 _amount, address indexed _referral); /// @notice Emit when request withdraw. /// @param _from msg.sender. /// @param _amount amount. /// @param _referral - referral address. event RequestWithdrawEvent(address indexed _from, uint256 _amount, address indexed _referral); /// @notice Emit when distribute rewards. /// @param _amount amount. event DistributeRewardsEvent(uint256 indexed _amount); /// @notice Emit when withdraw total delegated. /// @param _from msg.sender. /// @param _amount amount. event WithdrawTotalDelegatedEvent( address indexed _from, uint256 indexed _amount ); /// @notice Emit when delegate. /// @param _amountDelegated amount to delegate. /// @param _remainder remainder. event DelegateEvent( uint256 indexed _amountDelegated, uint256 indexed _remainder ); /// @notice Emit when ClaimTokens. /// @param _from msg.sender. /// @param _id token id. /// @param _amountClaimed amount Claimed. /// @param _amountBurned amount Burned. event ClaimTokensEvent( address indexed _from, uint256 indexed _id, uint256 indexed _amountClaimed, uint256 _amountBurned ); /// @notice Emit when set new InsuranceAddress. /// @param _newInsuranceAddress the new InsuranceAddress. event SetInsuranceAddress(address indexed _newInsuranceAddress); /// @notice Emit when set new NodeOperatorRegistryAddress. /// @param _newNodeOperatorRegistryAddress the new NodeOperatorRegistryAddress. event SetNodeOperatorRegistryAddress( address indexed _newNodeOperatorRegistryAddress ); /// @notice Emit when set new SetDelegationLowerBound. /// @param _delegationLowerBound the old DelegationLowerBound. event SetDelegationLowerBound(uint256 indexed _delegationLowerBound); /// @notice Emit when set new RewardDistributionLowerBound. /// @param oldRewardDistributionLowerBound the old RewardDistributionLowerBound. /// @param newRewardDistributionLowerBound the new RewardDistributionLowerBound. event SetRewardDistributionLowerBound( uint256 oldRewardDistributionLowerBound, uint256 newRewardDistributionLowerBound ); /// @notice Emit when set new LidoNFT. /// @param oldLidoNFT the old oldLidoNFT. /// @param newLidoNFT the new newLidoNFT. event SetLidoNFT(address oldLidoNFT, address newLidoNFT); /// @notice Emit when set new FxStateRootTunnel. /// @param oldFxStateRootTunnel the old FxStateRootTunnel. /// @param newFxStateRootTunnel the new FxStateRootTunnel. event SetFxStateRootTunnel( address oldFxStateRootTunnel, address newFxStateRootTunnel ); /// @notice Emit when set new DAO. /// @param oldDaoAddress the old DAO. /// @param newDaoAddress the new DAO. event SetDaoAddress(address oldDaoAddress, address newDaoAddress); /// @notice Emit when set fees. /// @param daoFee the new daoFee /// @param operatorsFee the new operatorsFee /// @param insuranceFee the new insuranceFee event SetFees(uint256 daoFee, uint256 operatorsFee, uint256 insuranceFee); /// @notice Emit when set ProtocolFee. /// @param oldProtocolFee the new ProtocolFee /// @param newProtocolFee the new ProtocolFee event SetProtocolFee(uint8 oldProtocolFee, uint8 newProtocolFee); /// @notice Emit when set ProtocolFee. /// @param validatorShare vaidatorshare address. /// @param amountClaimed amount claimed. event ClaimTotalDelegatedEvent( address indexed validatorShare, uint256 indexed amountClaimed ); /// @notice Emit when set version. /// @param oldVersion old. /// @param newVersion new. event Version( string oldVersion, string indexed newVersion ); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) pragma solidity ^0.8.0; import "../IERC20Upgradeable.sol"; /** * @dev Interface for the optional metadata functions from the ERC20 standard. * * _Available since v4.1._ */ interface IERC20MetadataUpgradeable is IERC20Upgradeable { /** * @dev Returns the name of the token. */ function name() external view returns (string memory); /** * @dev Returns the symbol of the token. */ function symbol() external view returns (string memory); /** * @dev Returns the decimals places of the token. */ function decimals() external view returns (uint8); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/Context.sol) pragma solidity ^0.8.0; import "../proxy/utils/Initializable.sol"; /** * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract ContextUpgradeable is Initializable { function __Context_init() internal onlyInitializing { } function __Context_init_unchained() internal onlyInitializing { } function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { return msg.data; } /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[50] private __gap; } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (proxy/utils/Initializable.sol) pragma solidity ^0.8.0; import "../../utils/AddressUpgradeable.sol"; /** * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. * * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. * * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. * * [CAUTION] * ==== * Avoid leaving a contract uninitialized. * * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation * contract, which may impact the proxy. To initialize the implementation contract, you can either invoke the * initializer manually, or you can include a constructor to automatically mark it as initialized when it is deployed: * * [.hljs-theme-light.nopadding] * ``` * /// @custom:oz-upgrades-unsafe-allow constructor * constructor() initializer {} * ``` * ==== */ abstract contract Initializable { /** * @dev Indicates that the contract has been initialized. */ bool private _initialized; /** * @dev Indicates that the contract is in the process of being initialized. */ bool private _initializing; /** * @dev Modifier to protect an initializer function from being invoked twice. */ modifier initializer() { // If the contract is initializing we ignore whether _initialized is set in order to support multiple // inheritance patterns, but we only do this in the context of a constructor, because in other contexts the // contract may have been reentered. require(_initializing ? _isConstructor() : !_initialized, "Initializable: contract is already initialized"); bool isTopLevelCall = !_initializing; if (isTopLevelCall) { _initializing = true; _initialized = true; } _; if (isTopLevelCall) { _initializing = false; } } /** * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the * {initializer} modifier, directly or indirectly. */ modifier onlyInitializing() { require(_initializing, "Initializable: contract is not initializing"); _; } function _isConstructor() private view returns (bool) { return !AddressUpgradeable.isContract(address(this)); } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol) pragma solidity ^0.8.1; /** * @dev Collection of functions related to the address type */ library AddressUpgradeable { /** * @dev Returns true if `account` is a contract. * * [IMPORTANT] * ==== * It is unsafe to assume that an address for which this function returns * false is an externally-owned account (EOA) and not a contract. * * Among others, `isContract` will return false for the following * types of addresses: * * - an externally-owned account * - a contract in construction * - an address where a contract will be created * - an address where a contract lived, but was destroyed * ==== * * [IMPORTANT] * ==== * You shouldn't rely on `isContract` to protect against flash loan attacks! * * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract * constructor. * ==== */ function isContract(address account) internal view returns (bool) { // This method relies on extcodesize/address.code.length, which returns 0 // for contracts in construction, since the code is only stored at the end // of the constructor execution. return account.code.length > 0; } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); (bool success, ) = recipient.call{value: amount}(""); require(success, "Address: unable to send value, recipient may have reverted"); } /** * @dev Performs a Solidity function call using a low level `call`. A * plain `call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason, it is bubbled up by this * function (like regular Solidity function calls). * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCall(target, data, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ function functionCallWithValue( address target, bytes memory data, uint256 value ) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCallWithValue( address target, bytes memory data, uint256 value, string memory errorMessage ) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); require(isContract(target), "Address: call to non-contract"); (bool success, bytes memory returndata) = target.call{value: value}(data); return verifyCallResult(success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { return functionStaticCall(target, data, "Address: low-level static call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall( address target, bytes memory data, string memory errorMessage ) internal view returns (bytes memory) { require(isContract(target), "Address: static call to non-contract"); (bool success, bytes memory returndata) = target.staticcall(data); return verifyCallResult(success, returndata, errorMessage); } /** * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the * revert reason using the provided one. * * _Available since v4.3._ */ function verifyCallResult( bool success, bytes memory returndata, string memory errorMessage ) internal pure returns (bytes memory) { if (success) { return returndata; } else { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol) pragma solidity ^0.8.0; /** * @dev External interface of AccessControl declared to support ERC165 detection. */ interface IAccessControlUpgradeable { /** * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` * * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite * {RoleAdminChanged} not being emitted signaling this. * * _Available since v3.1._ */ event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); /** * @dev Emitted when `account` is granted `role`. * * `sender` is the account that originated the contract call, an admin role * bearer except when using {AccessControl-_setupRole}. */ event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); /** * @dev Emitted when `account` is revoked `role`. * * `sender` is the account that originated the contract call: * - if using `revokeRole`, it is the admin role bearer * - if using `renounceRole`, it is the role bearer (i.e. `account`) */ event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); /** * @dev Returns `true` if `account` has been granted `role`. */ function hasRole(bytes32 role, address account) external view returns (bool); /** * @dev Returns the admin role that controls `role`. See {grantRole} and * {revokeRole}. * * To change a role's admin, use {AccessControl-_setRoleAdmin}. */ function getRoleAdmin(bytes32 role) external view returns (bytes32); /** * @dev Grants `role` to `account`. * * If `account` had not been already granted `role`, emits a {RoleGranted} * event. * * Requirements: * * - the caller must have ``role``'s admin role. */ function grantRole(bytes32 role, address account) external; /** * @dev Revokes `role` from `account`. * * If `account` had been granted `role`, emits a {RoleRevoked} event. * * Requirements: * * - the caller must have ``role``'s admin role. */ function revokeRole(bytes32 role, address account) external; /** * @dev Revokes `role` from the calling account. * * Roles are often managed via {grantRole} and {revokeRole}: this function's * purpose is to provide a mechanism for accounts to lose their privileges * if they are compromised (such as when a trusted device is misplaced). * * If the calling account had been granted `role`, emits a {RoleRevoked} * event. * * Requirements: * * - the caller must be `account`. */ function renounceRole(bytes32 role, address account) external; } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/Strings.sol) pragma solidity ^0.8.0; /** * @dev String operations. */ library StringsUpgradeable { bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; /** * @dev Converts a `uint256` to its ASCII `string` decimal representation. */ function toString(uint256 value) internal pure returns (string memory) { // Inspired by OraclizeAPI's implementation - MIT licence // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol if (value == 0) { return "0"; } uint256 temp = value; uint256 digits; while (temp != 0) { digits++; temp /= 10; } bytes memory buffer = new bytes(digits); while (value != 0) { digits -= 1; buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); value /= 10; } return string(buffer); } /** * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. */ function toHexString(uint256 value) internal pure returns (string memory) { if (value == 0) { return "0x00"; } uint256 temp = value; uint256 length = 0; while (temp != 0) { length++; temp >>= 8; } return toHexString(value, length); } /** * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. */ function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { bytes memory buffer = new bytes(2 * length + 2); buffer[0] = "0"; buffer[1] = "x"; for (uint256 i = 2 * length + 1; i > 1; --i) { buffer[i] = _HEX_SYMBOLS[value & 0xf]; value >>= 4; } require(value == 0, "Strings: hex length insufficient"); return string(buffer); } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol) pragma solidity ^0.8.0; import "./IERC165Upgradeable.sol"; import "../../proxy/utils/Initializable.sol"; /** * @dev Implementation of the {IERC165} interface. * * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check * for the additional interface id that will be supported. For example: * * ```solidity * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); * } * ``` * * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation. */ abstract contract ERC165Upgradeable is Initializable, IERC165Upgradeable { function __ERC165_init() internal onlyInitializing { } function __ERC165_init_unchained() internal onlyInitializing { } /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { return interfaceId == type(IERC165Upgradeable).interfaceId; } /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[50] private __gap; } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC165 standard, as defined in the * https://eips.ethereum.org/EIPS/eip-165[EIP]. * * Implementers can declare support of contract interfaces, which can then be * queried by others ({ERC165Checker}). * * For an implementation, see {ERC165}. */ interface IERC165Upgradeable { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC721/IERC721.sol) pragma solidity ^0.8.0; import "../../utils/introspection/IERC165Upgradeable.sol"; /** * @dev Required interface of an ERC721 compliant contract. */ interface IERC721Upgradeable is IERC165Upgradeable { /** * @dev Emitted when `tokenId` token is transferred from `from` to `to`. */ event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); /** * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. */ event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); /** * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. */ event ApprovalForAll(address indexed owner, address indexed operator, bool approved); /** * @dev Returns the number of tokens in ``owner``'s account. */ function balanceOf(address owner) external view returns (uint256 balance); /** * @dev Returns the owner of the `tokenId` token. * * Requirements: * * - `tokenId` must exist. */ function ownerOf(uint256 tokenId) external view returns (address owner); /** * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients * are aware of the ERC721 protocol to prevent tokens from being forever locked. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function safeTransferFrom( address from, address to, uint256 tokenId ) external; /** * @dev Transfers `tokenId` token from `from` to `to`. * * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must be owned by `from`. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * * Emits a {Transfer} event. */ function transferFrom( address from, address to, uint256 tokenId ) external; /** * @dev Gives permission to `to` to transfer `tokenId` token to another account. * The approval is cleared when the token is transferred. * * Only a single account can be approved at a time, so approving the zero address clears previous approvals. * * Requirements: * * - The caller must own the token or be an approved operator. * - `tokenId` must exist. * * Emits an {Approval} event. */ function approve(address to, uint256 tokenId) external; /** * @dev Returns the account approved for `tokenId` token. * * Requirements: * * - `tokenId` must exist. */ function getApproved(uint256 tokenId) external view returns (address operator); /** * @dev Approve or remove `operator` as an operator for the caller. * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. * * Requirements: * * - The `operator` cannot be the caller. * * Emits an {ApprovalForAll} event. */ function setApprovalForAll(address operator, bool _approved) external; /** * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. * * See {setApprovalForAll} */ function isApprovedForAll(address owner, address operator) external view returns (bool); /** * @dev Safely transfers `tokenId` token from `from` to `to`. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function safeTransferFrom( address from, address to, uint256 tokenId, bytes calldata data ) external; }