From d71a0700cf30971a439e2e1182c533a040bea211 Mon Sep 17 00:00:00 2001 From: Faisal Usmani Date: Sat, 1 Nov 2025 19:17:02 -0400 Subject: [PATCH 1/4] feat: OpenZeppelin V5 upgrade Signed-off-by: Faisal Usmani --- contracts/AcrossConfigStore.sol | 4 +- contracts/AdapterStore.sol | 2 +- contracts/Blast_DaiRetriever.sol | 12 +- contracts/BondToken.sol | 10 +- contracts/Ethereum_SpokePool.sol | 2 +- contracts/HubPool.sol | 84 +- contracts/Linea_SpokePool.sol | 4 +- contracts/LpTokenFactory.sol | 8 +- contracts/MerkleLib.sol | 2 +- contracts/Ovm_SpokePool.sol | 2 +- contracts/PermissionSplitterProxy.sol | 4 +- contracts/PolygonTokenBridger.sol | 4 +- contracts/PolygonZkEVM_SpokePool.sol | 10 +- contracts/SpokePool.sol | 94 +- contracts/SpokePoolPeriphery.sol | 16 +- contracts/SpokePoolVerifier.sol | 2 +- contracts/Universal_SpokePool.sol | 2 +- contracts/ZkSync_SpokePool.sol | 8 +- contracts/chain-adapters/Arbitrum_Adapter.sol | 4 +- .../Arbitrum_CustomGasToken_Adapter.sol | 12 +- .../chain-adapters/Arbitrum_RescueAdapter.sol | 11 +- .../Arbitrum_SendTokensAdapter.sol | 11 +- contracts/chain-adapters/Base_Adapter.sol | 11 +- contracts/chain-adapters/Blast_Adapter.sol | 11 +- .../chain-adapters/Blast_RescueAdapter.sol | 11 +- contracts/chain-adapters/Boba_Adapter.sol | 11 +- .../chain-adapters/DoctorWho_Adapter.sol | 11 +- contracts/chain-adapters/DonationBox.sol | 6 +- contracts/chain-adapters/Ethereum_Adapter.sol | 4 +- .../chain-adapters/Ethereum_RescueAdapter.sol | 11 +- contracts/chain-adapters/ForwarderBase.sol | 6 +- contracts/chain-adapters/Linea_Adapter.sol | 11 +- contracts/chain-adapters/Lisk_Adapter.sol | 11 +- contracts/chain-adapters/Mock_Adapter.sol | 9 +- contracts/chain-adapters/Mode_Adapter.sol | 11 +- contracts/chain-adapters/OP_Adapter.sol | 11 +- contracts/chain-adapters/Optimism_Adapter.sol | 11 +- contracts/chain-adapters/Ovm_Forwarder.sol | 2 +- .../chain-adapters/PolygonZkEVM_Adapter.sol | 11 +- contracts/chain-adapters/Polygon_Adapter.sol | 4 +- contracts/chain-adapters/Redstone_Adapter.sol | 11 +- contracts/chain-adapters/Scroll_Adapter.sol | 11 +- contracts/chain-adapters/Solana_Adapter.sol | 9 +- contracts/chain-adapters/ZkStack_Adapter.sol | 4 +- .../ZkStack_CustomGasToken_Adapter.sol | 4 +- contracts/chain-adapters/ZkSync_Adapter.sol | 10 +- contracts/chain-adapters/Zora_Adapter.sol | 11 +- .../l2/Arbitrum_WithdrawalHelper.sol | 10 +- .../l2/Ovm_WithdrawalHelper.sol | 12 +- .../l2/WithdrawalHelperBase.sol | 12 +- contracts/erc1155/MintableERC1155.sol | 10 +- contracts/erc7683/AcrossOriginSettler.sol | 6 +- contracts/erc7683/ERC7683OrderDepositor.sol | 45 +- .../implementation/AddressWhitelist.sol | 92 ++ .../common/implementation/AncillaryData.sol | 144 ++ .../common/implementation/ExpandedERC20.sol | 107 ++ .../common/implementation/FixedPoint.sol | 763 +++++++++++ .../common/implementation/HasFinder.sol | 13 + .../common/implementation/Lockable.sol | 77 ++ .../common/implementation/MultiCaller.sol | 28 + .../common/implementation/MultiRole.sol | 241 ++++ .../common/implementation/Multicall3.sol | 235 ++++ .../common/implementation/Stakeable.sol | 60 + .../common/implementation/Testable.sol | 52 + .../common/implementation/TestnetERC20.sol | 39 + .../contracts/common/implementation/Timer.sol | 30 + .../common/implementation/Withdrawable.sol | 61 + .../implementation/dsproxy/DSGuardFactory.sol | 123 ++ .../implementation/dsproxy/DSProxyFactory.sol | 195 +++ .../interfaces/AddressWhitelistInterface.sol | 12 + .../contracts/common/interfaces/Balancer.sol | 10 + .../common/interfaces/ExpandedIERC20.sol | 34 + .../interfaces/HarvestVaultInterface.sol | 16 + .../common/interfaces/IERC20Standard.sol | 22 + .../contracts/common/interfaces/Multicall.sol | 15 + .../common/interfaces/Multicall2.sol | 25 + .../common/interfaces/TransactionBatcher.sol | 9 + .../contracts/common/interfaces/UniswapV2.sol | 15 + .../contracts/common/interfaces/UniswapV3.sol | 23 + .../common/interfaces/VaultInterface.sol | 16 + .../common/test/AncillaryDataTest.sol | 42 + .../contracts/common/test/BalancerMock.sol | 21 + .../core/contracts/common/test/BasicERC20.sol | 61 + .../common/test/HarvestVaultMock.sol | 25 + .../contracts/common/test/MintableERC721.sol | 13 + .../contracts/common/test/MultiCallerTest.sol | 15 + .../contracts/common/test/MultiRoleTest.sol | 22 + .../contracts/common/test/MulticallMock.sol | 23 + .../contracts/common/test/PerpetualMock.sol | 52 + .../common/test/ReentrancyAttack.sol | 12 + .../common/test/ReentrancyChecker.sol | 52 + .../contracts/common/test/ReentrancyMock.sol | 65 + .../common/test/SignedFixedPointTest.sol | 154 +++ .../contracts/common/test/TestableTest.sol | 15 + .../contracts/common/test/UniswapV2Mock.sol | 18 + .../contracts/common/test/UniswapV3Mock.sol | 26 + .../common/test/UnsignedFixedPointTest.sol | 146 +++ .../core/contracts/common/test/VaultMock.sol | 25 + .../common/test/WithdrawableTest.sol | 26 + .../AncillaryDataCompression.sol | 49 + .../cross-chain-oracle/GovernorHub.sol | 52 + .../cross-chain-oracle/GovernorSpoke.sol | 56 + .../cross-chain-oracle/OracleBase.sol | 78 ++ .../cross-chain-oracle/OracleHub.sol | 148 +++ .../cross-chain-oracle/OracleSpoke.sol | 255 ++++ .../contracts/cross-chain-oracle/README.md | 325 +++++ .../cross-chain-oracle/SpokeBase.sol | 26 + .../chain-adapters/Admin_ChildMessenger.sol | 57 + .../Arbitrum_ChildMessenger.sol | 84 ++ .../Arbitrum_ParentMessenger.sol | 210 +++ .../Optimism_ChildMessenger.sol | 102 ++ .../Optimism_ParentMessenger.sol | 107 ++ .../chain-adapters/ParentMessengerBase.sol | 98 ++ .../chain-adapters/Polygon_ChildMessenger.sol | 69 + .../Polygon_ParentMessenger.sol | 66 + .../test/Arbitrum_inboxMock.sol | 51 + .../test/OVM_L1CrossDomainMessengerMock.sol | 12 + .../test/ParentMessengerBaseMock.sol | 12 + .../test/Polygon_ChildMessengerMock.sol | 16 + .../test/Polygon_ParentMessengerMock.sol | 16 + .../ChildMessengerConsumerInterface.sol | 7 + .../interfaces/ChildMessengerInterface.sol | 7 + .../ParentMessengerConsumerInterface.sol | 7 + .../interfaces/ParentMessengerInterface.sol | 10 + .../test/GovernorMessengerMock.sol | 23 + .../test/OracleBaseMock.sol | 27 + .../test/OracleMessengerMock.sol | 61 + .../data-verification-mechanism/README.md | 114 ++ .../implementation/AdminIdentifierLib.sol | 63 + .../implementation/Constants.sol | 34 + .../implementation/ContractCreator.sol | 32 + .../implementation/DesignatedVoting.sol | 117 ++ .../DesignatedVotingFactory.sol | 53 + .../implementation/DesignatedVotingV2.sol | 55 + .../DesignatedVotingV2Factory.sol | 37 + .../implementation/EmergencyProposer.sol | 260 ++++ .../FinancialContractsAdmin.sol | 29 + .../implementation/Finder.sol | 40 + .../FixedSlashSlashingLibrary.sol | 103 ++ .../implementation/Governor.sol | 208 +++ .../implementation/GovernorV2.sol | 234 ++++ .../implementation/IdentifierWhitelist.sol | 64 + .../implementation/Proposer.sol | 95 ++ .../implementation/ProposerV2.sol | 120 ++ .../implementation/Registry.sol | 202 +++ .../implementation/ResultComputation.sol | 94 ++ .../implementation/ResultComputationV2.sol | 82 ++ .../implementation/Staker.sol | 372 ++++++ .../implementation/Store.sol | 179 +++ .../implementation/TokenMigrator.sol | 63 + .../implementation/VoteTiming.sol | 65 + .../implementation/Voting.sol | 977 ++++++++++++++ .../implementation/VotingToken.sol | 52 + .../implementation/VotingV2.sol | 1156 +++++++++++++++++ .../test/EmergencyProposerTest.sol | 20 + .../implementation/test/GovernorTest.sol | 22 + .../implementation/test/GovernorV2Test.sol | 17 + .../implementation/test/MockAdministratee.sol | 22 + .../PriceIdentifierSlashingLibaryTest.sol | 61 + .../implementation/test/ProposerV2Test.sol | 20 + .../test/PunitiveSlashingLibraryTest.sol | 48 + .../test/ResultComputationTest.sol | 28 + .../implementation/test/StakerTest.sol | 44 + .../implementation/test/VoteTimingTest.sol | 28 + .../implementation/test/VotingTest.sol | 32 + .../implementation/test/VotingV2Test.sol | 107 ++ .../test/ZeroedSlashingLibaryTest.sol | 52 + .../interfaces/AdministrateeInterface.sol | 28 + .../interfaces/FinderInterface.sol | 22 + .../IdentifierWhitelistInterface.sol | 28 + .../MinimumVotingAncillaryInterface.sol | 20 + .../interfaces/OracleAncillaryInterface.sol | 43 + .../interfaces/OracleGovernanceInterface.sol | 20 + .../interfaces/OracleInterface.sol | 34 + .../interfaces/RegistryInterface.sol | 58 + .../interfaces/SlashingLibraryInterface.sol | 67 + .../interfaces/StakerInterface.sol | 38 + .../interfaces/StoreInterface.sol | 46 + .../interfaces/VotingAncillaryInterface.sol | 183 +++ .../interfaces/VotingInterface.sol | 170 +++ .../interfaces/VotingV2Interface.sol | 187 +++ .../test/MockOracle.sol | 138 ++ .../test/MockOracleAncillary.sol | 154 +++ .../test/MockOracleCombined.sol | 29 + .../test/MockOracleGovernance.sol | 28 + .../test/VotingAncillaryInterfaceTest.sol | 88 ++ .../test/VotingInterfaceTest.sol | 88 ++ .../uma/core/contracts/external/README.md | 3 + .../external/avm/AVM_CrossDomainEnabled.sol | 39 + .../avm/Arbitrum_CrossDomainEnabled.sol | 66 + .../external/avm/Arbitrum_Messenger.sol | 78 ++ .../core/contracts/external/avm/CHANGELOG.md | 25 + .../external/avm/interfaces/ArbSys.sol | 79 ++ .../avm/interfaces/iArbitrum_Inbox.sol | 28 + .../avm/interfaces/iArbitrum_Outbox.sol | 26 + .../external/boba/BobaAddressManager.sol | 11 + .../contracts/external/chainbridge/Bridge.sol | 451 +++++++ .../external/chainbridge/CHANGELOG.md | 26 + .../chainbridge/handlers/GenericHandler.sol | 237 ++++ .../chainbridge/interfaces/IBridge.sol | 16 + .../interfaces/IDepositExecute.sol | 29 + .../chainbridge/interfaces/IERCHandler.sol | 29 + .../interfaces/IGenericHandler.sol | 23 + .../interfaces/OptimismL1StandardBridge.sol | 25 + .../interfaces/OptimismL2StandardBridge.sol | 19 + .../interfaces/OptimismL2StandardERC20.sol | 9 + .../common/EmergencyShutdownable.sol | 59 + .../financial-templates/common/FeePayer.sol | 322 +++++ .../common/FundingRateApplier.sol | 338 +++++ .../common/SyntheticToken.sol | 86 ++ .../common/TokenFactory.sol | 30 + .../financial-templates/common/WETH9.sol | 72 + .../CoveredCallFinancialProductLibrary.sol | 94 ++ .../FinancialProductLibrary.sol | 49 + .../KpiOptionsFinancialProductLibrary.sol | 43 + ...rTransformationFinancialProductLibrary.sol | 64 + ...rTransformationFinancialProductLibrary.sol | 64 + .../StructuredNoteFinancialProductLibrary.sol | 102 ++ ...onLongShortPairFinancialProductLibrary.sol | 63 + ...arLongShortPairFinancialProductLibrary.sol | 79 ++ ...llLongShortPairFinancialProductLibrary.sol | 69 + ...arLongShortPairFinancialProductLibrary.sol | 97 ++ ...arLongShortPairFinancialProductLibrary.sol | 85 ++ .../LongShortPairFinancialProductLibrary.sol | 12 + ...ndLongShortPairFinancialProductLibrary.sol | 101 ++ ...enLongShortPairFinancialProductLibrary.sol | 85 ++ ...enLongShortPairFinancialProductLibrary.sol | 86 ++ .../ExpiringMultiParty.sol | 22 + .../ExpiringMultiPartyCreator.sol | 146 +++ .../ExpiringMultiPartyLib.sol | 22 + .../expiring-multiparty/Liquidatable.sol | 634 +++++++++ .../PricelessPositionManager.sol | 985 ++++++++++++++ .../long-short-pair/LongShortPair.sol | 431 ++++++ .../long-short-pair/LongShortPairCreator.sol | 166 +++ .../OptimisticDistributor.sol | 438 +++++++ .../OptimisticRewarder.sol | 114 ++ .../OptimisticRewarderBase.sol | 400 ++++++ .../OptimisticRewarderCreator.sol | 91 ++ .../OptimisticRewarderToken.sol | 33 + .../optimistic-rewarder/OptimisticStaker.sol | 91 ++ .../test/OptimisticRewarderTest.sol | 59 + .../perpetual-multiparty/ConfigStore.sol | 184 +++ .../ConfigStoreInterface.sol | 25 + .../perpetual-multiparty/Perpetual.sol | 22 + .../perpetual-multiparty/PerpetualCreator.sol | 167 +++ .../perpetual-multiparty/PerpetualLib.sol | 22 + .../PerpetualLiquidatable.sol | 595 +++++++++ .../PerpetualPositionManager.sol | 756 +++++++++++ .../test/ExpiringMultiPartyMock.sol | 66 + .../test/FinancialProductLibraryTest.sol | 52 + .../test/FundingRateApplierTest.sol | 52 + ...ngShortPairFinancialProjectLibraryTest.sol | 18 + .../test/LongShortPairMock.sol | 12 + .../implementation/MerkleDistributor.sol | 241 ++++ .../MerkleDistributorInterface.sol | 41 + .../optimistic-governor/test/TestAvatar.sol | 34 + .../test/TestModuleProxyFactory.sol | 6 + .../implementation/OptimisticOracleV2.sol | 711 ++++++++++ .../SkinnyOptimisticOracleV2.sol | 686 ++++++++++ .../interfaces/OptimisticOracleInterface.sol | 307 +++++ .../OptimisticOracleV2Interface.sol | 351 +++++ .../SkinnyOptimisticOracleInterface.sol | 250 ++++ .../SkinnyOptimisticOracleV2Interface.sol | 253 ++++ .../previous-versions/OptimisticOracle.sol | 629 +++++++++ .../SkinnyOptimisticOracle.sol | 695 ++++++++++ .../test/OptimisticRequesterTest.sol | 139 ++ .../test/SkinnyOptimisticRequesterTest.sol | 126 ++ .../test/SkinnyOptimisticV2RequesterTest.sol | 123 ++ .../implementation/ClaimData.sol | 4 + .../implementation/OptimisticOracleV3.sol | 488 +++++++ .../BaseEscalationManager.sol | 104 ++ .../DisputeLimitingEscalationManager.sol | 59 + .../FullPolicyEscalationManager.sol | 222 ++++ .../OwnerDiscardOracleEscalationManager.sol | 25 + .../OwnerSelectOracleEscalationManager.sol | 53 + .../SuperbondEscalationManager.sol | 35 + .../WhitelistAsserterEscalationManager.sol | 50 + .../WhitelistCallerEscalationManager.sol | 27 + .../WhitelistDisputerEscalationManager.sol | 29 + .../implementation/examples/DataAsserter.sol | 102 ++ .../implementation/examples/Insurance.sol | 109 ++ .../examples/PredictionMarket.sol | 258 ++++ .../test/AssertingCallerTest.sol | 42 + .../test/OptimisticOracleV3Test.sol | 19 + .../interfaces/EscalationManagerInterface.sol | 53 + ...sticOracleV3CallbackRecipientInterface.sol | 21 + .../OptimisticOracleV3Interface.sol | 178 +++ .../GovernorChildTunnel.sol | 49 + .../GovernorRootTunnel.sol | 36 + .../OracleBaseTunnel.sol | 81 ++ .../OracleChildTunnel.sol | 229 ++++ .../OracleRootTunnel.sol | 61 + .../polygon-cross-chain-oracle/README.md | 42 + .../test/FxChildMock.sol | 38 + .../test/FxRootMock.sol | 32 + .../test/OracleBaseTunnelMock.sol | 29 + .../test/OracleRootTunnelMock.sol | 47 + .../test/StateSyncMock.sol | 26 + .../ReserveCurrencyDisputer.sol | 137 ++ .../ReserveCurrencyLiquidator.sol | 197 +++ .../LiquidationWithdrawer.sol | 22 + .../bot-action-wrappers/PositionSettler.sol | 15 + .../bot-action-wrappers/TokenRedeemer.sol | 23 + .../bot-action-wrappers/TokenSender.sol | 12 + .../lsp-broker/LspUniswapV2Broker.sol | 305 +++++ .../uniswap-broker/UniswapV2Broker.sol | 278 ++++ .../uniswap-broker/UniswapV3Broker.sol | 623 +++++++++ .../snapshot-helpers/SnapshotVotingPower.sol | 46 + .../umip-helpers/OriginValidator.sol | 18 + .../contracts/umip-helpers/Umip3Upgrader.sol | 71 + .../contracts/umip-helpers/VotingUpgrader.sol | 75 ++ .../umip-helpers/VotingUpgraderV2.sol | 153 +++ contracts/handlers/MulticallHandler.sol | 8 +- contracts/interfaces/HubPoolInterface.sol | 28 +- .../SpokePoolPeripheryInterface.sol | 4 +- contracts/libraries/CircleCCTPAdapter.sol | 4 +- contracts/libraries/OFTTransportAdapter.sol | 4 +- .../AcrossMerkleDistributor.sol | 6 +- contracts/permit2-order/Permit2Depositor.sol | 12 +- contracts/test/ExpandedERC20WithBlacklist.sol | 14 +- contracts/test/MockERC1271.sol | 6 +- contracts/test/MockERC20.sol | 6 +- contracts/test/MockPermit2.sol | 8 +- contracts/test/MockSpokePool.sol | 8 +- contracts/test/PolygonERC20Test.sol | 2 +- contracts/test/PolygonMocks.sol | 8 +- contracts/test/V2MerkleLib.sol | 2 +- .../upgradeable/AddressLibUpgradeable.sol | 8 +- .../EIP712CrossChainUpgradeable.sol | 4 +- .../upgradeable/MultiCallerUpgradeable.sol | 2 +- foundry.toml | 7 +- hardhat.config.ts | 31 +- package.json | 8 +- script/001DeployHubPool.s.sol | 2 +- script/002DeployOptimismAdapter.s.sol | 2 +- script/004DeployArbitrumAdapter.s.sol | 2 +- script/009DeployPolygonAdapter.s.sol | 2 +- script/024DeployBaseAdapter.s.sol | 2 +- script/061DeployUnichainAdapter.s.sol | 2 +- script/utils/DeploymentUtils.sol | 2 +- .../fork/BlacklistedRelayerRecipient.t.sol | 26 +- .../foundry/fork/UniversalAdapterOFT.t.sol | 13 +- .../foundry/local/Blast_DaiRetriever.t.sol | 4 +- test/evm/foundry/local/Forwarder.t.sol | 6 +- .../local/MultiCallerUpgradeable.t.sol | 10 +- test/evm/foundry/local/MulticallHandler.t.sol | 2 +- test/evm/foundry/local/Router_Adapter.t.sol | 6 +- .../local/SpokePoolDeprecatedMethods.t.sol | 4 +- .../foundry/local/SpokePoolPeriphery.t.sol | 6 +- .../evm/foundry/local/SpokePoolVerifier.t.sol | 8 +- .../evm/foundry/local/SpokePool_EIP7702.t.sol | 2 +- .../evm/foundry/local/Universal_Adapter.t.sol | 2 +- .../foundry/local/Universal_SpokePool.t.sol | 4 +- test/evm/foundry/local/WithdrawalHelper.t.sol | 6 +- test/evm/foundry/local/ZkStack_Adapter.t.sol | 6 +- yarn.lock | 47 +- 356 files changed, 30556 insertions(+), 524 deletions(-) create mode 100644 contracts/external/uma/core/contracts/common/implementation/AddressWhitelist.sol create mode 100644 contracts/external/uma/core/contracts/common/implementation/AncillaryData.sol create mode 100644 contracts/external/uma/core/contracts/common/implementation/ExpandedERC20.sol create mode 100644 contracts/external/uma/core/contracts/common/implementation/FixedPoint.sol create mode 100644 contracts/external/uma/core/contracts/common/implementation/HasFinder.sol create mode 100644 contracts/external/uma/core/contracts/common/implementation/Lockable.sol create mode 100644 contracts/external/uma/core/contracts/common/implementation/MultiCaller.sol create mode 100644 contracts/external/uma/core/contracts/common/implementation/MultiRole.sol create mode 100644 contracts/external/uma/core/contracts/common/implementation/Multicall3.sol create mode 100644 contracts/external/uma/core/contracts/common/implementation/Stakeable.sol create mode 100644 contracts/external/uma/core/contracts/common/implementation/Testable.sol create mode 100644 contracts/external/uma/core/contracts/common/implementation/TestnetERC20.sol create mode 100644 contracts/external/uma/core/contracts/common/implementation/Timer.sol create mode 100644 contracts/external/uma/core/contracts/common/implementation/Withdrawable.sol create mode 100644 contracts/external/uma/core/contracts/common/implementation/dsproxy/DSGuardFactory.sol create mode 100644 contracts/external/uma/core/contracts/common/implementation/dsproxy/DSProxyFactory.sol create mode 100644 contracts/external/uma/core/contracts/common/interfaces/AddressWhitelistInterface.sol create mode 100644 contracts/external/uma/core/contracts/common/interfaces/Balancer.sol create mode 100644 contracts/external/uma/core/contracts/common/interfaces/ExpandedIERC20.sol create mode 100644 contracts/external/uma/core/contracts/common/interfaces/HarvestVaultInterface.sol create mode 100644 contracts/external/uma/core/contracts/common/interfaces/IERC20Standard.sol create mode 100644 contracts/external/uma/core/contracts/common/interfaces/Multicall.sol create mode 100644 contracts/external/uma/core/contracts/common/interfaces/Multicall2.sol create mode 100644 contracts/external/uma/core/contracts/common/interfaces/TransactionBatcher.sol create mode 100644 contracts/external/uma/core/contracts/common/interfaces/UniswapV2.sol create mode 100644 contracts/external/uma/core/contracts/common/interfaces/UniswapV3.sol create mode 100644 contracts/external/uma/core/contracts/common/interfaces/VaultInterface.sol create mode 100644 contracts/external/uma/core/contracts/common/test/AncillaryDataTest.sol create mode 100644 contracts/external/uma/core/contracts/common/test/BalancerMock.sol create mode 100644 contracts/external/uma/core/contracts/common/test/BasicERC20.sol create mode 100644 contracts/external/uma/core/contracts/common/test/HarvestVaultMock.sol create mode 100644 contracts/external/uma/core/contracts/common/test/MintableERC721.sol create mode 100644 contracts/external/uma/core/contracts/common/test/MultiCallerTest.sol create mode 100644 contracts/external/uma/core/contracts/common/test/MultiRoleTest.sol create mode 100644 contracts/external/uma/core/contracts/common/test/MulticallMock.sol create mode 100644 contracts/external/uma/core/contracts/common/test/PerpetualMock.sol create mode 100644 contracts/external/uma/core/contracts/common/test/ReentrancyAttack.sol create mode 100644 contracts/external/uma/core/contracts/common/test/ReentrancyChecker.sol create mode 100644 contracts/external/uma/core/contracts/common/test/ReentrancyMock.sol create mode 100644 contracts/external/uma/core/contracts/common/test/SignedFixedPointTest.sol create mode 100644 contracts/external/uma/core/contracts/common/test/TestableTest.sol create mode 100644 contracts/external/uma/core/contracts/common/test/UniswapV2Mock.sol create mode 100644 contracts/external/uma/core/contracts/common/test/UniswapV3Mock.sol create mode 100644 contracts/external/uma/core/contracts/common/test/UnsignedFixedPointTest.sol create mode 100644 contracts/external/uma/core/contracts/common/test/VaultMock.sol create mode 100644 contracts/external/uma/core/contracts/common/test/WithdrawableTest.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/AncillaryDataCompression.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/GovernorHub.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/GovernorSpoke.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/OracleBase.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/OracleHub.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/OracleSpoke.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/README.md create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/SpokeBase.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Admin_ChildMessenger.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Arbitrum_ChildMessenger.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Arbitrum_ParentMessenger.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Optimism_ChildMessenger.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Optimism_ParentMessenger.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/ParentMessengerBase.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Polygon_ChildMessenger.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Polygon_ParentMessenger.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Arbitrum_inboxMock.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/OVM_L1CrossDomainMessengerMock.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/ParentMessengerBaseMock.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Polygon_ChildMessengerMock.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Polygon_ParentMessengerMock.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ChildMessengerConsumerInterface.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ChildMessengerInterface.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ParentMessengerConsumerInterface.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ParentMessengerInterface.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/test/GovernorMessengerMock.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/test/OracleBaseMock.sol create mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/test/OracleMessengerMock.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/README.md create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/AdminIdentifierLib.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Constants.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ContractCreator.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVoting.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingFactory.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingV2.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingV2Factory.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/EmergencyProposer.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/FinancialContractsAdmin.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Finder.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/FixedSlashSlashingLibrary.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Governor.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/GovernorV2.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/IdentifierWhitelist.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Proposer.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ProposerV2.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Registry.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ResultComputation.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ResultComputationV2.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Staker.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Store.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/TokenMigrator.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VoteTiming.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Voting.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VotingToken.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VotingV2.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/EmergencyProposerTest.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/GovernorTest.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/GovernorV2Test.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/MockAdministratee.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/PriceIdentifierSlashingLibaryTest.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ProposerV2Test.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/PunitiveSlashingLibraryTest.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ResultComputationTest.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/StakerTest.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VoteTimingTest.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VotingTest.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VotingV2Test.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ZeroedSlashingLibaryTest.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/AdministrateeInterface.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/FinderInterface.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/MinimumVotingAncillaryInterface.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleAncillaryInterface.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleGovernanceInterface.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleInterface.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/RegistryInterface.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/SlashingLibraryInterface.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/StakerInterface.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/StoreInterface.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingAncillaryInterface.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingInterface.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingV2Interface.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracle.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleAncillary.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleCombined.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleGovernance.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/test/VotingAncillaryInterfaceTest.sol create mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/test/VotingInterfaceTest.sol create mode 100644 contracts/external/uma/core/contracts/external/README.md create mode 100644 contracts/external/uma/core/contracts/external/avm/AVM_CrossDomainEnabled.sol create mode 100644 contracts/external/uma/core/contracts/external/avm/Arbitrum_CrossDomainEnabled.sol create mode 100644 contracts/external/uma/core/contracts/external/avm/Arbitrum_Messenger.sol create mode 100644 contracts/external/uma/core/contracts/external/avm/CHANGELOG.md create mode 100644 contracts/external/uma/core/contracts/external/avm/interfaces/ArbSys.sol create mode 100644 contracts/external/uma/core/contracts/external/avm/interfaces/iArbitrum_Inbox.sol create mode 100644 contracts/external/uma/core/contracts/external/avm/interfaces/iArbitrum_Outbox.sol create mode 100644 contracts/external/uma/core/contracts/external/boba/BobaAddressManager.sol create mode 100644 contracts/external/uma/core/contracts/external/chainbridge/Bridge.sol create mode 100644 contracts/external/uma/core/contracts/external/chainbridge/CHANGELOG.md create mode 100644 contracts/external/uma/core/contracts/external/chainbridge/handlers/GenericHandler.sol create mode 100644 contracts/external/uma/core/contracts/external/chainbridge/interfaces/IBridge.sol create mode 100644 contracts/external/uma/core/contracts/external/chainbridge/interfaces/IDepositExecute.sol create mode 100644 contracts/external/uma/core/contracts/external/chainbridge/interfaces/IERCHandler.sol create mode 100644 contracts/external/uma/core/contracts/external/chainbridge/interfaces/IGenericHandler.sol create mode 100644 contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL1StandardBridge.sol create mode 100644 contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL2StandardBridge.sol create mode 100644 contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL2StandardERC20.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/common/EmergencyShutdownable.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/common/FeePayer.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/common/FundingRateApplier.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/common/SyntheticToken.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/common/TokenFactory.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/common/WETH9.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/CoveredCallFinancialProductLibrary.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/FinancialProductLibrary.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/KpiOptionsFinancialProductLibrary.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/PostExpirationIdentifierTransformationFinancialProductLibrary.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/PreExpirationIdentifierTransformationFinancialProductLibrary.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/StructuredNoteFinancialProductLibrary.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/BinaryOptionLongShortPairFinancialProductLibrary.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/CappedYieldDollarLongShortPairFinancialProductLibrary.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/CoveredCallLongShortPairFinancialProductLibrary.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/FlooredLinearLongShortPairFinancialProductLibrary.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/LinearLongShortPairFinancialProductLibrary.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/LongShortPairFinancialProductLibrary.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/RangeBondLongShortPairFinancialProductLibrary.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/SimpleSuccessTokenLongShortPairFinancialProductLibrary.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/SuccessTokenLongShortPairFinancialProductLibrary.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiParty.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiPartyCreator.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiPartyLib.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/Liquidatable.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/PricelessPositionManager.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/long-short-pair/LongShortPair.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/long-short-pair/LongShortPairCreator.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/optimistic-distributor/OptimisticDistributor.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarder.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderBase.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderCreator.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderToken.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticStaker.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/test/OptimisticRewarderTest.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/ConfigStore.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/ConfigStoreInterface.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/Perpetual.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualCreator.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualLib.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualLiquidatable.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualPositionManager.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/test/ExpiringMultiPartyMock.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/test/FinancialProductLibraryTest.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/test/FundingRateApplierTest.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/test/LongShortPairFinancialProjectLibraryTest.sol create mode 100644 contracts/external/uma/core/contracts/financial-templates/test/LongShortPairMock.sol create mode 100644 contracts/external/uma/core/contracts/merkle-distributor/implementation/MerkleDistributor.sol create mode 100644 contracts/external/uma/core/contracts/merkle-distributor/implementation/MerkleDistributorInterface.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-governor/test/TestAvatar.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-governor/test/TestModuleProxyFactory.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v2/implementation/OptimisticOracleV2.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v2/implementation/SkinnyOptimisticOracleV2.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/OptimisticOracleInterface.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/OptimisticOracleV2Interface.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/SkinnyOptimisticOracleInterface.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/SkinnyOptimisticOracleV2Interface.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v2/previous-versions/OptimisticOracle.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v2/previous-versions/SkinnyOptimisticOracle.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v2/test/OptimisticRequesterTest.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v2/test/SkinnyOptimisticRequesterTest.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v2/test/SkinnyOptimisticV2RequesterTest.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/ClaimData.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/OptimisticOracleV3.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/BaseEscalationManager.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/DisputeLimitingEscalationManager.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/FullPolicyEscalationManager.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/OwnerDiscardOracleEscalationManager.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/OwnerSelectOracleEscalationManager.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/SuperbondEscalationManager.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistAsserterEscalationManager.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistCallerEscalationManager.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistDisputerEscalationManager.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/DataAsserter.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/Insurance.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/PredictionMarket.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/test/AssertingCallerTest.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/test/OptimisticOracleV3Test.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/EscalationManagerInterface.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/OptimisticOracleV3CallbackRecipientInterface.sol create mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/OptimisticOracleV3Interface.sol create mode 100644 contracts/external/uma/core/contracts/polygon-cross-chain-oracle/GovernorChildTunnel.sol create mode 100644 contracts/external/uma/core/contracts/polygon-cross-chain-oracle/GovernorRootTunnel.sol create mode 100644 contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleBaseTunnel.sol create mode 100644 contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleChildTunnel.sol create mode 100644 contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleRootTunnel.sol create mode 100644 contracts/external/uma/core/contracts/polygon-cross-chain-oracle/README.md create mode 100644 contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/FxChildMock.sol create mode 100644 contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/FxRootMock.sol create mode 100644 contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/OracleBaseTunnelMock.sol create mode 100644 contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/OracleRootTunnelMock.sol create mode 100644 contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/StateSyncMock.sol create mode 100644 contracts/external/uma/core/contracts/proxy-scripts/atomic-disputer/ReserveCurrencyDisputer.sol create mode 100644 contracts/external/uma/core/contracts/proxy-scripts/atomic-liquidator/ReserveCurrencyLiquidator.sol create mode 100644 contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/LiquidationWithdrawer.sol create mode 100644 contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/PositionSettler.sol create mode 100644 contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/TokenRedeemer.sol create mode 100644 contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/TokenSender.sol create mode 100644 contracts/external/uma/core/contracts/proxy-scripts/lsp-broker/LspUniswapV2Broker.sol create mode 100644 contracts/external/uma/core/contracts/proxy-scripts/uniswap-broker/UniswapV2Broker.sol create mode 100644 contracts/external/uma/core/contracts/proxy-scripts/uniswap-broker/UniswapV3Broker.sol create mode 100644 contracts/external/uma/core/contracts/snapshot-helpers/SnapshotVotingPower.sol create mode 100644 contracts/external/uma/core/contracts/umip-helpers/OriginValidator.sol create mode 100644 contracts/external/uma/core/contracts/umip-helpers/Umip3Upgrader.sol create mode 100644 contracts/external/uma/core/contracts/umip-helpers/VotingUpgrader.sol create mode 100644 contracts/external/uma/core/contracts/umip-helpers/VotingUpgraderV2.sol diff --git a/contracts/AcrossConfigStore.sol b/contracts/AcrossConfigStore.sol index 6381086ea..92adb3d23 100644 --- a/contracts/AcrossConfigStore.sol +++ b/contracts/AcrossConfigStore.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import "@uma/core/contracts/common/implementation/MultiCaller.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; +import "contracts/external/uma/core/contracts/common/implementation/MultiCaller.sol"; +import "@openzeppelin/contracts-v4/access/Ownable.sol"; /** * @title Allows admin to set and update configuration settings for full contract system. These settings are designed diff --git a/contracts/AdapterStore.sol b/contracts/AdapterStore.sol index 1bf426f4d..bf3605d92 100644 --- a/contracts/AdapterStore.sol +++ b/contracts/AdapterStore.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.18; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { Ownable } from "@openzeppelin/contracts-v4/access/Ownable.sol"; import { IOFT } from "./interfaces/IOFT.sol"; /** diff --git a/contracts/Blast_DaiRetriever.sol b/contracts/Blast_DaiRetriever.sol index e472f8a7c..35814d4d3 100644 --- a/contracts/Blast_DaiRetriever.sol +++ b/contracts/Blast_DaiRetriever.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.0; import "./Lockable.sol"; -import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; -import "@uma/core/contracts/common/implementation/MultiCaller.sol"; +import "@openzeppelin/contracts-upgradeable-v4/token/ERC20/IERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable-v4/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "contracts/external/uma/core/contracts/common/implementation/MultiCaller.sol"; interface USDYieldManager { function claimWithdrawal(uint256 _requestId, uint256 _hintId) external returns (bool success); @@ -36,11 +36,7 @@ contract Blast_DaiRetriever is Lockable, MultiCaller { * @param _usdYieldManager USDCYieldManager contract on Ethereum. * @param _dai DAI token to be retrieved. */ - constructor( - address _hubPool, - USDYieldManager _usdYieldManager, - IERC20Upgradeable _dai - ) { + constructor(address _hubPool, USDYieldManager _usdYieldManager, IERC20Upgradeable _dai) { //slither-disable-next-line missing-zero-check hubPool = _hubPool; usdYieldManager = _usdYieldManager; diff --git a/contracts/BondToken.sol b/contracts/BondToken.sol index bc88873a7..ac2b856e7 100644 --- a/contracts/BondToken.sol +++ b/contracts/BondToken.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.0; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "@openzeppelin/contracts-v4/utils/Address.sol"; import "./interfaces/HubPoolInterface.sol"; import "./external/WETH9.sol"; @@ -71,11 +71,7 @@ contract BondToken is WETH9, Ownable { * @param amt Amount to transfer. * @return True on success. */ - function transferFrom( - address src, - address dst, - uint256 amt - ) public override returns (bool) { + function transferFrom(address src, address dst, uint256 amt) public override returns (bool) { if (dst == address(HUB_POOL)) { require(proposers[src] || HUB_POOL.rootBundleProposal().proposer != src, "Transfer not permitted"); } diff --git a/contracts/Ethereum_SpokePool.sol b/contracts/Ethereum_SpokePool.sol index de8660934..a20837bbc 100644 --- a/contracts/Ethereum_SpokePool.sol +++ b/contracts/Ethereum_SpokePool.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import "./SpokePool.sol"; -import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable-v4/access/OwnableUpgradeable.sol"; /** * @notice Ethereum L1 specific SpokePool. Used on Ethereum L1 to facilitate L2->L1 transfers. diff --git a/contracts/HubPool.sol b/contracts/HubPool.sol index ca69b8260..d281fb0a2 100644 --- a/contracts/HubPool.sol +++ b/contracts/HubPool.sol @@ -8,22 +8,22 @@ import "./Lockable.sol"; import "./interfaces/LpTokenFactoryInterface.sol"; import "./external/interfaces/WETH9Interface.sol"; -import "@uma/core/contracts/common/implementation/Testable.sol"; -import "@uma/core/contracts/common/implementation/MultiCaller.sol"; -import "@uma/core/contracts/common/interfaces/AddressWhitelistInterface.sol"; +import "contracts/external/uma/core/contracts/common/implementation/Testable.sol"; +import "contracts/external/uma/core/contracts/common/implementation/MultiCaller.sol"; +import "contracts/external/uma/core/contracts/common/interfaces/AddressWhitelistInterface.sol"; -import "@uma/core/contracts/data-verification-mechanism/interfaces/FinderInterface.sol"; -import "@uma/core/contracts/data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol"; -import "@uma/core/contracts/data-verification-mechanism/interfaces/StoreInterface.sol"; -import "@uma/core/contracts/data-verification-mechanism/implementation/Constants.sol"; +import "contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/FinderInterface.sol"; +import "contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol"; +import "contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/StoreInterface.sol"; +import "contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Constants.sol"; -import "@uma/core/contracts/optimistic-oracle-v2/interfaces/SkinnyOptimisticOracleInterface.sol"; -import "@uma/core/contracts/common/interfaces/ExpandedIERC20.sol"; +import "contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/SkinnyOptimisticOracleInterface.sol"; +import "contracts/external/uma/core/contracts/common/interfaces/ExpandedIERC20.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/utils/Address.sol"; /** * @notice Contract deployed on Ethereum that houses L1 token liquidity for all SpokePools. A dataworker can interact @@ -246,12 +246,10 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable { * @param chainId Chain with SpokePool to send message to. * @param functionData ABI encoded function call to send to SpokePool, but can be any arbitrary data technically. */ - function relaySpokePoolAdminFunction(uint256 chainId, bytes memory functionData) - public - override - onlyOwner - nonReentrant - { + function relaySpokePoolAdminFunction( + uint256 chainId, + bytes memory functionData + ) public override onlyOwner nonReentrant { _relaySpokePoolAdminFunction(chainId, functionData); } @@ -260,12 +258,10 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable { * @param newProtocolFeeCaptureAddress New protocol fee capture address. * @param newProtocolFeeCapturePct New protocol fee capture %. */ - function setProtocolFeeCapture(address newProtocolFeeCaptureAddress, uint256 newProtocolFeeCapturePct) - public - override - onlyOwner - nonReentrant - { + function setProtocolFeeCapture( + address newProtocolFeeCaptureAddress, + uint256 newProtocolFeeCapturePct + ) public override onlyOwner nonReentrant { require(newProtocolFeeCapturePct <= 1e18, "Bad protocolFeeCapturePct"); require(newProtocolFeeCaptureAddress != address(0), "Bad protocolFeeCaptureAddress"); protocolFeeCaptureAddress = newProtocolFeeCaptureAddress; @@ -278,13 +274,10 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable { * @param newBondToken New bond currency. * @param newBondAmount New bond amount. */ - function setBond(IERC20 newBondToken, uint256 newBondAmount) - public - override - onlyOwner - noActiveRequests - nonReentrant - { + function setBond( + IERC20 newBondToken, + uint256 newBondAmount + ) public override onlyOwner noActiveRequests nonReentrant { // Bond should not equal final fee otherwise every proposal will get cancelled in a dispute. // In practice we expect that bond amounts are set >> final fees so this shouldn't be an inconvenience. // The only way for the bond amount to be equal to the final fee is if the newBondAmount == 0. @@ -527,11 +520,10 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable { * @param relayedAmount The higher this amount, the higher the utilization. * @return % of liquid reserves currently being "used" and sitting in SpokePools plus the relayedAmount. */ - function liquidityUtilizationPostRelay(address l1Token, uint256 relayedAmount) - public - nonReentrant - returns (uint256) - { + function liquidityUtilizationPostRelay( + address l1Token, + uint256 relayedAmount + ) public nonReentrant returns (uint256) { return _liquidityUtilizationPostRelay(l1Token, relayedAmount); } @@ -826,12 +818,10 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable { * @return destinationToken address The destination token that is sent to spoke pools after this contract bridges * the l1Token to the destination chain. */ - function poolRebalanceRoute(uint256 destinationChainId, address l1Token) - external - view - override - returns (address destinationToken) - { + function poolRebalanceRoute( + uint256 destinationChainId, + address l1Token + ) external view override returns (address destinationToken) { return poolRebalanceRoutes[_poolRebalanceRouteKey(l1Token, destinationChainId)]; } @@ -1043,11 +1033,9 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable { return keccak256(abi.encode(l1Token, destinationChainId)); } - function _getInitializedCrossChainContracts(uint256 chainId) - internal - view - returns (address adapter, address spokePool) - { + function _getInitializedCrossChainContracts( + uint256 chainId + ) internal view returns (address adapter, address spokePool) { adapter = crossChainContracts[chainId].adapter; spokePool = crossChainContracts[chainId].spokePool; require(spokePool != address(0), "SpokePool not initialized"); diff --git a/contracts/Linea_SpokePool.sol b/contracts/Linea_SpokePool.sol index 926cba52d..22762eb37 100644 --- a/contracts/Linea_SpokePool.sol +++ b/contracts/Linea_SpokePool.sol @@ -7,8 +7,8 @@ pragma solidity ^0.8.19; import "./SpokePool.sol"; import "./libraries/CircleCCTPAdapter.sol"; import { IMessageService, ITokenBridge, IUSDCBridge } from "./external/interfaces/LineaInterfaces.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; /** * @notice Linea specific SpokePool. diff --git a/contracts/LpTokenFactory.sol b/contracts/LpTokenFactory.sol index b7e2e40c9..eafb7ce01 100644 --- a/contracts/LpTokenFactory.sol +++ b/contracts/LpTokenFactory.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import "./interfaces/LpTokenFactoryInterface.sol"; -import "@uma/core/contracts/common/implementation/ExpandedERC20.sol"; +import "contracts/external/uma/core/contracts/common/implementation/ExpandedERC20.sol"; /** * @notice Factory to create new LP ERC20 tokens that represent a liquidity provider's position. HubPool is the @@ -30,11 +30,7 @@ contract LpTokenFactory is LpTokenFactoryInterface { return address(lpToken); } - function _concatenate( - string memory a, - string memory b, - string memory c - ) internal pure returns (string memory) { + function _concatenate(string memory a, string memory b, string memory c) internal pure returns (string memory) { return string(abi.encodePacked(a, b, c)); } } diff --git a/contracts/MerkleLib.sol b/contracts/MerkleLib.sol index a606b29d0..688bfaf2b 100644 --- a/contracts/MerkleLib.sol +++ b/contracts/MerkleLib.sol @@ -5,7 +5,7 @@ import "./interfaces/SpokePoolInterface.sol"; import "./interfaces/V3SpokePoolInterface.sol"; import "./interfaces/HubPoolInterface.sol"; -import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; +import "@openzeppelin/contracts-v4/utils/cryptography/MerkleProof.sol"; /** * @notice Library to help with merkle roots, proofs, and claims. diff --git a/contracts/Ovm_SpokePool.sol b/contracts/Ovm_SpokePool.sol index 22c9e1e4d..97085ebca 100644 --- a/contracts/Ovm_SpokePool.sol +++ b/contracts/Ovm_SpokePool.sol @@ -5,7 +5,7 @@ import "./SpokePool.sol"; import "./external/interfaces/WETH9Interface.sol"; import "./libraries/CircleCCTPAdapter.sol"; -import "@openzeppelin/contracts-upgradeable/crosschain/optimism/LibOptimismUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable-v4/crosschain/optimism/LibOptimismUpgradeable.sol"; import "@eth-optimism/contracts/libraries/constants/Lib_PredeployAddresses.sol"; // https://github.com/ethereum-optimism/optimism/blob/bf51c4935261634120f31827c3910aa631f6bf9c/packages/contracts-bedrock/contracts/L2/L2StandardBridge.sol diff --git a/contracts/PermissionSplitterProxy.sol b/contracts/PermissionSplitterProxy.sol index eebbd07e0..76df44f6c 100644 --- a/contracts/PermissionSplitterProxy.sol +++ b/contracts/PermissionSplitterProxy.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.0; -import "@uma/core/contracts/common/implementation/MultiCaller.sol"; -import "@openzeppelin/contracts/access/AccessControl.sol"; +import "contracts/external/uma/core/contracts/common/implementation/MultiCaller.sol"; +import "@openzeppelin/contracts-v4/access/AccessControl.sol"; /** * @notice This contract is designed to own an Ownable "target" contract and gate access to specific diff --git a/contracts/PolygonTokenBridger.sol b/contracts/PolygonTokenBridger.sol index ab5dac349..4a623f62e 100644 --- a/contracts/PolygonTokenBridger.sol +++ b/contracts/PolygonTokenBridger.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.0; import "./Lockable.sol"; import "./external/interfaces/WETH9Interface.sol"; -import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable-v4/token/ERC20/IERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable-v4/token/ERC20/utils/SafeERC20Upgradeable.sol"; // Polygon Registry contract that stores their addresses. interface PolygonRegistry { diff --git a/contracts/PolygonZkEVM_SpokePool.sol b/contracts/PolygonZkEVM_SpokePool.sol index 0165a3913..25958643f 100644 --- a/contracts/PolygonZkEVM_SpokePool.sol +++ b/contracts/PolygonZkEVM_SpokePool.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.0; import "./SpokePool.sol"; import "./external/interfaces/IPolygonZkEVMBridge.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; /** * @notice Define interface for PolygonZkEVM Bridge message receiver @@ -18,11 +18,7 @@ interface IBridgeMessageReceiver { * @param originNetwork Polygon zkEVM's internal network id of source chain. * @param data Data to be received and executed on this contract. */ - function onMessageReceived( - address originAddress, - uint32 originNetwork, - bytes memory data - ) external payable; + function onMessageReceived(address originAddress, uint32 originNetwork, bytes memory data) external payable; } /** diff --git a/contracts/SpokePool.sol b/contracts/SpokePool.sol index b9656ddb2..f681417b0 100644 --- a/contracts/SpokePool.sol +++ b/contracts/SpokePool.sol @@ -15,12 +15,12 @@ import "./libraries/AddressConverters.sol"; import { IOFT, SendParam, MessagingFee } from "./interfaces/IOFT.sol"; import { OFTTransportAdapter } from "./libraries/OFTTransportAdapter.sol"; -import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; -import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; -import "@openzeppelin/contracts/utils/math/SignedMath.sol"; +import "@openzeppelin/contracts-upgradeable-v4/token/ERC20/IERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable-v4/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts-v4/utils/cryptography/SignatureChecker.sol"; +import "@openzeppelin/contracts-upgradeable-v4/proxy/utils/UUPSUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable-v4/security/ReentrancyGuardUpgradeable.sol"; +import "@openzeppelin/contracts-v4/utils/math/SignedMath.sol"; /** * @title SpokePool @@ -1727,6 +1727,88 @@ abstract contract SpokePool is updatedMessage ); } + + _emitFilledRelayEvent(relayExecution, relayData, relayer, fillType); + _transferTokensToRecipient(relayExecution, relayData, isSlowFill); + } + + /** + * @notice Emits the FilledRelay event for a completed relay fill. + * @param relayExecution The relay execution parameters. + * @param relayData The relay data. + * @param relayer The relayer address. + * @param fillType The type of fill being executed. + */ + function _emitFilledRelayEvent( + V3RelayExecutionParams memory relayExecution, + V3RelayData memory relayData, + bytes32 relayer, + FillType fillType + ) private { + emit FilledRelay( + relayData.inputToken, + relayData.outputToken, + relayData.inputAmount, + relayData.outputAmount, + relayExecution.repaymentChainId, + relayData.originChainId, + relayData.depositId, + relayData.fillDeadline, + relayData.exclusivityDeadline, + relayData.exclusiveRelayer, + relayer, + relayData.depositor, + relayData.recipient, + _hashNonEmptyMessage(relayData.message), + V3RelayExecutionEventInfo({ + updatedRecipient: relayExecution.updatedRecipient, + updatedMessageHash: _hashNonEmptyMessage(relayExecution.updatedMessage), + updatedOutputAmount: relayExecution.updatedOutputAmount, + fillType: fillType + }) + ); + } + + /** + * @notice Transfers tokens to the recipient based on the relay execution parameters. + * @param relayExecution The relay execution parameters. + * @param relayData The relay data. + * @param isSlowFill Whether this is a slow fill execution. + */ + function _transferTokensToRecipient( + V3RelayExecutionParams memory relayExecution, + V3RelayData memory relayData, + bool isSlowFill + ) private { + address outputToken = relayData.outputToken.toAddress(); + uint256 amountToSend = relayExecution.updatedOutputAmount; + address recipientToSend = relayExecution.updatedRecipient.toAddress(); + + // If relay token is wrappedNativeToken then unwrap and send native token. + if (outputToken == address(wrappedNativeToken)) { + // Note: useContractFunds is True if we want to send funds to the recipient directly out of this contract, + // otherwise we expect the caller to send funds to the recipient. If useContractFunds is True and the + // recipient wants wrappedNativeToken, then we can assume that wrappedNativeToken is already in the + // contract, otherwise we'll need the user to send wrappedNativeToken to this contract. Regardless, we'll + // need to unwrap it to native token before sending to the user. + if (!isSlowFill) IERC20Upgradeable(outputToken).safeTransferFrom(msg.sender, address(this), amountToSend); + _unwrapwrappedNativeTokenTo(payable(recipientToSend), amountToSend); + // Else, this is a normal ERC20 token. Send to recipient. + } else { + // Note: Similar to note above, send token directly from the contract to the user in the slow relay case. + if (!isSlowFill) IERC20Upgradeable(outputToken).safeTransferFrom(msg.sender, recipientToSend, amountToSend); + else IERC20Upgradeable(outputToken).safeTransfer(recipientToSend, amountToSend); + } + + bytes memory updatedMessage = relayExecution.updatedMessage; + if (updatedMessage.length > 0 && recipientToSend.isContract()) { + AcrossMessageHandler(recipientToSend).handleV3AcrossMessage( + outputToken, + amountToSend, + msg.sender, + updatedMessage + ); + } } // Determine whether the exclusivityDeadline implies active exclusivity. diff --git a/contracts/SpokePoolPeriphery.sol b/contracts/SpokePoolPeriphery.sol index 4733ac9c6..effdfe712 100644 --- a/contracts/SpokePoolPeriphery.sol +++ b/contracts/SpokePoolPeriphery.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.0; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; -import { Address } from "@openzeppelin/contracts/utils/Address.sol"; -import { MultiCaller } from "@uma/core/contracts/common/implementation/MultiCaller.sol"; -import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; -import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; +import { IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; +import { IERC20Permit } from "@openzeppelin/contracts-v4/token/ERC20/extensions/IERC20Permit.sol"; +import { Address } from "@openzeppelin/contracts-v4/utils/Address.sol"; +import { MultiCaller } from "contracts/external/uma/core/contracts/common/implementation/MultiCaller.sol"; +import { ReentrancyGuard } from "@openzeppelin/contracts-v4/security/ReentrancyGuard.sol"; +import { SignatureChecker } from "@openzeppelin/contracts-v4/utils/cryptography/SignatureChecker.sol"; +import { EIP712 } from "@openzeppelin/contracts-v4/utils/cryptography/EIP712.sol"; import { V3SpokePoolInterface } from "./interfaces/V3SpokePoolInterface.sol"; import { IERC20Auth } from "./external/interfaces/IERC20Auth.sol"; import { WETH9Interface } from "./external/interfaces/WETH9Interface.sol"; diff --git a/contracts/SpokePoolVerifier.sol b/contracts/SpokePoolVerifier.sol index a4f2774c9..c07946486 100644 --- a/contracts/SpokePoolVerifier.sol +++ b/contracts/SpokePoolVerifier.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import "@openzeppelin/contracts/utils/Address.sol"; +import "@openzeppelin/contracts-v4/utils/Address.sol"; import "./interfaces/V3SpokePoolInterface.sol"; import { AddressToBytes32 } from "./libraries/AddressConverters.sol"; diff --git a/contracts/Universal_SpokePool.sol b/contracts/Universal_SpokePool.sol index 4bf8b34ed..d9c87174a 100644 --- a/contracts/Universal_SpokePool.sol +++ b/contracts/Universal_SpokePool.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable-v4/access/OwnableUpgradeable.sol"; import { IHelios } from "./external/interfaces/IHelios.sol"; import "./libraries/CircleCCTPAdapter.sol"; diff --git a/contracts/ZkSync_SpokePool.sol b/contracts/ZkSync_SpokePool.sol index 83c51a633..2138cd9d7 100644 --- a/contracts/ZkSync_SpokePool.sol +++ b/contracts/ZkSync_SpokePool.sol @@ -1,18 +1,14 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import { CircleCCTPAdapter, CircleDomainIds, ITokenMessenger } from "./libraries/CircleCCTPAdapter.sol"; import { CrossDomainAddressUtils } from "./libraries/CrossDomainAddressUtils.sol"; import "./SpokePool.sol"; // https://github.com/matter-labs/era-contracts/blob/6391c0d7bf6184d7f6718060e3991ba6f0efe4a7/zksync/contracts/bridge/L2ERC20Bridge.sol#L104 interface ZkBridgeLike { - function withdraw( - address _l1Receiver, - address _l2Token, - uint256 _amount - ) external; + function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external; } interface IL2ETH { diff --git a/contracts/chain-adapters/Arbitrum_Adapter.sol b/contracts/chain-adapters/Arbitrum_Adapter.sol index 1f215c8d0..493b0e8b0 100644 --- a/contracts/chain-adapters/Arbitrum_Adapter.sol +++ b/contracts/chain-adapters/Arbitrum_Adapter.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.0; import "./interfaces/AdapterInterface.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import { IOFT } from "../interfaces/IOFT.sol"; import "../external/interfaces/CCTPInterfaces.sol"; import "../libraries/CircleCCTPAdapter.sol"; diff --git a/contracts/chain-adapters/Arbitrum_CustomGasToken_Adapter.sol b/contracts/chain-adapters/Arbitrum_CustomGasToken_Adapter.sol index 1093145ac..8e7369cb0 100644 --- a/contracts/chain-adapters/Arbitrum_CustomGasToken_Adapter.sol +++ b/contracts/chain-adapters/Arbitrum_CustomGasToken_Adapter.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.0; import { AdapterInterface } from "./interfaces/AdapterInterface.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import { ITokenMessenger as ICCTPTokenMessenger } from "../external/interfaces/CCTPInterfaces.sol"; import { CircleCCTPAdapter, CircleDomainIds } from "../libraries/CircleCCTPAdapter.sol"; import { ArbitrumERC20Bridge as ArbitrumL1ERC20Bridge, ArbitrumCustomGasTokenInbox as ArbitrumL1InboxLike, ArbitrumL1ERC20GatewayLike } from "../interfaces/ArbitrumBridge.sol"; @@ -243,7 +243,7 @@ contract Arbitrum_CustomGasToken_Adapter is AdapterInterface, CircleCCTPAdapter return amount; } else if (NATIVE_TOKEN_DECIMALS < 18) { // Round up the division result so that the L1 call value is always sufficient to cover the submission fee. - uint256 reductionFactor = 10**(18 - NATIVE_TOKEN_DECIMALS); + uint256 reductionFactor = 10 ** (18 - NATIVE_TOKEN_DECIMALS); uint256 divFloor = amount / reductionFactor; uint256 mod = amount % reductionFactor; if (mod != 0) { @@ -252,7 +252,7 @@ contract Arbitrum_CustomGasToken_Adapter is AdapterInterface, CircleCCTPAdapter return divFloor; } } else { - return amount * 10**(NATIVE_TOKEN_DECIMALS - 18); + return amount * 10 ** (NATIVE_TOKEN_DECIMALS - 18); } } @@ -260,9 +260,9 @@ contract Arbitrum_CustomGasToken_Adapter is AdapterInterface, CircleCCTPAdapter if (NATIVE_TOKEN_DECIMALS == 18) { return amount; } else if (NATIVE_TOKEN_DECIMALS < 18) { - return amount * 10**(18 - NATIVE_TOKEN_DECIMALS); + return amount * 10 ** (18 - NATIVE_TOKEN_DECIMALS); } else { - return amount / 10**(NATIVE_TOKEN_DECIMALS - 18); + return amount / 10 ** (NATIVE_TOKEN_DECIMALS - 18); } } } diff --git a/contracts/chain-adapters/Arbitrum_RescueAdapter.sol b/contracts/chain-adapters/Arbitrum_RescueAdapter.sol index 1a66e5133..80aeb0009 100644 --- a/contracts/chain-adapters/Arbitrum_RescueAdapter.sol +++ b/contracts/chain-adapters/Arbitrum_RescueAdapter.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.0; import "./interfaces/AdapterInterface.sol"; import "./Arbitrum_Adapter.sol"; // Used to import `ArbitrumL1ERC20GatewayLike` and `ArbitrumL1InboxLike` -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; /** * @notice Meant to copy the Arbitrum_Adapter exactly in how it sends L1 --> L2 messages but is designed only to be @@ -84,12 +84,7 @@ contract Arbitrum_RescueAdapter is AdapterInterface { /** * @notice Should never be called. */ - function relayTokens( - address, - address, - uint256, - address - ) external payable override { + function relayTokens(address, address, uint256, address) external payable override { revert("useless function"); } diff --git a/contracts/chain-adapters/Arbitrum_SendTokensAdapter.sol b/contracts/chain-adapters/Arbitrum_SendTokensAdapter.sol index 344684363..6ec9614e7 100644 --- a/contracts/chain-adapters/Arbitrum_SendTokensAdapter.sol +++ b/contracts/chain-adapters/Arbitrum_SendTokensAdapter.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.0; import "./interfaces/AdapterInterface.sol"; import { ArbitrumL1ERC20GatewayLike } from "./Arbitrum_Adapter.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; /** * @notice This adapter is built for emergencies to send funds from the Hub to a Spoke in the event that a spoke pool @@ -70,12 +70,7 @@ contract Arbitrum_SendTokensAdapter is AdapterInterface { /** * @notice Should never be called. */ - function relayTokens( - address, - address, - uint256, - address - ) external payable override { + function relayTokens(address, address, uint256, address) external payable override { revert("relayTokens disabled"); } diff --git a/contracts/chain-adapters/Base_Adapter.sol b/contracts/chain-adapters/Base_Adapter.sol index f79f1b7e1..9b9b2bc05 100644 --- a/contracts/chain-adapters/Base_Adapter.sol +++ b/contracts/chain-adapters/Base_Adapter.sol @@ -9,8 +9,8 @@ import "../external/interfaces/WETH9Interface.sol"; import "./CrossDomainEnabled.sol"; import "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import "../libraries/CircleCCTPAdapter.sol"; import "../external/interfaces/CCTPInterfaces.sol"; @@ -70,12 +70,7 @@ contract Base_Adapter is CrossDomainEnabled, AdapterInterface, CircleCCTPAdapter * @param amount Amount of L1 tokens to deposit and L2 tokens to receive. * @param to Bridge recipient. */ - function relayTokens( - address l1Token, - address l2Token, - uint256 amount, - address to - ) external payable override { + function relayTokens(address l1Token, address l2Token, uint256 amount, address to) external payable override { // If the l1Token is weth then unwrap it to ETH then send the ETH to the standard bridge. if (l1Token == address(L1_WETH)) { L1_WETH.withdraw(amount); diff --git a/contracts/chain-adapters/Blast_Adapter.sol b/contracts/chain-adapters/Blast_Adapter.sol index e3cdc514d..a5a8cf410 100644 --- a/contracts/chain-adapters/Blast_Adapter.sol +++ b/contracts/chain-adapters/Blast_Adapter.sol @@ -9,8 +9,8 @@ import "../external/interfaces/WETH9Interface.sol"; import "./CrossDomainEnabled.sol"; import { IL1StandardBridge } from "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import "../libraries/CircleCCTPAdapter.sol"; import "../external/interfaces/CCTPInterfaces.sol"; @@ -102,12 +102,7 @@ contract Blast_Adapter is CrossDomainEnabled, AdapterInterface, CircleCCTPAdapte * @param amount Amount of L1 tokens to deposit and L2 tokens to receive. * @param to Bridge recipient. */ - function relayTokens( - address l1Token, - address l2Token, - uint256 amount, - address to - ) external payable override { + function relayTokens(address l1Token, address l2Token, uint256 amount, address to) external payable override { // If token can be bridged into yield-ing version of ERC20 on L2 side, then use Blast Bridge, otherwise // use standard bridge. diff --git a/contracts/chain-adapters/Blast_RescueAdapter.sol b/contracts/chain-adapters/Blast_RescueAdapter.sol index 5b4140983..bf501f99d 100644 --- a/contracts/chain-adapters/Blast_RescueAdapter.sol +++ b/contracts/chain-adapters/Blast_RescueAdapter.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.0; import "./interfaces/AdapterInterface.sol"; import { USDYieldManager } from "../Blast_DaiRetriever.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; /** * @notice This adapter is built to to retrieve Blast USDB from the USDBYieldManager contract on Ethereum that was @@ -41,12 +41,7 @@ contract Blast_RescueAdapter is AdapterInterface { /** * @notice Should never be called. */ - function relayTokens( - address, - address, - uint256, - address - ) external payable override { + function relayTokens(address, address, uint256, address) external payable override { revert("relayTokens disabled"); } } diff --git a/contracts/chain-adapters/Boba_Adapter.sol b/contracts/chain-adapters/Boba_Adapter.sol index 76e23af07..bc859dcd9 100644 --- a/contracts/chain-adapters/Boba_Adapter.sol +++ b/contracts/chain-adapters/Boba_Adapter.sol @@ -9,8 +9,8 @@ import "../external/interfaces/WETH9Interface.sol"; import "./CrossDomainEnabled.sol"; import "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; /** * @notice Contract containing logic to send messages from L1 to Boba. This is a modified version of the Optimism adapter @@ -62,12 +62,7 @@ contract Boba_Adapter is CrossDomainEnabled, AdapterInterface { * @param amount Amount of L1 tokens to deposit and L2 tokens to receive. * @param to Bridge recipient. */ - function relayTokens( - address l1Token, - address l2Token, - uint256 amount, - address to - ) external payable override { + function relayTokens(address l1Token, address l2Token, uint256 amount, address to) external payable override { // If the l1Token is weth then unwrap it to ETH then send the ETH to the standard bridge. if (l1Token == address(L1_WETH)) { L1_WETH.withdraw(amount); diff --git a/contracts/chain-adapters/DoctorWho_Adapter.sol b/contracts/chain-adapters/DoctorWho_Adapter.sol index 75af3aa41..2275892f0 100644 --- a/contracts/chain-adapters/DoctorWho_Adapter.sol +++ b/contracts/chain-adapters/DoctorWho_Adapter.sol @@ -9,8 +9,8 @@ import "../external/interfaces/WETH9Interface.sol"; import "./CrossDomainEnabled.sol"; import "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import "../libraries/CircleCCTPAdapter.sol"; import "../external/interfaces/CCTPInterfaces.sol"; @@ -73,12 +73,7 @@ contract DoctorWho_Adapter is CrossDomainEnabled, AdapterInterface, CircleCCTPAd * @param amount Amount of L1 tokens to deposit and L2 tokens to receive. * @param to Bridge recipient. */ - function relayTokens( - address l1Token, - address l2Token, - uint256 amount, - address to - ) external payable override { + function relayTokens(address l1Token, address l2Token, uint256 amount, address to) external payable override { // If the l1Token is weth then unwrap it to ETH then send the ETH to the standard bridge. if (l1Token == address(L1_WETH)) { L1_WETH.withdraw(amount); diff --git a/contracts/chain-adapters/DonationBox.sol b/contracts/chain-adapters/DonationBox.sol index 6c1221848..9a0eff508 100644 --- a/contracts/chain-adapters/DonationBox.sol +++ b/contracts/chain-adapters/DonationBox.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { Ownable } from "@openzeppelin/contracts-v4/access/Ownable.sol"; +import { IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; /** * @notice Users can donate tokens to this contract that only the owner can withdraw. diff --git a/contracts/chain-adapters/Ethereum_Adapter.sol b/contracts/chain-adapters/Ethereum_Adapter.sol index ceef12ab3..b6c5ea07a 100644 --- a/contracts/chain-adapters/Ethereum_Adapter.sol +++ b/contracts/chain-adapters/Ethereum_Adapter.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.0; import "./interfaces/AdapterInterface.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; /** * @custom:security-contact bugs@across.to diff --git a/contracts/chain-adapters/Ethereum_RescueAdapter.sol b/contracts/chain-adapters/Ethereum_RescueAdapter.sol index c8fc200ac..cebb14462 100644 --- a/contracts/chain-adapters/Ethereum_RescueAdapter.sol +++ b/contracts/chain-adapters/Ethereum_RescueAdapter.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.0; import "./interfaces/AdapterInterface.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; /** * @notice This adapter is built for emergencies to rescue funds from a Hub in the event of a misconfiguration or @@ -38,12 +38,7 @@ contract Ethereum_RescueAdapter is AdapterInterface { /** * @notice Should never be called. */ - function relayTokens( - address, - address, - uint256, - address - ) external payable override { + function relayTokens(address, address, uint256, address) external payable override { revert("relayTokens disabled"); } } diff --git a/contracts/chain-adapters/ForwarderBase.sol b/contracts/chain-adapters/ForwarderBase.sol index 0e793cded..a4ddc1b9e 100644 --- a/contracts/chain-adapters/ForwarderBase.sol +++ b/contracts/chain-adapters/ForwarderBase.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable-v4/proxy/utils/UUPSUpgradeable.sol"; +import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable-v4/security/ReentrancyGuardUpgradeable.sol"; import { ForwarderInterface } from "./interfaces/ForwarderInterface.sol"; import { AdapterInterface } from "./interfaces/AdapterInterface.sol"; -import { MultiCaller } from "@uma/core/contracts/common/implementation/MultiCaller.sol"; +import { MultiCaller } from "../external/uma/core/contracts/common/implementation/MultiCaller.sol"; import { WETH9Interface } from "../external/interfaces/WETH9Interface.sol"; /** diff --git a/contracts/chain-adapters/Linea_Adapter.sol b/contracts/chain-adapters/Linea_Adapter.sol index 1af43e06c..97e3afb41 100644 --- a/contracts/chain-adapters/Linea_Adapter.sol +++ b/contracts/chain-adapters/Linea_Adapter.sol @@ -7,8 +7,8 @@ import "../libraries/CircleCCTPAdapter.sol"; import { IMessageService, ITokenBridge, IUSDCBridge } from "../external/interfaces/LineaInterfaces.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; /** * @notice Supports sending messages and tokens from L1 to Linea. @@ -60,12 +60,7 @@ contract Linea_Adapter is AdapterInterface, CircleCCTPAdapter { * @param amount Amount of L1 tokens to deposit and L2 tokens to receive. * @param to Bridge recipient. */ - function relayTokens( - address l1Token, - address l2Token, - uint256 amount, - address to - ) external payable override { + function relayTokens(address l1Token, address l2Token, uint256 amount, address to) external payable override { if (l1Token == address(usdcToken) && _isCCTPEnabled()) { _transferUsdc(to, amount); } diff --git a/contracts/chain-adapters/Lisk_Adapter.sol b/contracts/chain-adapters/Lisk_Adapter.sol index bea4f65f0..ea240f6ef 100644 --- a/contracts/chain-adapters/Lisk_Adapter.sol +++ b/contracts/chain-adapters/Lisk_Adapter.sol @@ -9,8 +9,8 @@ import "../external/interfaces/WETH9Interface.sol"; import "./CrossDomainEnabled.sol"; import "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import "../libraries/CircleCCTPAdapter.sol"; import "../external/interfaces/CCTPInterfaces.sol"; @@ -75,12 +75,7 @@ contract Lisk_Adapter is CrossDomainEnabled, AdapterInterface, CircleCCTPAdapter * @param amount Amount of L1 tokens to deposit and L2 tokens to receive. * @param to Bridge recipient. */ - function relayTokens( - address l1Token, - address l2Token, - uint256 amount, - address to - ) external payable override { + function relayTokens(address l1Token, address l2Token, uint256 amount, address to) external payable override { // If the l1Token is weth then unwrap it to ETH then send the ETH to the standard bridge. if (l1Token == address(L1_WETH)) { L1_WETH.withdraw(amount); diff --git a/contracts/chain-adapters/Mock_Adapter.sol b/contracts/chain-adapters/Mock_Adapter.sol index 3095ab138..88caa7ba6 100644 --- a/contracts/chain-adapters/Mock_Adapter.sol +++ b/contracts/chain-adapters/Mock_Adapter.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import "./interfaces/AdapterInterface.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; /** * @notice Contract used for testing communication between HubPool and Adapter. @@ -25,12 +25,7 @@ contract Mock_Adapter is AdapterInterface { emit RelayMessageCalled(target, message, msg.sender); } - function relayTokens( - address l1Token, - address l2Token, - uint256 amount, - address to - ) external payable override { + function relayTokens(address l1Token, address l2Token, uint256 amount, address to) external payable override { IERC20(l1Token).approve(address(bridge), amount); bridge.bridgeTokens(l1Token, amount); emit RelayTokensCalled(l1Token, l2Token, amount, to, msg.sender); diff --git a/contracts/chain-adapters/Mode_Adapter.sol b/contracts/chain-adapters/Mode_Adapter.sol index 29e398779..0467183e1 100644 --- a/contracts/chain-adapters/Mode_Adapter.sol +++ b/contracts/chain-adapters/Mode_Adapter.sol @@ -9,8 +9,8 @@ import "../external/interfaces/WETH9Interface.sol"; import "./CrossDomainEnabled.sol"; import "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import "../libraries/CircleCCTPAdapter.sol"; import "../external/interfaces/CCTPInterfaces.sol"; @@ -75,12 +75,7 @@ contract Mode_Adapter is CrossDomainEnabled, AdapterInterface, CircleCCTPAdapter * @param amount Amount of L1 tokens to deposit and L2 tokens to receive. * @param to Bridge recipient. */ - function relayTokens( - address l1Token, - address l2Token, - uint256 amount, - address to - ) external payable override { + function relayTokens(address l1Token, address l2Token, uint256 amount, address to) external payable override { // If the l1Token is weth then unwrap it to ETH then send the ETH to the standard bridge. if (l1Token == address(L1_WETH)) { L1_WETH.withdraw(amount); diff --git a/contracts/chain-adapters/OP_Adapter.sol b/contracts/chain-adapters/OP_Adapter.sol index d33817649..0fcf1a975 100644 --- a/contracts/chain-adapters/OP_Adapter.sol +++ b/contracts/chain-adapters/OP_Adapter.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import { CircleCCTPAdapter, CircleDomainIds } from "../libraries/CircleCCTPAdapter.sol"; import { ITokenMessenger } from "../external/interfaces/CCTPInterfaces.sol"; @@ -79,12 +79,7 @@ contract OP_Adapter is CrossDomainEnabled, AdapterInterface, CircleCCTPAdapter { * @param amount Amount of L1 tokens to deposit and L2 tokens to receive. * @param to Bridge recipient. */ - function relayTokens( - address l1Token, - address l2Token, - uint256 amount, - address to - ) external payable override { + function relayTokens(address l1Token, address l2Token, uint256 amount, address to) external payable override { // If the l1Token is weth then unwrap it to ETH then send the ETH to the standard bridge. if (l1Token == address(L1_WETH)) { L1_WETH.withdraw(amount); diff --git a/contracts/chain-adapters/Optimism_Adapter.sol b/contracts/chain-adapters/Optimism_Adapter.sol index 22eadabec..ab0a09f79 100644 --- a/contracts/chain-adapters/Optimism_Adapter.sol +++ b/contracts/chain-adapters/Optimism_Adapter.sol @@ -11,8 +11,8 @@ import "../external/interfaces/CCTPInterfaces.sol"; import "./CrossDomainEnabled.sol"; import "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; /** * @notice Interface for Synthetix custom bridge to Optimism. @@ -93,12 +93,7 @@ contract Optimism_Adapter is CrossDomainEnabled, AdapterInterface, CircleCCTPAda * @param amount Amount of L1 tokens to deposit and L2 tokens to receive. * @param to Bridge recipient. */ - function relayTokens( - address l1Token, - address l2Token, - uint256 amount, - address to - ) external payable override { + function relayTokens(address l1Token, address l2Token, uint256 amount, address to) external payable override { // If the l1Token is weth then unwrap it to ETH then send the ETH to the standard bridge. if (l1Token == address(L1_WETH)) { L1_WETH.withdraw(amount); diff --git a/contracts/chain-adapters/Ovm_Forwarder.sol b/contracts/chain-adapters/Ovm_Forwarder.sol index 116dcba4d..c950dff7d 100644 --- a/contracts/chain-adapters/Ovm_Forwarder.sol +++ b/contracts/chain-adapters/Ovm_Forwarder.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import { ForwarderBase } from "./ForwarderBase.sol"; -import { LibOptimismUpgradeable } from "@openzeppelin/contracts-upgradeable/crosschain/optimism/LibOptimismUpgradeable.sol"; +import { LibOptimismUpgradeable } from "@openzeppelin/contracts-upgradeable-v4/crosschain/optimism/LibOptimismUpgradeable.sol"; import { Lib_PredeployAddresses } from "@eth-optimism/contracts/libraries/constants/Lib_PredeployAddresses.sol"; import { WETH9Interface } from "../external/interfaces/WETH9Interface.sol"; diff --git a/contracts/chain-adapters/PolygonZkEVM_Adapter.sol b/contracts/chain-adapters/PolygonZkEVM_Adapter.sol index 318e87195..08b8a9cf0 100644 --- a/contracts/chain-adapters/PolygonZkEVM_Adapter.sol +++ b/contracts/chain-adapters/PolygonZkEVM_Adapter.sol @@ -5,8 +5,8 @@ import "./interfaces/AdapterInterface.sol"; import "../external/interfaces/WETH9Interface.sol"; import "../external/interfaces/IPolygonZkEVMBridge.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; /** * @notice Supports sending messages and tokens from L1 to PolygonZkEVM. @@ -51,12 +51,7 @@ contract PolygonZkEVM_Adapter is AdapterInterface { * @param amount Amount of L1 tokens to deposit and L2 tokens to receive. * @param to Bridge recipient. */ - function relayTokens( - address l1Token, - address l2Token, - uint256 amount, - address to - ) external payable override { + function relayTokens(address l1Token, address l2Token, uint256 amount, address to) external payable override { // The mapped WETH address in the native Polygon zkEVM bridge contract does not match // the official WETH address. Therefore, if the l1Token is WETH then unwrap it to ETH // and send the ETH directly via as msg.value. diff --git a/contracts/chain-adapters/Polygon_Adapter.sol b/contracts/chain-adapters/Polygon_Adapter.sol index 37698a16f..08ea8005e 100644 --- a/contracts/chain-adapters/Polygon_Adapter.sol +++ b/contracts/chain-adapters/Polygon_Adapter.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.0; import "./interfaces/AdapterInterface.sol"; import "../external/interfaces/WETH9Interface.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import "../libraries/CircleCCTPAdapter.sol"; import "../external/interfaces/CCTPInterfaces.sol"; diff --git a/contracts/chain-adapters/Redstone_Adapter.sol b/contracts/chain-adapters/Redstone_Adapter.sol index 65fc07a42..8cb5926c8 100644 --- a/contracts/chain-adapters/Redstone_Adapter.sol +++ b/contracts/chain-adapters/Redstone_Adapter.sol @@ -9,8 +9,8 @@ import "../external/interfaces/WETH9Interface.sol"; import "./CrossDomainEnabled.sol"; import "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import "../libraries/CircleCCTPAdapter.sol"; import "../external/interfaces/CCTPInterfaces.sol"; @@ -75,12 +75,7 @@ contract Redstone_Adapter is CrossDomainEnabled, AdapterInterface, CircleCCTPAda * @param amount Amount of L1 tokens to deposit and L2 tokens to receive. * @param to Bridge recipient. */ - function relayTokens( - address l1Token, - address l2Token, - uint256 amount, - address to - ) external payable override { + function relayTokens(address l1Token, address l2Token, uint256 amount, address to) external payable override { // If the l1Token is weth then unwrap it to ETH then send the ETH to the standard bridge. if (l1Token == address(L1_WETH)) { L1_WETH.withdraw(amount); diff --git a/contracts/chain-adapters/Scroll_Adapter.sol b/contracts/chain-adapters/Scroll_Adapter.sol index aa7ec7509..5c7dc1eeb 100644 --- a/contracts/chain-adapters/Scroll_Adapter.sol +++ b/contracts/chain-adapters/Scroll_Adapter.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import "@scroll-tech/contracts/L1/gateways/IL1GatewayRouter.sol"; import "@scroll-tech/contracts/L1/rollup/IL2GasPriceOracle.sol"; import "@scroll-tech/contracts/L1/IL1ScrollMessenger.sol"; @@ -103,12 +103,7 @@ contract Scroll_Adapter is AdapterInterface { * @param amount Amount of `l1Token` to bridge. * @param to Bridge recipient. */ - function relayTokens( - address l1Token, - address l2Token, - uint256 amount, - address to - ) external payable { + function relayTokens(address l1Token, address l2Token, uint256 amount, address to) external payable { IL1GatewayRouter _l1GatewayRouter = L1_GATEWAY_ROUTER; // Confirm that the l2Token that we're trying to send is the correct counterpart diff --git a/contracts/chain-adapters/Solana_Adapter.sol b/contracts/chain-adapters/Solana_Adapter.sol index f85cd1e46..f3c1742c5 100644 --- a/contracts/chain-adapters/Solana_Adapter.sol +++ b/contracts/chain-adapters/Solana_Adapter.sol @@ -7,7 +7,7 @@ import { AdapterInterface } from "./interfaces/AdapterInterface.sol"; import { CircleCCTPAdapter, CircleDomainIds } from "../libraries/CircleCCTPAdapter.sol"; import { Bytes32ToAddress } from "../libraries/AddressConverters.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; /** * @notice Contract containing logic to send messages from L1 to Solana via CCTP. @@ -119,12 +119,7 @@ contract Solana_Adapter is AdapterInterface, CircleCCTPAdapter { * @param amount Amount of L1 tokens to deposit and L2 tokens to receive. * @param to Bridge recipient. */ - function relayTokens( - address l1Token, - address l2Token, - uint256 amount, - address to - ) external payable override { + function relayTokens(address l1Token, address l2Token, uint256 amount, address to) external payable override { if (l1Token != address(usdcToken)) { revert InvalidL1Token(l1Token); } diff --git a/contracts/chain-adapters/ZkStack_Adapter.sol b/contracts/chain-adapters/ZkStack_Adapter.sol index 81b379da1..166dda74f 100644 --- a/contracts/chain-adapters/ZkStack_Adapter.sol +++ b/contracts/chain-adapters/ZkStack_Adapter.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.0; import "./interfaces/AdapterInterface.sol"; import "../external/interfaces/WETH9Interface.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import { BridgeHubInterface } from "../interfaces/ZkStackBridgeHub.sol"; import { CircleCCTPAdapter } from "../libraries/CircleCCTPAdapter.sol"; import { ITokenMessenger } from "../external/interfaces/CCTPInterfaces.sol"; diff --git a/contracts/chain-adapters/ZkStack_CustomGasToken_Adapter.sol b/contracts/chain-adapters/ZkStack_CustomGasToken_Adapter.sol index cf638b223..b0562c8bb 100644 --- a/contracts/chain-adapters/ZkStack_CustomGasToken_Adapter.sol +++ b/contracts/chain-adapters/ZkStack_CustomGasToken_Adapter.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.0; import "./interfaces/AdapterInterface.sol"; import "../external/interfaces/WETH9Interface.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import { BridgeHubInterface } from "../interfaces/ZkStackBridgeHub.sol"; import { CircleCCTPAdapter } from "../libraries/CircleCCTPAdapter.sol"; import { ITokenMessenger } from "../external/interfaces/CCTPInterfaces.sol"; diff --git a/contracts/chain-adapters/ZkSync_Adapter.sol b/contracts/chain-adapters/ZkSync_Adapter.sol index 0826ece0a..8976a0b13 100644 --- a/contracts/chain-adapters/ZkSync_Adapter.sol +++ b/contracts/chain-adapters/ZkSync_Adapter.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.0; import "./interfaces/AdapterInterface.sol"; import "../external/interfaces/WETH9Interface.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; interface ZkSyncInterface { // _contractL2: L2 address of the contract to be called. @@ -171,11 +171,7 @@ contract ZkSync_Adapter is AdapterInterface { * @param _l2RefundAddress address that recieves excess gas refunds on L2. * @param _maxTxGasPrice The maximum effective gas price any transaction sent to this adapter may have. */ - constructor( - WETH9Interface _l1Weth, - address _l2RefundAddress, - uint256 _maxTxGasPrice - ) { + constructor(WETH9Interface _l1Weth, address _l2RefundAddress, uint256 _maxTxGasPrice) { l1Weth = _l1Weth; l2RefundAddress = _l2RefundAddress; MAX_TX_GASPRICE = _maxTxGasPrice; diff --git a/contracts/chain-adapters/Zora_Adapter.sol b/contracts/chain-adapters/Zora_Adapter.sol index d0de83ceb..188f12aec 100644 --- a/contracts/chain-adapters/Zora_Adapter.sol +++ b/contracts/chain-adapters/Zora_Adapter.sol @@ -9,8 +9,8 @@ import "../external/interfaces/WETH9Interface.sol"; import "./CrossDomainEnabled.sol"; import "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import "../libraries/CircleCCTPAdapter.sol"; import "../external/interfaces/CCTPInterfaces.sol"; @@ -75,12 +75,7 @@ contract Zora_Adapter is CrossDomainEnabled, AdapterInterface, CircleCCTPAdapter * @param amount Amount of L1 tokens to deposit and L2 tokens to receive. * @param to Bridge recipient. */ - function relayTokens( - address l1Token, - address l2Token, - uint256 amount, - address to - ) external payable override { + function relayTokens(address l1Token, address l2Token, uint256 amount, address to) external payable override { // If the l1Token is weth then unwrap it to ETH then send the ETH to the standard bridge. if (l1Token == address(L1_WETH)) { L1_WETH.withdraw(amount); diff --git a/contracts/chain-adapters/l2/Arbitrum_WithdrawalHelper.sol b/contracts/chain-adapters/l2/Arbitrum_WithdrawalHelper.sol index c4416d7d5..a04ffe530 100644 --- a/contracts/chain-adapters/l2/Arbitrum_WithdrawalHelper.sol +++ b/contracts/chain-adapters/l2/Arbitrum_WithdrawalHelper.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import { ArbitrumL2ERC20GatewayLike } from "../../interfaces/ArbitrumBridge.sol"; import { WithdrawalHelperBase } from "./WithdrawalHelperBase.sol"; import { ITokenMessenger } from "../../external/interfaces/CCTPInterfaces.sol"; @@ -62,11 +62,7 @@ contract Arbitrum_WithdrawalHelper is WithdrawalHelperBase { * @param l2Token Address of the L2 token to send back. * @param amountToReturn Amount of l2Token to send back. */ - function withdrawToken( - address l1Token, - address l2Token, - uint256 amountToReturn - ) public override { + function withdrawToken(address l1Token, address l2Token, uint256 amountToReturn) public override { // If the l2TokenAddress is UDSC, we need to use the CCTP bridge. if (l2Token == address(usdcToken) && _isCCTPEnabled()) { _transferUsdc(TOKEN_RECIPIENT, amountToReturn); diff --git a/contracts/chain-adapters/l2/Ovm_WithdrawalHelper.sol b/contracts/chain-adapters/l2/Ovm_WithdrawalHelper.sol index 68aab32ba..c42da08bc 100644 --- a/contracts/chain-adapters/l2/Ovm_WithdrawalHelper.sol +++ b/contracts/chain-adapters/l2/Ovm_WithdrawalHelper.sol @@ -2,12 +2,12 @@ pragma solidity ^0.8.0; import { WithdrawalHelperBase } from "./WithdrawalHelperBase.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import { WETH9Interface } from "../../external/interfaces/WETH9Interface.sol"; import { ITokenMessenger } from "../../external/interfaces/CCTPInterfaces.sol"; import { Lib_PredeployAddresses } from "@eth-optimism/contracts/libraries/constants/Lib_PredeployAddresses.sol"; -import { LibOptimismUpgradeable } from "@openzeppelin/contracts-upgradeable/crosschain/optimism/LibOptimismUpgradeable.sol"; +import { LibOptimismUpgradeable } from "@openzeppelin/contracts-upgradeable-v4/crosschain/optimism/LibOptimismUpgradeable.sol"; import { IL2ERC20Bridge } from "../../Ovm_SpokePool.sol"; /** @@ -102,11 +102,7 @@ contract Ovm_WithdrawalHelper is WithdrawalHelperBase { * New lines of code correspond to instances where this contract queries state from the spoke pool, such as determining * the appropriate token bridge for the withdrawal or finding the remoteL1Token to withdraw. */ - function withdrawToken( - address, - address l2Token, - uint256 amountToReturn - ) public override { + function withdrawToken(address, address l2Token, uint256 amountToReturn) public override { // Fetch the current l1Gas defined in the Ovm_SpokePool. uint32 l1Gas = spokePool.l1Gas(); // If the token being bridged is WETH then we need to first unwrap it to ETH and then send ETH over the diff --git a/contracts/chain-adapters/l2/WithdrawalHelperBase.sol b/contracts/chain-adapters/l2/WithdrawalHelperBase.sol index f8de7a86d..e75be0e84 100644 --- a/contracts/chain-adapters/l2/WithdrawalHelperBase.sol +++ b/contracts/chain-adapters/l2/WithdrawalHelperBase.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { MultiCaller } from "@uma/core/contracts/common/implementation/MultiCaller.sol"; +import { IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import { MultiCaller } from "../../external/uma/core/contracts/common/implementation/MultiCaller.sol"; import { CircleCCTPAdapter, ITokenMessenger, CircleDomainIds } from "../../libraries/CircleCCTPAdapter.sol"; import { WETH9Interface } from "../../external/interfaces/WETH9Interface.sol"; -import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable-v4/proxy/utils/UUPSUpgradeable.sol"; /** * @title WithdrawalHelperBase @@ -102,11 +102,7 @@ abstract contract WithdrawalHelperBase is CircleCCTPAdapter, MultiCaller, UUPSUp * L1/L2 given knowledge of only one of the addresses. Both arguments are provided to enable a flexible interface; however, due * to this, `withdrawToken` MUST account for situations where the L1/L2 token mapping is incorrect. */ - function withdrawToken( - address l1Token, - address l2Token, - uint256 amountToReturn - ) public virtual; + function withdrawToken(address l1Token, address l2Token, uint256 amountToReturn) public virtual; /* * @notice Wraps the contract's entire balance of the native token. diff --git a/contracts/erc1155/MintableERC1155.sol b/contracts/erc1155/MintableERC1155.sol index 7603c9b4d..7df725fca 100644 --- a/contracts/erc1155/MintableERC1155.sol +++ b/contracts/erc1155/MintableERC1155.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "@openzeppelin/contracts-v4/token/ERC1155/ERC1155.sol"; /** * @title MintableERC1155 @@ -26,11 +26,7 @@ contract MintableERC1155 is ERC1155, Ownable { * @param tokenId Token type to airdrop. * @param amount Amount of token types to airdrop. */ - function airdrop( - uint256 tokenId, - address[] memory recipients, - uint256 amount - ) public onlyOwner { + function airdrop(uint256 tokenId, address[] memory recipients, uint256 amount) public onlyOwner { for (uint256 i = 0; i < recipients.length; i++) { _mint(recipients[i], tokenId, amount, ""); } diff --git a/contracts/erc7683/AcrossOriginSettler.sol b/contracts/erc7683/AcrossOriginSettler.sol index 39b6a061e..067a521f4 100644 --- a/contracts/erc7683/AcrossOriginSettler.sol +++ b/contracts/erc7683/AcrossOriginSettler.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/access/Ownable.sol"; import { ERC7683OrderDepositor } from "./ERC7683OrderDepositor.sol"; import "../SpokePool.sol"; import "../external/interfaces/IPermit2.sol"; -import "@uma/core/contracts/common/implementation/MultiCaller.sol"; +import "../external/uma/core/contracts/common/implementation/MultiCaller.sol"; /** * @notice AcrossOriginSettler processes an external order type and translates it into an AcrossV3Deposit diff --git a/contracts/erc7683/ERC7683OrderDepositor.sol b/contracts/erc7683/ERC7683OrderDepositor.sol index cfd97c5fd..d63d2a259 100644 --- a/contracts/erc7683/ERC7683OrderDepositor.sol +++ b/contracts/erc7683/ERC7683OrderDepositor.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.0; import "../external/interfaces/IPermit2.sol"; import { V3SpokePoolInterface } from "../interfaces/V3SpokePoolInterface.sol"; -import "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/utils/math/SafeCast.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import { Output, GaslessCrossChainOrder, OnchainCrossChainOrder, ResolvedCrossChainOrder, IOriginSettler, FillInstruction } from "./ERC7683.sol"; import { AcrossOrderData, AcrossOriginFillerData, ERC7683Permit2Lib, ACROSS_ORDER_DATA_TYPE_HASH } from "./ERC7683Permit2Lib.sol"; @@ -122,11 +122,10 @@ abstract contract ERC7683OrderDepositor is IOriginSettler { * @param order the ERC-7683 compliant order. * @param originFillerData Across-specific fillerData. */ - function resolveFor(GaslessCrossChainOrder calldata order, bytes calldata originFillerData) - public - view - returns (ResolvedCrossChainOrder memory resolvedOrder) - { + function resolveFor( + GaslessCrossChainOrder calldata order, + bytes calldata originFillerData + ) public view returns (ResolvedCrossChainOrder memory resolvedOrder) { (resolvedOrder, , ) = _resolveFor(order, originFillerData); } @@ -134,11 +133,9 @@ abstract contract ERC7683OrderDepositor is IOriginSettler { * @notice Constructs a ResolvedOrder from a CrossChainOrder. * @param order the ERC7683 compliant order. */ - function resolve(OnchainCrossChainOrder calldata order) - public - view - returns (ResolvedCrossChainOrder memory resolvedOrder) - { + function resolve( + OnchainCrossChainOrder calldata order + ) public view returns (ResolvedCrossChainOrder memory resolvedOrder) { (resolvedOrder, ) = _resolve(order); } @@ -149,11 +146,10 @@ abstract contract ERC7683OrderDepositor is IOriginSettler { * @return acrossOrderData decoded AcrossOrderData. * @return acrossOriginFillerData decoded AcrossOriginFillerData. */ - function decode(bytes memory orderData, bytes memory fillerData) - public - pure - returns (AcrossOrderData memory, AcrossOriginFillerData memory) - { + function decode( + bytes memory orderData, + bytes memory fillerData + ) public pure returns (AcrossOrderData memory, AcrossOriginFillerData memory) { return (abi.decode(orderData, (AcrossOrderData)), abi.decode(fillerData, (AcrossOriginFillerData))); } @@ -176,7 +172,10 @@ abstract contract ERC7683OrderDepositor is IOriginSettler { */ function computeDepositId(uint256 depositNonce, address depositor) public view virtual returns (uint256); - function _resolveFor(GaslessCrossChainOrder calldata order, bytes calldata fillerData) + function _resolveFor( + GaslessCrossChainOrder calldata order, + bytes calldata fillerData + ) internal view returns ( @@ -260,11 +259,9 @@ abstract contract ERC7683OrderDepositor is IOriginSettler { }); } - function _resolve(OnchainCrossChainOrder calldata order) - internal - view - returns (ResolvedCrossChainOrder memory resolvedOrder, AcrossOrderData memory acrossOrderData) - { + function _resolve( + OnchainCrossChainOrder calldata order + ) internal view returns (ResolvedCrossChainOrder memory resolvedOrder, AcrossOrderData memory acrossOrderData) { if (order.orderDataType != ACROSS_ORDER_DATA_TYPE_HASH) { revert WrongOrderDataType(); } diff --git a/contracts/external/uma/core/contracts/common/implementation/AddressWhitelist.sol b/contracts/external/uma/core/contracts/common/implementation/AddressWhitelist.sol new file mode 100644 index 000000000..8f30a1c8e --- /dev/null +++ b/contracts/external/uma/core/contracts/common/implementation/AddressWhitelist.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../interfaces/AddressWhitelistInterface.sol"; +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "./Lockable.sol"; + +/** + * @title A contract to track a whitelist of addresses. + */ +contract AddressWhitelist is AddressWhitelistInterface, Ownable, Lockable { + enum Status { + None, + In, + Out + } + mapping(address => Status) public whitelist; + + address[] public whitelistIndices; + + event AddedToWhitelist(address indexed addedAddress); + event RemovedFromWhitelist(address indexed removedAddress); + + /** + * @notice Adds an address to the whitelist. + * @param newElement the new address to add. + */ + function addToWhitelist(address newElement) external override nonReentrant onlyOwner { + // Ignore if address is already included + if (whitelist[newElement] == Status.In) { + return; + } + + // Only append new addresses to the array, never a duplicate + if (whitelist[newElement] == Status.None) { + whitelistIndices.push(newElement); + } + + whitelist[newElement] = Status.In; + + emit AddedToWhitelist(newElement); + } + + /** + * @notice Removes an address from the whitelist. + * @param elementToRemove the existing address to remove. + */ + function removeFromWhitelist(address elementToRemove) external override nonReentrant onlyOwner { + if (whitelist[elementToRemove] != Status.Out) { + whitelist[elementToRemove] = Status.Out; + emit RemovedFromWhitelist(elementToRemove); + } + } + + /** + * @notice Checks whether an address is on the whitelist. + * @param elementToCheck the address to check. + * @return True if `elementToCheck` is on the whitelist, or False. + */ + function isOnWhitelist(address elementToCheck) external view override nonReentrantView returns (bool) { + return whitelist[elementToCheck] == Status.In; + } + + /** + * @notice Gets all addresses that are currently included in the whitelist. + * @dev Note: This method skips over, but still iterates through addresses. It is possible for this call to run out + * of gas if a large number of addresses have been removed. To reduce the likelihood of this unlikely scenario, we + * can modify the implementation so that when addresses are removed, the last addresses in the array is moved to + * the empty index. + * @return activeWhitelist the list of addresses on the whitelist. + */ + function getWhitelist() external view override nonReentrantView returns (address[] memory activeWhitelist) { + // Determine size of whitelist first + uint256 activeCount = 0; + for (uint256 i = 0; i < whitelistIndices.length; i++) { + if (whitelist[whitelistIndices[i]] == Status.In) { + activeCount++; + } + } + + // Populate whitelist + activeWhitelist = new address[](activeCount); + activeCount = 0; + for (uint256 i = 0; i < whitelistIndices.length; i++) { + address addr = whitelistIndices[i]; + if (whitelist[addr] == Status.In) { + activeWhitelist[activeCount] = addr; + activeCount++; + } + } + } +} diff --git a/contracts/external/uma/core/contracts/common/implementation/AncillaryData.sol b/contracts/external/uma/core/contracts/common/implementation/AncillaryData.sol new file mode 100644 index 000000000..c48fb5ec0 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/implementation/AncillaryData.sol @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +/** + * @title Library for encoding and decoding ancillary data for DVM price requests. + * @notice We assume that on-chain ancillary data can be formatted directly from bytes to utf8 encoding via + * web3.utils.hexToUtf8, and that clients will parse the utf8-encoded ancillary data as a comma-delimitted key-value + * dictionary. Therefore, this library provides internal methods that aid appending to ancillary data from Solidity + * smart contracts. More details on UMA's ancillary data guidelines below: + * https://docs.google.com/document/d/1zhKKjgY1BupBGPPrY_WOJvui0B6DMcd-xDR8-9-SPDw/edit + */ +library AncillaryData { + // This converts the bottom half of a bytes32 input to hex in a highly gas-optimized way. + // Source: the brilliant implementation at https://gitter.im/ethereum/solidity?at=5840d23416207f7b0ed08c9b. + function toUtf8Bytes32Bottom(bytes32 bytesIn) private pure returns (bytes32) { + unchecked { + uint256 x = uint256(bytesIn); + + // Nibble interleave + x = x & 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff; + x = (x | (x * 2 ** 64)) & 0x0000000000000000ffffffffffffffff0000000000000000ffffffffffffffff; + x = (x | (x * 2 ** 32)) & 0x00000000ffffffff00000000ffffffff00000000ffffffff00000000ffffffff; + x = (x | (x * 2 ** 16)) & 0x0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff; + x = (x | (x * 2 ** 8)) & 0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff; + x = (x | (x * 2 ** 4)) & 0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f; + + // Hex encode + uint256 h = (x & 0x0808080808080808080808080808080808080808080808080808080808080808) / 8; + uint256 i = (x & 0x0404040404040404040404040404040404040404040404040404040404040404) / 4; + uint256 j = (x & 0x0202020202020202020202020202020202020202020202020202020202020202) / 2; + x = x + (h & (i | j)) * 0x27 + 0x3030303030303030303030303030303030303030303030303030303030303030; + + // Return the result. + return bytes32(x); + } + } + + /** + * @notice Returns utf8-encoded bytes32 string that can be read via web3.utils.hexToUtf8. + * @dev Will return bytes32 in all lower case hex characters and without the leading 0x. + * This has minor changes from the toUtf8BytesAddress to control for the size of the input. + * @param bytesIn bytes32 to encode. + * @return utf8 encoded bytes32. + */ + function toUtf8Bytes(bytes32 bytesIn) internal pure returns (bytes memory) { + return abi.encodePacked(toUtf8Bytes32Bottom(bytesIn >> 128), toUtf8Bytes32Bottom(bytesIn)); + } + + /** + * @notice Returns utf8-encoded address that can be read via web3.utils.hexToUtf8. + * Source: https://ethereum.stackexchange.com/questions/8346/convert-address-to-string/8447#8447 + * @dev Will return address in all lower case characters and without the leading 0x. + * @param x address to encode. + * @return utf8 encoded address bytes. + */ + function toUtf8BytesAddress(address x) internal pure returns (bytes memory) { + return + abi.encodePacked(toUtf8Bytes32Bottom(bytes32(bytes20(x)) >> 128), bytes8(toUtf8Bytes32Bottom(bytes20(x)))); + } + + /** + * @notice Converts a uint into a base-10, UTF-8 representation stored in a `string` type. + * @dev This method is based off of this code: https://stackoverflow.com/a/65707309. + */ + function toUtf8BytesUint(uint256 x) internal pure returns (bytes memory) { + if (x == 0) { + return "0"; + } + uint256 j = x; + uint256 len; + while (j != 0) { + len++; + j /= 10; + } + bytes memory bstr = new bytes(len); + uint256 k = len; + while (x != 0) { + k = k - 1; + uint8 temp = (48 + uint8(x - (x / 10) * 10)); + bytes1 b1 = bytes1(temp); + bstr[k] = b1; + x /= 10; + } + return bstr; + } + + function appendKeyValueBytes32( + bytes memory currentAncillaryData, + bytes memory key, + bytes32 value + ) internal pure returns (bytes memory) { + bytes memory prefix = constructPrefix(currentAncillaryData, key); + return abi.encodePacked(currentAncillaryData, prefix, toUtf8Bytes(value)); + } + + /** + * @notice Adds "key:value" to `currentAncillaryData` where `value` is an address that first needs to be converted + * to utf8 bytes. For example, if `utf8(currentAncillaryData)="k1:v1"`, then this function will return + * `utf8(k1:v1,key:value)`, and if `currentAncillaryData` is blank, then this will return `utf8(key:value)`. + * @param currentAncillaryData This bytes data should ideally be able to be utf8-decoded, but its OK if not. + * @param key Again, this bytes data should ideally be able to be utf8-decoded, but its OK if not. + * @param value An address to set as the value in the key:value pair to append to `currentAncillaryData`. + * @return Newly appended ancillary data. + */ + function appendKeyValueAddress( + bytes memory currentAncillaryData, + bytes memory key, + address value + ) internal pure returns (bytes memory) { + bytes memory prefix = constructPrefix(currentAncillaryData, key); + return abi.encodePacked(currentAncillaryData, prefix, toUtf8BytesAddress(value)); + } + + /** + * @notice Adds "key:value" to `currentAncillaryData` where `value` is a uint that first needs to be converted + * to utf8 bytes. For example, if `utf8(currentAncillaryData)="k1:v1"`, then this function will return + * `utf8(k1:v1,key:value)`, and if `currentAncillaryData` is blank, then this will return `utf8(key:value)`. + * @param currentAncillaryData This bytes data should ideally be able to be utf8-decoded, but its OK if not. + * @param key Again, this bytes data should ideally be able to be utf8-decoded, but its OK if not. + * @param value A uint to set as the value in the key:value pair to append to `currentAncillaryData`. + * @return Newly appended ancillary data. + */ + function appendKeyValueUint( + bytes memory currentAncillaryData, + bytes memory key, + uint256 value + ) internal pure returns (bytes memory) { + bytes memory prefix = constructPrefix(currentAncillaryData, key); + return abi.encodePacked(currentAncillaryData, prefix, toUtf8BytesUint(value)); + } + + /** + * @notice Helper method that returns the left hand side of a "key:value" pair plus the colon ":" and a leading + * comma "," if the `currentAncillaryData` is not empty. The return value is intended to be prepended as a prefix to + * some utf8 value that is ultimately added to a comma-delimited, key-value dictionary. + */ + function constructPrefix(bytes memory currentAncillaryData, bytes memory key) internal pure returns (bytes memory) { + if (currentAncillaryData.length > 0) { + return abi.encodePacked(",", key, ":"); + } else { + return abi.encodePacked(key, ":"); + } + } +} diff --git a/contracts/external/uma/core/contracts/common/implementation/ExpandedERC20.sol b/contracts/external/uma/core/contracts/common/implementation/ExpandedERC20.sol new file mode 100644 index 000000000..c2253c6d8 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/implementation/ExpandedERC20.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/ERC20.sol"; +import "./MultiRole.sol"; +import "../interfaces/ExpandedIERC20.sol"; + +/** + * @title An ERC20 with permissioned burning and minting. The contract deployer will initially + * be the owner who is capable of adding new roles. + */ +contract ExpandedERC20 is ExpandedIERC20, ERC20, MultiRole { + enum Roles { + // Can set the minter and burner. + Owner, + // Addresses that can mint new tokens. + Minter, + // Addresses that can burn tokens that address owns. + Burner + } + + uint8 _decimals; + + /** + * @notice Constructs the ExpandedERC20. + * @param _tokenName The name which describes the new token. + * @param _tokenSymbol The ticker abbreviation of the name. Ideally < 5 chars. + * @param _tokenDecimals The number of decimals to define token precision. + */ + constructor( + string memory _tokenName, + string memory _tokenSymbol, + uint8 _tokenDecimals + ) ERC20(_tokenName, _tokenSymbol) { + _decimals = _tokenDecimals; + _createExclusiveRole(uint256(Roles.Owner), uint256(Roles.Owner), msg.sender); + _createSharedRole(uint256(Roles.Minter), uint256(Roles.Owner), new address[](0)); + _createSharedRole(uint256(Roles.Burner), uint256(Roles.Owner), new address[](0)); + } + + function decimals() public view virtual override(ERC20) returns (uint8) { + return _decimals; + } + + /** + * @dev Mints `value` tokens to `recipient`, returning true on success. + * @param recipient address to mint to. + * @param value amount of tokens to mint. + * @return True if the mint succeeded, or False. + */ + function mint( + address recipient, + uint256 value + ) external override onlyRoleHolder(uint256(Roles.Minter)) returns (bool) { + _mint(recipient, value); + return true; + } + + /** + * @dev Burns `value` tokens owned by `msg.sender`. + * @param value amount of tokens to burn. + */ + function burn(uint256 value) external override onlyRoleHolder(uint256(Roles.Burner)) { + _burn(msg.sender, value); + } + + /** + * @dev Burns `value` tokens owned by `recipient`. + * @param recipient address to burn tokens from. + * @param value amount of tokens to burn. + * @return True if the burn succeeded, or False. + */ + function burnFrom( + address recipient, + uint256 value + ) external override onlyRoleHolder(uint256(Roles.Burner)) returns (bool) { + _burn(recipient, value); + return true; + } + + /** + * @notice Add Minter role to account. + * @dev The caller must have the Owner role. + * @param account The address to which the Minter role is added. + */ + function addMinter(address account) external virtual override { + addMember(uint256(Roles.Minter), account); + } + + /** + * @notice Add Burner role to account. + * @dev The caller must have the Owner role. + * @param account The address to which the Burner role is added. + */ + function addBurner(address account) external virtual override { + addMember(uint256(Roles.Burner), account); + } + + /** + * @notice Reset Owner role to account. + * @dev The caller must have the Owner role. + * @param account The new holder of the Owner role. + */ + function resetOwner(address account) external virtual override { + resetMember(uint256(Roles.Owner), account); + } +} diff --git a/contracts/external/uma/core/contracts/common/implementation/FixedPoint.sol b/contracts/external/uma/core/contracts/common/implementation/FixedPoint.sol new file mode 100644 index 000000000..1115bcebc --- /dev/null +++ b/contracts/external/uma/core/contracts/common/implementation/FixedPoint.sol @@ -0,0 +1,763 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; +import "@openzeppelin/contracts-v4/utils/math/SignedSafeMath.sol"; + +/** + * @title Library for fixed point arithmetic on uints + */ +library FixedPoint { + using SafeMath for uint256; + using SignedSafeMath for int256; + + // Supports 18 decimals. E.g., 1e18 represents "1", 5e17 represents "0.5". + // For unsigned values: + // This can represent a value up to (2^256 - 1)/10^18 = ~10^59. 10^59 will be stored internally as uint256 10^77. + uint256 private constant FP_SCALING_FACTOR = 10 ** 18; + + // --------------------------------------- UNSIGNED ----------------------------------------------------------------------------- + struct Unsigned { + uint256 rawValue; + } + + /** + * @notice Constructs an `Unsigned` from an unscaled uint, e.g., `b=5` gets stored internally as `5*(10**18)`. + * @param a uint to convert into a FixedPoint. + * @return the converted FixedPoint. + */ + function fromUnscaledUint(uint256 a) internal pure returns (Unsigned memory) { + return Unsigned(a.mul(FP_SCALING_FACTOR)); + } + + /** + * @notice Whether `a` is equal to `b`. + * @param a a FixedPoint. + * @param b a uint256. + * @return True if equal, or False. + */ + function isEqual(Unsigned memory a, uint256 b) internal pure returns (bool) { + return a.rawValue == fromUnscaledUint(b).rawValue; + } + + /** + * @notice Whether `a` is equal to `b`. + * @param a a FixedPoint. + * @param b a FixedPoint. + * @return True if equal, or False. + */ + function isEqual(Unsigned memory a, Unsigned memory b) internal pure returns (bool) { + return a.rawValue == b.rawValue; + } + + /** + * @notice Whether `a` is greater than `b`. + * @param a a FixedPoint. + * @param b a FixedPoint. + * @return True if `a > b`, or False. + */ + function isGreaterThan(Unsigned memory a, Unsigned memory b) internal pure returns (bool) { + return a.rawValue > b.rawValue; + } + + /** + * @notice Whether `a` is greater than `b`. + * @param a a FixedPoint. + * @param b a uint256. + * @return True if `a > b`, or False. + */ + function isGreaterThan(Unsigned memory a, uint256 b) internal pure returns (bool) { + return a.rawValue > fromUnscaledUint(b).rawValue; + } + + /** + * @notice Whether `a` is greater than `b`. + * @param a a uint256. + * @param b a FixedPoint. + * @return True if `a > b`, or False. + */ + function isGreaterThan(uint256 a, Unsigned memory b) internal pure returns (bool) { + return fromUnscaledUint(a).rawValue > b.rawValue; + } + + /** + * @notice Whether `a` is greater than or equal to `b`. + * @param a a FixedPoint. + * @param b a FixedPoint. + * @return True if `a >= b`, or False. + */ + function isGreaterThanOrEqual(Unsigned memory a, Unsigned memory b) internal pure returns (bool) { + return a.rawValue >= b.rawValue; + } + + /** + * @notice Whether `a` is greater than or equal to `b`. + * @param a a FixedPoint. + * @param b a uint256. + * @return True if `a >= b`, or False. + */ + function isGreaterThanOrEqual(Unsigned memory a, uint256 b) internal pure returns (bool) { + return a.rawValue >= fromUnscaledUint(b).rawValue; + } + + /** + * @notice Whether `a` is greater than or equal to `b`. + * @param a a uint256. + * @param b a FixedPoint. + * @return True if `a >= b`, or False. + */ + function isGreaterThanOrEqual(uint256 a, Unsigned memory b) internal pure returns (bool) { + return fromUnscaledUint(a).rawValue >= b.rawValue; + } + + /** + * @notice Whether `a` is less than `b`. + * @param a a FixedPoint. + * @param b a FixedPoint. + * @return True if `a < b`, or False. + */ + function isLessThan(Unsigned memory a, Unsigned memory b) internal pure returns (bool) { + return a.rawValue < b.rawValue; + } + + /** + * @notice Whether `a` is less than `b`. + * @param a a FixedPoint. + * @param b a uint256. + * @return True if `a < b`, or False. + */ + function isLessThan(Unsigned memory a, uint256 b) internal pure returns (bool) { + return a.rawValue < fromUnscaledUint(b).rawValue; + } + + /** + * @notice Whether `a` is less than `b`. + * @param a a uint256. + * @param b a FixedPoint. + * @return True if `a < b`, or False. + */ + function isLessThan(uint256 a, Unsigned memory b) internal pure returns (bool) { + return fromUnscaledUint(a).rawValue < b.rawValue; + } + + /** + * @notice Whether `a` is less than or equal to `b`. + * @param a a FixedPoint. + * @param b a FixedPoint. + * @return True if `a <= b`, or False. + */ + function isLessThanOrEqual(Unsigned memory a, Unsigned memory b) internal pure returns (bool) { + return a.rawValue <= b.rawValue; + } + + /** + * @notice Whether `a` is less than or equal to `b`. + * @param a a FixedPoint. + * @param b a uint256. + * @return True if `a <= b`, or False. + */ + function isLessThanOrEqual(Unsigned memory a, uint256 b) internal pure returns (bool) { + return a.rawValue <= fromUnscaledUint(b).rawValue; + } + + /** + * @notice Whether `a` is less than or equal to `b`. + * @param a a uint256. + * @param b a FixedPoint. + * @return True if `a <= b`, or False. + */ + function isLessThanOrEqual(uint256 a, Unsigned memory b) internal pure returns (bool) { + return fromUnscaledUint(a).rawValue <= b.rawValue; + } + + /** + * @notice The minimum of `a` and `b`. + * @param a a FixedPoint. + * @param b a FixedPoint. + * @return the minimum of `a` and `b`. + */ + function min(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) { + return a.rawValue < b.rawValue ? a : b; + } + + /** + * @notice The maximum of `a` and `b`. + * @param a a FixedPoint. + * @param b a FixedPoint. + * @return the maximum of `a` and `b`. + */ + function max(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) { + return a.rawValue > b.rawValue ? a : b; + } + + /** + * @notice Adds two `Unsigned`s, reverting on overflow. + * @param a a FixedPoint. + * @param b a FixedPoint. + * @return the sum of `a` and `b`. + */ + function add(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) { + return Unsigned(a.rawValue.add(b.rawValue)); + } + + /** + * @notice Adds an `Unsigned` to an unscaled uint, reverting on overflow. + * @param a a FixedPoint. + * @param b a uint256. + * @return the sum of `a` and `b`. + */ + function add(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) { + return add(a, fromUnscaledUint(b)); + } + + /** + * @notice Subtracts two `Unsigned`s, reverting on overflow. + * @param a a FixedPoint. + * @param b a FixedPoint. + * @return the difference of `a` and `b`. + */ + function sub(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) { + return Unsigned(a.rawValue.sub(b.rawValue)); + } + + /** + * @notice Subtracts an unscaled uint256 from an `Unsigned`, reverting on overflow. + * @param a a FixedPoint. + * @param b a uint256. + * @return the difference of `a` and `b`. + */ + function sub(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) { + return sub(a, fromUnscaledUint(b)); + } + + /** + * @notice Subtracts an `Unsigned` from an unscaled uint256, reverting on overflow. + * @param a a uint256. + * @param b a FixedPoint. + * @return the difference of `a` and `b`. + */ + function sub(uint256 a, Unsigned memory b) internal pure returns (Unsigned memory) { + return sub(fromUnscaledUint(a), b); + } + + /** + * @notice Multiplies two `Unsigned`s, reverting on overflow. + * @dev This will "floor" the product. + * @param a a FixedPoint. + * @param b a FixedPoint. + * @return the product of `a` and `b`. + */ + function mul(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) { + // There are two caveats with this computation: + // 1. Max output for the represented number is ~10^41, otherwise an intermediate value overflows. 10^41 is + // stored internally as a uint256 ~10^59. + // 2. Results that can't be represented exactly are truncated not rounded. E.g., 1.4 * 2e-18 = 2.8e-18, which + // would round to 3, but this computation produces the result 2. + // No need to use SafeMath because FP_SCALING_FACTOR != 0. + return Unsigned(a.rawValue.mul(b.rawValue) / FP_SCALING_FACTOR); + } + + /** + * @notice Multiplies an `Unsigned` and an unscaled uint256, reverting on overflow. + * @dev This will "floor" the product. + * @param a a FixedPoint. + * @param b a uint256. + * @return the product of `a` and `b`. + */ + function mul(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) { + return Unsigned(a.rawValue.mul(b)); + } + + /** + * @notice Multiplies two `Unsigned`s and "ceil's" the product, reverting on overflow. + * @param a a FixedPoint. + * @param b a FixedPoint. + * @return the product of `a` and `b`. + */ + function mulCeil(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) { + uint256 mulRaw = a.rawValue.mul(b.rawValue); + uint256 mulFloor = mulRaw / FP_SCALING_FACTOR; + uint256 mod = mulRaw.mod(FP_SCALING_FACTOR); + if (mod != 0) { + return Unsigned(mulFloor.add(1)); + } else { + return Unsigned(mulFloor); + } + } + + /** + * @notice Multiplies an `Unsigned` and an unscaled uint256 and "ceil's" the product, reverting on overflow. + * @param a a FixedPoint. + * @param b a FixedPoint. + * @return the product of `a` and `b`. + */ + function mulCeil(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) { + // Since b is an uint, there is no risk of truncation and we can just mul it normally + return Unsigned(a.rawValue.mul(b)); + } + + /** + * @notice Divides one `Unsigned` by an `Unsigned`, reverting on overflow or division by 0. + * @dev This will "floor" the quotient. + * @param a a FixedPoint numerator. + * @param b a FixedPoint denominator. + * @return the quotient of `a` divided by `b`. + */ + function div(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) { + // There are two caveats with this computation: + // 1. Max value for the number dividend `a` represents is ~10^41, otherwise an intermediate value overflows. + // 10^41 is stored internally as a uint256 10^59. + // 2. Results that can't be represented exactly are truncated not rounded. E.g., 2 / 3 = 0.6 repeating, which + // would round to 0.666666666666666667, but this computation produces the result 0.666666666666666666. + return Unsigned(a.rawValue.mul(FP_SCALING_FACTOR).div(b.rawValue)); + } + + /** + * @notice Divides one `Unsigned` by an unscaled uint256, reverting on overflow or division by 0. + * @dev This will "floor" the quotient. + * @param a a FixedPoint numerator. + * @param b a uint256 denominator. + * @return the quotient of `a` divided by `b`. + */ + function div(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) { + return Unsigned(a.rawValue.div(b)); + } + + /** + * @notice Divides one unscaled uint256 by an `Unsigned`, reverting on overflow or division by 0. + * @dev This will "floor" the quotient. + * @param a a uint256 numerator. + * @param b a FixedPoint denominator. + * @return the quotient of `a` divided by `b`. + */ + function div(uint256 a, Unsigned memory b) internal pure returns (Unsigned memory) { + return div(fromUnscaledUint(a), b); + } + + /** + * @notice Divides one `Unsigned` by an `Unsigned` and "ceil's" the quotient, reverting on overflow or division by 0. + * @param a a FixedPoint numerator. + * @param b a FixedPoint denominator. + * @return the quotient of `a` divided by `b`. + */ + function divCeil(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) { + uint256 aScaled = a.rawValue.mul(FP_SCALING_FACTOR); + uint256 divFloor = aScaled.div(b.rawValue); + uint256 mod = aScaled.mod(b.rawValue); + if (mod != 0) { + return Unsigned(divFloor.add(1)); + } else { + return Unsigned(divFloor); + } + } + + /** + * @notice Divides one `Unsigned` by an unscaled uint256 and "ceil's" the quotient, reverting on overflow or division by 0. + * @param a a FixedPoint numerator. + * @param b a uint256 denominator. + * @return the quotient of `a` divided by `b`. + */ + function divCeil(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) { + // Because it is possible that a quotient gets truncated, we can't just call "Unsigned(a.rawValue.div(b))" + // similarly to mulCeil with a uint256 as the second parameter. Therefore we need to convert b into an Unsigned. + // This creates the possibility of overflow if b is very large. + return divCeil(a, fromUnscaledUint(b)); + } + + /** + * @notice Raises an `Unsigned` to the power of an unscaled uint256, reverting on overflow. E.g., `b=2` squares `a`. + * @dev This will "floor" the result. + * @param a a FixedPoint numerator. + * @param b a uint256 denominator. + * @return output is `a` to the power of `b`. + */ + function pow(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory output) { + output = fromUnscaledUint(1); + for (uint256 i = 0; i < b; i = i.add(1)) { + output = mul(output, a); + } + } + + // ------------------------------------------------- SIGNED ------------------------------------------------------------- + // Supports 18 decimals. E.g., 1e18 represents "1", 5e17 represents "0.5". + // For signed values: + // This can represent a value up (or down) to +-(2^255 - 1)/10^18 = ~10^58. 10^58 will be stored internally as int256 10^76. + int256 private constant SFP_SCALING_FACTOR = 10 ** 18; + + struct Signed { + int256 rawValue; + } + + function fromSigned(Signed memory a) internal pure returns (Unsigned memory) { + require(a.rawValue >= 0, "Negative value provided"); + return Unsigned(uint256(a.rawValue)); + } + + function fromUnsigned(Unsigned memory a) internal pure returns (Signed memory) { + require(a.rawValue <= uint256(type(int256).max), "Unsigned too large"); + return Signed(int256(a.rawValue)); + } + + /** + * @notice Constructs a `Signed` from an unscaled int, e.g., `b=5` gets stored internally as `5*(10**18)`. + * @param a int to convert into a FixedPoint.Signed. + * @return the converted FixedPoint.Signed. + */ + function fromUnscaledInt(int256 a) internal pure returns (Signed memory) { + return Signed(a.mul(SFP_SCALING_FACTOR)); + } + + /** + * @notice Whether `a` is equal to `b`. + * @param a a FixedPoint.Signed. + * @param b a int256. + * @return True if equal, or False. + */ + function isEqual(Signed memory a, int256 b) internal pure returns (bool) { + return a.rawValue == fromUnscaledInt(b).rawValue; + } + + /** + * @notice Whether `a` is equal to `b`. + * @param a a FixedPoint.Signed. + * @param b a FixedPoint.Signed. + * @return True if equal, or False. + */ + function isEqual(Signed memory a, Signed memory b) internal pure returns (bool) { + return a.rawValue == b.rawValue; + } + + /** + * @notice Whether `a` is greater than `b`. + * @param a a FixedPoint.Signed. + * @param b a FixedPoint.Signed. + * @return True if `a > b`, or False. + */ + function isGreaterThan(Signed memory a, Signed memory b) internal pure returns (bool) { + return a.rawValue > b.rawValue; + } + + /** + * @notice Whether `a` is greater than `b`. + * @param a a FixedPoint.Signed. + * @param b an int256. + * @return True if `a > b`, or False. + */ + function isGreaterThan(Signed memory a, int256 b) internal pure returns (bool) { + return a.rawValue > fromUnscaledInt(b).rawValue; + } + + /** + * @notice Whether `a` is greater than `b`. + * @param a an int256. + * @param b a FixedPoint.Signed. + * @return True if `a > b`, or False. + */ + function isGreaterThan(int256 a, Signed memory b) internal pure returns (bool) { + return fromUnscaledInt(a).rawValue > b.rawValue; + } + + /** + * @notice Whether `a` is greater than or equal to `b`. + * @param a a FixedPoint.Signed. + * @param b a FixedPoint.Signed. + * @return True if `a >= b`, or False. + */ + function isGreaterThanOrEqual(Signed memory a, Signed memory b) internal pure returns (bool) { + return a.rawValue >= b.rawValue; + } + + /** + * @notice Whether `a` is greater than or equal to `b`. + * @param a a FixedPoint.Signed. + * @param b an int256. + * @return True if `a >= b`, or False. + */ + function isGreaterThanOrEqual(Signed memory a, int256 b) internal pure returns (bool) { + return a.rawValue >= fromUnscaledInt(b).rawValue; + } + + /** + * @notice Whether `a` is greater than or equal to `b`. + * @param a an int256. + * @param b a FixedPoint.Signed. + * @return True if `a >= b`, or False. + */ + function isGreaterThanOrEqual(int256 a, Signed memory b) internal pure returns (bool) { + return fromUnscaledInt(a).rawValue >= b.rawValue; + } + + /** + * @notice Whether `a` is less than `b`. + * @param a a FixedPoint.Signed. + * @param b a FixedPoint.Signed. + * @return True if `a < b`, or False. + */ + function isLessThan(Signed memory a, Signed memory b) internal pure returns (bool) { + return a.rawValue < b.rawValue; + } + + /** + * @notice Whether `a` is less than `b`. + * @param a a FixedPoint.Signed. + * @param b an int256. + * @return True if `a < b`, or False. + */ + function isLessThan(Signed memory a, int256 b) internal pure returns (bool) { + return a.rawValue < fromUnscaledInt(b).rawValue; + } + + /** + * @notice Whether `a` is less than `b`. + * @param a an int256. + * @param b a FixedPoint.Signed. + * @return True if `a < b`, or False. + */ + function isLessThan(int256 a, Signed memory b) internal pure returns (bool) { + return fromUnscaledInt(a).rawValue < b.rawValue; + } + + /** + * @notice Whether `a` is less than or equal to `b`. + * @param a a FixedPoint.Signed. + * @param b a FixedPoint.Signed. + * @return True if `a <= b`, or False. + */ + function isLessThanOrEqual(Signed memory a, Signed memory b) internal pure returns (bool) { + return a.rawValue <= b.rawValue; + } + + /** + * @notice Whether `a` is less than or equal to `b`. + * @param a a FixedPoint.Signed. + * @param b an int256. + * @return True if `a <= b`, or False. + */ + function isLessThanOrEqual(Signed memory a, int256 b) internal pure returns (bool) { + return a.rawValue <= fromUnscaledInt(b).rawValue; + } + + /** + * @notice Whether `a` is less than or equal to `b`. + * @param a an int256. + * @param b a FixedPoint.Signed. + * @return True if `a <= b`, or False. + */ + function isLessThanOrEqual(int256 a, Signed memory b) internal pure returns (bool) { + return fromUnscaledInt(a).rawValue <= b.rawValue; + } + + /** + * @notice The minimum of `a` and `b`. + * @param a a FixedPoint.Signed. + * @param b a FixedPoint.Signed. + * @return the minimum of `a` and `b`. + */ + function min(Signed memory a, Signed memory b) internal pure returns (Signed memory) { + return a.rawValue < b.rawValue ? a : b; + } + + /** + * @notice The maximum of `a` and `b`. + * @param a a FixedPoint.Signed. + * @param b a FixedPoint.Signed. + * @return the maximum of `a` and `b`. + */ + function max(Signed memory a, Signed memory b) internal pure returns (Signed memory) { + return a.rawValue > b.rawValue ? a : b; + } + + /** + * @notice Adds two `Signed`s, reverting on overflow. + * @param a a FixedPoint.Signed. + * @param b a FixedPoint.Signed. + * @return the sum of `a` and `b`. + */ + function add(Signed memory a, Signed memory b) internal pure returns (Signed memory) { + return Signed(a.rawValue.add(b.rawValue)); + } + + /** + * @notice Adds an `Signed` to an unscaled int, reverting on overflow. + * @param a a FixedPoint.Signed. + * @param b an int256. + * @return the sum of `a` and `b`. + */ + function add(Signed memory a, int256 b) internal pure returns (Signed memory) { + return add(a, fromUnscaledInt(b)); + } + + /** + * @notice Subtracts two `Signed`s, reverting on overflow. + * @param a a FixedPoint.Signed. + * @param b a FixedPoint.Signed. + * @return the difference of `a` and `b`. + */ + function sub(Signed memory a, Signed memory b) internal pure returns (Signed memory) { + return Signed(a.rawValue.sub(b.rawValue)); + } + + /** + * @notice Subtracts an unscaled int256 from an `Signed`, reverting on overflow. + * @param a a FixedPoint.Signed. + * @param b an int256. + * @return the difference of `a` and `b`. + */ + function sub(Signed memory a, int256 b) internal pure returns (Signed memory) { + return sub(a, fromUnscaledInt(b)); + } + + /** + * @notice Subtracts an `Signed` from an unscaled int256, reverting on overflow. + * @param a an int256. + * @param b a FixedPoint.Signed. + * @return the difference of `a` and `b`. + */ + function sub(int256 a, Signed memory b) internal pure returns (Signed memory) { + return sub(fromUnscaledInt(a), b); + } + + /** + * @notice Multiplies two `Signed`s, reverting on overflow. + * @dev This will "floor" the product. + * @param a a FixedPoint.Signed. + * @param b a FixedPoint.Signed. + * @return the product of `a` and `b`. + */ + function mul(Signed memory a, Signed memory b) internal pure returns (Signed memory) { + // There are two caveats with this computation: + // 1. Max output for the represented number is ~10^41, otherwise an intermediate value overflows. 10^41 is + // stored internally as an int256 ~10^59. + // 2. Results that can't be represented exactly are truncated not rounded. E.g., 1.4 * 2e-18 = 2.8e-18, which + // would round to 3, but this computation produces the result 2. + // No need to use SafeMath because SFP_SCALING_FACTOR != 0. + return Signed(a.rawValue.mul(b.rawValue) / SFP_SCALING_FACTOR); + } + + /** + * @notice Multiplies an `Signed` and an unscaled int256, reverting on overflow. + * @dev This will "floor" the product. + * @param a a FixedPoint.Signed. + * @param b an int256. + * @return the product of `a` and `b`. + */ + function mul(Signed memory a, int256 b) internal pure returns (Signed memory) { + return Signed(a.rawValue.mul(b)); + } + + /** + * @notice Multiplies two `Signed`s and "ceil's" the product, reverting on overflow. + * @param a a FixedPoint.Signed. + * @param b a FixedPoint.Signed. + * @return the product of `a` and `b`. + */ + function mulAwayFromZero(Signed memory a, Signed memory b) internal pure returns (Signed memory) { + int256 mulRaw = a.rawValue.mul(b.rawValue); + int256 mulTowardsZero = mulRaw / SFP_SCALING_FACTOR; + // Manual mod because SignedSafeMath doesn't support it. + int256 mod = mulRaw % SFP_SCALING_FACTOR; + if (mod != 0) { + bool isResultPositive = isLessThan(a, 0) == isLessThan(b, 0); + int256 valueToAdd = isResultPositive ? int256(1) : int256(-1); + return Signed(mulTowardsZero.add(valueToAdd)); + } else { + return Signed(mulTowardsZero); + } + } + + /** + * @notice Multiplies an `Signed` and an unscaled int256 and "ceil's" the product, reverting on overflow. + * @param a a FixedPoint.Signed. + * @param b a FixedPoint.Signed. + * @return the product of `a` and `b`. + */ + function mulAwayFromZero(Signed memory a, int256 b) internal pure returns (Signed memory) { + // Since b is an int, there is no risk of truncation and we can just mul it normally + return Signed(a.rawValue.mul(b)); + } + + /** + * @notice Divides one `Signed` by an `Signed`, reverting on overflow or division by 0. + * @dev This will "floor" the quotient. + * @param a a FixedPoint numerator. + * @param b a FixedPoint denominator. + * @return the quotient of `a` divided by `b`. + */ + function div(Signed memory a, Signed memory b) internal pure returns (Signed memory) { + // There are two caveats with this computation: + // 1. Max value for the number dividend `a` represents is ~10^41, otherwise an intermediate value overflows. + // 10^41 is stored internally as an int256 10^59. + // 2. Results that can't be represented exactly are truncated not rounded. E.g., 2 / 3 = 0.6 repeating, which + // would round to 0.666666666666666667, but this computation produces the result 0.666666666666666666. + return Signed(a.rawValue.mul(SFP_SCALING_FACTOR).div(b.rawValue)); + } + + /** + * @notice Divides one `Signed` by an unscaled int256, reverting on overflow or division by 0. + * @dev This will "floor" the quotient. + * @param a a FixedPoint numerator. + * @param b an int256 denominator. + * @return the quotient of `a` divided by `b`. + */ + function div(Signed memory a, int256 b) internal pure returns (Signed memory) { + return Signed(a.rawValue.div(b)); + } + + /** + * @notice Divides one unscaled int256 by an `Signed`, reverting on overflow or division by 0. + * @dev This will "floor" the quotient. + * @param a an int256 numerator. + * @param b a FixedPoint denominator. + * @return the quotient of `a` divided by `b`. + */ + function div(int256 a, Signed memory b) internal pure returns (Signed memory) { + return div(fromUnscaledInt(a), b); + } + + /** + * @notice Divides one `Signed` by an `Signed` and "ceil's" the quotient, reverting on overflow or division by 0. + * @param a a FixedPoint numerator. + * @param b a FixedPoint denominator. + * @return the quotient of `a` divided by `b`. + */ + function divAwayFromZero(Signed memory a, Signed memory b) internal pure returns (Signed memory) { + int256 aScaled = a.rawValue.mul(SFP_SCALING_FACTOR); + int256 divTowardsZero = aScaled.div(b.rawValue); + // Manual mod because SignedSafeMath doesn't support it. + int256 mod = aScaled % b.rawValue; + if (mod != 0) { + bool isResultPositive = isLessThan(a, 0) == isLessThan(b, 0); + int256 valueToAdd = isResultPositive ? int256(1) : int256(-1); + return Signed(divTowardsZero.add(valueToAdd)); + } else { + return Signed(divTowardsZero); + } + } + + /** + * @notice Divides one `Signed` by an unscaled int256 and "ceil's" the quotient, reverting on overflow or division by 0. + * @param a a FixedPoint numerator. + * @param b an int256 denominator. + * @return the quotient of `a` divided by `b`. + */ + function divAwayFromZero(Signed memory a, int256 b) internal pure returns (Signed memory) { + // Because it is possible that a quotient gets truncated, we can't just call "Signed(a.rawValue.div(b))" + // similarly to mulCeil with an int256 as the second parameter. Therefore we need to convert b into an Signed. + // This creates the possibility of overflow if b is very large. + return divAwayFromZero(a, fromUnscaledInt(b)); + } + + /** + * @notice Raises an `Signed` to the power of an unscaled uint256, reverting on overflow. E.g., `b=2` squares `a`. + * @dev This will "floor" the result. + * @param a a FixedPoint.Signed. + * @param b a uint256 (negative exponents are not allowed). + * @return output is `a` to the power of `b`. + */ + function pow(Signed memory a, uint256 b) internal pure returns (Signed memory output) { + output = fromUnscaledInt(1); + for (uint256 i = 0; i < b; i = i.add(1)) { + output = mul(output, a); + } + } +} diff --git a/contracts/external/uma/core/contracts/common/implementation/HasFinder.sol b/contracts/external/uma/core/contracts/common/implementation/HasFinder.sol new file mode 100644 index 000000000..bf64ebbb8 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/implementation/HasFinder.sol @@ -0,0 +1,13 @@ +pragma solidity ^0.8.9; + +// SPDX-License-Identifier: UNLICENSED +import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; + +// Contract stores a reference to the DVM Finder contract which can be used to locate other important DVM contracts. +contract HasFinder { + FinderInterface public finder; + + constructor(address _finder) { + finder = FinderInterface(_finder); + } +} diff --git a/contracts/external/uma/core/contracts/common/implementation/Lockable.sol b/contracts/external/uma/core/contracts/common/implementation/Lockable.sol new file mode 100644 index 000000000..419d8119d --- /dev/null +++ b/contracts/external/uma/core/contracts/common/implementation/Lockable.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +/** + * @title A contract that provides modifiers to prevent reentrancy to state-changing and view-only methods. This contract + * is inspired by https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/ReentrancyGuard.sol + * and https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol. + */ +contract Lockable { + bool private _notEntered; + + constructor() { + // Storing an initial non-zero value makes deployment a bit more expensive, but in exchange the refund on every + // call to nonReentrant will be lower in amount. Since refunds are capped to a percentage of the total + // transaction's gas, it is best to keep them low in cases like this one, to increase the likelihood of the full + // refund coming into effect. + _notEntered = true; + } + + /** + * @dev Prevents a contract from calling itself, directly or indirectly. + * Calling a `nonReentrant` function from another `nonReentrant` function is not supported. It is possible to + * prevent this from happening by making the `nonReentrant` function external, and making it call a `private` + * function that does the actual state modification. + */ + modifier nonReentrant() { + _preEntranceCheck(); + _preEntranceSet(); + _; + _postEntranceReset(); + } + + /** + * @dev Designed to prevent a view-only method from being re-entered during a call to a `nonReentrant()` state-changing method. + */ + modifier nonReentrantView() { + _preEntranceCheck(); + _; + } + + // Internal methods are used to avoid copying the require statement's bytecode to every `nonReentrant()` method. + // On entry into a function, `_preEntranceCheck()` should always be called to check if the function is being + // re-entered. Then, if the function modifies state, it should call `_postEntranceSet()`, perform its logic, and + // then call `_postEntranceReset()`. + // View-only methods can simply call `_preEntranceCheck()` to make sure that it is not being re-entered. + function _preEntranceCheck() internal view { + // On the first call to nonReentrant, _notEntered will be true + require(_notEntered, "ReentrancyGuard: reentrant call"); + } + + function _preEntranceSet() internal { + // Any calls to nonReentrant after this point will fail + _notEntered = false; + } + + function _postEntranceReset() internal { + // By storing the original value once again, a refund is triggered (see + // https://eips.ethereum.org/EIPS/eip-2200) + _notEntered = true; + } + + // These functions are intended to be used by child contracts to temporarily disable and re-enable the guard. + // Intended use: + // _startReentrantGuardDisabled(); + // ... + // _endReentrantGuardDisabled(); + // + // IMPORTANT: these should NEVER be used in a method that isn't inside a nonReentrant block. Otherwise, it's + // possible to permanently lock your contract. + function _startReentrantGuardDisabled() internal { + _notEntered = true; + } + + function _endReentrantGuardDisabled() internal { + _notEntered = false; + } +} diff --git a/contracts/external/uma/core/contracts/common/implementation/MultiCaller.sol b/contracts/external/uma/core/contracts/common/implementation/MultiCaller.sol new file mode 100644 index 000000000..a3794a129 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/implementation/MultiCaller.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +// This contract is taken from Uniswap's multi call implementation (https://github.com/Uniswap/uniswap-v3-periphery/blob/main/contracts/base/Multicall.sol) +// and was modified to be solidity 0.8 compatible. Additionally, the method was restricted to only work with msg.value +// set to 0 to avoid any nasty attack vectors on function calls that use value sent with deposits. + +/// @title MultiCaller +/// @notice Enables calling multiple methods in a single call to the contract +contract MultiCaller { + function multicall(bytes[] calldata data) external returns (bytes[] memory results) { + results = new bytes[](data.length); + for (uint256 i = 0; i < data.length; i++) { + (bool success, bytes memory result) = address(this).delegatecall(data[i]); + + if (!success) { + // Next 5 lines from https://ethereum.stackexchange.com/a/83577 + if (result.length < 68) revert(); + assembly { + result := add(result, 0x04) + } + revert(abi.decode(result, (string))); + } + + results[i] = result; + } + } +} diff --git a/contracts/external/uma/core/contracts/common/implementation/MultiRole.sol b/contracts/external/uma/core/contracts/common/implementation/MultiRole.sol new file mode 100644 index 000000000..0a8bd2d9c --- /dev/null +++ b/contracts/external/uma/core/contracts/common/implementation/MultiRole.sol @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +library Exclusive { + struct RoleMembership { + address member; + } + + function isMember(RoleMembership storage roleMembership, address memberToCheck) internal view returns (bool) { + return roleMembership.member == memberToCheck; + } + + function resetMember(RoleMembership storage roleMembership, address newMember) internal { + require(newMember != address(0x0), "Cannot set an exclusive role to 0x0"); + roleMembership.member = newMember; + } + + function getMember(RoleMembership storage roleMembership) internal view returns (address) { + return roleMembership.member; + } + + function init(RoleMembership storage roleMembership, address initialMember) internal { + resetMember(roleMembership, initialMember); + } +} + +library Shared { + struct RoleMembership { + mapping(address => bool) members; + } + + function isMember(RoleMembership storage roleMembership, address memberToCheck) internal view returns (bool) { + return roleMembership.members[memberToCheck]; + } + + function addMember(RoleMembership storage roleMembership, address memberToAdd) internal { + require(memberToAdd != address(0x0), "Cannot add 0x0 to a shared role"); + roleMembership.members[memberToAdd] = true; + } + + function removeMember(RoleMembership storage roleMembership, address memberToRemove) internal { + roleMembership.members[memberToRemove] = false; + } + + function init(RoleMembership storage roleMembership, address[] memory initialMembers) internal { + for (uint256 i = 0; i < initialMembers.length; i++) { + addMember(roleMembership, initialMembers[i]); + } + } +} + +/** + * @title Base class to manage permissions for the derived class. + */ +abstract contract MultiRole { + using Exclusive for Exclusive.RoleMembership; + using Shared for Shared.RoleMembership; + + enum RoleType { + Invalid, + Exclusive, + Shared + } + + struct Role { + uint256 managingRole; + RoleType roleType; + Exclusive.RoleMembership exclusiveRoleMembership; + Shared.RoleMembership sharedRoleMembership; + } + + mapping(uint256 => Role) private roles; + + event ResetExclusiveMember(uint256 indexed roleId, address indexed newMember, address indexed manager); + event AddedSharedMember(uint256 indexed roleId, address indexed newMember, address indexed manager); + event RemovedSharedMember(uint256 indexed roleId, address indexed oldMember, address indexed manager); + + /** + * @notice Reverts unless the caller is a member of the specified roleId. + */ + modifier onlyRoleHolder(uint256 roleId) { + require(holdsRole(roleId, msg.sender), "Sender does not hold required role"); + _; + } + + /** + * @notice Reverts unless the caller is a member of the manager role for the specified roleId. + */ + modifier onlyRoleManager(uint256 roleId) { + require(holdsRole(roles[roleId].managingRole, msg.sender), "Can only be called by a role manager"); + _; + } + + /** + * @notice Reverts unless the roleId represents an initialized, exclusive roleId. + */ + modifier onlyExclusive(uint256 roleId) { + require(roles[roleId].roleType == RoleType.Exclusive, "Must be called on an initialized Exclusive role"); + _; + } + + /** + * @notice Reverts unless the roleId represents an initialized, shared roleId. + */ + modifier onlyShared(uint256 roleId) { + require(roles[roleId].roleType == RoleType.Shared, "Must be called on an initialized Shared role"); + _; + } + + /** + * @notice Whether `memberToCheck` is a member of roleId. + * @dev Reverts if roleId does not correspond to an initialized role. + * @param roleId the Role to check. + * @param memberToCheck the address to check. + * @return True if `memberToCheck` is a member of `roleId`. + */ + function holdsRole(uint256 roleId, address memberToCheck) public view returns (bool) { + Role storage role = roles[roleId]; + if (role.roleType == RoleType.Exclusive) { + return role.exclusiveRoleMembership.isMember(memberToCheck); + } else if (role.roleType == RoleType.Shared) { + return role.sharedRoleMembership.isMember(memberToCheck); + } + revert("Invalid roleId"); + } + + /** + * @notice Changes the exclusive role holder of `roleId` to `newMember`. + * @dev Reverts if the caller is not a member of the managing role for `roleId` or if `roleId` is not an + * initialized, ExclusiveRole. + * @param roleId the ExclusiveRole membership to modify. + * @param newMember the new ExclusiveRole member. + */ + function resetMember(uint256 roleId, address newMember) public onlyExclusive(roleId) onlyRoleManager(roleId) { + roles[roleId].exclusiveRoleMembership.resetMember(newMember); + emit ResetExclusiveMember(roleId, newMember, msg.sender); + } + + /** + * @notice Gets the current holder of the exclusive role, `roleId`. + * @dev Reverts if `roleId` does not represent an initialized, exclusive role. + * @param roleId the ExclusiveRole membership to check. + * @return the address of the current ExclusiveRole member. + */ + function getMember(uint256 roleId) public view onlyExclusive(roleId) returns (address) { + return roles[roleId].exclusiveRoleMembership.getMember(); + } + + /** + * @notice Adds `newMember` to the shared role, `roleId`. + * @dev Reverts if `roleId` does not represent an initialized, SharedRole or if the caller is not a member of the + * managing role for `roleId`. + * @param roleId the SharedRole membership to modify. + * @param newMember the new SharedRole member. + */ + function addMember(uint256 roleId, address newMember) public onlyShared(roleId) onlyRoleManager(roleId) { + roles[roleId].sharedRoleMembership.addMember(newMember); + emit AddedSharedMember(roleId, newMember, msg.sender); + } + + /** + * @notice Removes `memberToRemove` from the shared role, `roleId`. + * @dev Reverts if `roleId` does not represent an initialized, SharedRole or if the caller is not a member of the + * managing role for `roleId`. + * @param roleId the SharedRole membership to modify. + * @param memberToRemove the current SharedRole member to remove. + */ + function removeMember(uint256 roleId, address memberToRemove) public onlyShared(roleId) onlyRoleManager(roleId) { + roles[roleId].sharedRoleMembership.removeMember(memberToRemove); + emit RemovedSharedMember(roleId, memberToRemove, msg.sender); + } + + /** + * @notice Removes caller from the role, `roleId`. + * @dev Reverts if the caller is not a member of the role for `roleId` or if `roleId` is not an + * initialized, SharedRole. + * @param roleId the SharedRole membership to modify. + */ + function renounceMembership(uint256 roleId) public onlyShared(roleId) onlyRoleHolder(roleId) { + roles[roleId].sharedRoleMembership.removeMember(msg.sender); + emit RemovedSharedMember(roleId, msg.sender, msg.sender); + } + + /** + * @notice Reverts if `roleId` is not initialized. + */ + modifier onlyValidRole(uint256 roleId) { + require(roles[roleId].roleType != RoleType.Invalid, "Attempted to use an invalid roleId"); + _; + } + + /** + * @notice Reverts if `roleId` is initialized. + */ + modifier onlyInvalidRole(uint256 roleId) { + require(roles[roleId].roleType == RoleType.Invalid, "Cannot use a pre-existing role"); + _; + } + + /** + * @notice Internal method to initialize a shared role, `roleId`, which will be managed by `managingRoleId`. + * `initialMembers` will be immediately added to the role. + * @dev Should be called by derived contracts, usually at construction time. Will revert if the role is already + * initialized. + */ + function _createSharedRole( + uint256 roleId, + uint256 managingRoleId, + address[] memory initialMembers + ) internal onlyInvalidRole(roleId) { + Role storage role = roles[roleId]; + role.roleType = RoleType.Shared; + role.managingRole = managingRoleId; + role.sharedRoleMembership.init(initialMembers); + require( + roles[managingRoleId].roleType != RoleType.Invalid, + "Attempted to use an invalid role to manage a shared role" + ); + } + + /** + * @notice Internal method to initialize an exclusive role, `roleId`, which will be managed by `managingRoleId`. + * `initialMember` will be immediately added to the role. + * @dev Should be called by derived contracts, usually at construction time. Will revert if the role is already + * initialized. + */ + function _createExclusiveRole( + uint256 roleId, + uint256 managingRoleId, + address initialMember + ) internal onlyInvalidRole(roleId) { + Role storage role = roles[roleId]; + role.roleType = RoleType.Exclusive; + role.managingRole = managingRoleId; + role.exclusiveRoleMembership.init(initialMember); + require( + roles[managingRoleId].roleType != RoleType.Invalid, + "Attempted to use an invalid role to manage an exclusive role" + ); + } +} diff --git a/contracts/external/uma/core/contracts/common/implementation/Multicall3.sol b/contracts/external/uma/core/contracts/common/implementation/Multicall3.sol new file mode 100644 index 000000000..bdba8fda4 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/implementation/Multicall3.sol @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// Source: https://github.com/mds1/multicall +/// @title Multicall3 +/// @notice Aggregate results from multiple function calls +/// @dev Multicall & Multicall2 backwards-compatible +/// @dev Aggregate methods are marked `payable` to save 24 gas per call +/// @author Michael Elliot +/// @author Joshua Levine +/// @author Nick Johnson +/// @author Andreas Bigger +/// @author Matt Solomon +contract Multicall3 { + struct Call { + address target; + bytes callData; + } + + struct Call3 { + address target; + bool allowFailure; + bytes callData; + } + + struct Call3Value { + address target; + bool allowFailure; + uint256 value; + bytes callData; + } + + struct Result { + bool success; + bytes returnData; + } + + /// @notice Backwards-compatible call aggregation with Multicall + /// @param calls An array of Call structs + /// @return blockNumber The block number where the calls were executed + /// @return returnData An array of bytes containing the responses + function aggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes[] memory returnData) { + blockNumber = block.number; + uint256 length = calls.length; + returnData = new bytes[](length); + Call calldata call; + for (uint256 i = 0; i < length; ) { + bool success; + call = calls[i]; + (success, returnData[i]) = call.target.call(call.callData); + require(success, "Multicall3: call failed"); + unchecked { + ++i; + } + } + } + + /// @notice Backwards-compatible with Multicall2 + /// @notice Aggregate calls without requiring success + /// @param requireSuccess If true, require all calls to succeed + /// @param calls An array of Call structs + /// @return returnData An array of Result structs + function tryAggregate( + bool requireSuccess, + Call[] calldata calls + ) public payable returns (Result[] memory returnData) { + uint256 length = calls.length; + returnData = new Result[](length); + Call calldata call; + for (uint256 i = 0; i < length; ) { + Result memory result = returnData[i]; + call = calls[i]; + (result.success, result.returnData) = call.target.call(call.callData); + if (requireSuccess) require(result.success, "Multicall3: call failed"); + unchecked { + ++i; + } + } + } + + /// @notice Backwards-compatible with Multicall2 + /// @notice Aggregate calls and allow failures using tryAggregate + /// @param calls An array of Call structs + /// @return blockNumber The block number where the calls were executed + /// @return blockHash The hash of the block where the calls were executed + /// @return returnData An array of Result structs + function tryBlockAndAggregate( + bool requireSuccess, + Call[] calldata calls + ) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) { + blockNumber = block.number; + blockHash = blockhash(block.number); + returnData = tryAggregate(requireSuccess, calls); + } + + /// @notice Backwards-compatible with Multicall2 + /// @notice Aggregate calls and allow failures using tryAggregate + /// @param calls An array of Call structs + /// @return blockNumber The block number where the calls were executed + /// @return blockHash The hash of the block where the calls were executed + /// @return returnData An array of Result structs + function blockAndAggregate( + Call[] calldata calls + ) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) { + (blockNumber, blockHash, returnData) = tryBlockAndAggregate(true, calls); + } + + /// @notice Aggregate calls, ensuring each returns success if required + /// @param calls An array of Call3 structs + /// @return returnData An array of Result structs + function aggregate3(Call3[] calldata calls) public payable returns (Result[] memory returnData) { + uint256 length = calls.length; + returnData = new Result[](length); + Call3 calldata calli; + for (uint256 i = 0; i < length; ) { + Result memory result = returnData[i]; + calli = calls[i]; + (result.success, result.returnData) = calli.target.call(calli.callData); + assembly { + // Revert if the call fails and failure is not allowed + // `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)` + if iszero(or(calldataload(add(calli, 0x20)), mload(result))) { + // set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)"))) + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + // set data offset + mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020) + // set length of revert string + mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017) + // set revert string: bytes32(abi.encodePacked("Multicall3: call failed")) + mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000) + revert(0x00, 0x64) + } + } + unchecked { + ++i; + } + } + } + + /// @notice Aggregate calls with a msg value + /// @notice Reverts if msg.value is less than the sum of the call values + /// @param calls An array of Call3Value structs + /// @return returnData An array of Result structs + function aggregate3Value(Call3Value[] calldata calls) public payable returns (Result[] memory returnData) { + uint256 valAccumulator; + uint256 length = calls.length; + returnData = new Result[](length); + Call3Value calldata calli; + for (uint256 i = 0; i < length; ) { + Result memory result = returnData[i]; + calli = calls[i]; + uint256 val = calli.value; + // Humanity will be a Type V Kardashev Civilization before this overflows - andreas + // ~ 10^25 Wei in existence << ~ 10^76 size uint fits in a uint256 + unchecked { + valAccumulator += val; + } + (result.success, result.returnData) = calli.target.call{ value: val }(calli.callData); + assembly { + // Revert if the call fails and failure is not allowed + // `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)` + if iszero(or(calldataload(add(calli, 0x20)), mload(result))) { + // set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)"))) + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + // set data offset + mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020) + // set length of revert string + mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017) + // set revert string: bytes32(abi.encodePacked("Multicall3: call failed")) + mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000) + revert(0x00, 0x84) + } + } + unchecked { + ++i; + } + } + // Finally, make sure the msg.value = SUM(call[0...i].value) + require(msg.value == valAccumulator, "Multicall3: value mismatch"); + } + + /// @notice Returns the block hash for the given block number + /// @param blockNumber The block number + function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) { + blockHash = blockhash(blockNumber); + } + + /// @notice Returns the block number + function getBlockNumber() public view returns (uint256 blockNumber) { + blockNumber = block.number; + } + + /// @notice Returns the block coinbase + function getCurrentBlockCoinbase() public view returns (address coinbase) { + coinbase = block.coinbase; + } + + /// @notice Returns the block difficulty + function getCurrentBlockDifficulty() public view returns (uint256 difficulty) { + difficulty = block.difficulty; + } + + /// @notice Returns the block gas limit + function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) { + gaslimit = block.gaslimit; + } + + /// @notice Returns the block timestamp + function getCurrentBlockTimestamp() public view returns (uint256 timestamp) { + timestamp = block.timestamp; + } + + /// @notice Returns the (ETH) balance of a given address + function getEthBalance(address addr) public view returns (uint256 balance) { + balance = addr.balance; + } + + /// @notice Returns the block hash of the last block + function getLastBlockHash() public view returns (bytes32 blockHash) { + unchecked { + blockHash = blockhash(block.number - 1); + } + } + + /// @notice Gets the base fee of the given block + /// @notice Can revert if the BASEFEE opcode is not implemented by the given chain + function getBasefee() public view returns (uint256 basefee) { + basefee = block.basefee; + } + + /// @notice Returns the chain id + function getChainId() public view returns (uint256 chainid) { + chainid = block.chainid; + } +} diff --git a/contracts/external/uma/core/contracts/common/implementation/Stakeable.sol b/contracts/external/uma/core/contracts/common/implementation/Stakeable.sol new file mode 100644 index 000000000..718ebea94 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/implementation/Stakeable.sol @@ -0,0 +1,60 @@ +/** + * Stakeable contract. + */ + +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../data-verification-mechanism/interfaces/StakerInterface.sol"; +import "./Withdrawable.sol"; + +/** + * @title Base contract that extends the Withdrawable contract enabling a specific role to stake ERC20 tokens against the + * Voting contract. Voting contract is fed in as a param rather than fetched from the finder to enable upgradability. + */ +abstract contract Stakeable is Withdrawable { + using SafeERC20 for IERC20; + + uint256 private roleId; + + /** + * @notice Stake ERC20 tokens from this contract to the votingContract. + * @param amount amount of tokens to stake. + * @param votingContract Address of the voting contract to stake into. + */ + function stake(uint128 amount, address votingContract) external onlyRoleHolder(roleId) { + StakerInterface voting = StakerInterface(votingContract); + IERC20 votingToken = IERC20(voting.votingToken()); + votingToken.approve(votingContract, amount); + voting.stake(amount); + } + + /** + * @notice Request unstaking of ERC20 tokens from this contract to the votingContract. + * @param amount amount of tokens to unstake. + * @param votingContract Address of the voting contract to unstake from. + */ + function requestUnstake(uint128 amount, address votingContract) external onlyRoleHolder(roleId) { + StakerInterface voting = StakerInterface(votingContract); + voting.requestUnstake(amount); + } + + /** + * @notice Execute an unstake request that has passed liveness on the voting contract. + * @param votingContract Address of the voting contract to execute the unstake from. + */ + function executeUnstake(address votingContract) external onlyRoleHolder(roleId) { + StakerInterface voting = StakerInterface(votingContract); + voting.executeUnstake(); + } + + /** + * @notice Internal method that allows derived contracts to choose the role for stakeable. + * @dev The role `setRoleId` must exist. Either this method or `_setStakeRole` must be + * called by the derived class for this contract to function properly. + * @param setRoleId ID corresponding to role whose members can stakeable. + */ + function _setStakeRole(uint256 setRoleId) internal onlyValidRole(setRoleId) { + roleId = setRoleId; + } +} diff --git a/contracts/external/uma/core/contracts/common/implementation/Testable.sol b/contracts/external/uma/core/contracts/common/implementation/Testable.sol new file mode 100644 index 000000000..164f9d7fc --- /dev/null +++ b/contracts/external/uma/core/contracts/common/implementation/Testable.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "./Timer.sol"; + +/** + * @title Base class that provides time overrides, but only if being run in test mode. + */ +abstract contract Testable { + // If the contract is being run in production, then `timerAddress` will be the 0x0 address. + // Note: this variable should be set on construction and never modified. + address public timerAddress; + + /** + * @notice Constructs the Testable contract. Called by child contracts. + * @param _timerAddress Contract that stores the current time in a testing environment. + * Must be set to 0x0 for production environments that use live time. + */ + constructor(address _timerAddress) { + timerAddress = _timerAddress; + } + + /** + * @notice Reverts if not running in test mode. + */ + modifier onlyIfTest() { + require(timerAddress != address(0x0)); + _; + } + + /** + * @notice Sets the current time. + * @dev Will revert if not running in test mode. + * @param time timestamp to set current Testable time to. + */ + function setCurrentTime(uint256 time) external onlyIfTest { + Timer(timerAddress).setCurrentTime(time); + } + + /** + * @notice Gets the current time. Will return the last time set in `setCurrentTime` if running in test mode. + * Otherwise, it will return the block timestamp. + * @return uint for the current Testable timestamp. + */ + function getCurrentTime() public view virtual returns (uint256) { + if (timerAddress != address(0x0)) { + return Timer(timerAddress).getCurrentTime(); + } else { + return block.timestamp; // solhint-disable-line not-rely-on-time + } + } +} diff --git a/contracts/external/uma/core/contracts/common/implementation/TestnetERC20.sol b/contracts/external/uma/core/contracts/common/implementation/TestnetERC20.sol new file mode 100644 index 000000000..19bc497ea --- /dev/null +++ b/contracts/external/uma/core/contracts/common/implementation/TestnetERC20.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/ERC20.sol"; + +/** + * @title An implementation of ERC20 with the same interface as the Compound project's testnet tokens (mainly DAI) + * @dev This contract can be deployed or the interface can be used to communicate with Compound's ERC20 tokens. Note: + * this token should never be used to store real value since it allows permissionless minting. + */ + +contract TestnetERC20 is ERC20 { + uint8 _decimals; + + /** + * @notice Constructs the TestnetERC20. + * @param _name The name which describes the new token. + * @param _symbol The ticker abbreviation of the name. Ideally < 5 chars. + * @param _tokenDecimals The number of decimals to define token precision. + */ + constructor(string memory _name, string memory _symbol, uint8 _tokenDecimals) ERC20(_name, _symbol) { + _decimals = _tokenDecimals; + } + + function decimals() public view virtual override(ERC20) returns (uint8) { + return _decimals; + } + + // Sample token information. + + /** + * @notice Mints value tokens to the owner address. + * @param ownerAddress the address to mint to. + * @param value the amount of tokens to mint. + */ + function allocateTo(address ownerAddress, uint256 value) external { + _mint(ownerAddress, value); + } +} diff --git a/contracts/external/uma/core/contracts/common/implementation/Timer.sol b/contracts/external/uma/core/contracts/common/implementation/Timer.sol new file mode 100644 index 000000000..9d76f6602 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/implementation/Timer.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +/** + * @title Universal store of current contract time for testing environments. + */ +contract Timer { + uint256 private currentTime; + + constructor() { + currentTime = block.timestamp; // solhint-disable-line not-rely-on-time + } + + /** + * @notice Sets the current time. + * @dev Will revert if not running in test mode. + * @param time timestamp to set `currentTime` to. + */ + function setCurrentTime(uint256 time) external { + currentTime = time; + } + + /** + * @notice Gets the currentTime variable set in the Timer. + * @return uint256 for the current Testable timestamp. + */ + function getCurrentTime() public view returns (uint256) { + return currentTime; + } +} diff --git a/contracts/external/uma/core/contracts/common/implementation/Withdrawable.sol b/contracts/external/uma/core/contracts/common/implementation/Withdrawable.sol new file mode 100644 index 000000000..cca2acc1c --- /dev/null +++ b/contracts/external/uma/core/contracts/common/implementation/Withdrawable.sol @@ -0,0 +1,61 @@ +/** + * Withdrawable contract. + */ + +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/utils/Address.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +import "./MultiRole.sol"; + +/** + * @title Base contract that allows a specific role to withdraw any ETH and/or ERC20 tokens that the contract holds. + */ +abstract contract Withdrawable is MultiRole { + using SafeERC20 for IERC20; + + uint256 private roleId; + + /** + * @notice Withdraws ETH from the contract. + */ + function withdraw(uint256 amount) external onlyRoleHolder(roleId) { + Address.sendValue(payable(msg.sender), amount); + } + + /** + * @notice Withdraws ERC20 tokens from the contract. + * @param erc20Address ERC20 token to withdraw. + * @param amount amount of tokens to withdraw. + */ + function withdrawErc20(address erc20Address, uint256 amount) external onlyRoleHolder(roleId) { + IERC20 erc20 = IERC20(erc20Address); + erc20.safeTransfer(msg.sender, amount); + } + + /** + * @notice Internal method that allows derived contracts to create a role for withdrawal. + * @dev Either this method or `_setWithdrawRole` must be called by the derived class for this contract to function + * properly. + * @param newRoleId ID corresponding to role whose members can withdraw. + * @param managingRoleId ID corresponding to managing role who can modify the withdrawable role's membership. + * @param withdrawerAddress new manager of withdrawable role. + */ + function _createWithdrawRole(uint256 newRoleId, uint256 managingRoleId, address withdrawerAddress) internal { + roleId = newRoleId; + _createExclusiveRole(newRoleId, managingRoleId, withdrawerAddress); + } + + /** + * @notice Internal method that allows derived contracts to choose the role for withdrawal. + * @dev The role `setRoleId` must exist. Either this method or `_createWithdrawRole` must be + * called by the derived class for this contract to function properly. + * @param setRoleId ID corresponding to role whose members can withdraw. + */ + function _setWithdrawRole(uint256 setRoleId) internal onlyValidRole(setRoleId) { + roleId = setRoleId; + } +} diff --git a/contracts/external/uma/core/contracts/common/implementation/dsproxy/DSGuardFactory.sol b/contracts/external/uma/core/contracts/common/implementation/dsproxy/DSGuardFactory.sol new file mode 100644 index 000000000..395d88eb8 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/implementation/dsproxy/DSGuardFactory.sol @@ -0,0 +1,123 @@ +// This file was originally taken from dapphub DSGuard and modified. Original source code can be found +// here: https://github.com/dapphub/ds-guard/blob/master/src/guard.sol +// Changes are limited to updating the Solidity version and some stylistic modifications. + +// guard.sol -- simple whitelist implementation of DSAuthority + +// Copyright (C) 2017 DappHub, LLC + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.0; + +abstract contract DSAuthority { + function canCall(address src, address dst, bytes4 sig) public view virtual returns (bool); +} + +contract DSAuthEvents { + event LogSetAuthority(address indexed authority); + event LogSetOwner(address indexed owner); +} + +contract DSAuth is DSAuthEvents { + DSAuthority public authority; + address public owner; + + constructor() { + owner = msg.sender; + emit LogSetOwner(msg.sender); + } + + function setOwner(address owner_) public auth { + owner = owner_; + emit LogSetOwner(owner); + } + + function setAuthority(DSAuthority authority_) public auth { + authority = authority_; + emit LogSetAuthority(address(authority)); + } + + modifier auth() { + require(isAuthorized(msg.sender, msg.sig)); + _; + } + + function isAuthorized(address src, bytes4 sig) internal view returns (bool) { + if (src == address(this)) { + return true; + } else if (src == owner) { + return true; + } else if (authority == DSAuthority(address(0))) { + return false; + } else { + return authority.canCall(src, address(this), sig); + } + } +} + +contract DSGuardEvents { + event LogPermit(bytes32 indexed src, bytes32 indexed dst, bytes32 indexed sig); + + event LogForbid(bytes32 indexed src, bytes32 indexed dst, bytes32 indexed sig); +} + +contract DSGuard is DSAuth, DSAuthority, DSGuardEvents { + bytes32 public constant ANY = bytes32(type(uint256).max); + + mapping(bytes32 => mapping(bytes32 => mapping(bytes32 => bool))) acl; + + function canCall(address src_, address dst_, bytes4 sig) public view override returns (bool) { + bytes32 src = bytes32(bytes20(src_)); + bytes32 dst = bytes32(bytes20(dst_)); + + return + acl[src][dst][sig] || + acl[src][dst][ANY] || + acl[src][ANY][sig] || + acl[src][ANY][ANY] || + acl[ANY][dst][sig] || + acl[ANY][dst][ANY] || + acl[ANY][ANY][sig] || + acl[ANY][ANY][ANY]; + } + + function permit(bytes32 src, bytes32 dst, bytes32 sig) public auth { + acl[src][dst][sig] = true; + emit LogPermit(src, dst, sig); + } + + function forbid(bytes32 src, bytes32 dst, bytes32 sig) public auth { + acl[src][dst][sig] = false; + emit LogForbid(src, dst, sig); + } + + function permit(address src, address dst, bytes32 sig) public { + permit(bytes32(bytes20(src)), bytes32(bytes20(dst)), sig); + } + + function forbid(address src, address dst, bytes32 sig) public { + forbid(bytes32(bytes20(src)), bytes32(bytes20(dst)), sig); + } +} + +contract DSGuardFactory { + mapping(address => bool) public isGuard; + + function newGuard() public returns (DSGuard guard) { + guard = new DSGuard(); + guard.setOwner(msg.sender); + isGuard[address(guard)] = true; + } +} diff --git a/contracts/external/uma/core/contracts/common/implementation/dsproxy/DSProxyFactory.sol b/contracts/external/uma/core/contracts/common/implementation/dsproxy/DSProxyFactory.sol new file mode 100644 index 000000000..97261b838 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/implementation/dsproxy/DSProxyFactory.sol @@ -0,0 +1,195 @@ +// This file was originally taken from the Maker DS Proxy Factory and modified. The original source code can be found +// here: https://etherscan.io/address/0xa26e15c895efc0616177b7c1e7270a4c7d51c997#code +// Changes are limited to updating the Solidity version and some stylistic modifications. + +// proxy.sol - execute actions atomically through the proxy's identity + +// Copyright (C) 2017 DappHub, LLC + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.0; + +abstract contract DSAuthority { + function canCall(address src, address dst, bytes4 sig) public view virtual returns (bool); +} + +contract DSAuthEvents { + event LogSetAuthority(address indexed authority); + event LogSetOwner(address indexed owner); +} + +contract DSAuth is DSAuthEvents { + DSAuthority public authority; + address public owner; + + constructor() { + owner = msg.sender; + emit LogSetOwner(msg.sender); + } + + function setOwner(address owner_) public auth { + owner = owner_; + emit LogSetOwner(owner); + } + + function setAuthority(DSAuthority authority_) public auth { + authority = authority_; + emit LogSetAuthority(address(authority)); + } + + modifier auth() { + require(isAuthorized(msg.sender, msg.sig)); + _; + } + + function isAuthorized(address src, bytes4 sig) internal view returns (bool) { + if (src == address(this)) { + return true; + } else if (src == owner) { + return true; + } else if (authority == DSAuthority(address(0))) { + return false; + } else { + return authority.canCall(src, address(this), sig); + } + } +} + +contract DSNote { + event LogNote( + bytes4 indexed sig, + address indexed guy, + bytes32 indexed foo, + bytes32 indexed bar, + bytes fax + ) anonymous; + + modifier note() { + bytes32 foo; + bytes32 bar; + + assembly { + foo := calldataload(4) + bar := calldataload(36) + } + + emit LogNote(msg.sig, msg.sender, foo, bar, msg.data); + + _; + } +} + +// DSProxy +// Allows code execution using a persistant identity This can be very +// useful to execute a sequence of atomic actions. Since the owner of +// the proxy can be changed, this allows for dynamic ownership models +// i.e. a multisig +contract DSProxy is DSAuth, DSNote { + DSProxyCache public cache; // global cache for contracts + + constructor(address _cacheAddr) { + require(setCache(_cacheAddr)); + } + + fallback() external payable {} + + // use the proxy to execute calldata _data on contract _code + function execute(bytes memory _code, bytes memory _data) public payable returns (address target, bytes32 response) { + target = cache.read(_code); + if (target == address(0x0)) { + // deploy contract & store its address in cache + target = cache.write(_code); + } + + response = execute(target, _data); + } + + function execute(address _target, bytes memory _data) public payable auth note returns (bytes32 response) { + require(_target != address(0x0), "Target cant be 0x address"); + + // call contract in current context + assembly { + let succeeded := delegatecall(sub(gas(), 5000), _target, add(_data, 0x20), mload(_data), 0, 32) + response := mload(0) // load delegatecall output + switch iszero(succeeded) + case 1 { + // throw if delegatecall failed + revert(0, 0) + } + } + } + + //set new cache + function setCache(address _cacheAddr) public payable auth note returns (bool) { + require(_cacheAddr != address(0x0)); // invalid cache address + cache = DSProxyCache(_cacheAddr); // overwrite cache + return true; + } +} + +// DSProxyFactory +// This factory deploys new proxy instances through build() +// Deployed proxy addresses are logged +contract DSProxyFactory { + event Created(address indexed sender, address indexed owner, address proxy, address cache); + mapping(address => bool) public isProxy; + DSProxyCache public cache = new DSProxyCache(); + + // deploys a new proxy instance + // sets owner of proxy to caller + function build() public returns (DSProxy proxy) { + proxy = build(msg.sender); + } + + // deploys a new proxy instance + // sets custom owner of proxy + function build(address owner) public returns (DSProxy proxy) { + proxy = new DSProxy(address(cache)); + emit Created(msg.sender, owner, address(proxy), address(cache)); + proxy.setOwner(owner); + isProxy[address(proxy)] = true; + } +} + +// DSProxyCache +// This global cache stores addresses of contracts previously deployed +// by a proxy. This saves gas from repeat deployment of the same +// contracts and eliminates blockchain bloat. + +// By default, all proxies deployed from the same factory store +// contracts in the same cache. The cache a proxy instance uses can be +// changed. The cache uses the sha3 hash of a contract's bytecode to +// lookup the address +contract DSProxyCache { + mapping(bytes32 => address) cache; + + function read(bytes memory _code) public view returns (address) { + bytes32 hash = keccak256(_code); + return cache[hash]; + } + + function write(bytes memory _code) public returns (address target) { + assembly { + target := create(0, add(_code, 0x20), mload(_code)) + switch iszero(extcodesize(target)) + case 1 { + // throw if contract failed to deploy + revert(0, 0) + } + } + bytes32 hash = keccak256(_code); + cache[hash] = target; + } +} diff --git a/contracts/external/uma/core/contracts/common/interfaces/AddressWhitelistInterface.sol b/contracts/external/uma/core/contracts/common/interfaces/AddressWhitelistInterface.sol new file mode 100644 index 000000000..939344588 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/interfaces/AddressWhitelistInterface.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +interface AddressWhitelistInterface { + function addToWhitelist(address newElement) external; + + function removeFromWhitelist(address newElement) external; + + function isOnWhitelist(address newElement) external view returns (bool); + + function getWhitelist() external view returns (address[] memory); +} diff --git a/contracts/external/uma/core/contracts/common/interfaces/Balancer.sol b/contracts/external/uma/core/contracts/common/interfaces/Balancer.sol new file mode 100644 index 000000000..5b8cdb313 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/interfaces/Balancer.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +/** + * @title Interface for Balancer. + * @dev This only contains the methods/events that we use in our contracts or offchain infrastructure. + */ +abstract contract Balancer { + function getSpotPriceSansFee(address tokenIn, address tokenOut) external view virtual returns (uint256 spotPrice); +} diff --git a/contracts/external/uma/core/contracts/common/interfaces/ExpandedIERC20.sol b/contracts/external/uma/core/contracts/common/interfaces/ExpandedIERC20.sol new file mode 100644 index 000000000..a56adffda --- /dev/null +++ b/contracts/external/uma/core/contracts/common/interfaces/ExpandedIERC20.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; + +/** + * @title ERC20 interface that includes burn and mint methods. + */ +abstract contract ExpandedIERC20 is IERC20 { + /** + * @notice Burns a specific amount of the caller's tokens. + * @dev Only burns the caller's tokens, so it is safe to leave this method permissionless. + */ + function burn(uint256 value) external virtual; + + /** + * @dev Burns `value` tokens owned by `recipient`. + * @param recipient address to burn tokens from. + * @param value amount of tokens to burn. + */ + function burnFrom(address recipient, uint256 value) external virtual returns (bool); + + /** + * @notice Mints tokens and adds them to the balance of the `to` address. + * @dev This method should be permissioned to only allow designated parties to mint tokens. + */ + function mint(address to, uint256 value) external virtual returns (bool); + + function addMinter(address account) external virtual; + + function addBurner(address account) external virtual; + + function resetOwner(address account) external virtual; +} diff --git a/contracts/external/uma/core/contracts/common/interfaces/HarvestVaultInterface.sol b/contracts/external/uma/core/contracts/common/interfaces/HarvestVaultInterface.sol new file mode 100644 index 000000000..95eda77e8 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/interfaces/HarvestVaultInterface.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; + +/** + * @title Interface for Harvest-style vaults. + * @dev This only contains the methods/events that we use in our contracts or offchain infrastructure. + */ +abstract contract HarvestVaultInterface { + // Return the underlying token. + function underlying() external view virtual returns (IERC20); + + // Gets the number of return tokens that a "share" of this vault is worth. + function getPricePerFullShare() external view virtual returns (uint256); +} diff --git a/contracts/external/uma/core/contracts/common/interfaces/IERC20Standard.sol b/contracts/external/uma/core/contracts/common/interfaces/IERC20Standard.sol new file mode 100644 index 000000000..07d523e73 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/interfaces/IERC20Standard.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; + +/** + * @title ERC20 interface that includes the decimals read only method. + */ +interface IERC20Standard is IERC20 { + /** + * @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 {_setupDecimals} is called. + * + * 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() external view returns (uint8); +} diff --git a/contracts/external/uma/core/contracts/common/interfaces/Multicall.sol b/contracts/external/uma/core/contracts/common/interfaces/Multicall.sol new file mode 100644 index 000000000..ddc44ba6e --- /dev/null +++ b/contracts/external/uma/core/contracts/common/interfaces/Multicall.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.5.0; + +/** + * @title interface for MakerDao's Multicall contract. + * @dev This only contains the methods/events that we use in our contracts or offchain infrastructure. + */ +contract Multicall { + struct Call { + address target; + bytes callData; + } + + function aggregate(Call[] memory calls) public virtual returns (uint256 blockNumber, bytes[] memory returnData) {} +} diff --git a/contracts/external/uma/core/contracts/common/interfaces/Multicall2.sol b/contracts/external/uma/core/contracts/common/interfaces/Multicall2.sol new file mode 100644 index 000000000..b93296d7e --- /dev/null +++ b/contracts/external/uma/core/contracts/common/interfaces/Multicall2.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.5.0; + +/** + * @title interface for MakerDao's Multicall2 contract. + * @dev This adds method to allow calls within the batch to fail + * @dev Full implementation can be found here: https://github.com/makerdao/multicall/blob/16ec5e2859b3a4829ceed4ee1ef609e6e9a744ee/src/Multicall2.sol + */ +abstract contract Multicall2 { + struct Call { + address target; + bytes callData; + } + struct Result { + bool success; + bytes returnData; + } + + function aggregate(Call[] memory calls) public virtual returns (uint256 blockNumber, bytes[] memory returnData); + + function tryBlockAndAggregate( + bool requireSuccess, + Call[] memory calls + ) public virtual returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData); +} diff --git a/contracts/external/uma/core/contracts/common/interfaces/TransactionBatcher.sol b/contracts/external/uma/core/contracts/common/interfaces/TransactionBatcher.sol new file mode 100644 index 000000000..4aa32d8b3 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/interfaces/TransactionBatcher.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +// This is an interface to interact with a deployed implementation by https://github.com/kleros/action-callback-bots for +// batching on-chain transactions. +// See deployed implementation here: https://etherscan.io/address/0x82458d1c812d7c930bb3229c9e159cbabd9aa8cb. +abstract contract TransactionBatcher { + function batchSend(address[] memory targets, uint256[] memory values, bytes[] memory datas) public payable virtual; +} diff --git a/contracts/external/uma/core/contracts/common/interfaces/UniswapV2.sol b/contracts/external/uma/core/contracts/common/interfaces/UniswapV2.sol new file mode 100644 index 000000000..13a0b98c7 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/interfaces/UniswapV2.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +/** + * @title Interface for Uniswap v2. + * @dev This only contains the methods/events that we use in our contracts or offchain infrastructure. + */ +abstract contract UniswapV2 { + // Called after every swap showing the new uniswap "price" for this token pair. + event Sync(uint112 reserve0, uint112 reserve1); + // Base currency. + address public token0; + // Quote currency. + address public token1; +} diff --git a/contracts/external/uma/core/contracts/common/interfaces/UniswapV3.sol b/contracts/external/uma/core/contracts/common/interfaces/UniswapV3.sol new file mode 100644 index 000000000..4055ca94d --- /dev/null +++ b/contracts/external/uma/core/contracts/common/interfaces/UniswapV3.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +/** + * @title Interface for Uniswap v3. + * @dev This only contains the methods/events that we use in our contracts or offchain infrastructure. + */ +abstract contract UniswapV3 { + // Called after every swap showing the new uniswap price for this token pair. + event Swap( + address indexed sender, + address indexed recipient, + int256 amount0, + int256 amount1, + uint160 sqrtPriceX96, + uint128 liquidity, + int24 tick + ); + // Base currency. + address public token0; + // Quote currency. + address public token1; +} diff --git a/contracts/external/uma/core/contracts/common/interfaces/VaultInterface.sol b/contracts/external/uma/core/contracts/common/interfaces/VaultInterface.sol new file mode 100644 index 000000000..1f3531b06 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/interfaces/VaultInterface.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; + +/** + * @title Interface for Yearn-style vaults. + * @dev This only contains the methods/events that we use in our contracts or offchain infrastructure. + */ +abstract contract VaultInterface { + // Return the underlying token. + function token() external view virtual returns (IERC20); + + // Gets the number of return tokens that a "share" of this vault is worth. + function getPricePerFullShare() external view virtual returns (uint256); +} diff --git a/contracts/external/uma/core/contracts/common/test/AncillaryDataTest.sol b/contracts/external/uma/core/contracts/common/test/AncillaryDataTest.sol new file mode 100644 index 000000000..834184ad9 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/test/AncillaryDataTest.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../implementation/AncillaryData.sol"; + +contract AncillaryDataTest { + function toUtf8BytesAddress(address x) external pure returns (bytes memory) { + return AncillaryData.toUtf8BytesAddress(x); + } + + function toUtf8BytesUint(uint256 v) external pure returns (bytes memory) { + return AncillaryData.toUtf8BytesUint(v); + } + + function appendKeyValueAddress( + bytes memory currentAncillaryData, + bytes memory key, + address value + ) external pure returns (bytes memory) { + return AncillaryData.appendKeyValueAddress(currentAncillaryData, key, value); + } + + function appendKeyValueBytes32( + bytes memory currentAncillaryData, + bytes memory key, + bytes32 value + ) external pure returns (bytes memory) { + return AncillaryData.appendKeyValueBytes32(currentAncillaryData, key, value); + } + + function appendKeyValueUint( + bytes memory currentAncillaryData, + bytes memory key, + uint256 value + ) external pure returns (bytes memory) { + return AncillaryData.appendKeyValueUint(currentAncillaryData, key, value); + } + + function constructPrefix(bytes memory currentAncillaryData, bytes memory key) external pure returns (bytes memory) { + return AncillaryData.constructPrefix(currentAncillaryData, key); + } +} diff --git a/contracts/external/uma/core/contracts/common/test/BalancerMock.sol b/contracts/external/uma/core/contracts/common/test/BalancerMock.sol new file mode 100644 index 000000000..27615909f --- /dev/null +++ b/contracts/external/uma/core/contracts/common/test/BalancerMock.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../interfaces/Balancer.sol"; + +/** + * @title Balancer Mock + */ +contract BalancerMock is Balancer { + uint256 price = 0; + + // these params arent used in the mock, but this is to maintain compatibility with balancer API + function getSpotPriceSansFee(address, address) external view virtual override returns (uint256 spotPrice) { + return price; + } + + // this is not a balancer call, but for testing for changing price. + function setPrice(uint256 newPrice) external { + price = newPrice; + } +} diff --git a/contracts/external/uma/core/contracts/common/test/BasicERC20.sol b/contracts/external/uma/core/contracts/common/test/BasicERC20.sol new file mode 100644 index 000000000..ef9a7cf63 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/test/BasicERC20.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; + +/** + * @title Implements only the required ERC20 methods. This contract is used + * test how contracts handle ERC20 contracts that have not implemented `decimals()` + * @dev Mostly copied from Consensys EIP-20 implementation: + * https://github.com/ConsenSys/Tokens/blob/fdf687c69d998266a95f15216b1955a4965a0a6d/contracts/eip20/EIP20.sol + */ +contract BasicERC20 is IERC20 { + uint256 private constant MAX_UINT256 = 2 ** 256 - 1; + mapping(address => uint256) public balances; + mapping(address => mapping(address => uint256)) public allowed; + + uint256 private _totalSupply; + + constructor(uint256 _initialAmount) { + balances[msg.sender] = _initialAmount; + _totalSupply = _initialAmount; + } + + function totalSupply() public view override returns (uint256) { + return _totalSupply; + } + + function transfer(address _to, uint256 _value) public override returns (bool success) { + require(balances[msg.sender] >= _value); + balances[msg.sender] -= _value; + balances[_to] += _value; + emit Transfer(msg.sender, _to, _value); + return true; + } + + function transferFrom(address _from, address _to, uint256 _value) public override returns (bool success) { + uint256 _allowance = allowed[_from][msg.sender]; + require(balances[_from] >= _value && _allowance >= _value); + balances[_to] += _value; + balances[_from] -= _value; + if (_allowance < MAX_UINT256) { + allowed[_from][msg.sender] -= _value; + } + emit Transfer(_from, _to, _value); //solhint-disable-line indent, no-unused-vars + return true; + } + + function balanceOf(address _owner) public view override returns (uint256 balance) { + return balances[_owner]; + } + + function approve(address _spender, uint256 _value) public override returns (bool success) { + allowed[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); //solhint-disable-line indent, no-unused-vars + return true; + } + + function allowance(address _owner, address _spender) public view override returns (uint256 remaining) { + return allowed[_owner][_spender]; + } +} diff --git a/contracts/external/uma/core/contracts/common/test/HarvestVaultMock.sol b/contracts/external/uma/core/contracts/common/test/HarvestVaultMock.sol new file mode 100644 index 000000000..473eb9b80 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/test/HarvestVaultMock.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../interfaces/HarvestVaultInterface.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; + +/** + * @title Mock for Harvest-style vaults for use in tests. + */ +contract HarvestVaultMock is HarvestVaultInterface { + IERC20 public override underlying; + uint256 private pricePerFullShare = 0; + + constructor(IERC20 _underlying) { + underlying = _underlying; + } + + function getPricePerFullShare() external view override returns (uint256) { + return pricePerFullShare; + } + + function setPricePerFullShare(uint256 _pricePerFullShare) external { + pricePerFullShare = _pricePerFullShare; + } +} diff --git a/contracts/external/uma/core/contracts/common/test/MintableERC721.sol b/contracts/external/uma/core/contracts/common/test/MintableERC721.sol new file mode 100644 index 000000000..e7655faac --- /dev/null +++ b/contracts/external/uma/core/contracts/common/test/MintableERC721.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC721/ERC721.sol"; + +// Test ERC721 contract that allows free minting +contract MintableERC721 is ERC721 { + constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {} + + function mint(address to, uint256 tokenId) public { + _safeMint(to, tokenId); + } +} diff --git a/contracts/external/uma/core/contracts/common/test/MultiCallerTest.sol b/contracts/external/uma/core/contracts/common/test/MultiCallerTest.sol new file mode 100644 index 000000000..9007ef898 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/test/MultiCallerTest.sol @@ -0,0 +1,15 @@ +pragma solidity ^0.8.0; + +import "../implementation/MultiCaller.sol"; + +contract MultiCallerTest is MultiCaller { + uint256 public value; + + function call(bool shouldFail) public pure { + require(shouldFail, "shouldFail set to true"); + } + + function add(uint256 amount) public { + value += amount; + } +} diff --git a/contracts/external/uma/core/contracts/common/test/MultiRoleTest.sol b/contracts/external/uma/core/contracts/common/test/MultiRoleTest.sol new file mode 100644 index 000000000..5fdaf9cf7 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/test/MultiRoleTest.sol @@ -0,0 +1,22 @@ +/* + MultiRoleTest contract. +*/ + +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../implementation/MultiRole.sol"; + +// The purpose of this contract is to make the MultiRole creation methods externally callable for testing purposes. +contract MultiRoleTest is MultiRole { + function createSharedRole(uint256 roleId, uint256 managingRoleId, address[] calldata initialMembers) external { + _createSharedRole(roleId, managingRoleId, initialMembers); + } + + function createExclusiveRole(uint256 roleId, uint256 managingRoleId, address initialMember) external { + _createExclusiveRole(roleId, managingRoleId, initialMember); + } + + // solhint-disable-next-line no-empty-blocks + function revertIfNotHoldingRole(uint256 roleId) external view onlyRoleHolder(roleId) {} +} diff --git a/contracts/external/uma/core/contracts/common/test/MulticallMock.sol b/contracts/external/uma/core/contracts/common/test/MulticallMock.sol new file mode 100644 index 000000000..38ad2ae8e --- /dev/null +++ b/contracts/external/uma/core/contracts/common/test/MulticallMock.sol @@ -0,0 +1,23 @@ +pragma solidity >=0.5.0; + +/// @title Multicall - Aggregate results from multiple read-only function calls +/// @author Michael Elliot +/// @author Joshua Levine +/// @author Nick Johnson + +contract MulticallMock { + struct Call { + address target; + bytes callData; + } + + function aggregate(Call[] memory calls) public returns (uint256 blockNumber, bytes[] memory returnData) { + blockNumber = block.number; + returnData = new bytes[](calls.length); + for (uint256 i = 0; i < calls.length; i++) { + (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData); + require(success); + returnData[i] = ret; + } + } +} diff --git a/contracts/external/uma/core/contracts/common/test/PerpetualMock.sol b/contracts/external/uma/core/contracts/common/test/PerpetualMock.sol new file mode 100644 index 000000000..b7929235b --- /dev/null +++ b/contracts/external/uma/core/contracts/common/test/PerpetualMock.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/FixedPoint.sol"; + +/** + * @title Simple Perpetual Mock to serve trivial functions + */ +contract PerpetualMock { + struct FundingRate { + FixedPoint.Signed rate; + bytes32 identifier; + FixedPoint.Unsigned cumulativeMultiplier; + uint256 updateTime; + uint256 applicationTime; + uint256 proposalTime; + } + + using FixedPoint for FixedPoint.Unsigned; + using FixedPoint for FixedPoint.Signed; + + FundingRate public fundingRate; + + // Interface functions required to be implemented in order for an instance of this contract to be passed into the + // off-chain FinancialContractClient helper module: + FixedPoint.Unsigned public collateralRequirement; + uint256 public liquidationLiveness; + FixedPoint.Unsigned public cumulativeFeeMultiplier; + mapping(address => uint256) public positions; + mapping(address => uint256) public liquidations; + event NewSponsor(address indexed sponsor); + event EndedSponsorPosition(); + event LiquidationCreated(); + + function getCurrentTime() public view returns (uint256) { + return block.timestamp; + } + + // Public methods that are useful for tests: + function setFundingRate(FundingRate memory _fundingRate) external { + fundingRate = _fundingRate; + } + + function applyFundingRate() external { + fundingRate.applicationTime = block.timestamp; + // Simplified rate calcualtion. + // multiplier = multiplier * (1 + rate) + fundingRate.cumulativeMultiplier = fundingRate.cumulativeMultiplier.mul( + FixedPoint.fromSigned(FixedPoint.fromUnscaledInt(1).add(fundingRate.rate)) + ); + } +} diff --git a/contracts/external/uma/core/contracts/common/test/ReentrancyAttack.sol b/contracts/external/uma/core/contracts/common/test/ReentrancyAttack.sol new file mode 100644 index 000000000..b6818a621 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/test/ReentrancyAttack.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +// Tests reentrancy guards defined in Lockable.sol. +// Copied from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.1/contracts/mocks/ReentrancyAttack.sol. +contract ReentrancyAttack { + function callSender(bytes4 data) public { + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = msg.sender.call(abi.encodeWithSelector(data)); + require(success, "ReentrancyAttack: failed call"); + } +} diff --git a/contracts/external/uma/core/contracts/common/test/ReentrancyChecker.sol b/contracts/external/uma/core/contracts/common/test/ReentrancyChecker.sol new file mode 100644 index 000000000..e844ced84 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/test/ReentrancyChecker.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +// The Reentrancy Checker causes failures if it is successfully able to re-enter a contract. +// How to use: +// 1. Call setTransactionData with the transaction data you want the Reentrancy Checker to reenter the calling +// contract with. +// 2. Get the calling contract to call into the reentrancy checker with any call. The fallback function will receive +// this call and reenter the contract with the transaction data provided in 1. If that reentrancy call does not +// revert, then the reentrancy checker reverts the initial call, likely causeing the entire transaction to revert. +// +// Note: the reentrancy checker has a guard to prevent an infinite cycle of reentrancy. Inifinite cycles will run out +// of gas in all cases, potentially causing a revert when the contract is adequately protected from reentrancy. +contract ReentrancyChecker { + bytes public txnData; + bool hasBeenCalled; + + // Used to prevent infinite cycles where the reentrancy is cycled forever. + modifier skipIfReentered() { + if (hasBeenCalled) { + return; + } + hasBeenCalled = true; + _; + hasBeenCalled = false; + } + + function setTransactionData(bytes memory _txnData) public { + txnData = _txnData; + } + + function _executeCall(address to, uint256 value, bytes memory data) private returns (bool success) { + // Mostly copied from: + // solhint-disable-next-line max-line-length + // https://github.com/gnosis/safe-contracts/blob/59cfdaebcd8b87a0a32f87b50fead092c10d3a05/contracts/base/Executor.sol#L23-L31 + // solhint-disable-next-line no-inline-assembly + + assembly { + let inputData := add(data, 0x20) + let inputDataSize := mload(data) + success := call(gas(), to, value, inputData, inputDataSize, 0, 0) + } + } + + fallback() external skipIfReentered { + // Attampt to re-enter with the set txnData. + bool success = _executeCall(msg.sender, 0, txnData); + + // Fail if the call succeeds because that means the re-entrancy was successful. + require(!success, "Re-entrancy was successful"); + } +} diff --git a/contracts/external/uma/core/contracts/common/test/ReentrancyMock.sol b/contracts/external/uma/core/contracts/common/test/ReentrancyMock.sol new file mode 100644 index 000000000..2ece3ee4d --- /dev/null +++ b/contracts/external/uma/core/contracts/common/test/ReentrancyMock.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../implementation/Lockable.sol"; +import "./ReentrancyAttack.sol"; + +// Tests reentrancy guards defined in Lockable.sol. +// Extends https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.1/contracts/mocks/ReentrancyMock.sol. +contract ReentrancyMock is Lockable { + uint256 public counter; + + constructor() { + counter = 0; + } + + function callback() external nonReentrant { + _count(); + } + + function countAndSend(ReentrancyAttack attacker) external nonReentrant { + _count(); + bytes4 func = bytes4(keccak256("callback()")); + attacker.callSender(func); + } + + function countAndCall(ReentrancyAttack attacker) external nonReentrant { + _count(); + bytes4 func = bytes4(keccak256("getCount()")); + attacker.callSender(func); + } + + function countLocalRecursive(uint256 n) public nonReentrant { + if (n > 0) { + _count(); + countLocalRecursive(n - 1); + } + } + + function countThisRecursive(uint256 n) public nonReentrant { + if (n > 0) { + _count(); + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = address(this).call(abi.encodeWithSignature("countThisRecursive(uint256)", n - 1)); + require(success, "ReentrancyMock: failed call"); + } + } + + function countLocalCall() public nonReentrant { + getCount(); + } + + function countThisCall() public nonReentrant { + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = address(this).call(abi.encodeWithSignature("getCount()")); + require(success, "ReentrancyMock: failed call"); + } + + function getCount() public view nonReentrantView returns (uint256) { + return counter; + } + + function _count() private { + counter += 1; + } +} diff --git a/contracts/external/uma/core/contracts/common/test/SignedFixedPointTest.sol b/contracts/external/uma/core/contracts/common/test/SignedFixedPointTest.sol new file mode 100644 index 000000000..e6148c33a --- /dev/null +++ b/contracts/external/uma/core/contracts/common/test/SignedFixedPointTest.sol @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../implementation/FixedPoint.sol"; + +// Wraps the FixedPoint library for testing purposes. +contract SignedFixedPointTest { + using FixedPoint for FixedPoint.Signed; + using FixedPoint for int256; + using SafeMath for int256; + + function wrapFromSigned(int256 a) external pure returns (uint256) { + return FixedPoint.fromSigned(FixedPoint.Signed(a)).rawValue; + } + + function wrapFromUnsigned(uint256 a) external pure returns (int256) { + return FixedPoint.fromUnsigned(FixedPoint.Unsigned(a)).rawValue; + } + + function wrapFromUnscaledInt(int256 a) external pure returns (int256) { + return FixedPoint.fromUnscaledInt(a).rawValue; + } + + function wrapIsEqual(int256 a, int256 b) external pure returns (bool) { + return FixedPoint.Signed(a).isEqual(FixedPoint.Signed(b)); + } + + function wrapMixedIsEqual(int256 a, int256 b) external pure returns (bool) { + return FixedPoint.Signed(a).isEqual(b); + } + + function wrapIsGreaterThan(int256 a, int256 b) external pure returns (bool) { + return FixedPoint.Signed(a).isGreaterThan(FixedPoint.Signed(b)); + } + + function wrapIsGreaterThanOrEqual(int256 a, int256 b) external pure returns (bool) { + return FixedPoint.Signed(a).isGreaterThanOrEqual(FixedPoint.Signed(b)); + } + + function wrapMixedIsGreaterThan(int256 a, int256 b) external pure returns (bool) { + return FixedPoint.Signed(a).isGreaterThan(b); + } + + function wrapMixedIsGreaterThanOrEqual(int256 a, int256 b) external pure returns (bool) { + return FixedPoint.Signed(a).isGreaterThanOrEqual(b); + } + + function wrapMixedIsGreaterThanOpposite(int256 a, int256 b) external pure returns (bool) { + return a.isGreaterThan(FixedPoint.Signed(b)); + } + + function wrapMixedIsGreaterThanOrEqualOpposite(int256 a, int256 b) external pure returns (bool) { + return a.isGreaterThanOrEqual(FixedPoint.Signed(b)); + } + + function wrapIsLessThan(int256 a, int256 b) external pure returns (bool) { + return FixedPoint.Signed(a).isLessThan(FixedPoint.Signed(b)); + } + + function wrapIsLessThanOrEqual(int256 a, int256 b) external pure returns (bool) { + return FixedPoint.Signed(a).isLessThanOrEqual(FixedPoint.Signed(b)); + } + + function wrapMixedIsLessThan(int256 a, int256 b) external pure returns (bool) { + return FixedPoint.Signed(a).isLessThan(b); + } + + function wrapMixedIsLessThanOrEqual(int256 a, int256 b) external pure returns (bool) { + return FixedPoint.Signed(a).isLessThanOrEqual(b); + } + + function wrapMixedIsLessThanOpposite(int256 a, int256 b) external pure returns (bool) { + return a.isLessThan(FixedPoint.Signed(b)); + } + + function wrapMixedIsLessThanOrEqualOpposite(int256 a, int256 b) external pure returns (bool) { + return a.isLessThanOrEqual(FixedPoint.Signed(b)); + } + + function wrapMin(int256 a, int256 b) external pure returns (int256) { + return FixedPoint.Signed(a).min(FixedPoint.Signed(b)).rawValue; + } + + function wrapMax(int256 a, int256 b) external pure returns (int256) { + return FixedPoint.Signed(a).max(FixedPoint.Signed(b)).rawValue; + } + + function wrapAdd(int256 a, int256 b) external pure returns (int256) { + return FixedPoint.Signed(a).add(FixedPoint.Signed(b)).rawValue; + } + + // The first int256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. + function wrapMixedAdd(int256 a, int256 b) external pure returns (int256) { + return FixedPoint.Signed(a).add(b).rawValue; + } + + function wrapSub(int256 a, int256 b) external pure returns (int256) { + return FixedPoint.Signed(a).sub(FixedPoint.Signed(b)).rawValue; + } + + // The first int256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. + function wrapMixedSub(int256 a, int256 b) external pure returns (int256) { + return FixedPoint.Signed(a).sub(b).rawValue; + } + + // The second int256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. + function wrapMixedSubOpposite(int256 a, int256 b) external pure returns (int256) { + return a.sub(FixedPoint.Signed(b)).rawValue; + } + + function wrapMul(int256 a, int256 b) external pure returns (int256) { + return FixedPoint.Signed(a).mul(FixedPoint.Signed(b)).rawValue; + } + + function wrapMulAwayFromZero(int256 a, int256 b) external pure returns (int256) { + return FixedPoint.Signed(a).mulAwayFromZero(FixedPoint.Signed(b)).rawValue; + } + + // The first int256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. + function wrapMixedMul(int256 a, int256 b) external pure returns (int256) { + return FixedPoint.Signed(a).mul(b).rawValue; + } + + function wrapMixedMulAwayFromZero(int256 a, int256 b) external pure returns (int256) { + return FixedPoint.Signed(a).mulAwayFromZero(b).rawValue; + } + + function wrapDiv(int256 a, int256 b) external pure returns (int256) { + return FixedPoint.Signed(a).div(FixedPoint.Signed(b)).rawValue; + } + + function wrapDivAwayFromZero(int256 a, int256 b) external pure returns (int256) { + return FixedPoint.Signed(a).divAwayFromZero(FixedPoint.Signed(b)).rawValue; + } + + // The first int256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. + function wrapMixedDiv(int256 a, int256 b) external pure returns (int256) { + return FixedPoint.Signed(a).div(b).rawValue; + } + + function wrapMixedDivAwayFromZero(int256 a, int256 b) external pure returns (int256) { + return FixedPoint.Signed(a).divAwayFromZero(b).rawValue; + } + + // The second int256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. + function wrapMixedDivOpposite(int256 a, int256 b) external pure returns (int256) { + return a.div(FixedPoint.Signed(b)).rawValue; + } + + // The first int256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. + function wrapPow(int256 a, uint256 b) external pure returns (int256) { + return FixedPoint.Signed(a).pow(b).rawValue; + } +} diff --git a/contracts/external/uma/core/contracts/common/test/TestableTest.sol b/contracts/external/uma/core/contracts/common/test/TestableTest.sol new file mode 100644 index 000000000..09a80d72c --- /dev/null +++ b/contracts/external/uma/core/contracts/common/test/TestableTest.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../implementation/Testable.sol"; + +// TestableTest is derived from the abstract contract Testable for testing purposes. +contract TestableTest is Testable { + // solhint-disable-next-line no-empty-blocks + constructor(address _timerAddress) Testable(_timerAddress) {} + + function getTestableTimeAndBlockTime() external view returns (uint256 testableTime, uint256 blockTime) { + // solhint-disable-next-line not-rely-on-time + return (getCurrentTime(), block.timestamp); + } +} diff --git a/contracts/external/uma/core/contracts/common/test/UniswapV2Mock.sol b/contracts/external/uma/core/contracts/common/test/UniswapV2Mock.sol new file mode 100644 index 000000000..d0359c309 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/test/UniswapV2Mock.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../interfaces/UniswapV2.sol"; + +/** + * @title Uniswap v2 Mock that allows manual price injection. + */ +contract UniswapV2Mock is UniswapV2 { + function setTokens(address _token0, address _token1) external { + token0 = _token0; + token1 = _token1; + } + + function setPrice(uint112 reserve0, uint112 reserve1) external { + emit Sync(reserve0, reserve1); + } +} diff --git a/contracts/external/uma/core/contracts/common/test/UniswapV3Mock.sol b/contracts/external/uma/core/contracts/common/test/UniswapV3Mock.sol new file mode 100644 index 000000000..0e0bef183 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/test/UniswapV3Mock.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../interfaces/UniswapV3.sol"; + +/** + * @title Uniswap v3 Mock that allows manual price injection. + */ +contract UniswapV3Mock is UniswapV3 { + function setTokens(address _token0, address _token1) external { + token0 = _token0; + token1 = _token1; + } + + function setPrice( + address sender, + address recipient, + int256 amount0, + int256 amount1, + uint160 sqrtPriceX96, + uint128 liquidity, + int24 tick + ) external { + emit Swap(sender, recipient, amount0, amount1, sqrtPriceX96, liquidity, tick); + } +} diff --git a/contracts/external/uma/core/contracts/common/test/UnsignedFixedPointTest.sol b/contracts/external/uma/core/contracts/common/test/UnsignedFixedPointTest.sol new file mode 100644 index 000000000..9230cdc81 --- /dev/null +++ b/contracts/external/uma/core/contracts/common/test/UnsignedFixedPointTest.sol @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../implementation/FixedPoint.sol"; + +// Wraps the FixedPoint library for testing purposes. +contract UnsignedFixedPointTest { + using FixedPoint for FixedPoint.Unsigned; + using FixedPoint for uint256; + using SafeMath for uint256; + + function wrapFromUnscaledUint(uint256 a) external pure returns (uint256) { + return FixedPoint.fromUnscaledUint(a).rawValue; + } + + function wrapIsEqual(uint256 a, uint256 b) external pure returns (bool) { + return FixedPoint.Unsigned(a).isEqual(FixedPoint.Unsigned(b)); + } + + function wrapMixedIsEqual(uint256 a, uint256 b) external pure returns (bool) { + return FixedPoint.Unsigned(a).isEqual(b); + } + + function wrapIsGreaterThan(uint256 a, uint256 b) external pure returns (bool) { + return FixedPoint.Unsigned(a).isGreaterThan(FixedPoint.Unsigned(b)); + } + + function wrapIsGreaterThanOrEqual(uint256 a, uint256 b) external pure returns (bool) { + return FixedPoint.Unsigned(a).isGreaterThanOrEqual(FixedPoint.Unsigned(b)); + } + + function wrapMixedIsGreaterThan(uint256 a, uint256 b) external pure returns (bool) { + return FixedPoint.Unsigned(a).isGreaterThan(b); + } + + function wrapMixedIsGreaterThanOrEqual(uint256 a, uint256 b) external pure returns (bool) { + return FixedPoint.Unsigned(a).isGreaterThanOrEqual(b); + } + + function wrapMixedIsGreaterThanOpposite(uint256 a, uint256 b) external pure returns (bool) { + return a.isGreaterThan(FixedPoint.Unsigned(b)); + } + + function wrapMixedIsGreaterThanOrEqualOpposite(uint256 a, uint256 b) external pure returns (bool) { + return a.isGreaterThanOrEqual(FixedPoint.Unsigned(b)); + } + + function wrapIsLessThan(uint256 a, uint256 b) external pure returns (bool) { + return FixedPoint.Unsigned(a).isLessThan(FixedPoint.Unsigned(b)); + } + + function wrapIsLessThanOrEqual(uint256 a, uint256 b) external pure returns (bool) { + return FixedPoint.Unsigned(a).isLessThanOrEqual(FixedPoint.Unsigned(b)); + } + + function wrapMixedIsLessThan(uint256 a, uint256 b) external pure returns (bool) { + return FixedPoint.Unsigned(a).isLessThan(b); + } + + function wrapMixedIsLessThanOrEqual(uint256 a, uint256 b) external pure returns (bool) { + return FixedPoint.Unsigned(a).isLessThanOrEqual(b); + } + + function wrapMixedIsLessThanOpposite(uint256 a, uint256 b) external pure returns (bool) { + return a.isLessThan(FixedPoint.Unsigned(b)); + } + + function wrapMixedIsLessThanOrEqualOpposite(uint256 a, uint256 b) external pure returns (bool) { + return a.isLessThanOrEqual(FixedPoint.Unsigned(b)); + } + + function wrapMin(uint256 a, uint256 b) external pure returns (uint256) { + return FixedPoint.Unsigned(a).min(FixedPoint.Unsigned(b)).rawValue; + } + + function wrapMax(uint256 a, uint256 b) external pure returns (uint256) { + return FixedPoint.Unsigned(a).max(FixedPoint.Unsigned(b)).rawValue; + } + + function wrapAdd(uint256 a, uint256 b) external pure returns (uint256) { + return FixedPoint.Unsigned(a).add(FixedPoint.Unsigned(b)).rawValue; + } + + // The first uint256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. + function wrapMixedAdd(uint256 a, uint256 b) external pure returns (uint256) { + return FixedPoint.Unsigned(a).add(b).rawValue; + } + + function wrapSub(uint256 a, uint256 b) external pure returns (uint256) { + return FixedPoint.Unsigned(a).sub(FixedPoint.Unsigned(b)).rawValue; + } + + // The first uint256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. + function wrapMixedSub(uint256 a, uint256 b) external pure returns (uint256) { + return FixedPoint.Unsigned(a).sub(b).rawValue; + } + + // The second uint256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. + function wrapMixedSubOpposite(uint256 a, uint256 b) external pure returns (uint256) { + return a.sub(FixedPoint.Unsigned(b)).rawValue; + } + + function wrapMul(uint256 a, uint256 b) external pure returns (uint256) { + return FixedPoint.Unsigned(a).mul(FixedPoint.Unsigned(b)).rawValue; + } + + function wrapMulCeil(uint256 a, uint256 b) external pure returns (uint256) { + return FixedPoint.Unsigned(a).mulCeil(FixedPoint.Unsigned(b)).rawValue; + } + + // The first uint256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. + function wrapMixedMul(uint256 a, uint256 b) external pure returns (uint256) { + return FixedPoint.Unsigned(a).mul(b).rawValue; + } + + function wrapMixedMulCeil(uint256 a, uint256 b) external pure returns (uint256) { + return FixedPoint.Unsigned(a).mulCeil(b).rawValue; + } + + function wrapDiv(uint256 a, uint256 b) external pure returns (uint256) { + return FixedPoint.Unsigned(a).div(FixedPoint.Unsigned(b)).rawValue; + } + + function wrapDivCeil(uint256 a, uint256 b) external pure returns (uint256) { + return FixedPoint.Unsigned(a).divCeil(FixedPoint.Unsigned(b)).rawValue; + } + + // The first uint256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. + function wrapMixedDiv(uint256 a, uint256 b) external pure returns (uint256) { + return FixedPoint.Unsigned(a).div(b).rawValue; + } + + function wrapMixedDivCeil(uint256 a, uint256 b) external pure returns (uint256) { + return FixedPoint.Unsigned(a).divCeil(b).rawValue; + } + + // The second uint256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. + function wrapMixedDivOpposite(uint256 a, uint256 b) external pure returns (uint256) { + return a.div(FixedPoint.Unsigned(b)).rawValue; + } + + // The first uint256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. + function wrapPow(uint256 a, uint256 b) external pure returns (uint256) { + return FixedPoint.Unsigned(a).pow(b).rawValue; + } +} diff --git a/contracts/external/uma/core/contracts/common/test/VaultMock.sol b/contracts/external/uma/core/contracts/common/test/VaultMock.sol new file mode 100644 index 000000000..e4def688e --- /dev/null +++ b/contracts/external/uma/core/contracts/common/test/VaultMock.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../interfaces/VaultInterface.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; + +/** + * @title Mock for yearn-style vaults for use in tests. + */ +contract VaultMock is VaultInterface { + IERC20 public override token; + uint256 private pricePerFullShare = 0; + + constructor(IERC20 _token) { + token = _token; + } + + function getPricePerFullShare() external view override returns (uint256) { + return pricePerFullShare; + } + + function setPricePerFullShare(uint256 _pricePerFullShare) external { + pricePerFullShare = _pricePerFullShare; + } +} diff --git a/contracts/external/uma/core/contracts/common/test/WithdrawableTest.sol b/contracts/external/uma/core/contracts/common/test/WithdrawableTest.sol new file mode 100644 index 000000000..d98167aea --- /dev/null +++ b/contracts/external/uma/core/contracts/common/test/WithdrawableTest.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../implementation/Withdrawable.sol"; + +// WithdrawableTest is derived from the abstract contract Withdrawable for testing purposes. +contract WithdrawableTest is Withdrawable { + enum Roles { + Governance, + Withdraw + } + + // solhint-disable-next-line no-empty-blocks + constructor() { + _createExclusiveRole(uint256(Roles.Governance), uint256(Roles.Governance), msg.sender); + _createWithdrawRole(uint256(Roles.Withdraw), uint256(Roles.Governance), msg.sender); + } + + function pay() external payable { + require(msg.value > 0); + } + + function setInternalWithdrawRole(uint256 setRoleId) public { + _setWithdrawRole(setRoleId); + } +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/AncillaryDataCompression.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/AncillaryDataCompression.sol new file mode 100644 index 000000000..359e3d62f --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/AncillaryDataCompression.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import { AncillaryData } from "../common/implementation/AncillaryData.sol"; + +/** + * @title Library for compressing ancillary data when bridging DVM price requests to mainnet. + * @notice This provides internal method for origin chain oracles to compress ancillary data by replacing it with the + * hash of the original ancillary data and adding additional information to track back the original ancillary data on + * mainnet. + */ +library AncillaryDataCompression { + /** + * @notice Compresses ancillary data by providing sufficient information to track back the original ancillary data + * on mainnet. + * @dev The compression replaces original ancillary data with its hash and adds address of origin chain oracle and + * block number so that it is more efficient to fetch original ancillary data from PriceRequestBridged event on + * origin chain indexed by parentRequestId. This parentRequestId can be reconstructed by taking keccak256 hash of + * ABI encoded price identifier, time and compressed ancillary data. + * @param ancillaryData original ancillary data to be processed. + * @param requester address of the requester who initiated the price request. + * @param requestBlockNumber block number when the price request was initiated. + * @return compressed ancillary data. + */ + function compress( + bytes memory ancillaryData, + address requester, + uint256 requestBlockNumber + ) internal view returns (bytes memory) { + return + AncillaryData.appendKeyValueUint( + AncillaryData.appendKeyValueAddress( + AncillaryData.appendKeyValueAddress( + AncillaryData.appendKeyValueUint( + AncillaryData.appendKeyValueBytes32("", "ancillaryDataHash", keccak256(ancillaryData)), + "childBlockNumber", + requestBlockNumber + ), + "childOracle", + address(this) + ), + "childRequester", + requester + ), + "childChainId", + block.chainid + ); + } +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/GovernorHub.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/GovernorHub.sol new file mode 100644 index 000000000..16d80dccc --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/GovernorHub.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../common/implementation/Lockable.sol"; +import "../common/implementation/MultiCaller.sol"; +import "./interfaces/ParentMessengerInterface.sol"; +import "./GovernorSpoke.sol"; +import "@openzeppelin/contracts-v4/access/Ownable.sol"; + +/** + * @title Cross-chain Oracle L1 Governor Hub. + * @notice Governance relayer contract to be deployed on Ethereum that receives messages from the owner (Governor) and + * sends them to spoke contracts on child chains. + */ + +contract GovernorHub is Ownable, Lockable, MultiCaller { + // Associates chain ID with ParentMessenger contract to use to send governance actions to that chain's GovernorSpoke + // contract. + mapping(uint256 => ParentMessengerInterface) public messengers; + + event RelayedGovernanceRequest( + uint256 indexed chainId, + address indexed messenger, + GovernorSpoke.Call[] calls, + bytes dataSentToChild + ); + event SetParentMessenger(uint256 indexed chainId, address indexed parentMessenger); + + /** + * @notice Set new ParentMessenger contract for chainId. + * @param chainId child network that messenger contract will communicate with. + * @param messenger ParentMessenger contract that sends messages to ChildMessenger on network with ID `chainId`. + * @dev Only callable by the owner (presumably the Ethereum Governor contract). + */ + function setMessenger(uint256 chainId, ParentMessengerInterface messenger) public nonReentrant onlyOwner { + messengers[chainId] = messenger; + emit SetParentMessenger(chainId, address(messenger)); + } + + /** + * @notice This should be called in order to relay a governance request to the `GovernorSpoke` contract deployed to + * the child chain associated with `chainId`. + * @param chainId network that messenger contract will communicate with + * @param calls the calls to be made by the GovernorSpoke. Should encode a `to` and `data` prop for each call. + * @dev Only callable by the owner (presumably the UMA DVM Governor contract, on L1 Ethereum). + */ + function relayGovernance(uint256 chainId, GovernorSpoke.Call[] memory calls) external nonReentrant onlyOwner { + bytes memory dataSentToChild = abi.encode(calls); + messengers[chainId].sendMessageToChild(dataSentToChild); + emit RelayedGovernanceRequest(chainId, address(messengers[chainId]), calls, dataSentToChild); + } +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/GovernorSpoke.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/GovernorSpoke.sol new file mode 100644 index 000000000..3c65c8a73 --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/GovernorSpoke.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "./interfaces/ChildMessengerConsumerInterface.sol"; +import "../common/implementation/Lockable.sol"; +import "./SpokeBase.sol"; + +/** + * @title Cross-chain Oracle L2 Governor Spoke. + * @notice Governor contract deployed on L2 that receives governance actions from Ethereum. + */ +contract GovernorSpoke is Lockable, SpokeBase, ChildMessengerConsumerInterface { + struct Call { + address to; + bytes data; + } + + constructor(address _finderAddress) HasFinder(_finderAddress) {} + + event ExecutedGovernanceTransaction(address indexed to, bytes data); + + /** + * @notice Executes governance transaction created on Ethereum. + * @dev Can only be called by ChildMessenger contract that wants to execute governance action on this child chain + * that originated from DVM voters on root chain. ChildMessenger should only receive communication from + * ParentMessenger on mainnet. See the SpokeBase for the onlyMessenger modifier. + + * @param data Contains the target address and the encoded function selector + ABI encoded params to include in + * delegated transaction. + */ + function processMessageFromParent(bytes memory data) public override nonReentrant onlyMessenger { + Call[] memory calls = abi.decode(data, (Call[])); + + for (uint256 i = 0; i < calls.length; i++) { + (address to, bytes memory inputData) = (calls[i].to, calls[i].data); + require(_executeCall(to, inputData), "execute call failed"); + emit ExecutedGovernanceTransaction(to, inputData); + } + } + + // Note: this snippet of code is copied from Governor.sol. + function _executeCall(address to, bytes memory data) private returns (bool) { + // Note: this snippet of code is copied from Governor.sol and modified to not include any "value" field. + // solhint-disable-next-line no-inline-assembly + + bool success; + assembly { + let inputData := add(data, 0x20) + let inputDataSize := mload(data) + // Hardcode value to be 0 for relayed governance calls in order to avoid addressing complexity of bridging + // value cross-chain. + success := call(gas(), to, 0, inputData, inputDataSize, 0, 0) + } + return success; + } +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/OracleBase.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/OracleBase.sol new file mode 100644 index 000000000..0128c2d24 --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/OracleBase.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../data-verification-mechanism/interfaces/FinderInterface.sol"; +import "../data-verification-mechanism/implementation/Constants.sol"; +import "../common/implementation/HasFinder.sol"; + +/** + * @title Cross-chain Oracle L1 Oracle Base. + * @notice Enforces lifecycle of price requests for deriving contract. + */ +abstract contract OracleBase is HasFinder { + enum RequestState { + NeverRequested, + Requested, + Resolved + } + + struct Price { + RequestState state; + int256 price; + } + + // Mapping of encoded price requests {identifier, time, ancillaryData} to Price objects. + mapping(bytes32 => Price) internal prices; + + event PriceRequestAdded(bytes32 indexed identifier, uint256 time, bytes ancillaryData, bytes32 indexed requestHash); + event PushedPrice( + bytes32 indexed identifier, + uint256 time, + bytes ancillaryData, + int256 price, + bytes32 indexed requestHash + ); + + /** + * @notice Enqueues a request (if a request isn't already present) for the given (identifier, time, + * ancillary data) combination. Will only emit an event if the request has never been requested. + * @return True if price request is new, false otherwise. This is useful for caller to keep track of + * duplicate price requests. + */ + function _requestPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData) internal returns (bool) { + bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); + Price storage lookup = prices[priceRequestId]; + if (lookup.state == RequestState.NeverRequested) { + lookup.state = RequestState.Requested; + emit PriceRequestAdded(identifier, time, ancillaryData, priceRequestId); + return true; + } else { + return false; + } + } + + /** + * @notice Publishes price for a requested query. + * @dev Does not update price state if price is already resolved. + */ + function _publishPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData, int256 price) internal { + bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); + Price storage lookup = prices[priceRequestId]; + if (lookup.state == RequestState.Resolved) return; + lookup.price = price; + lookup.state = RequestState.Resolved; + emit PushedPrice(identifier, time, ancillaryData, lookup.price, priceRequestId); + } + + /** + * @notice Returns the convenient way to store price requests, uniquely identified by {identifier, time, + * ancillaryData }. + */ + function _encodePriceRequest( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) internal pure returns (bytes32) { + return keccak256(abi.encode(identifier, time, ancillaryData)); + } +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/OracleHub.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/OracleHub.sol new file mode 100644 index 000000000..ea2b866ea --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/OracleHub.sol @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "./OracleBase.sol"; +import "../data-verification-mechanism/interfaces/OracleAncillaryInterface.sol"; +import "../data-verification-mechanism/interfaces/StoreInterface.sol"; +import "../common/implementation/Lockable.sol"; +import "../common/implementation/MultiCaller.sol"; +import "./interfaces/ParentMessengerInterface.sol"; +import "./interfaces/ParentMessengerConsumerInterface.sol"; +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +/** + * @title Cross-chain Oracle L1 Oracle Hub. + * @notice Gatekeeper contract deployed on mainnet that validates and sends price requests from sidechain to the DVM on + * mainnet. This is a "gate keeper" contract because it performs the final validation for any messages originating from + * a child chain's oracle before submitting price requests to the DVM. This contract also can publish DVM price + * resolution data to OracleSpokes on any chainId via the messenger for that chainId. + * @dev This contract must be a registered financial contract in order to make and query DVM price requests. + */ + +contract OracleHub is OracleBase, ParentMessengerConsumerInterface, Ownable, Lockable, MultiCaller { + using SafeERC20 for IERC20; + + // Currency that final fees are paid in. + IERC20 public token; + + // Associates chain ID with ParentMessenger contract to use to send price resolutions to that chain's OracleSpoke + // contract via its ChildMessenger contract. + mapping(uint256 => ParentMessengerInterface) public messengers; + + event SetParentMessenger(uint256 indexed chainId, address indexed parentMessenger); + + constructor(address _finderAddress, IERC20 _token) HasFinder(_finderAddress) { + token = _token; + } + + modifier onlyMessenger(uint256 chainId) { + require(msg.sender == address(messengers[chainId]), "Caller must be messenger for network"); + _; + } + + /** + * @notice Set new ParentMessenger contract for chainId. + * @param chainId network that has a child messenger contract that parent messenger contract will communicate with. + * @param messenger ParentMessenger contract that sends messages to ChildMessenger on network with ID `chainId`. + * @dev Only callable by the owner (presumably the Ethereum Governor contract). + */ + function setMessenger(uint256 chainId, ParentMessengerInterface messenger) public nonReentrant onlyOwner { + messengers[chainId] = messenger; + emit SetParentMessenger(chainId, address(messenger)); + } + + /** + * @notice Publishes a DVM resolved price to the OracleSpoke deployed on the network linked with `chainId`, or + * reverts if not resolved yet. This contract must be registered with the DVM to query price requests. + * The DVM price resolution is communicated to the OracleSpoke via the Parent-->Child messenger channel. + * @dev This method will always attempt to call `messenger.sendMessageToChild` even if it is a duplicate call for + * this price request. Therefore the Messenger contract for this `chainId` should determine how to handle duplicate + * calls. + * @dev This method is `payable` so that ETH can be forwarded to Messenger contracts that need to send ETH + * from L1 to L2, like Arbitrum messengers for example. For networks that do not use ETH, the caller will + * lose ETH, therefore it is the caller's responsibility to know when to send ETH. This is allowed to be + * `payable` because any EOA can call this function. + * @param chainId Network to resolve price for. + * @param identifier Identifier of price request to resolve. + * @param time Timestamp of price request to resolve. + * @param ancillaryData extra data of price request to resolve. + */ + function publishPrice( + uint256 chainId, + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) public payable nonReentrant { + // `getPrice` will revert if there is no price. + int256 price = _getOracle().getPrice(identifier, time, ancillaryData); + _publishPrice(identifier, time, ancillaryData, price); + + // Require caller to include enough ETH to pass to Messenger so that caller cannot take advantage of excess + // ETH held by the Messenger. Caller can easily query messenger to get exact amount of ETh to send. + uint256 requiredL1CallValue = messengers[chainId].getL1CallValue(); + require(msg.value == requiredL1CallValue, "Insufficient msg.value"); + + // Call returns a boolean value indicating success or failure. + // This is the current recommended method to use: https://solidity-by-example.org/sending-ether/ + if (msg.value > 0) { + (bool sent, ) = address(messengers[chainId]).call{ value: msg.value }(""); + require(sent, "Cannot send ETH to messenger"); + } + + // Pass all msg.value to Messenger: + messengers[chainId].sendMessageToChild(abi.encode(identifier, time, ancillaryData, price)); + } + + /** + * @notice Submits a price request originating from an OracleSpoke. Request data must be sent via the + * Child --> Parent Messenger communication channel. Returns silently if price request is a duplicate. + * @dev This contract must be registered to submit price requests to the DVM. Only the ParentMessenger + * can call this method. If the original requester on the child chain wants to expedite the Child --> Parent + * message, then they can call `requestPrice` on this contract for the same unique price request. + * @param chainId id of the child chain that sent the price request. + * @param data ABI encoded params with which to call `_requestPrice`. + */ + function processMessageFromChild( + uint256 chainId, + bytes memory data + ) public override nonReentrant onlyMessenger(chainId) { + (bytes32 identifier, uint256 time, bytes memory ancillaryData) = abi.decode(data, (bytes32, uint256, bytes)); + bool newPriceRequested = _requestPrice(identifier, time, ancillaryData); + if (newPriceRequested) { + _getOracle().requestPrice(identifier, time, ancillaryData); + } + } + + /** + * @notice Anyone can call this method to directly request a price to the DVM. This could be used by the child + * chain requester in the case where Child --> Parent communication takes too long and the requester wants to speed + * up the price resolution process. Returns silently if price request is a duplicate. Calling this method from + * the user's point of view is no different than calling the OptimisticOracle.requestPrice method, but with a + * different interface. + * @dev The caller must pay a final fee and have approved this contract to pull final fee from it. + * @dev If the price request params including the ancillary data does not match exactly the price request submitted + * on the child chain, then the child chain's price request will not resolve. The caller is recommended to use the + * `compressAncillaryData` method on the OracleSpoke to reconstruct the ancillary data. + * @param identifier Identifier for price request. + * @param time time for price request. + * @param ancillaryData Extra data for price request. + */ + function requestPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData) public nonReentrant { + bool newPriceRequested = _requestPrice(identifier, time, ancillaryData); + if (newPriceRequested) { + uint256 finalFee = _getStore().computeFinalFee(address(token)).rawValue; + token.safeTransferFrom(msg.sender, address(_getStore()), finalFee); + _getOracle().requestPrice(identifier, time, ancillaryData); + } + } + + function _getOracle() internal view returns (OracleAncillaryInterface) { + return OracleAncillaryInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); + } + + function _getStore() internal view returns (StoreInterface) { + return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store)); + } +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/OracleSpoke.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/OracleSpoke.sol new file mode 100644 index 000000000..c9d0e2c74 --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/OracleSpoke.sol @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../data-verification-mechanism/interfaces/OracleAncillaryInterface.sol"; +import "../data-verification-mechanism/interfaces/OracleInterface.sol"; +import "../data-verification-mechanism/interfaces/RegistryInterface.sol"; +import "./AncillaryDataCompression.sol"; +import "./OracleBase.sol"; +import "../common/implementation/AncillaryData.sol"; +import "../common/implementation/Lockable.sol"; +import "./interfaces/ChildMessengerInterface.sol"; +import "./interfaces/ChildMessengerConsumerInterface.sol"; +import "./SpokeBase.sol"; + +/** + * @title Cross-chain Oracle L2 Oracle Spoke. + * @notice This contract is primarily intended to receive messages on the child chain from a parent chain and allow + * contracts deployed on the child chain to interact with this contract as an Oracle. Moreover, this contract gives + * child chain contracts the ability to trigger cross-chain price requests to the mainnet DVM. This Spoke knows how + * to communicate with the parent chain via a "ChildMessenger" contract which directly communicates with the + * "ParentMessenger" on mainnet. + * @dev The intended client of this contract is an OptimisticOracle on sidechain that needs price + * resolution secured by the DVM on mainnet. + */ +contract OracleSpoke is + OracleBase, + SpokeBase, + OracleAncillaryInterface, + OracleInterface, + ChildMessengerConsumerInterface, + Lockable +{ + using AncillaryDataCompression for bytes; + + // Mapping of parent request ID to child request ID. + mapping(bytes32 => bytes32) public childRequestIds; + + event PriceRequestBridged( + address indexed requester, + bytes32 identifier, + uint256 time, + bytes ancillaryData, + bytes32 indexed childRequestId, + bytes32 indexed parentRequestId + ); + event ResolvedLegacyRequest( + bytes32 indexed identifier, + uint256 time, + bytes ancillaryData, + int256 price, + bytes32 indexed requestHash, + bytes32 indexed legacyRequestHash + ); + + constructor(address _finderAddress) HasFinder(_finderAddress) {} + + // This assumes that the local network has a Registry that resembles the mainnet registry. + modifier onlyRegisteredContract() { + RegistryInterface registry = RegistryInterface(finder.getImplementationAddress(OracleInterfaces.Registry)); + require(registry.isContractRegistered(msg.sender), "Caller must be registered"); + _; + } + + /** + * @notice This is called to bridge a price request to mainnet. This method will enqueue a new price request + * or return silently if already requested. Price requests are relayed to mainnet (the "Parent" chain) via the + * ChildMessenger contract. + * @dev Can be called only by a registered contract that is allowed to make DVM price requests. Will mark this + * price request as Requested, and therefore able to receive the price resolution data from mainnet. + * @dev Contract registration enables the DVM to validate that the calling contract correctly pays final fees. + * Therefore, this function does not directly attempt to pull a final fee from the caller. + * @param identifier Identifier of price request. + * @param time Timestamp of price request. + * @param ancillaryData extra data of price request. + */ + function requestPrice( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) public override nonReentrant onlyRegisteredContract { + _requestPriceSpoke(identifier, time, ancillaryData); + } + + /** + * @notice Overloaded function to provide backwards compatibility for legacy financial contracts that do not use + * ancillary data. + */ + function requestPrice(bytes32 identifier, uint256 time) public override nonReentrant onlyRegisteredContract { + _requestPriceSpoke(identifier, time, ""); + } + + function _requestPriceSpoke(bytes32 identifier, uint256 time, bytes memory ancillaryData) internal { + address requester = msg.sender; + bytes32 childRequestId = _encodePriceRequest(identifier, time, ancillaryData); + Price storage lookup = prices[childRequestId]; + + // Send the request to mainnet only if it has not been requested yet. + if (lookup.state != RequestState.NeverRequested) return; + lookup.state = RequestState.Requested; + + // Only the compressed ancillary data is sent to the mainnet. As it includes the request block number that is + // not available when getting the resolved price, we map the derived request ID. + bytes memory parentAncillaryData = ancillaryData.compress(requester, block.number); + bytes32 parentRequestId = _encodePriceRequest(identifier, time, parentAncillaryData); + childRequestIds[parentRequestId] = childRequestId; + + // Emit all required information so that voters on mainnet can track the origin of the request and full + // ancillary data by using the parentRequestId that is derived from identifier, time and ancillary data as + // observed on mainnet. + emit PriceRequestBridged(requester, identifier, time, ancillaryData, childRequestId, parentRequestId); + emit PriceRequestAdded(identifier, time, parentAncillaryData, parentRequestId); + + getChildMessenger().sendMessageToParent(abi.encode(identifier, time, parentAncillaryData)); + } + + /** + * @notice Resolves a price request originating from a message sent by the DVM on the parent chain. + * @dev Can only be called by the ChildMessenger contract which is designed to communicate only with the + * ParentMessenger contract on Mainnet. See the SpokeBase for the onlyMessenger modifier. + * @param data ABI encoded params with which to call `_publishPrice`. + */ + function processMessageFromParent(bytes memory data) public override nonReentrant onlyMessenger { + (bytes32 identifier, uint256 time, bytes memory ancillaryData, int256 price) = abi.decode( + data, + (bytes32, uint256, bytes, int256) + ); + bytes32 parentRequestId = _encodePriceRequest(identifier, time, ancillaryData); + + // Resolve the requestId used when requesting and checking the price. The childRequestIds value in the mapping + // could be uninitialized if the request was originated from: + // - the previous implementation of this contract, or + // - another chain and was pushed to this chain by mistake. + bytes32 priceRequestId = childRequestIds[parentRequestId]; + if (priceRequestId == bytes32(0)) priceRequestId = parentRequestId; + Price storage lookup = prices[priceRequestId]; + + // In order to support resolving the requests initiated from the previous implementation of this contract, we + // only update the state and emit an event if it has not yet been resolved. + if (lookup.state == RequestState.Resolved) return; + lookup.price = price; + lookup.state = RequestState.Resolved; + emit PushedPrice(identifier, time, ancillaryData, price, priceRequestId); + } + + /** + * @notice This method handles a special case when a price request was originated on the previous implementation of + * this contract, but was not settled before the upgrade. + * @dev Duplicates the resolved state from the legacy request to the new request where original ancillary data is + * used for request ID derivation. Will revert if the legacy request has not been pushed from mainnet. + * @param identifier Identifier of price request to resolve. + * @param time Timestamp of price request to resolve. + * @param ancillaryData Original ancillary data passed by the requester before stamping by the legacy spoke. + */ + function resolveLegacyRequest(bytes32 identifier, uint256 time, bytes memory ancillaryData) external { + bytes32 legacyRequestId = _encodePriceRequest(identifier, time, _legacyStampAncillaryData(ancillaryData)); + Price storage legacyLookup = prices[legacyRequestId]; + require(legacyLookup.state == RequestState.Resolved, "Price has not been resolved"); + + bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); + Price storage lookup = prices[priceRequestId]; + + // Update the state and emit an event only if the legacy request has not been resolved yet. + if (lookup.state == RequestState.Resolved) return; + lookup.price = legacyLookup.price; + lookup.state = RequestState.Resolved; + emit ResolvedLegacyRequest(identifier, time, ancillaryData, lookup.price, priceRequestId, legacyRequestId); + } + + /** + * @notice Returns whether a price has resolved for the request. This method will not revert. + * @param identifier Identifier of price request. + * @param time Timestamp of price request + * @param ancillaryData extra data of price request. + * @return True if a price is available, False otherwise. If true, then getPrice will succeed for the request. + */ + function hasPrice( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) public view override nonReentrantView onlyRegisteredContract returns (bool) { + bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); + return prices[priceRequestId].state == RequestState.Resolved; + } + + /** + * @notice Overloaded function to provide backwards compatibility for legacy financial contracts that do not use + * ancillary data. + */ + function hasPrice( + bytes32 identifier, + uint256 time + ) public view override nonReentrantView onlyRegisteredContract returns (bool) { + bytes32 priceRequestId = _encodePriceRequest(identifier, time, ""); + return prices[priceRequestId].state == RequestState.Resolved; + } + + /** + * @notice Returns resolved price for the request. Reverts if price is not available. + * @param identifier Identifier of price request. + * @param time Timestamp of price request + * @param ancillaryData extra data of price request. + * @return int256 Price, or reverts if no resolved price for any reason. + */ + function getPrice( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) public view override nonReentrantView onlyRegisteredContract returns (int256) { + bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); + Price storage lookup = prices[priceRequestId]; + require(lookup.state == RequestState.Resolved, "Price has not been resolved"); + return lookup.price; + } + + /** + * @notice Overloaded function to provide backwards compatibility for legacy financial contracts that do not use + * ancillary data. + */ + function getPrice( + bytes32 identifier, + uint256 time + ) public view override nonReentrantView onlyRegisteredContract returns (int256) { + bytes32 priceRequestId = _encodePriceRequest(identifier, time, ""); + Price storage lookup = prices[priceRequestId]; + require(lookup.state == RequestState.Resolved, "Price has not been resolved"); + return lookup.price; + } + + /** + * @notice Compresses ancillary data by providing sufficient information to track back the original ancillary data + * mainnet. + * @dev This is expected to be used in offchain infrastructure when speeding up requests to the mainnet. + * @param ancillaryData original ancillary data to be processed. + * @param requester address of the requester who initiated the price request. + * @param requestBlockNumber block number when the price request was initiated. + * @return compressed ancillary data. + */ + function compressAncillaryData( + bytes memory ancillaryData, + address requester, + uint256 requestBlockNumber + ) external view returns (bytes memory) { + return ancillaryData.compress(requester, requestBlockNumber); + } + + /** + * @dev This replicates the implementation of `_stampAncillaryData` from the previous version of this contract for + * the purpose of resolving legacy requests if they had not been resolved before the upgrade. + */ + function _legacyStampAncillaryData(bytes memory ancillaryData) internal view returns (bytes memory) { + // This contract should stamp the child network's ID so that voters on the parent network can + // deterministically track unique price requests back to this contract. + return AncillaryData.appendKeyValueUint(ancillaryData, "childChainId", block.chainid); + } +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/README.md b/contracts/external/uma/core/contracts/cross-chain-oracle/README.md new file mode 100644 index 000000000..fdff0cde3 --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/README.md @@ -0,0 +1,325 @@ +# Deployments + +Follow these instructions to deploy two smart contract suites to an L2 that are secured by and can communicate with the core DVM contracts on L1: + +- Cross chain contracts that relay messages between L1 and L2. +- Optimistic oracle contracts that allow external contracts to make price requests from L2 to L1. + +## Setup + +Before running anything below, make sure you have run `yarn build` from the root of the repo. + +Note that the fork network instructions can be used as a test run prior to the public network +deployments. + +## Starting a fork + +To run a forked network deployment, run the following command: + +```sh +HARDHAT_CHAIN_ID= yarn hardhat node --fork --no-deploy --port +``` + +You'll need to run two forks in separate terminals on different ports to do the deployments below. + +Note: in the commands below, you'll need to set the relevant `NODE_URL_X` environment variable to the url of the locally forked network `http://localhost:`. + +If you're having trouble redeploying contracts because `hardhat` wants to "reuse" contracts, then run `yarn clean && yarn` in the `core` package to reset `deployments`. + +## Step-by-step guide to deployments on rollups (Arbitrum, Boba, Optimism, Base and Blast) + +The steps below explain how to deploy the cross-chain oracle onto supported rollups. + +1. Start by exporting some environment variables (or storing them in a .env): + +```sh +# Set only the following environment variables based on which L2 you're deploying to. For example, if you're deploying to mainnet and arbitrum, set NODE_URL_1 and NODE_URL_42161. +# When running against a forked network, set the URL to http://localhost: +export NODE_URL_1= +export NODE_URL_42161= +export NODE_URL_288= +export NODE_URL_10= +export NODE_URL_8453= +export NODE_URL_81457= +export MNEMONIC="Your 12-word mnemonic here" +export ETHERSCAN_API_KEY="Your L2 Etherscan API key" +``` + +2. Add new network variables in `@uma/common` and `@uma/financial-templates-lib` packages: + +- Hardhat `defaultConfig.networks` configuration in [packages/common/src/HardhatConfig.ts](/packages/common/src/HardhatConfig.ts). Adding `companionNetworks` parameter pointing to L1 mainnet is required for approved collateral migration task. +- `etherscan.customChains` configuration in [packages/common/src/HardhatConfig.ts](/packages/common/src/HardhatConfig.ts) if the L2 is not supported by `@nomicfoundation/hardhat-verify` library. +- `PublicNetworks` configuration in [packages/common/src/PublicNetworks.ts](/packages/common/src/PublicNetworks.ts) +- `averageBlockTimeSeconds` in [packages/common/src/TimeUtils.ts](/packages/common/src/TimeUtils.ts) +- Default RPC to `getNodeUrl` in [packages/common/src/ProviderUtils.ts](/packages/common/src/ProviderUtils.ts) +- Add L2 network specific task in the form `setup-l1--xchain` in [packages/common/src/hardhat/tasks/xchainSetup.ts](/packages/common/src/hardhat/tasks/xchainSetup.ts) +- Update `migrate-collateral-whitelist` task in [packages/common/src/hardhat/tasks/collateralWhitelist.ts](/packages/common/src/hardhat/tasks/collateralWhitelist.ts) to support target L2 network +- Gas estimator settings to `MAPPING_BY_NETWORK` in [packages/financial-templates-lib/src/helpers/GasEstimator.ts](/packages/financial-templates-lib/src/helpers/GasEstimator.ts) +- Rebuild `@uma/common` package: + + ```sh + cd packages/common + yarn build + ``` + +3. Update L1 contract deploy scripts under [packages/core/deploy](/packages/core/deploy): + +- Create `_ParentMessenger` deploy script and make sure to add L2 network specific tag in the form `l1--xchain`. +- Add L2 network specific tag in the form `l1--xchain` to [Oracle Hub deploy script](/packages/core/deploy/039_deploy_oracle_hub.js) +- Add L2 network specific tag in the form `l1--xchain` to [Governor Hub deploy script](/packages/core/deploy/041_deploy_governor_hub.js) + +4. Deploy L1 contracts. Use L2 network specific tag in the form `l1--xchain` as added in the step 3 above. + +```sh +yarn hardhat deploy --network mainnet --tags l1--xchain +``` + +Add the deployed `_ParentMessenger` contract to the associated networks file. + +5. Update L2 contract deploy scripts under [packages/core/deploy](/packages/core/deploy): + +- Create `_ChildMessenger` deploy script and make sure to add L2 network specific tag in the form `l2--xchain`. +- Add L2 network specific tag in the form `l2--xchain` to [Oracle Spoke deploy script](/packages/core/deploy/038_deploy_oracle_spoke.js) +- Add L2 network specific tag in the form `l2--xchain` to [Governor Spoke deploy script](/packages/core/deploy/040_deploy_governor_spoke.js) + +6. Deploy L2 contracts. Use L2 name specific tag in the form `l2--xchain` as added in the step 5 above. + +```sh +yarn hardhat deploy --network --tags l2--xchain,Registry + +``` + +Add the deployed L2 `Registry`, `Finder`, `OracleSpoke`, `GovernorSpoke` and `_ChildMessenger` contracts to the associated networks file. + +7. Setup mainnet contracts + +```sh +yarn hardhat setup-l1--xchain --network mainnet +``` + +8. Setup L2 contracts + +```sh +yarn hardhat setup-l2-xchain --network +``` + +9. We will now deploy the `OptimisticOracle`, `OptimisticOracleV2` and required contracts to the L2. `OptimisticOracleV3` contract is deployed in a later step. + +```sh +yarn hardhat deploy --network --tags OptimisticOracle,OptimisticOracleV2,IdentifierWhitelist,AddressWhitelist,Store +``` + +Add the deployed `OptimisticOracle`, `OptimisticOracleV2`, `IdentifierWhitelist`, `AddressWhitelist` and `Store` contracts to the associated networks file under [packages/core/networks](/packages/core/networks). + +10. Setup L2 `OptimisticOracle` and `OptimisticOracleV2`: + +```sh +# Seed IdentifierWhitelist with all identifiers already approved on mainnet. Note the --from address is the IdentifierWhitelist deployed on mainnet. +CROSS_CHAIN_NODE_URL=$NODE_URL_1 yarn hardhat migrate-identifiers --network --from 0xcF649d9Da4D1362C4DAEa67573430Bd6f945e570 --crosschain true + +# Seed Collateral whitelist with all collaterals already approved on mainnet. This will also pull the final fee from the L1 store and set it in the L2 Store. +# Replace with L2 chainId value. +yarn hardhat --network migrate-collateral-whitelist --l1chainid 1 --l2chainid + +# Alternatively, add individual tokens to L2 collateral whitelist: +yarn hardhat --network whitelist-collateral --address --finalfee + +# Point L2 Finder to remaining Optimistic Oracle system contracts. +yarn hardhat setup-finder --oraclespoke --identifierwhitelist --addresswhitelist --optimisticoracle --optimisticoraclev2 --store --network + +# Register OptimisticOracle as registered contract (repeat for OptimisticOracleV2). +yarn hardhat register-accounts --network --account +``` + +11. Deploy `OptimisticOracleV3` to the L2. + +First, update `ADDRESSES_FOR_NETWORK` variable in `OptimisticOracleV3` deploy script [057_deploy_optimistic_oracle_V3.js](/packages/core/deploy/057_deploy_optimistic_oracle_V3.js) with `defaultCurrency` (e.g. USDC) for selected network. + +Deploy `OptimisticOracleV3` instance: + +```sh +yarn hardhat deploy --network --tags OptimisticOracleV3 +``` + +Add the deployed `OptimisticOracleV3` contract to the associated networks file under [packages/core/networks](/packages/core/networks). + +12. Setup L2 `OptimisticOracleV3`: + +```sh +# Point L2 Finder to remaining OptimisticOracleV3 contract. +yarn hardhat setup-finder --optimisticoraclev3 --network + +# Register OptimisticOracleV3 as registered contract. +yarn hardhat register-accounts --network --account +``` + +13. Transfer ownership: + +Transfer deployed contract ownership for each of `Registry`, `Store`, `IdentifierWhitelist`, `AddressWhitelist`, `Finder` and `OptimisticOracleV3` instances to `GovernorSpoke` on L2: + +```sh +yarn hardhat transfer-owner --network --contract --owner +``` + +Also transfer the ownership of `_ParentMessenger` on mainnet to `GovernorV2`: + +```sh +yarn hardhat transfer-owner --network mainnet --contract --owner +``` + +14. Verify contracts: + +```sh +# mainnet +yarn hardhat --network mainnet etherscan-verify --api-key --license GPL-3.0 --force-license +# arbitrum +yarn hardhat --network arbitrum etherscan-verify --api-key --license GPL-3.0 --force-license +# boba +yarn hardhat --network boba sourcify +``` + +For networks that don't have `etherscan-verify` support, use `hardhat verify`: + +```sh +yarn hardhat --network verify --contract +``` + +In case of more complex constructor arguments, pass them from file `--constructor-args arguments.js` as discussed in [hardhat-verify docs](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify#complex-arguments). In order to get constructor argument values, check them in `args` property from respective contract deployment json file (appropriate network directory under `packages/core/deployments`). + +15. Run the following script to check all required steps: + +```sh +yarn hardhat verify-xchain --network mainnet --l2 +``` + +## L2->L1 Message passing and finalization + +The cross-chain-oracle contracts send messages from L1<->L2 under different situations. L1->L2 transactions are auto finalized on all recipient chains currently supported by the cross-chain oracle. However, L2->L1 transactions are not auto finalized on L1 in most cases. For example, when sending transactions from Optimism or Arbitrum back to Ethereum L1 the transaction needs to be executed manually on L1 after the 7 day liveness. In other UMA infrastructure, such as Across, there is an automatic finalizer bot that that picks up L2->L1 token transfers and automatically finalizes them on L1. This kind of infrastructure has not yet been built out for the cross-chain-oracle and therefore will need to be done manually if the situation arrives. Both Optimism and Arbitrum provide their own sets of scripts to facilitate this: + +To finalize **Arbitrum** transactions see the docs [here](https://github.com/OffchainLabs/arbitrum-tutorials/tree/master/packages/outbox-execute) on how to do this. Alternatively, Arbiscan provides a list of L2->L1 transactions and a UI for finalizing those that have passed liveness. This can also be used if you don't want to run the `outbox-execute` script. The relevant page can be found [here](https://arbiscan.io/txsExit). + +To finalize **Optimism** transactions see the equivalent script [here](https://github.com/ethereum-optimism/optimism/blob/34e7450873548f65bf3160ca58eed2328907310a/packages/sdk/tasks/finalize-withdrawal.ts#L14). Optimism's Etherscan also provides a UI, similar to Arbitrum, which can be found [here](https://optimistic.etherscan.io/txsExit). + +To finalize **Base** transactions see the equivalent script [here](https://github.com/ethereum-optimism/optimism/blob/develop/packages/message-relayer/src/exec/withdraw.ts). Base's Etherscan also provides a UI, similar to Optimism, which can be found [here](https://basescan.org/txsExit). + +**Boba** at present auto-finalizes all L2->L1 transactions without any required user intervention. + +## Step-by-step guide to deployments for Non-rollup chains + +The steps below explain how to deploy the cross-chain oracle onto non-rollup chains using the `Admin_ChildMessenger`. This enables a multisig to act as the "messenger" thereby enabling the UMA Optimistic oracle to be deployed onto chains that do not have a supporting canonical bridge. + +1. Start by exporting some environment variables (or storing them in a `.env`): + +```sh +# Set only the following environment variables based on which L2 you're deploying to. For example, if you're deploying to xDAI, set NODE_URL_100. +# When running against a forked network, set the URL to http://localhost: +export NODE_URL_100= +export MNEMONIC="Your 12-word mnemonic here" +export ETHERSCAN_API_KEY="Your L2 Etherscan API key" +``` + +2. Add new network variables in `@uma/common` and `@uma/financial-templates-lib` packages: + +- Hardhat `defaultConfig.networks` configuration in [packages/common/src/HardhatConfig.ts](/packages/common/src/HardhatConfig.ts). Adding `companionNetworks` parameter pointing to L1 mainnet is required for approved collateral migration task. +- `etherscan.customChains` configuration in [packages/common/src/HardhatConfig.ts](/packages/common/src/HardhatConfig.ts) if the L2 is not supported by `@nomicfoundation/hardhat-verify` library. +- `PublicNetworks` configuration in [packages/common/src/PublicNetworks.ts](/packages/common/src/PublicNetworks.ts) +- `averageBlockTimeSeconds` in [packages/common/src/TimeUtils.ts](/packages/common/src/TimeUtils.ts) +- Default RPC to `getNodeUrl` in [packages/common/src/ProviderUtils.ts](/packages/common/src/ProviderUtils.ts) +- Gas estimator settings to `MAPPING_BY_NETWORK` in [packages/financial-templates-lib/src/helpers/GasEstimator.ts](/packages/financial-templates-lib/src/helpers/GasEstimator.ts) +- Rebuild `@uma/common` package: + + ```sh + cd packages/common + yarn build + ``` + +3. Deploy L2 Contracts: + +```sh +# Replace with network name added in Hardhat configuration from Step 2 above. +yarn hardhat deploy --network --tags l2-admin-xchain,Registry +``` + +Add the deployed `Registry`, `Finder`, `OracleSpoke`, `GovernorSpoke` and `Admin_ChildMessenger` contracts to the associated networks file under [packages/core/networks](/packages/core/networks). + +4. Setup L2 contracts: + +```sh +yarn hardhat setup-l2-admin-xchain --network +``` + +5. We will now deploy the `OptimisticOracle`, `OptimisticOracleV2` and required contracts to the L2. + +```sh +yarn hardhat deploy --network --tags OptimisticOracle,OptimisticOracleV2,IdentifierWhitelist,AddressWhitelist,Store +``` + +Add the deployed `OptimisticOracle`, `OptimisticOracleV2`, `IdentifierWhitelist`, `AddressWhitelist` and `Store` contracts to the associated networks file under [packages/core/networks](/packages/core/networks). + +6. Setup L2 `OptimisticOracle` and `OptimisticOracleV2`: + +```sh +# Seed IdentifierWhitelist with all identifiers already approved on mainnet. Note the --from address is the IdentifierWhitelist deployed on mainnet. +CROSS_CHAIN_NODE_URL= yarn hardhat migrate-identifiers --network --from 0xcF649d9Da4D1362C4DAEa67573430Bd6f945e570 --crosschain true + +# Seed Collateral whitelist with all collaterals already approved on mainnet. This will also pull the final fee from the L1 store and set it in the L2 Store. +# Replace with L2 chainId value. +yarn hardhat --network migrate-collateral-whitelist --l1chainid 1 --l2chainid + +# Point L2 Finder to remaining Optimistic Oracle system contracts. +yarn hardhat setup-finder --oraclespoke --identifierwhitelist --addresswhitelist --optimisticoracle --optimisticoraclev2 --store --network + +# Register OptimisticOracle as registered contract (repeat for OptimisticOracleV2). +yarn hardhat register-accounts --network --account +``` + +7. Deploy `OptimisticOracleV3` to the L2. + +First, update `ADDRESSES_FOR_NETWORK` variable in `OptimisticOracleV3` deploy script [057_deploy_optimistic_oracle_V3.js](/packages/core/deploy/057_deploy_optimistic_oracle_V3.js) with `defaultCurrency` (e.g. USDC) for selected network. + +Deploy `OptimisticOracleV3` instance: + +```sh +yarn hardhat deploy --network --tags OptimisticOracleV3 +``` + +Add the deployed `OptimisticOracleV3` contract to the associated networks file under [packages/core/networks](/packages/core/networks). + +8. Setup L2 `OptimisticOracleV3`: + +```sh +# Point L2 Finder to remaining OptimisticOracleV3 contract. +yarn hardhat setup-finder --optimisticoraclev3 --network + +# Register OptimisticOracleV3 as registered contract. +yarn hardhat register-accounts --network --account +``` + +9. Verify contracts: + +```sh +# Use hardhat verify on each deployed contract above. +yarn hardhat --network verify --contract +``` + +In case of more complex constructor arguments, pass them from file `--constructor-args arguments.js` as discussed in [hardhat-verify docs](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify#complex-arguments). In order to get constructor argument values, check them in `args` property from respective contract deployment json file (appropriate network directory under `packages/core/deployments`). + +10. Transfer ownership: + +Transfer deployed contract ownership for each of `Registry`, `Store`, `IdentifierWhitelist`, `AddressWhitelist`, `Finder` and `OptimisticOracleV3` instances to `GovernorSpoke`: + +```sh +yarn hardhat transfer-owner --network --contract --owner +``` + +Also transfer `Admin_ChildMessenger` contract ownership to multisig: + +```sh +yarn hardhat transfer-owner --network --contract --owner +``` + +11. Run the following script to check all required steps: + +```sh +yarn hardhat verify-admin-xchain --network +``` diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/SpokeBase.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/SpokeBase.sol new file mode 100644 index 000000000..199c8f99a --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/SpokeBase.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; +import "./interfaces/ChildMessengerInterface.sol"; + +import "../data-verification-mechanism/implementation/Constants.sol"; +import "../common/implementation/HasFinder.sol"; + +/** + * @title Cross-chain Oracle L2 Spoke Base. + * @notice Provides access control to Governance and Oracle spoke L2 contracts. + */ + +abstract contract SpokeBase is HasFinder { + modifier onlyMessenger() { + require(msg.sender == address(getChildMessenger()), "Caller must be messenger"); + _; + } + + /** + * @notice Returns the child messenger address set in the finder. + * @return ChildMessengerInterface instance of child messenger deployed on L2. + */ + function getChildMessenger() public view returns (ChildMessengerInterface) { + return ChildMessengerInterface(finder.getImplementationAddress(OracleInterfaces.ChildMessenger)); + } +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Admin_ChildMessenger.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Admin_ChildMessenger.sol new file mode 100644 index 000000000..5ad5c2368 --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Admin_ChildMessenger.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "../interfaces/ChildMessengerInterface.sol"; +import "../interfaces/ChildMessengerConsumerInterface.sol"; +import "../../common/implementation/Lockable.sol"; + +/** + * @notice A version of the child messenger that allows an admin to relay messages on its behalf. + * @dev No parent messenger is needed for this case, as the admin could be trusted to manually send DVM requests on + * mainnet. This is intended to be used as a "beta" deployment compatible with any EVM-compatible chains before + * implementing a full bridge adapter. Put simply, it is meant as a stop-gap. + */ +contract Admin_ChildMessenger is Ownable, Lockable, ChildMessengerInterface { + // The only child network contract that can send messages over the bridge via the messenger is the oracle spoke. + address public oracleSpoke; + + event SetOracleSpoke(address newOracleSpoke); + event MessageSentToParent(bytes data, address indexed oracleSpoke); + event MessageReceivedFromParent(bytes data, address indexed targetSpoke, address indexed caller); + + /** + * @notice Changes the stored address of the Oracle spoke, deployed on L2. + * @dev The caller of this function must be the admin. + * @param newOracleSpoke address of the new oracle spoke, deployed on L2. + */ + function setOracleSpoke(address newOracleSpoke) public onlyOwner nonReentrant { + oracleSpoke = newOracleSpoke; + emit SetOracleSpoke(newOracleSpoke); + } + + /** + * @notice Logs a message to be manually relayed to L1. + * @dev The caller must be the OracleSpoke on L2. No other contract is permissioned to call this function. + * @param data data message sent to the L1 messenger. Should be an encoded function call or packed data. + */ + function sendMessageToParent(bytes memory data) public override nonReentrant { + require(msg.sender == oracleSpoke, "Only callable by oracleSpoke"); + + // Note: only emit an event. These messages will be manually relayed. + emit MessageSentToParent(data, oracleSpoke); + } + + /** + * @notice Process a received message from the admin. + * @dev The caller must be the the admin. + * @param data data message sent from the admin. Should be an encoded function call or packed data. + * @param target desired recipient of `data`. Target must implement the `processMessageFromParent` function. Having + * this as a param enables the Admin to send messages to arbitrary addresses from the messenger contract. This is + * primarily used to send messages to the OracleSpoke and GovernorSpoke. + */ + function processMessageFromCrossChainParent(bytes memory data, address target) public onlyOwner nonReentrant { + ChildMessengerConsumerInterface(target).processMessageFromParent(data); + emit MessageReceivedFromParent(data, target, msg.sender); + } +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Arbitrum_ChildMessenger.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Arbitrum_ChildMessenger.sol new file mode 100644 index 000000000..3c9025eb5 --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Arbitrum_ChildMessenger.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../interfaces/ChildMessengerInterface.sol"; +import "../interfaces/ChildMessengerConsumerInterface.sol"; +import "../../common/implementation/Lockable.sol"; +import "../../external/avm/AVM_CrossDomainEnabled.sol"; + +/** + * @notice Sends and receives cross chain messages between Arbitrum L2 and Ethereum L1 network. + * @dev This contract is ownable via the onlyCrossDomainAccount modifier, restricting ownership to the cross-domain + * parent messenger contract that lives on L1. + */ +contract Arbitrum_ChildMessenger is AVM_CrossDomainEnabled, ChildMessengerInterface, Lockable { + // The only child network contract that can send messages over the bridge via the messenger is the oracle spoke. + address public oracleSpoke; + + // Messenger contract on the other side of the L1<->L2 bridge. + address public parentMessenger; + + event SetOracleSpoke(address newOracleSpoke); + event SetParentMessenger(address newParentMessenger); + event MessageSentToParent(bytes data, address indexed parentAddress, address indexed oracleSpoke, uint256 id); + event MessageReceivedFromParent(bytes data, address indexed targetSpoke, address indexed parentAddress); + + /** + * @notice Construct the Arbitrum_ChildMessenger contract. + * @param _parentMessenger The address of the L1 parent messenger. Acts as the "owner" of this contract. + */ + constructor(address _parentMessenger) { + parentMessenger = _parentMessenger; + } + + /** + * @notice Changes the stored address of the Oracle spoke, deployed on L2. + * @dev The caller of this function must be the parent messenger, over the canonical bridge. + * @param newOracleSpoke address of the new oracle spoke, deployed on L2. + */ + function setOracleSpoke(address newOracleSpoke) public onlyFromCrossDomainAccount(parentMessenger) nonReentrant { + oracleSpoke = newOracleSpoke; + emit SetOracleSpoke(newOracleSpoke); + } + + /** + * @notice Changes the stored address of the parent messenger, deployed on L1. + * @dev The caller of this function must be the parent messenger, over the canonical bridge. + * @param newParentMessenger address of the new parent messenger, deployed on L1. + */ + function setParentMessenger( + address newParentMessenger + ) public onlyFromCrossDomainAccount(parentMessenger) nonReentrant { + parentMessenger = newParentMessenger; + emit SetParentMessenger(newParentMessenger); + } + + /** + * @notice Sends a message to the parent messenger via the canonical message bridge. + * @dev The caller must be the OracleSpoke on L2. No other contract is permissioned to call this function. + * @dev The L1 target, the parent messenger, must implement processMessageFromChild to consume the message. + * @param data data message sent to the L1 messenger. Should be an encoded function call or packed data. + */ + function sendMessageToParent(bytes memory data) public override nonReentrant { + require(msg.sender == oracleSpoke, "Only callable by oracleSpoke"); + bytes memory dataSentToParent = abi.encodeWithSignature("processMessageFromCrossChainChild(bytes)", data); + uint256 id = sendCrossDomainMessage(msg.sender, parentMessenger, dataSentToParent); + emit MessageSentToParent(dataSentToParent, parentMessenger, oracleSpoke, id); + } + + /** + * @notice Process a received message from the parent messenger via the canonical message bridge. + * @dev The caller must be the the parent messenger, sent over the canonical message bridge. + * @param data data message sent from the L1 messenger. Should be an encoded function call or packed data. + * @param target desired recipient of `data`. Target must implement the `processMessageFromParent` function. Having + * this as a param enables the L1 Messenger to send messages to arbitrary addresses on the L1. This is primarily + * used to send messages to the OracleSpoke and GovernorSpoke on L2. + */ + function processMessageFromCrossChainParent( + bytes memory data, + address target + ) public onlyFromCrossDomainAccount(parentMessenger) nonReentrant { + ChildMessengerConsumerInterface(target).processMessageFromParent(data); + emit MessageReceivedFromParent(data, target, parentMessenger); + } +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Arbitrum_ParentMessenger.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Arbitrum_ParentMessenger.sol new file mode 100644 index 000000000..f163e7d54 --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Arbitrum_ParentMessenger.sol @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../external/avm/Arbitrum_Messenger.sol"; +import "../interfaces/ParentMessengerInterface.sol"; +import "../interfaces/ParentMessengerConsumerInterface.sol"; +import "./ParentMessengerBase.sol"; +import "../../common/implementation/Lockable.sol"; + +/** + * @notice Sends cross chain messages from Ethereum L1 to Arbitrum L2 network. + * @dev This contract is ownable and should be owned by the DVM governor. + */ +contract Arbitrum_ParentMessenger is Arbitrum_Messenger, ParentMessengerInterface, ParentMessengerBase, Lockable { + event SetDefaultGasLimit(uint32 newDefaultGasLimit); + event SetDefaultMaxSubmissionCost(uint256 newMaxSubmissionCost); + event SetDefaultGasPrice(uint256 newDefaultGasPrice); + event SetRefundL2Address(address newRefundL2Address); + event MessageSentToChild( + bytes data, + address indexed targetSpoke, + uint256 l1CallValue, + uint32 gasLimit, + uint256 gasPrice, + uint256 maxSubmissionCost, + address refundL2Address, + address indexed childMessenger, + uint256 sequenceNumber + ); + event MessageReceivedFromChild(bytes data, address indexed childMessenger, address indexed targetHub); + + // Gas limit for immediate L2 execution attempt (can be estimated via NodeInterface.estimateRetryableTicket). + // NodeInterface precompile interface exists at L2 address 0x00000000000000000000000000000000000000C8 + uint32 public defaultGasLimit = 5_000_000; + + // Amount of ETH allocated to pay for the base submission fee. The base submission fee is a parameter unique to + // retryable transactions; the user is charged the base submission fee to cover the storage costs of keeping their + // ticket’s calldata in the retry buffer. (current base submission fee is queryable via + // ArbRetryableTx.getSubmissionPrice). ArbRetryableTicket precompile interface exists at L2 address + // 0x000000000000000000000000000000000000006E. + uint256 public defaultMaxSubmissionCost = 0.1e18; + + // L2 Gas price bid for immediate L2 execution attempt (queryable via standard eth*gasPrice RPC) + uint256 public defaultGasPrice = 10e9; // 10 gWei + + // This address on L2 receives extra ETH that is left over after relaying a message via the inbox. + address public refundL2Address; + + /** + * @notice Construct the Optimism_ParentMessenger contract. + * @param _inbox Contract that sends generalized messages to the Arbitrum chain. + * @param _childChainId The chain id of the Optimism L2 network this messenger should connect to. + **/ + constructor(address _inbox, uint256 _childChainId) Arbitrum_Messenger(_inbox) ParentMessengerBase(_childChainId) { + refundL2Address = owner(); + } + + /** + * @notice Changes the refund address on L2 that receives excess gas or the full msg.value if the retryable + * ticket reverts. + * @dev The caller of this function must be the owner, which should be set to the DVM governor. + * @param newRefundl2Address the new refund address to set. This should be set to an L2 address that is trusted by + * the owner as it can spend Arbitrum L2 refunds for excess gas when sending transactions on Arbitrum. + */ + function setRefundL2Address(address newRefundl2Address) public onlyOwner nonReentrant { + refundL2Address = newRefundl2Address; + emit SetRefundL2Address(refundL2Address); + } + + /** + * @notice Changes the default gas limit that is sent along with transactions to Arbitrum. + * @dev The caller of this function must be the owner, which should be set to the DVM governor. + * @param newDefaultGasLimit the new L2 gas limit to be set. + */ + function setDefaultGasLimit(uint32 newDefaultGasLimit) public onlyOwner nonReentrant { + defaultGasLimit = newDefaultGasLimit; + emit SetDefaultGasLimit(newDefaultGasLimit); + } + + /** + * @notice Changes the default gas price that is sent along with transactions to Arbitrum. + * @dev The caller of this function must be the owner, which should be set to the DVM governor. + * @param newDefaultGasPrice the new L2 gas price to be set. + */ + function setDefaultGasPrice(uint256 newDefaultGasPrice) public onlyOwner nonReentrant { + defaultGasPrice = newDefaultGasPrice; + emit SetDefaultGasPrice(newDefaultGasPrice); + } + + /** + * @notice Changes the default max submission cost that is sent along with transactions to Arbitrum. + * @dev The caller of this function must be the owner, which should be set to the DVM governor. + * @param newDefaultMaxSubmissionCost the new L2 max submission cost to be set. + */ + function setDefaultMaxSubmissionCost(uint256 newDefaultMaxSubmissionCost) public onlyOwner nonReentrant { + defaultMaxSubmissionCost = newDefaultMaxSubmissionCost; + emit SetDefaultMaxSubmissionCost(newDefaultMaxSubmissionCost); + } + + /** + * @notice Changes the address of the oracle spoke on L2 via the child messenger. + * @dev The caller of this function must be the owner, which should be set to the DVM governor. + * @dev This function will only succeed if this contract has enough ETH to cover the approximate L1 call value. + * @param newOracleSpoke the new oracle spoke address set on L2. + */ + function setChildOracleSpoke(address newOracleSpoke) public onlyOwner nonReentrant { + bytes memory dataSentToChild = abi.encodeWithSignature("setOracleSpoke(address)", newOracleSpoke); + _sendMessageToChild(dataSentToChild, childMessenger); + } + + /** + * @notice Changes the address of the parent messenger on L2 via the child messenger. + * @dev The caller of this function must be the owner, which should be set to the DVM governor. + * @dev This function will only succeed if this contract has enough ETH to cover the approximate L1 call value. + * @param newParentMessenger the new parent messenger contract to be set on L2. + */ + function setChildParentMessenger(address newParentMessenger) public onlyOwner nonReentrant { + bytes memory dataSentToChild = abi.encodeWithSignature("setParentMessenger(address)", newParentMessenger); + _sendMessageToChild(dataSentToChild, childMessenger); + } + + /** + * @notice Sends a message to the child messenger via the canonical message bridge. + * @dev The caller must be the either the OracleHub or the GovernorHub. This is to send either a + * price or initiate a governance action to the OracleSpoke or GovernorSpoke on the child network. + * @dev The recipient of this message is the child messenger. The messenger must implement processMessageFromParent + * which then forwards the data to the target either the OracleSpoke or the governorSpoke depending on the caller. + * @dev This function will only succeed if this contract has enough ETH to cover the approximate L1 call value. + * @param data data message sent to the child messenger. Should be an encoded function call or packed data. + */ + function sendMessageToChild(bytes memory data) external override onlyHubContract nonReentrant { + address target = msg.sender == oracleHub ? oracleSpoke : governorSpoke; + bytes memory dataSentToChild = abi.encodeWithSignature( + "processMessageFromCrossChainParent(bytes,address)", + data, + target + ); + _sendMessageToChild(dataSentToChild, target); + } + + /** + * @notice Process a received message from the child messenger via the canonical message bridge. + * @dev The caller must be the the child messenger, sent over the canonical message bridge. + * @dev Note that only the OracleHub can receive messages from the child messenger. Therefore we can always forward + * these messages to this contract. The OracleHub must implement processMessageFromChild to handle this message. + * @param data data message sent from the child messenger. Should be an encoded function call or packed data. + */ + function processMessageFromCrossChainChild( + bytes memory data + ) public onlyFromCrossDomainAccount(childMessenger) nonReentrant { + ParentMessengerConsumerInterface(oracleHub).processMessageFromChild(childChainId, data); + emit MessageReceivedFromChild(data, childMessenger, oracleHub); + } + + /** + * @notice This function is expected to be queried by Hub contracts that need to determine how much ETH + * to include in msg.value when calling `sendMessageToChild`. + * @return Amount of msg.value to include to send cross-chain message. + */ + function getL1CallValue() + public + view + override(ParentMessengerBase, ParentMessengerInterface) + nonReentrantView + returns (uint256) + { + return _getL1CallValue(); + } + + // We need to allow this contract to receive ETH, so that it can include some msg.value amount on external calls + // to the `sendMessageToChild` function. We shouldn't expect the owner of this contract to send + // ETH because the owner is intended to be a contract (e.g. the Governor) and we don't want to change the + // Governor interface. + fallback() external payable {} + + // Used to determine how much ETH to include in msg.value when calling admin functions like + // `setChildParentMessenger` and sending messages across the bridge. + function _getL1CallValue() internal view returns (uint256) { + // This could overflow if these values are set too high, but since they are configurable by trusted owner + // we won't catch this case. + return defaultMaxSubmissionCost + defaultGasPrice * defaultGasLimit; + } + + // This function will only succeed if this contract has enough ETH to cover the approximate L1 call value. + function _sendMessageToChild(bytes memory data, address target) internal { + uint256 requiredL1CallValue = _getL1CallValue(); + require(address(this).balance >= requiredL1CallValue, "Insufficient ETH balance"); + + uint256 seqNumber = sendTxToL2NoAliassing( + childMessenger, + refundL2Address, + requiredL1CallValue, + defaultMaxSubmissionCost, + defaultGasLimit, + defaultGasPrice, + data + ); + emit MessageSentToChild( + data, + target, + requiredL1CallValue, + defaultGasLimit, + defaultGasPrice, + defaultMaxSubmissionCost, + refundL2Address, + childMessenger, + seqNumber + ); + } +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Optimism_ChildMessenger.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Optimism_ChildMessenger.sol new file mode 100644 index 000000000..814d40ad7 --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Optimism_ChildMessenger.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +// This should be replaced with a "real" import when Optimism release their new contract versions. +import "@eth-optimism/contracts/libraries/bridge/CrossDomainEnabled.sol"; +import "@eth-optimism/contracts/libraries/constants/Lib_PredeployAddresses.sol"; +import "../interfaces/ChildMessengerInterface.sol"; +import "../interfaces/ChildMessengerConsumerInterface.sol"; +import "../../common/implementation/Lockable.sol"; + +/** + * @notice Sends cross chain messages from Optimism L2 to Ethereum L1 network. + * @dev This contract is ownable via the onlyFromCrossDomainAccount. modifier, restricting ownership to the cross-domain + * parent messenger contract that lives on L1. + */ +contract Optimism_ChildMessenger is CrossDomainEnabled, ChildMessengerInterface, Lockable { + // The only child network contract that can send messages over the bridge via the messenger is the oracle spoke. + address public oracleSpoke; + + // Messenger contract on the other side of the L1<->L2 bridge. + address public parentMessenger; + + // Hard coded default gas limit for L1 transactions. + uint32 public defaultGasLimit = 5_000_000; + + event SetOracleSpoke(address newOracleSpoke); + event SetParentMessenger(address newParentMessenger); + event SetDefaultGasLimit(uint32 newDefaultGasLimit); + event MessageSentToParent(bytes data, address indexed parentAddress, address oracleSpoke, uint32 gasLimit); + event MessageReceivedFromParent(bytes data, address indexed targetSpoke, address indexed parentAddress); + + /** + * @notice Construct the Optimism_ChildMessenger contract. + * @param _parentMessenger The address of the L1 parent messenger. Acts as the "owner" of this contract. + */ + constructor(address _parentMessenger) CrossDomainEnabled(Lib_PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER) { + parentMessenger = _parentMessenger; + } + + /** + * @notice Changes the stored address of the Oracle spoke, deployed on L2. + * @dev The caller of this function must be the parent messenger, over the canonical bridge. + * @param newOracleSpoke address of the new oracle spoke, deployed on L2. + */ + function setOracleSpoke(address newOracleSpoke) public onlyFromCrossDomainAccount(parentMessenger) nonReentrant { + oracleSpoke = newOracleSpoke; + emit SetOracleSpoke(newOracleSpoke); + } + + /** + * @notice Changes the stored address of the parent messenger, deployed on L1. + * @dev The caller of this function must be the parent messenger, over the canonical bridge. + * @param newParentMessenger address of the new parent messenger, deployed on L1. + */ + function setParentMessenger( + address newParentMessenger + ) public onlyFromCrossDomainAccount(parentMessenger) nonReentrant { + parentMessenger = newParentMessenger; + emit SetParentMessenger(newParentMessenger); + } + + /** + * @notice Changes the default gas limit that is sent along with transactions to Ethereum. + * @dev The caller of this function must be the parent messenger, over the canonical bridge. + * @param newDefaultGasLimit the new L1 gas limit to be set. + */ + function setDefaultGasLimit( + uint32 newDefaultGasLimit + ) public onlyFromCrossDomainAccount(parentMessenger) nonReentrant { + defaultGasLimit = newDefaultGasLimit; + emit SetDefaultGasLimit(newDefaultGasLimit); + } + + /** + * @notice Sends a message to the parent messenger via the canonical message bridge. + * @dev The caller must be the OracleSpoke on L2. No other contract is permissioned to call this function. + * @dev The L1 target, the parent messenger, must implement processMessageFromChild to consume the message. + * @param data data message sent to the L1 messenger. Should be an encoded function call or packed data. + */ + function sendMessageToParent(bytes memory data) public override nonReentrant { + require(msg.sender == oracleSpoke, "Only callable by oracleSpoke"); + bytes memory dataSentToParent = abi.encodeWithSignature("processMessageFromCrossChainChild(bytes)", data); + sendCrossDomainMessage(parentMessenger, defaultGasLimit, dataSentToParent); + emit MessageSentToParent(dataSentToParent, parentMessenger, oracleSpoke, defaultGasLimit); + } + + /** + * @notice Process a received message from the parent messenger via the canonical message bridge. + * @dev The caller must be the the parent messenger, sent over the canonical message bridge. + * @param data data message sent from the L1 messenger. Should be an encoded function call or packed data. + * @param target desired recipient of `data`. Target must implement the `processMessageFromParent` function. Having + * this as a param enables the L1 Messenger to send messages to arbitrary addresses on the L2. This is primarily + * used to send messages to the OracleSpoke and GovernorSpoke on L2. + */ + function processMessageFromCrossChainParent( + bytes memory data, + address target + ) public onlyFromCrossDomainAccount(parentMessenger) nonReentrant { + ChildMessengerConsumerInterface(target).processMessageFromParent(data); + emit MessageReceivedFromParent(data, target, parentMessenger); + } +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Optimism_ParentMessenger.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Optimism_ParentMessenger.sol new file mode 100644 index 000000000..f7983b296 --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Optimism_ParentMessenger.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +// This should be replaced with a "real" import when Optimism release their new contract versions. +import "@eth-optimism/contracts/libraries/bridge/CrossDomainEnabled.sol"; +import "../interfaces/ParentMessengerInterface.sol"; +import "../interfaces/ParentMessengerConsumerInterface.sol"; +import "./ParentMessengerBase.sol"; +import "../../common/implementation/Lockable.sol"; + +/** + * @notice Sends cross chain messages from Ethereum L1 to Optimism L2 network. + * @dev This contract is ownable and should be owned by the DVM governor. + */ +contract Optimism_ParentMessenger is CrossDomainEnabled, ParentMessengerInterface, ParentMessengerBase, Lockable { + event SetDefaultGasLimit(uint32 newDefaultGasLimit); + event MessageSentToChild(bytes data, address indexed targetSpoke, uint32 gasLimit, address indexed childMessenger); + event MessageReceivedFromChild(bytes data, address indexed childMessenger, address indexed targetHub); + + uint32 public defaultGasLimit = 5_000_000; + + /** + * @notice Construct the Optimism_ParentMessenger contract. + * @param _crossDomainMessenger The address of the Optimism cross domain messenger contract. + * @param _childChainId The chain id of the Optimism L2 network this messenger should connect to. + **/ + constructor( + address _crossDomainMessenger, + uint256 _childChainId + ) CrossDomainEnabled(_crossDomainMessenger) ParentMessengerBase(_childChainId) {} + + /** + * @notice Changes the default gas limit that is sent along with transactions to Optimism. + * @dev The caller of this function must be the owner. This should be set to the DVM governor. + * @param newDefaultGasLimit the new L2 gas limit to be set. + */ + function setDefaultGasLimit(uint32 newDefaultGasLimit) public onlyOwner nonReentrant { + defaultGasLimit = newDefaultGasLimit; + emit SetDefaultGasLimit(newDefaultGasLimit); + } + + /** + * @notice Changes the address of the oracle spoke on L2 via the child messenger. + * @dev The caller of this function must be the owner. This should be set to the DVM governor. + * @param newOracleSpoke the new oracle spoke address set on L2. + */ + function setChildOracleSpoke(address newOracleSpoke) public onlyOwner nonReentrant { + bytes memory dataSentToChild = abi.encodeWithSignature("setOracleSpoke(address)", newOracleSpoke); + sendCrossDomainMessage(childMessenger, defaultGasLimit, dataSentToChild); + emit MessageSentToChild(dataSentToChild, address(0), defaultGasLimit, childMessenger); + } + + /** + * @notice Changes the address of the parent messenger on L2 via the child messenger. + * @dev The caller of this function must be the owner. This should be set to the DVM governor. + * @param newParentMessenger the new parent messenger contract to be set on L2. + */ + function setChildParentMessenger(address newParentMessenger) public onlyOwner nonReentrant { + bytes memory dataSentToChild = abi.encodeWithSignature("setParentMessenger(address)", newParentMessenger); + sendCrossDomainMessage(childMessenger, defaultGasLimit, dataSentToChild); + emit MessageSentToChild(dataSentToChild, address(0), defaultGasLimit, childMessenger); + } + + /** + * @notice Changes the Optimism_ChildMessenger default gas limit on L2 via the child messenger. + * @dev The caller of this function must be the owner. This should be set to the DVM governor. + * @param newDefaultGasLimit the new default gas limit set on L2. + */ + function setChildDefaultGasLimit(uint32 newDefaultGasLimit) public onlyOwner nonReentrant { + bytes memory dataSentToChild = abi.encodeWithSignature("setDefaultGasLimit(uint32)", newDefaultGasLimit); + sendCrossDomainMessage(childMessenger, defaultGasLimit, dataSentToChild); + emit MessageSentToChild(dataSentToChild, address(0), defaultGasLimit, childMessenger); + } + + /** + * @notice Sends a message to the child messenger via the canonical message bridge. + * @dev The caller must be the either the OracleHub or the GovernorHub. This is to send either a + * price or initiate a governance action to the OracleSpoke or GovernorSpoke on the child network. + * @dev The recipient of this message is the child messenger. The messenger must implement processMessageFromParent + * which then forwards the data to the target either the OracleSpoke or the governorSpoke depending on the caller. + * @param data data message sent to the child messenger. Should be an encoded function call or packed data. + */ + function sendMessageToChild(bytes memory data) public override onlyHubContract nonReentrant { + address target = msg.sender == oracleHub ? oracleSpoke : governorSpoke; + bytes memory dataSentToChild = abi.encodeWithSignature( + "processMessageFromCrossChainParent(bytes,address)", + data, + target + ); + sendCrossDomainMessage(childMessenger, defaultGasLimit, dataSentToChild); + emit MessageSentToChild(dataSentToChild, target, defaultGasLimit, childMessenger); + } + + /** + * @notice Process a received message from the child messenger via the canonical message bridge. + * @dev The caller must be the the child messenger, sent over the canonical message bridge. + * @dev Note that only the OracleHub can receive messages from the child messenger. Therefore we can always forward + * these messages to this contract. The OracleHub must implement processMessageFromChild to handle this message. + * @param data data message sent from the child messenger. Should be an encoded function call or packed data. + */ + function processMessageFromCrossChainChild( + bytes memory data + ) public onlyFromCrossDomainAccount(childMessenger) nonReentrant { + ParentMessengerConsumerInterface(oracleHub).processMessageFromChild(childChainId, data); + emit MessageReceivedFromChild(data, childMessenger, oracleHub); + } +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/ParentMessengerBase.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/ParentMessengerBase.sol new file mode 100644 index 000000000..8b52f7bf8 --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/ParentMessengerBase.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "../interfaces/ParentMessengerInterface.sol"; + +abstract contract ParentMessengerBase is Ownable, ParentMessengerInterface { + uint256 public childChainId; + + address public childMessenger; + + address public oracleHub; + address public governorHub; + + address public oracleSpoke; + address public governorSpoke; + + event SetChildMessenger(address indexed childMessenger); + event SetOracleHub(address indexed oracleHub); + event SetGovernorHub(address indexed governorHub); + event SetOracleSpoke(address indexed oracleSpoke); + event SetGovernorSpoke(address indexed governorSpoke); + + modifier onlyHubContract() { + require(msg.sender == oracleHub || msg.sender == governorHub, "Only privileged caller"); + _; + } + + /** + * @notice Construct the ParentMessengerBase contract. + * @param _childChainId The chain id of the L2 network this messenger should connect to. + **/ + constructor(uint256 _childChainId) { + childChainId = _childChainId; + } + + /******************* + * OWNER METHODS * + *******************/ + + /** + * @notice Changes the stored address of the child messenger, deployed on L2. + * @dev The caller of this function must be the owner. This should be set to the DVM governor. + * @param newChildMessenger address of the new child messenger, deployed on L2. + */ + function setChildMessenger(address newChildMessenger) public onlyOwner { + childMessenger = newChildMessenger; + emit SetChildMessenger(childMessenger); + } + + /** + * @notice Changes the stored address of the Oracle hub, deployed on L1. + * @dev The caller of this function must be the owner. This should be set to the DVM governor. + * @param newOracleHub address of the new oracle hub, deployed on L1 Ethereum. + */ + function setOracleHub(address newOracleHub) public onlyOwner { + oracleHub = newOracleHub; + emit SetOracleHub(oracleHub); + } + + /** + * @notice Changes the stored address of the Governor hub, deployed on L1. + * @dev The caller of this function must be the owner. This should be set to the DVM governor. + * @param newGovernorHub address of the new governor hub, deployed on L1 Ethereum. + */ + function setGovernorHub(address newGovernorHub) public onlyOwner { + governorHub = newGovernorHub; + emit SetGovernorHub(governorHub); + } + + /** + * @notice Changes the stored address of the oracle spoke, deployed on L2. + * @dev The caller of this function must be the owner. This should be set to the DVM governor. + * @param newOracleSpoke address of the new oracle spoke, deployed on L2. + */ + function setOracleSpoke(address newOracleSpoke) public onlyOwner { + oracleSpoke = newOracleSpoke; + emit SetOracleSpoke(oracleSpoke); + } + + /** + * @notice Changes the stored address of the governor spoke, deployed on L2. + * @dev The caller of this function must be the owner. This should be set to the DVM governor. + * @param newGovernorSpoke address of the new governor spoke, deployed on L2. + */ + function setGovernorSpoke(address newGovernorSpoke) public onlyOwner { + governorSpoke = newGovernorSpoke; + emit SetGovernorSpoke(governorSpoke); + } + + /** + * @notice Returns the amount of ETH required for a caller to pass as msg.value when calling `sendMessageToChild`. + * @return The amount of ETH required for a caller to pass as msg.value when calling `sendMessageToChild`. + */ + function getL1CallValue() external view virtual override returns (uint256) { + return 0; + } +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Polygon_ChildMessenger.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Polygon_ChildMessenger.sol new file mode 100644 index 000000000..e93dd0b86 --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Polygon_ChildMessenger.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@maticnetwork/fx-portal/contracts/tunnel/FxBaseChildTunnel.sol"; +import "../interfaces/ChildMessengerInterface.sol"; +import "../interfaces/ChildMessengerConsumerInterface.sol"; +import "../../common/implementation/Lockable.sol"; +import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; +import "../../data-verification-mechanism/implementation/Constants.sol"; +import "../../common/implementation/HasFinder.sol"; + +/** + * @notice Sends cross chain messages from Polygon to Ethereum network. + * @dev This contract extends the `FxBaseChildTunnel` contract and therefore is 1-to-1 mapped with the + * `FxBaseRootTunnel` extended by the `Polygon_ParentMessenger` contract deployed on Polygon. This mapping ensures that + * the internal `_processMessageFromRoot` function is only callable indirectly by the `Polygon_ParentMessenger`. + */ +contract Polygon_ChildMessenger is FxBaseChildTunnel, ChildMessengerInterface, Lockable, HasFinder { + event MessageSentToParent(bytes data, address indexed targetHub, address indexed oracleSpoke); + event MessageReceivedFromParent(address indexed targetSpoke, bytes dataToSendToTarget); + + /** + * @notice Construct the Polygon_ChildMessenger contract. + * @param _finder Used to locate contracts for this network. + * @param _fxChild Polygon system contract deployed on Mainnet, required to construct new FxBaseRootTunnel + * that can send messages via native Polygon data tunnel. + */ + constructor(address _fxChild, address _finder) FxBaseChildTunnel(_fxChild) HasFinder(_finder) {} + + /** + * @notice Sends a message to the OracleSpoke via the parent messenger and the canonical message bridge. + * @dev The caller must be the OracleSpoke on child network. No other contract is permissioned to call this + * function. + * @dev The L1 target, the parent messenger, must implement processMessageFromChild to consume the message. + * @param data data message sent to the L1 messenger. Should be an encoded function call or packed data. + */ + function sendMessageToParent(bytes memory data) public override nonReentrant { + require(msg.sender == getOracleSpoke(), "Only callable by oracleSpoke"); + _sendMessageToRoot(abi.encode(data, getOracleHub())); + emit MessageSentToParent(data, getOracleHub(), getOracleSpoke()); + } + + /** + * @notice Process a received message from the parent messenger via the canonical message bridge. + * @dev The data will be received automatically from the state receiver when the state is synced between Ethereum + * and Polygon. This will revert if the Root chain sender is not the `fxRootTunnel` contract. + * @dev This call will revert if `setFxRoot` has not been called and the `sender` is not set to the + * FxRoot contract address. FxRoot should be set to Polygon_ParentMessenger. + * @param sender The sender of `data` from the Root chain. + * @param data ABI encoded params with which to call function on OracleHub or GovernorHub. + */ + function _processMessageFromRoot( + uint256 /* stateId */, + address sender, + bytes memory data + ) internal override validateSender(sender) nonReentrant { + (bytes memory dataToSendToTarget, address target) = abi.decode(data, (bytes, address)); + ChildMessengerConsumerInterface(target).processMessageFromParent(dataToSendToTarget); + emit MessageReceivedFromParent(target, dataToSendToTarget); + } + + function getOracleSpoke() public view returns (address) { + return finder.getImplementationAddress(OracleInterfaces.OracleSpoke); + } + + function getOracleHub() public view returns (address) { + return finder.getImplementationAddress(OracleInterfaces.OracleHub); + } +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Polygon_ParentMessenger.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Polygon_ParentMessenger.sol new file mode 100644 index 000000000..efdf82f85 --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Polygon_ParentMessenger.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@maticnetwork/fx-portal/contracts/tunnel/FxBaseRootTunnel.sol"; +import "../interfaces/ParentMessengerInterface.sol"; +import "../interfaces/ParentMessengerConsumerInterface.sol"; +import "./ParentMessengerBase.sol"; +import "../../common/implementation/Lockable.sol"; + +/** + * @notice Sends cross chain messages from Ethereum to Polygon network. + * @dev This contract extends the `FxBaseRootTunnel` contract and therefore is 1-to-1 mapped with the + * `FxBaseChildTunnel` extended by the `Polygon_ChildMessenger` contract deployed on Polygon. This mapping ensures that + * the internal `_processMessageFromChild` function is only callable indirectly by the `Polygon_ChildMessenger`. + */ +contract Polygon_ParentMessenger is FxBaseRootTunnel, ParentMessengerInterface, ParentMessengerBase, Lockable { + event MessageSentToChild(bytes data, address indexed targetSpoke); + event MessageReceivedFromChild(address indexed targetHub, bytes dataToSendToTarget); + + /** + * @notice Construct the Optimism_ParentMessenger contract. + * @param _checkpointManager The address of the Polygon checkpoint manager deployed on Mainnet. Required to + * construct new FxBaseRootTunnel that can connect to native Polygon data tunnel. + * @param _fxRoot Polygon system contract deployed on Mainnet, required to construct new FxBaseRootTunnel + * that can send messages via native Polygon data tunnel. + * @param _childChainId The chain id of the Optimism L2 network this messenger should connect to. + **/ + constructor( + address _checkpointManager, + address _fxRoot, + uint256 _childChainId + ) FxBaseRootTunnel(_checkpointManager, _fxRoot) ParentMessengerBase(_childChainId) {} + + /** + * @notice Sends a message to the child messenger via the canonical message bridge. + * @dev The caller must be the either the OracleHub or the GovernorHub. This is to send either a + * price or initiate a governance action to the OracleSpoke or GovernorSpoke on the child chain. + * @dev The recipient of this message is the child messenger. The messenger must implement _processMessageFromRoot + * which then forwards the data to the target either the OracleSpoke or the governorSpoke depending on the caller. + * @param data data message sent to the child messenger. Should be an encoded function call or packed data. + */ + function sendMessageToChild(bytes memory data) public override onlyHubContract nonReentrant { + address target = msg.sender == oracleHub ? oracleSpoke : governorSpoke; + bytes memory dataToSendToChild = abi.encode(data, target); + _sendMessageToChild(dataToSendToChild); + emit MessageSentToChild(dataToSendToChild, target); + } + + /** + * @notice Process a received message from the child messenger via the canonical message bridge. + * @dev This internal method will be called inside `FxBaseRootTunnel.receiveMessage(bytes memory inputData)`, + * which must be called by an EOA to finalize the relay of the message from Polygon to Ethereum. + * The `inputData` is a proof of transaction that is derived from the transaction hash of the transaction on the + * child chain that originated the cross-chain price request via _sendMessageToRoot. + * @dev This call will revert if `setFxChild` has not been called. Fx Child should be set to Polygon_ChildMessenger. + * @param data ABI encoded params with which to call function on OracleHub or GovernorHub. + */ + function _processMessageFromChild(bytes memory data) internal override nonReentrant { + // We know that this internal execution can only be triggered by the ChildMessenger, which inherits + // FxBaseChildTunnel and is mapped 1-to-1 with this contract's FxBaseRootTunnel via + // `setFxRootTunnel/setFxChildTunnel`. + (bytes memory dataToSendToTarget, address target) = abi.decode(data, (bytes, address)); + ParentMessengerConsumerInterface(target).processMessageFromChild(childChainId, dataToSendToTarget); + emit MessageReceivedFromChild(target, dataToSendToTarget); + } +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Arbitrum_inboxMock.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Arbitrum_inboxMock.sol new file mode 100644 index 000000000..480622a7e --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Arbitrum_inboxMock.sol @@ -0,0 +1,51 @@ +pragma solidity ^0.8.0; + +import "../../../external/avm/interfaces/iArbitrum_Inbox.sol"; +import "../../../cross-chain-oracle/chain-adapters/Arbitrum_ParentMessenger.sol"; + +contract Arbitrum_OutboxMock { + function l2ToL1Sender() external view returns (address) { + // Function not called in tests, only smocked. + return address(this); + } +} + +contract Arbitrum_BridgeMock { + address public outbox; + + function setOutbox(address _outbox) external { + outbox = _outbox; + } + + function activeOutbox() external view returns (address) { + return outbox; + } + + // This function can be called by an EOA to send a call to the parent messenger, which is important in tests + // because `processMessageFromCrossChainChild` can only be called by the Bridge contract. + function processMessageFromCrossChainChild(address payable messengerToCall, bytes memory data) external { + Arbitrum_ParentMessenger(messengerToCall).processMessageFromCrossChainChild(data); + } +} + +contract Arbitrum_InboxMock is iArbitrum_Inbox { + // We leave these unused function parameters named because this contract is used with smockit and makes testing + // this function's call inputs easier. + function createRetryableTicketNoRefundAliasRewrite( + address destAddr, + uint256 l2CallValue, + uint256 maxSubmissionCost, + address excessFeeRefundAddress, + address callValueRefundAddress, + uint256 maxGas, + uint256 gasPriceBid, + bytes calldata data + ) external payable override returns (uint256) { + return 0; + } + + function bridge() external view returns (address) { + // Function not called in tests, only smocked. + return address(this); + } +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/OVM_L1CrossDomainMessengerMock.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/OVM_L1CrossDomainMessengerMock.sol new file mode 100644 index 000000000..ce35bdfb1 --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/OVM_L1CrossDomainMessengerMock.sol @@ -0,0 +1,12 @@ +pragma solidity ^0.8.0; +import "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol"; + +contract OVM_L1CrossDomainMessengerMock is ICrossDomainMessenger { + function xDomainMessageSender() external view override returns (address) { + // Trivial return this contract's address. + return address(this); + } + + function sendMessage(address _target, bytes calldata _message, uint32 _gasLimit) external override {} + // Do nothing. +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/ParentMessengerBaseMock.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/ParentMessengerBaseMock.sol new file mode 100644 index 000000000..6df37ee28 --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/ParentMessengerBaseMock.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../ParentMessengerBase.sol"; + +contract ParentMessengerBaseMock is ParentMessengerBase { + constructor(uint256 _childChainId) ParentMessengerBase(_childChainId) {} + + function sendMessageToChild(bytes memory) public view override { + require(false, "unused function"); + } +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Polygon_ChildMessengerMock.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Polygon_ChildMessengerMock.sol new file mode 100644 index 000000000..dc590cf2e --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Polygon_ChildMessengerMock.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../Polygon_ChildMessenger.sol"; + +contract Polygon_ChildMessengerMock is Polygon_ChildMessenger { + constructor(address _fxChild, address _finder) Polygon_ChildMessenger(_fxChild, _finder) {} + + function processMessageFromRoot(address sender, bytes memory data) external { + _processMessageFromRoot( + 1, // Unused param set to arbitrary value. + sender, + data + ); + } +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Polygon_ParentMessengerMock.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Polygon_ParentMessengerMock.sol new file mode 100644 index 000000000..386b0755d --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Polygon_ParentMessengerMock.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../Polygon_ParentMessenger.sol"; + +contract Polygon_ParentMessengerMock is Polygon_ParentMessenger { + constructor( + address _checkpointManager, + address _fxRoot, + uint256 _childChainId + ) Polygon_ParentMessenger(_checkpointManager, _fxRoot, _childChainId) {} + + function processMessageFromChild(bytes memory data) external { + _processMessageFromChild(data); + } +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ChildMessengerConsumerInterface.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ChildMessengerConsumerInterface.sol new file mode 100644 index 000000000..ed5448a23 --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ChildMessengerConsumerInterface.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +interface ChildMessengerConsumerInterface { + // Called on L2 by child messenger. + function processMessageFromParent(bytes memory data) external; +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ChildMessengerInterface.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ChildMessengerInterface.sol new file mode 100644 index 000000000..5e7a0d759 --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ChildMessengerInterface.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +interface ChildMessengerInterface { + // Should send cross-chain message to Parent messenger contract or revert. + function sendMessageToParent(bytes memory data) external; +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ParentMessengerConsumerInterface.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ParentMessengerConsumerInterface.sol new file mode 100644 index 000000000..24fcf3209 --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ParentMessengerConsumerInterface.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +interface ParentMessengerConsumerInterface { + // Function called on Oracle hub to pass in data send from L2, with chain ID. + function processMessageFromChild(uint256 chainId, bytes memory data) external; +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ParentMessengerInterface.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ParentMessengerInterface.sol new file mode 100644 index 000000000..5d909c76a --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ParentMessengerInterface.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +interface ParentMessengerInterface { + // Should send cross-chain message to Child messenger contract or revert. + function sendMessageToChild(bytes memory data) external; + + // Informs Hub how much msg.value they need to include to call `sendMessageToChild`. + function getL1CallValue() external view returns (uint256); +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/test/GovernorMessengerMock.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/test/GovernorMessengerMock.sol new file mode 100644 index 000000000..cf9b646c4 --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/test/GovernorMessengerMock.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../GovernorSpoke.sol"; + +/** + * @notice Can be used as either a Parent or Child messenger mock in unit tests for the Governor Hub and Spoke + * cross-chain contracts. The reason that this can't also be used for the Oracle Hub and Spoke is that the + * sendMessageToChild is called with different encoded data in the Oracle versus the Governor. + */ +contract GovernorMessengerMock { + GovernorSpoke.Call[] private _latestCalls; + + function latestCalls() external view returns (GovernorSpoke.Call[] memory) { + return _latestCalls; + } + + function sendMessageToChild(bytes memory data) external { + delete _latestCalls; + GovernorSpoke.Call[] memory calls = abi.decode(data, (GovernorSpoke.Call[])); + for (uint256 i = 0; i < calls.length; i++) _latestCalls.push(calls[i]); + } +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/test/OracleBaseMock.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/test/OracleBaseMock.sol new file mode 100644 index 000000000..237613fad --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/test/OracleBaseMock.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../OracleBase.sol"; + +/** + * @title Test implementation of OracleBase enabling unit tests on internal methods. + */ +contract OracleBaseMock is OracleBase { + constructor(address _finderAddress) HasFinder(_finderAddress) {} + + function requestPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData) public returns (bool) { + return _requestPrice(identifier, time, ancillaryData); + } + + function encodePriceRequest( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) public pure returns (bytes32) { + return _encodePriceRequest(identifier, time, ancillaryData); + } + + function publishPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData, int256 price) public { + _publishPrice(identifier, time, ancillaryData, price); + } +} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/test/OracleMessengerMock.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/test/OracleMessengerMock.sol new file mode 100644 index 000000000..3d90b4907 --- /dev/null +++ b/contracts/external/uma/core/contracts/cross-chain-oracle/test/OracleMessengerMock.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../OracleHub.sol"; +import "../OracleSpoke.sol"; +import "../interfaces/ParentMessengerInterface.sol"; + +/** + * @notice Can be used as either a Parent or Child messenger mock in unit tests for the Oracle Hub and Spoke + * cross-chain contracts. The reason that this can't also be used for the Governor Hub and Spoke is that the + * sendMessageToChild is called with different encoded data in the Oracle versus the Governor. + */ +contract OracleMessengerMock is ParentMessengerInterface { + bytes public latestAncillaryData; + uint256 public latestTime; + bytes32 public latestIdentifier; + int256 public latestPrice; + + uint256 public messageCount; + + // OracleHub calls `sendMessageToChild` + function sendMessageToChild(bytes memory data) external override { + (latestIdentifier, latestTime, latestAncillaryData, latestPrice) = abi.decode( + data, + (bytes32, uint256, bytes, int256) + ); + messageCount++; + } + + function getL1CallValue() public pure override returns (uint256) { + return 0; + } + + // This calls `processMessageFromChild` on OracleHub + function requestPrice( + address oracleHub, + uint256 chainId, + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) external { + OracleHub(oracleHub).processMessageFromChild(chainId, abi.encode(identifier, time, ancillaryData)); + } + + // OracleSpoke calls `sendMessageToParent` + function sendMessageToParent(bytes memory data) external { + (latestIdentifier, latestTime, latestAncillaryData) = abi.decode(data, (bytes32, uint256, bytes)); + messageCount++; + } + + // This calls `processMessageFromParent` on OracleSpoke + function publishPrice( + address oracleSpoke, + bytes32 identifier, + uint256 time, + bytes memory ancillaryData, + int256 price + ) external { + OracleSpoke(oracleSpoke).processMessageFromParent(abi.encode(identifier, time, ancillaryData, price)); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/README.md b/contracts/external/uma/core/contracts/data-verification-mechanism/README.md new file mode 100644 index 000000000..8d11499f5 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/README.md @@ -0,0 +1,114 @@ +# Deployments + +Follow these instructions to deploy DVMv2 on a new L1 testnet. Note that mainnet deployments used their dedicated +upgrade scripts when migrating from DVMv1. + +Cross chain setup between testnets is outside the scope of these instructions. + +## Setup + +Before running anything below, make sure you have run `yarn build` from the root of the repo. + +Note that the fork network instructions can be used as a test run prior to the public network deployments. + +## Starting a fork + +To run a forked network deployment, run the following command: + +```sh +HARDHAT_CHAIN_ID= yarn hardhat node --fork --no-deploy --port +``` + +You'll need to run two forks in separate terminals on different ports to do the deployments below. + +Note: in the commands below, you'll need to set the relevant `NODE_URL_X` environment variable to the url of the locally +forked network `http://localhost:`. + +If you're having trouble redeploying contracts because `hardhat` wants to "reuse" contracts, then delete the associated +networks file under [packages/core/networks](/packages/core/networks) and run `yarn clean && yarn` in the `core` +package to reset `deployments`. + +## Step-by-step guide to deployment on Sepolia testnet + +The steps below explain how to deploy DVMv2 on Sepolia testnet. + +1. Start by exporting some environment variables (or storing them in a .env): + +```sh +# Set only the following environment variables based on which L1 testnet you're deploying to. For example, if you're +# deploying to Sepolia, set NODE_URL_11155111. +# When running against a forked network, set the URL to http://localhost: +export NODE_URL_11155111= +export MNEMONIC="Your 12-word mnemonic here" +export ETHERSCAN_API_KEY="Your testnet Etherscan API key" +``` + +2. Add new network variables in `@uma/common` package: + +- Hardhat `defaultConfig.networks` configuration in [packages/common/src/HardhatConfig.ts](/packages/common/src/HardhatConfig.ts). +- `etherscan.customChains` configuration in [packages/common/src/HardhatConfig.ts](/packages/common/src/HardhatConfig.ts) + if the testnet is not supported by `@nomicfoundation/hardhat-verify` library (not required for Sepolia). +- `PublicNetworks` configuration in [packages/common/src/PublicNetworks.ts](/packages/common/src/PublicNetworks.ts) +- Default RPC to `getNodeUrl` in [packages/common/src/ProviderUtils.ts](/packages/common/src/ProviderUtils.ts) if the + testnet is not supported by Infura (not required for Sepolia). +- Rebuild `@uma/common` package: + + ```sh + cd packages/common + yarn build + ``` + +3. Deploy L1 testnet contracts: + +```sh +# Replace --network parameter if deploying in other testnet than Sepolia. +yarn hardhat deploy --network sepolia --tags dvmv2,MockOracle +``` + +Add all the deployed contracts to the associated networks file under [packages/core/networks](/packages/core/networks). + +4. Setup DVM contracts with Mock Oracle: + +```sh +# Replace --network parameter if deploying in other testnet than Sepolia. +yarn hardhat setup-dvmv2-testnet --network sepolia --mockoracle +``` + +This would resolve price requests via mocked Oracle contract. If instead you need to resolve them via `VotingV2`, just +skip the `--mockoracle` flag. + +5. Deploy and setup `OptimisticOracleV3` contract. + +Before deploying `OptimisticOracleV3` first, approve its default price identifier and bonding currency, e.g.: + +```sh +yarn hardhat whitelist-identifiers --network sepolia --id "ASSERT_TRUTH" +yarn hardhat whitelist-collateral --network sepolia --address 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238 +``` + +Now deploy `OptimisticOracleV3`: + +```sh +# Replace --network parameter if deploying in other testnet than Sepolia. +yarn hardhat deploy --network sepolia --tags OptimisticOracleV3 +``` + +Add the deployed `OptimisticOracleV3` contract to the associated networks file under [packages/core/networks](/packages/core/networks). + +Also re-run DVM setup script from step 4 above, so that `OptimisticOracleV3` gets registered and synced: + +```sh +yarn hardhat setup-dvmv2-testnet --network sepolia --mockoracle +``` + +6. Verify contracts: + +```sh +# etherscan-verify does not work on Sepolia so we use hardhat verify on each deployed contract above. +yarn hardhat --network sepolia verify --contract +``` + +In case of more complex constructor arguments, pass them from file `--constructor-args arguments.js` as discussed in +[hardhat-verify docs](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify#complex-arguments). +In order to get constructor argument values, check them in `args` property from respective contract deployment json +file (appropriate network directory under `packages/core/deployments`). diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/AdminIdentifierLib.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/AdminIdentifierLib.sol new file mode 100644 index 000000000..42f82b7f8 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/AdminIdentifierLib.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +/** + * @title Library to construct admin identifiers. + */ +library AdminIdentifierLib { + // Returns a UTF-8 identifier representing a particular admin proposal. + // The identifier is of the form "Admin n", where n is the proposal id provided. + function _constructIdentifier(uint256 id) internal pure returns (bytes32) { + bytes32 bytesId = _uintToUtf8(id); + return _addPrefix(bytesId, "Admin ", 6); + } + + // This method converts the integer `v` into a base-10, UTF-8 representation stored in a `bytes32` type. + // If the input cannot be represented by 32 base-10 digits, it returns only the highest 32 digits. + // This method is based off of this code: https://ethereum.stackexchange.com/a/6613/47801. + function _uintToUtf8(uint256 v) internal pure returns (bytes32) { + bytes32 ret; + if (v == 0) { + // Handle 0 case explicitly. + ret = "0"; + } else { + // Constants. + uint256 bitsPerByte = 8; + uint256 base = 10; // Note: the output should be base-10. The below implementation will not work for bases > 10. + uint256 utf8NumberOffset = 48; + while (v > 0) { + // Downshift the entire bytes32 to allow the new digit to be added at the "front" of the bytes32, which + // translates to the beginning of the UTF-8 representation. + ret = ret >> bitsPerByte; + + // Separate the last digit that remains in v by modding by the base of desired output representation. + uint256 leastSignificantDigit = v % base; + + // Digits 0-9 are represented by 48-57 in UTF-8, so an offset must be added to create the character. + bytes32 utf8Digit = bytes32(leastSignificantDigit + utf8NumberOffset); + + // The top byte of ret has already been cleared to make room for the new digit. + // Upshift by 31 bytes to put it in position, and OR it with ret to leave the other characters untouched. + ret |= utf8Digit << (31 * bitsPerByte); + + // Divide v by the base to remove the digit that was just added. + v /= base; + } + } + return ret; + } + + // This method takes two UTF-8 strings represented as bytes32 and outputs one as a prefixed by the other. + // `input` is the UTF-8 that should have the prefix prepended. + // `prefix` is the UTF-8 that should be prepended onto input. + // `prefixLength` is number of UTF-8 characters represented by `prefix`. + // Notes: + // 1. If the resulting UTF-8 is larger than 32 characters, then only the first 32 characters will be represented + // by the bytes32 output. + // 2. If `prefix` has more characters than `prefixLength`, the function will produce an invalid result. + function _addPrefix(bytes32 input, bytes32 prefix, uint256 prefixLength) internal pure returns (bytes32) { + // Downshift `input` to open space at the "front" of the bytes32 + bytes32 shiftedInput = input >> (prefixLength * 8); + return shiftedInput | prefix; + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Constants.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Constants.sol new file mode 100644 index 000000000..490b70a00 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Constants.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +/** + * @title Stores common interface names used throughout the DVM by registration in the Finder. + */ +library OracleInterfaces { + bytes32 public constant Oracle = "Oracle"; + bytes32 public constant IdentifierWhitelist = "IdentifierWhitelist"; + bytes32 public constant Store = "Store"; + bytes32 public constant FinancialContractsAdmin = "FinancialContractsAdmin"; + bytes32 public constant Registry = "Registry"; + bytes32 public constant CollateralWhitelist = "CollateralWhitelist"; + bytes32 public constant OptimisticOracle = "OptimisticOracle"; + bytes32 public constant OptimisticOracleV2 = "OptimisticOracleV2"; + bytes32 public constant OptimisticOracleV3 = "OptimisticOracleV3"; + bytes32 public constant Bridge = "Bridge"; + bytes32 public constant GenericHandler = "GenericHandler"; + bytes32 public constant SkinnyOptimisticOracle = "SkinnyOptimisticOracle"; + bytes32 public constant ChildMessenger = "ChildMessenger"; + bytes32 public constant OracleHub = "OracleHub"; + bytes32 public constant OracleSpoke = "OracleSpoke"; +} + +/** + * @title Commonly re-used values for contracts associated with the OptimisticOracle. + */ +library OptimisticOracleConstraints { + // Any price request submitted to the OptimisticOracle must contain ancillary data no larger than this value. + // This value must be <= the Voting contract's `ancillaryBytesLimit` constant value otherwise it is possible + // that a price can be requested to the OptimisticOracle successfully, but cannot be resolved by the DVM which + // refuses to accept a price request made with ancillary data length over a certain size. + uint256 public constant ancillaryBytesLimit = 8192; +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ContractCreator.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ContractCreator.sol new file mode 100644 index 000000000..522b998c8 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ContractCreator.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../interfaces/FinderInterface.sol"; +import "../../common/implementation/AddressWhitelist.sol"; +import "./Registry.sol"; +import "./Constants.sol"; + +/** + * @title Base contract for all financial contract creators + */ +abstract contract ContractCreator { + address internal finderAddress; + + constructor(address _finderAddress) { + finderAddress = _finderAddress; + } + + function _requireWhitelistedCollateral(address collateralAddress) internal view { + FinderInterface finder = FinderInterface(finderAddress); + AddressWhitelist collateralWhitelist = AddressWhitelist( + finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist) + ); + require(collateralWhitelist.isOnWhitelist(collateralAddress), "Collateral not whitelisted"); + } + + function _registerContract(address[] memory parties, address contractToRegister) internal { + FinderInterface finder = FinderInterface(finderAddress); + Registry registry = Registry(finder.getImplementationAddress(OracleInterfaces.Registry)); + registry.registerContract(parties, contractToRegister); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVoting.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVoting.sol new file mode 100644 index 000000000..643e33317 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVoting.sol @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/MultiRole.sol"; +import "../../common/implementation/Withdrawable.sol"; +import "../interfaces/VotingAncillaryInterface.sol"; +import "../interfaces/FinderInterface.sol"; +import "./Constants.sol"; + +/** + * @title Proxy to allow voting from another address. + * @dev Allows a UMA token holder to designate another address to vote on their behalf. + * Each voter must deploy their own instance of this contract. + */ +contract DesignatedVoting is Withdrawable { + /**************************************** + * INTERNAL VARIABLES AND STORAGE * + ****************************************/ + + enum Roles { + Owner, // Can set the Voter role. Is also permanently permissioned as the minter role. + Voter // Can vote through this contract. + } + + // Reference to the UMA Finder contract, allowing Voting upgrades to be performed + // without requiring any calls to this contract. + FinderInterface private finder; + + /** + * @notice Construct the DesignatedVoting contract. + * @param finderAddress keeps track of all contracts within the system based on their interfaceName. + * @param ownerAddress address of the owner of the DesignatedVoting contract. + * @param voterAddress address to which the owner has delegated their voting power. + */ + constructor(address finderAddress, address ownerAddress, address voterAddress) { + _createExclusiveRole(uint256(Roles.Owner), uint256(Roles.Owner), ownerAddress); + _createExclusiveRole(uint256(Roles.Voter), uint256(Roles.Owner), voterAddress); + _setWithdrawRole(uint256(Roles.Owner)); + + finder = FinderInterface(finderAddress); + } + + /**************************************** + * VOTING AND REWARD FUNCTIONALITY * + ****************************************/ + + /** + * @notice Forwards a commit to Voting. + * @param identifier uniquely identifies the feed for this vote. EG BTC/USD price pair. + * @param time specifies the unix timestamp of the price being voted on. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + * @param hash the keccak256 hash of the price you want to vote for and a random integer salt value. + */ + function commitVote( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData, + bytes32 hash + ) external onlyRoleHolder(uint256(Roles.Voter)) { + _getVotingAddress().commitVote(identifier, time, ancillaryData, hash); + } + + /** + * @notice Forwards a batch commit to Voting. + * @param commits struct to encapsulate an `identifier`, `time`, `hash` and optional `encryptedVote`. + */ + function batchCommit( + VotingAncillaryInterface.CommitmentAncillary[] calldata commits + ) external onlyRoleHolder(uint256(Roles.Voter)) { + _getVotingAddress().batchCommit(commits); + } + + /** + * @notice Forwards a reveal to Voting. + * @param identifier voted on in the commit phase. EG BTC/USD price pair. + * @param time specifies the unix timestamp of the price being voted on. + * @param price used along with the `salt` to produce the `hash` during the commit phase. + * @param salt used along with the `price` to produce the `hash` during the commit phase. + */ + function revealVote( + bytes32 identifier, + uint256 time, + int256 price, + bytes memory ancillaryData, + int256 salt + ) external onlyRoleHolder(uint256(Roles.Voter)) { + _getVotingAddress().revealVote(identifier, time, price, ancillaryData, salt); + } + + /** + * @notice Forwards a batch reveal to Voting. + * @param reveals is an array of the Reveal struct which contains an identifier, time, price and salt. + */ + function batchReveal( + VotingAncillaryInterface.RevealAncillary[] calldata reveals + ) external onlyRoleHolder(uint256(Roles.Voter)) { + _getVotingAddress().batchReveal(reveals); + } + + /** + * @notice Forwards a reward retrieval to Voting. + * @dev Rewards are added to the tokens already held by this contract. + * @param roundId defines the round from which voting rewards will be retrieved from. + * @param toRetrieve an array of PendingRequests which rewards are retrieved from. + * @return amount of rewards that the user should receive. + */ + function retrieveRewards( + uint256 roundId, + VotingAncillaryInterface.PendingRequestAncillary[] memory toRetrieve + ) public onlyRoleHolder(uint256(Roles.Voter)) returns (FixedPoint.Unsigned memory) { + return _getVotingAddress().retrieveRewards(address(this), roundId, toRetrieve); + } + + function _getVotingAddress() private view returns (VotingAncillaryInterface) { + return VotingAncillaryInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingFactory.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingFactory.sol new file mode 100644 index 000000000..6ff29591f --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingFactory.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/Withdrawable.sol"; +import "./DesignatedVoting.sol"; + +/** + * @title Factory to deploy new instances of DesignatedVoting and look up previously deployed instances. + * @dev Allows off-chain infrastructure to look up a hot wallet's deployed DesignatedVoting contract. + */ +contract DesignatedVotingFactory is Withdrawable { + /**************************************** + * INTERNAL VARIABLES AND STORAGE * + ****************************************/ + + enum Roles { + Withdrawer // Can withdraw any ETH or ERC20 sent accidentally to this contract. + } + + address private finder; + mapping(address => DesignatedVoting) public designatedVotingContracts; + + /** + * @notice Construct the DesignatedVotingFactory contract. + * @param finderAddress keeps track of all contracts within the system based on their interfaceName. + */ + constructor(address finderAddress) { + finder = finderAddress; + + _createWithdrawRole(uint256(Roles.Withdrawer), uint256(Roles.Withdrawer), msg.sender); + } + + /** + * @notice Deploys a new `DesignatedVoting` contract. + * @param ownerAddress defines who will own the deployed instance of the designatedVoting contract. + * @return designatedVoting a new DesignatedVoting contract. + */ + function newDesignatedVoting(address ownerAddress) external returns (DesignatedVoting) { + DesignatedVoting designatedVoting = new DesignatedVoting(finder, ownerAddress, msg.sender); + designatedVotingContracts[msg.sender] = designatedVoting; + return designatedVoting; + } + + /** + * @notice Associates a `DesignatedVoting` instance with `msg.sender`. + * @param designatedVotingAddress address to designate voting to. + * @dev This is generally only used if the owner of a `DesignatedVoting` contract changes their `voter` + * address and wants that reflected here. + */ + function setDesignatedVoting(address designatedVotingAddress) external { + designatedVotingContracts[msg.sender] = DesignatedVoting(designatedVotingAddress); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingV2.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingV2.sol new file mode 100644 index 000000000..712072e3d --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingV2.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "../../common/implementation/MultiCaller.sol"; +import "../../common/implementation/Stakeable.sol"; +import "../interfaces/FinderInterface.sol"; +import "./Constants.sol"; + +/** + * @title Proxy to allow voting from another address. + * @dev Allows a UMA token holder to designate another address to vote on their behalf. + * Each voter must deploy their own instance of this contract. + */ +contract DesignatedVotingV2 is Stakeable, MultiCaller { + /**************************************** + * INTERNAL VARIABLES AND STORAGE * + ****************************************/ + + enum Roles { + Owner, // Can set the Voter role. + Voter // Can vote through this contract. + } + + // Reference to UMA Finder contract, allowing Voting upgrades to be without requiring any calls to this contract. + FinderInterface public immutable finder; + + /** + * @notice Construct the DesignatedVotingV2 contract. + * @param finderAddress keeps track of all contracts within the system based on their interfaceName. + * @param ownerAddress address of the owner of the DesignatedVotingV2 contract. + * @param voterAddress address to which the owner has delegated their voting power. + */ + constructor(address finderAddress, address ownerAddress, address voterAddress) { + _createExclusiveRole(uint256(Roles.Owner), uint256(Roles.Owner), ownerAddress); + _createExclusiveRole(uint256(Roles.Voter), uint256(Roles.Owner), voterAddress); + _setWithdrawRole(uint256(Roles.Owner)); + _setStakeRole(uint256(Roles.Owner)); + + finder = FinderInterface(finderAddress); + } + + /** + * @notice This method essentially syncs the voter role with the current voting delegate. + * @dev Because this is essentially a state sync method, there is no reason to restrict its permissioning. + */ + function delegateToVoter() public { + address voter = getMember(uint256(Roles.Voter)); + _getVotingContract().setDelegate(voter); + } + + // Returns the Voting contract address, named "Oracle" in the finder. + function _getVotingContract() private view returns (StakerInterface) { + return StakerInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingV2Factory.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingV2Factory.sol new file mode 100644 index 000000000..04b64f238 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingV2Factory.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "./DesignatedVotingV2.sol"; +import "../../common/implementation/MultiCaller.sol"; + +/** + * @title Factory to deploy new instances of DesignatedVotingV2 and look up previously deployed instances. + * @dev Allows off-chain infrastructure to look up a hot wallet's deployed DesignatedVoting contract. + */ +contract DesignatedVotingV2Factory is MultiCaller { + address public immutable finder; // Finder contract that stores addresses of UMA system contracts. + + event NewDesignatedVoting(address indexed voter, address indexed owner, address indexed designatedVoting); + + /** + * @notice Construct the DesignatedVotingFactory contract. + * @param _finder keeps track of all contracts within the system based on their interfaceName. + */ + constructor(address _finder) { + finder = _finder; + } + + /** + * @notice Deploys a new `DesignatedVoting` contract. + * @param owner defines who will own the deployed instance of the designatedVoting contract. + * @param voter defines who will be able to vote on behalf of the owner, using the designatedVoting contract. + * @return designatedVoting a new DesignatedVoting contract. + */ + function newDesignatedVoting(address owner, address voter) external returns (DesignatedVotingV2) { + DesignatedVotingV2 designatedVoting = new DesignatedVotingV2(finder, owner, voter); + + emit NewDesignatedVoting(voter, owner, address(designatedVoting)); + + return designatedVoting; + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/EmergencyProposer.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/EmergencyProposer.sol new file mode 100644 index 000000000..0ce2f69ca --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/EmergencyProposer.sol @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "./GovernorV2.sol"; +import "../../common/implementation/Lockable.sol"; +import "../../common/implementation/MultiCaller.sol"; +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +/** + * @title Emergency Proposer contract + * @dev This is a contract that allows anyone to construct an emergency recovery transaction to bypass the + * standard voting process by submitting a very large bond, which is considered a quorum in this case. This bond is + * expected to be about as large as the GAT in the VotingV2 contract. If a proposal is considered invalid, UMA token + * holders can vote to slash and remove this proposal through the standard governance flow. If valid, a proposal must + * wait minimumWaitTime before it can be executed and it can only be executed by a privileged account, executor. This + * includes three tiers of protection to ensure that abuse is extremely risky both from creating market volatility in + * the underlying token and the threat of the locked tokens being slashed. + */ +contract EmergencyProposer is Ownable, Lockable, MultiCaller { + using SafeERC20 for IERC20; + + /**************************************** + * EMERGENCY PROPOSAL STATE * + ****************************************/ + + // Identifies a unique emergency proposal. + struct EmergencyProposal { + address sender; // Sender of the proposal. Address that receives the bond refund in the case of execution. + uint64 expiryTime; // Time at which the proposal expires and can be executed. + uint256 lockedTokens; // Tokens locked for the proposal. Tokens are returned when proposal is executed. + GovernorV2.Transaction[] transactions; // Array of transactions to be executed in the emergency action. + } + + // Array of all proposed emergency proposals. + EmergencyProposal[] public emergencyProposals; + + // UMA Governor, used to execute transactions. The Governor is the owner of all other UMA ecosystem contracts. + GovernorV2 public immutable governor; + + // Voting token, used to bond proposes. + IERC20 public immutable token; + + // The number of tokens needed to propose an emergency action. + uint256 public quorum; + + // The minimum time that must elapse between from when a proposal is created to when it can be executed. + uint64 public minimumWaitTime; + + // The only address that can execute an emergency proposal. Will be set to a multisig. Acts to guardrail the + // emergency recovery mechanism and ensure that only valid proposals can be executed. Note that while this address + // is somewhat privileged, it can't unilaterally push through proposals as a proposal must pass the minimum wait + // time without the DVM voters voting to slash the proposal. + address public executor; + + /**************************************** + * EVENTS * + ****************************************/ + + event QuorumSet(uint256 quorum); + event ExecutorSet(address executor); + event MinimumWaitTimeSet(uint256 minimumWaitTime); + event EmergencyTransactionsProposed( + uint256 indexed id, + address indexed sender, + address indexed caller, + uint64 expiryTime, + uint256 lockedTokens, + GovernorV2.Transaction[] transactions + ); + event EmergencyProposalRemoved( + uint256 indexed id, + address indexed sender, + address indexed caller, + uint64 expiryTime, + uint256 lockedTokens, + GovernorV2.Transaction[] transactions + ); + event EmergencyProposalSlashed( + uint256 indexed id, + address indexed sender, + address indexed caller, + uint64 expiryTime, + uint256 lockedTokens, + GovernorV2.Transaction[] transactions + ); + event EmergencyProposalExecuted( + uint256 indexed id, + address indexed sender, + address indexed caller, + uint64 expiryTime, + uint256 lockedTokens, + GovernorV2.Transaction[] transactions + ); + + /** + * @notice Construct the EmergencyProposer contract. + * @param _token the ERC20 token that the quorum is in. + * @param _quorum the tokens needed to propose an emergency action. + * @param _governor the governor contract that this contract makes proposals to. + * @param _executor the address that can execute an emergency proposal. + * @param _minimumWaitTime the minimum time that must elapse between from when a proposal is created to when it can + * be executed. + */ + constructor(IERC20 _token, uint256 _quorum, GovernorV2 _governor, address _executor, uint64 _minimumWaitTime) { + token = _token; + governor = _governor; + setExecutor(_executor); + setQuorum(_quorum); + + setMinimumWaitTime(_minimumWaitTime); + transferOwnership(address(_governor)); + } + + /** + * @notice Propose an emergency admin action to execute on the DVM as a set of proposed transactions. + * @dev Caller of this method must approve (and have) quorum amount of token to be pulled from their wallet. + * @param transactions array of transactions to be executed in the emergency action. When executed, will be sent + * via the governor contract. + * @return uint256 the emergency proposal id. + */ + function emergencyPropose(GovernorV2.Transaction[] memory transactions) external nonReentrant returns (uint256) { + require(msg.sender != address(governor), "Governor can't propose"); // The governor should never be the proposer. + require(transactions.length > 0, "No transactions to propose"); + token.safeTransferFrom(msg.sender, address(this), quorum); + uint256 id = emergencyProposals.length; + EmergencyProposal storage proposal = emergencyProposals.push(); + proposal.sender = msg.sender; + proposal.lockedTokens = quorum; + proposal.expiryTime = uint64(getCurrentTime()) + minimumWaitTime; + + for (uint256 i = 0; i < transactions.length; i++) proposal.transactions.push(transactions[i]); + + emit EmergencyTransactionsProposed(id, msg.sender, msg.sender, proposal.expiryTime, quorum, transactions); + return id; + } + + /** + * @notice After the proposal is executable, the executor or owner can use this function to remove the proposal + * without slashing. + * @dev This means that the DVM didn't explicitly reject the proposal. Allowing the executor to slash the quorum + * would give the executor too much power. So the only control either party has is to remove the proposal, + * releasing the bond. The proposal should not be removable before its liveness/expiry to ensure the regular Voting + * system's slash cannot be frontrun. + * @param id id of the proposal. + */ + function removeProposal(uint256 id) external nonReentrant { + EmergencyProposal storage proposal = emergencyProposals[id]; + require(proposal.expiryTime <= getCurrentTime(), "must be expired to remove"); + require(msg.sender == owner() || msg.sender == executor, "owner or executor"); + require(proposal.lockedTokens != 0, "invalid proposal"); + token.safeTransfer(proposal.sender, proposal.lockedTokens); + emit EmergencyProposalRemoved( + id, + proposal.sender, + msg.sender, + proposal.expiryTime, + proposal.lockedTokens, + proposal.transactions + ); + delete emergencyProposals[id]; + } + + /** + * @notice Before a proposal expires (or after), this method can be used by the owner, which should generally be + * the GovernorV2 contract, to slash the proposer. + * @dev The slash results in the proposer's tokens being sent to the Governor contract. + * @param id id of the proposal. + */ + function slashProposal(uint256 id) external nonReentrant onlyOwner { + EmergencyProposal storage proposal = emergencyProposals[id]; + require(proposal.lockedTokens != 0, "invalid proposal"); + token.safeTransfer(address(governor), proposal.lockedTokens); + emit EmergencyProposalSlashed( + id, + proposal.sender, + msg.sender, + proposal.expiryTime, + proposal.lockedTokens, + proposal.transactions + ); + delete emergencyProposals[id]; + } + + /** + * @notice After a proposal expires, this method can be used by the executor to execute the proposal. + * @dev This method effectively gives the executor veto power over any proposal. + * @dev The first transaction execution sends the total amount of ETH required to complete all payable + * transactions in the Governor. The EmergencyProposer must receive this amount of ETH in advance. + * The executed transactions are then able to use this ETH by including a nonzero value. + * @param id id of the proposal. + */ + function executeEmergencyProposal(uint256 id) external payable nonReentrant { + require(msg.sender == executor, "must be called by executor"); + + EmergencyProposal storage proposal = emergencyProposals[id]; + require(proposal.lockedTokens != 0, "invalid proposal"); + require(proposal.expiryTime <= getCurrentTime(), "must be expired to execute"); + + for (uint256 i = 0; i < proposal.transactions.length; i++) + governor.emergencyExecute{ value: address(this).balance }(proposal.transactions[i]); + + token.safeTransfer(proposal.sender, proposal.lockedTokens); + emit EmergencyProposalExecuted( + id, + proposal.sender, + msg.sender, + proposal.expiryTime, + proposal.lockedTokens, + proposal.transactions + ); + delete emergencyProposals[id]; + } + + /** + * @notice Admin method to set the quorum (bond) size. + * @dev Admin is intended to be the governance system. + * @param newQuorum the new quorum. + */ + function setQuorum(uint256 newQuorum) public nonReentrant onlyOwner { + require(newQuorum != 0, "quorum must be > 0"); + require(newQuorum < token.totalSupply(), "quorum must be < totalSupply"); + quorum = newQuorum; + emit QuorumSet(newQuorum); + } + + /** + * @notice Admin method to set the executor address. + * @dev Admin is intended to be the governance system. + * @param newExecutor the new executor address. + */ + function setExecutor(address newExecutor) public nonReentrant onlyOwner { + executor = newExecutor; + emit ExecutorSet(newExecutor); + } + + /** + * @notice Admin method to set the minimum wait time for a proposal to be executed. + * @dev Admin is intended to be the governance system. The minimum wait time is added to the current time at the + * time of the proposal to determine when the proposal will be executable. Any changes to this value after that + * point will have no impact on the proposal. + * @param newMinimumWaitTime the new minimum wait time. + */ + function setMinimumWaitTime(uint64 newMinimumWaitTime) public nonReentrant onlyOwner { + require(newMinimumWaitTime != 0, "minimumWaitTime == 0"); + require(newMinimumWaitTime <= 4 weeks, "minimumWaitTime > 1 month"); + minimumWaitTime = newMinimumWaitTime; + emit MinimumWaitTimeSet(newMinimumWaitTime); + } + + /** + * @notice Returns the current block timestamp. + * @dev Can be overridden to control contract time. + * @return the current block timestamp. + */ + function getCurrentTime() public view virtual returns (uint256) { + return block.timestamp; + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/FinancialContractsAdmin.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/FinancialContractsAdmin.sol new file mode 100644 index 000000000..ff4a89d0f --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/FinancialContractsAdmin.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../interfaces/AdministrateeInterface.sol"; +import "@openzeppelin/contracts-v4/access/Ownable.sol"; + +/** + * @title Admin for financial contracts in the UMA system. + * @dev Allows appropriately permissioned admin roles to interact with financial contracts. + */ +contract FinancialContractsAdmin is Ownable { + /** + * @notice Calls emergency shutdown on the provided financial contract. + * @param financialContract address of the FinancialContract to be shut down. + */ + function callEmergencyShutdown(address financialContract) external onlyOwner { + AdministrateeInterface administratee = AdministrateeInterface(financialContract); + administratee.emergencyShutdown(); + } + + /** + * @notice Calls remargin on the provided financial contract. + * @param financialContract address of the FinancialContract to be remargined. + */ + function callRemargin(address financialContract) external onlyOwner { + AdministrateeInterface administratee = AdministrateeInterface(financialContract); + administratee.remargin(); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Finder.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Finder.sol new file mode 100644 index 000000000..44eaf5081 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Finder.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "../interfaces/FinderInterface.sol"; + +/** + * @title Provides addresses of the live contracts implementing certain interfaces. + * @dev Examples of interfaces with implementations that Finder locates are the Oracle and Store interfaces. + */ +contract Finder is FinderInterface, Ownable { + mapping(bytes32 => address) public interfacesImplemented; + + event InterfaceImplementationChanged(bytes32 indexed interfaceName, address indexed newImplementationAddress); + + /** + * @notice Updates the address of the contract that implements `interfaceName`. + * @param interfaceName bytes32 of the interface name that is either changed or registered. + * @param implementationAddress address of the implementation contract. + */ + function changeImplementationAddress( + bytes32 interfaceName, + address implementationAddress + ) external override onlyOwner { + interfacesImplemented[interfaceName] = implementationAddress; + + emit InterfaceImplementationChanged(interfaceName, implementationAddress); + } + + /** + * @notice Gets the address of the contract that implements the given `interfaceName`. + * @param interfaceName queried interface. + * @return implementationAddress address of the defined interface. + */ + function getImplementationAddress(bytes32 interfaceName) external view override returns (address) { + address implementationAddress = interfacesImplemented[interfaceName]; + require(implementationAddress != address(0x0), "Implementation not found"); + return implementationAddress; + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/FixedSlashSlashingLibrary.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/FixedSlashSlashingLibrary.sol new file mode 100644 index 000000000..7a6314f0d --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/FixedSlashSlashingLibrary.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "../interfaces/SlashingLibraryInterface.sol"; + +/** + * @title Slashing Library contract. Returns the how much a voter should be slashed per staked token as a function of + * the total staked, total votes and total correct votes. Can be upgraded to a new implementation to enable more + elaborate slashing algorithms via UMA governance. + */ + +contract FixedSlashSlashingLibrary is SlashingLibraryInterface { + uint256 public immutable baseSlashAmount; // Slash amount per token for missed votes and wrong non-governance votes. + uint256 public immutable governanceSlashAmount; // Slash amount per token for wrong governance votes. + + /** + * @notice Construct the FixedSlashSlashingLibrary contract. + * @param _baseSlashAmount Slash amount per token for missed votes and wrong non-governance votes. + * @param _governanceSlashAmount Slash amount per token for wrong governance votes. + */ + constructor(uint256 _baseSlashAmount, uint256 _governanceSlashAmount) { + require(_baseSlashAmount < 1e18, "Invalid base slash amount"); + require(_governanceSlashAmount < 1e18, "Invalid governance slash amount"); + baseSlashAmount = _baseSlashAmount; // Slash amount per token for missed votes and wrong non-governance votes. + governanceSlashAmount = _governanceSlashAmount; // Slash amount per token for wrong governance votes. + } + + /** + * @notice Calculates the wrong vote slash per token. + * @param totalStaked The total amount of tokens staked. + * @param totalVotes The total amount of votes. + * @param totalCorrectVotes The total amount of correct votes. + * @param priceRequestIndex The price request index within the resolvedPriceRequestIds array. + * @return uint256 The amount of tokens to slash per token staked. + */ + function calcWrongVoteSlashPerToken( + uint256 totalStaked, + uint256 totalVotes, + uint256 totalCorrectVotes, + uint256 priceRequestIndex + ) public view returns (uint256) { + return baseSlashAmount; + } + + /** + * @notice Calculates the wrong vote slash per token for governance requests. + * @param totalStaked The total amount of tokens staked. + * @param totalVotes The total amount of votes. + * @param totalCorrectVotes The total amount of correct votes. + * @param priceRequestIndex The price request index within the resolvedPriceRequestIds array. + * @return uint256 The amount of tokens to slash per token staked. + */ + function calcWrongVoteSlashPerTokenGovernance( + uint256 totalStaked, + uint256 totalVotes, + uint256 totalCorrectVotes, + uint256 priceRequestIndex + ) public view returns (uint256) { + return governanceSlashAmount; + } + + /** + * @notice Calculates the no vote slash per token. + * @param totalStaked The total amount of tokens staked. + * @param totalVotes The total amount of votes. + * @param totalCorrectVotes The total amount of correct votes. + * @param priceRequestIndex The price request index within the resolvedPriceRequestIds array. + * @return uint256 The amount of tokens to slash per token staked. + */ + function calcNoVoteSlashPerToken( + uint256 totalStaked, + uint256 totalVotes, + uint256 totalCorrectVotes, + uint256 priceRequestIndex + ) public view returns (uint256) { + return baseSlashAmount; + } + + /** + * @notice Calculates all slashing trackers in one go to decrease cross-contract calls needed. + * @param totalStaked The total amount of tokens staked. + * @param totalVotes The total amount of votes. + * @param totalCorrectVotes The total amount of correct votes. + * @param priceRequestIndex The price request index within the resolvedPriceRequestIds array. + * @param isGovernance Whether the request is a governance request. + * @return wrongVoteSlashPerToken The amount of tokens to slash for voting wrong. + * @return noVoteSlashPerToken The amount of tokens to slash for not voting. + */ + function calcSlashing( + uint256 totalStaked, + uint256 totalVotes, + uint256 totalCorrectVotes, + uint256 priceRequestIndex, + bool isGovernance + ) external view returns (uint256 wrongVoteSlashPerToken, uint256 noVoteSlashPerToken) { + return ( + isGovernance + ? calcWrongVoteSlashPerTokenGovernance(totalStaked, totalVotes, totalCorrectVotes, priceRequestIndex) + : calcWrongVoteSlashPerToken(totalStaked, totalVotes, totalCorrectVotes, priceRequestIndex), + calcNoVoteSlashPerToken(totalStaked, totalVotes, totalCorrectVotes, priceRequestIndex) + ); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Governor.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Governor.sol new file mode 100644 index 000000000..e9514a020 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Governor.sol @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/MultiRole.sol"; +import "../../common/implementation/FixedPoint.sol"; +import "../../common/implementation/Testable.sol"; +import "../interfaces/FinderInterface.sol"; +import "../interfaces/IdentifierWhitelistInterface.sol"; +import "../interfaces/OracleInterface.sol"; +import "./Constants.sol"; +import "./AdminIdentifierLib.sol"; + +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; +import "@openzeppelin/contracts-v4/utils/Address.sol"; + +/** + * @title Takes proposals for certain governance actions and allows UMA token holders to vote on them. + */ +contract Governor is MultiRole, Testable { + using SafeMath for uint256; + using Address for address; + + /**************************************** + * INTERNAL VARIABLES AND STORAGE * + ****************************************/ + + enum Roles { + Owner, // Can set the proposer. + Proposer // Address that can make proposals. + } + + struct Transaction { + address to; + uint256 value; + bytes data; + } + + struct Proposal { + Transaction[] transactions; + uint256 requestTime; + } + + FinderInterface private finder; + Proposal[] public proposals; + + /**************************************** + * EVENTS * + ****************************************/ + + // Emitted when a new proposal is created. + event NewProposal(uint256 indexed id, Transaction[] transactions); + + // Emitted when an existing proposal is executed. + event ProposalExecuted(uint256 indexed id, uint256 transactionIndex); + + /** + * @notice Construct the Governor contract. + * @param _finderAddress keeps track of all contracts within the system based on their interfaceName. + * @param _startingId the initial proposal id that the contract will begin incrementing from. + * @param _timerAddress Contract that stores the current time in a testing environment. + * Must be set to 0x0 for production environments that use live time. + */ + constructor(address _finderAddress, uint256 _startingId, address _timerAddress) Testable(_timerAddress) { + finder = FinderInterface(_finderAddress); + _createExclusiveRole(uint256(Roles.Owner), uint256(Roles.Owner), msg.sender); + _createExclusiveRole(uint256(Roles.Proposer), uint256(Roles.Owner), msg.sender); + + // Ensure the startingId is not set unreasonably high to avoid it being set such that new proposals overwrite + // other storage slots in the contract. + uint256 maxStartingId = 10 ** 18; + require(_startingId <= maxStartingId, "Cannot set startingId larger than 10^18"); + + // This just sets the initial length of the array to the startingId since modifying length directly has been + // disallowed in solidity 0.6. + assembly { + sstore(proposals.slot, _startingId) + } + } + + /**************************************** + * PROPOSAL ACTIONS * + ****************************************/ + + /** + * @notice Proposes a new governance action. Can only be called by the holder of the Proposer role. + * @param transactions list of transactions that are being proposed. + * @dev You can create the data portion of each transaction by doing the following: + * ``` + * const truffleContractInstance = await TruffleContract.deployed() + * const data = truffleContractInstance.methods.methodToCall(arg1, arg2).encodeABI() + * ``` + * Note: this method must be public because of a solidity limitation that + * disallows structs arrays to be passed to external functions. + */ + function propose(Transaction[] memory transactions) public onlyRoleHolder(uint256(Roles.Proposer)) { + uint256 id = proposals.length; + uint256 time = getCurrentTime(); + + // Note: doing all of this array manipulation manually is necessary because directly setting an array of + // structs in storage to an an array of structs in memory is currently not implemented in solidity :/. + + // Add a zero-initialized element to the proposals array. + proposals.push(); + + // Initialize the new proposal. + Proposal storage proposal = proposals[id]; + proposal.requestTime = time; + + // Initialize the transaction array. + for (uint256 i = 0; i < transactions.length; i++) { + require(transactions[i].to != address(0), "The `to` address cannot be 0x0"); + // If the transaction has any data with it the recipient must be a contract, not an EOA. + if (transactions[i].data.length > 0) { + require(transactions[i].to.isContract(), "EOA can't accept tx with data"); + } + proposal.transactions.push(transactions[i]); + } + + bytes32 identifier = AdminIdentifierLib._constructIdentifier(id); + + // Request a vote on this proposal in the DVM. + OracleInterface oracle = _getOracle(); + IdentifierWhitelistInterface supportedIdentifiers = _getIdentifierWhitelist(); + supportedIdentifiers.addSupportedIdentifier(identifier); + + oracle.requestPrice(identifier, time); + supportedIdentifiers.removeSupportedIdentifier(identifier); + + emit NewProposal(id, transactions); + } + + /** + * @notice Executes a proposed governance action that has been approved by voters. + * @dev This can be called by any address. Caller is expected to send enough ETH to execute payable transactions. + * @param id unique id for the executed proposal. + * @param transactionIndex unique transaction index for the executed proposal. + */ + function executeProposal(uint256 id, uint256 transactionIndex) external payable { + Proposal storage proposal = proposals[id]; + int256 price = _getOracle().getPrice(AdminIdentifierLib._constructIdentifier(id), proposal.requestTime); + + Transaction memory transaction = proposal.transactions[transactionIndex]; + + require( + transactionIndex == 0 || proposal.transactions[transactionIndex.sub(1)].to == address(0), + "Previous tx not yet executed" + ); + require(transaction.to != address(0), "Tx already executed"); + require(price != 0, "Proposal was rejected"); + require(msg.value == transaction.value, "Must send exact amount of ETH"); + + // Delete the transaction before execution to avoid any potential re-entrancy issues. + delete proposal.transactions[transactionIndex]; + + require(_executeCall(transaction.to, transaction.value, transaction.data), "Tx execution failed"); + + emit ProposalExecuted(id, transactionIndex); + } + + /**************************************** + * GOVERNOR STATE GETTERS * + ****************************************/ + + /** + * @notice Gets the total number of proposals (includes executed and non-executed). + * @return uint256 representing the current number of proposals. + */ + function numProposals() external view returns (uint256) { + return proposals.length; + } + + /** + * @notice Gets the proposal data for a particular id. + * @dev after a proposal is executed, its data will be zeroed out, except for the request time. + * @param id uniquely identify the identity of the proposal. + * @return proposal struct containing transactions[] and requestTime. + */ + function getProposal(uint256 id) external view returns (Proposal memory) { + return proposals[id]; + } + + /**************************************** + * PRIVATE GETTERS AND FUNCTIONS * + ****************************************/ + + function _executeCall(address to, uint256 value, bytes memory data) private returns (bool) { + // Mostly copied from: + // solhint-disable-next-line max-line-length + // https://github.com/gnosis/safe-contracts/blob/59cfdaebcd8b87a0a32f87b50fead092c10d3a05/contracts/base/Executor.sol#L23-L31 + // solhint-disable-next-line no-inline-assembly + + bool success; + assembly { + let inputData := add(data, 0x20) + let inputDataSize := mload(data) + success := call(gas(), to, value, inputData, inputDataSize, 0, 0) + } + return success; + } + + function _getOracle() private view returns (OracleInterface) { + return OracleInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); + } + + function _getIdentifierWhitelist() private view returns (IdentifierWhitelistInterface supportedIdentifiers) { + return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/GovernorV2.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/GovernorV2.sol new file mode 100644 index 000000000..f537e78cd --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/GovernorV2.sol @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "../../common/implementation/Lockable.sol"; +import "../../common/implementation/MultiCaller.sol"; +import "../../common/implementation/MultiRole.sol"; +import "../interfaces/FinderInterface.sol"; +import "../interfaces/IdentifierWhitelistInterface.sol"; +import "../interfaces/OracleGovernanceInterface.sol"; +import "./Constants.sol"; +import "./AdminIdentifierLib.sol"; + +import "@openzeppelin/contracts-v4/utils/Address.sol"; + +/** + * @title Takes proposals for certain governance actions and allows UMA token holders to vote on them. + */ +contract GovernorV2 is MultiRole, Lockable, MultiCaller { + using Address for address; + + /**************************************** + * GOVERNOR STATE * + ****************************************/ + + // Permissioned governor rolls. + enum Roles { + Owner, // Can set the proposer. + Proposer, // Address that can make proposals. + EmergencyProposer // Address that can make emergency proposals. + } + + // Structure to represent a transaction. + struct Transaction { + address to; // Target. + uint256 value; // value, in eth, to be sent as the msg.value. + bytes data; // payload data to be sent to the target. Would include encoded function call data usually. + } + + // Structure to represent a governance proposal. + struct Proposal { + Transaction[] transactions; // Set of transactions to be sent, if the proposal is executed. + uint256 requestTime; // Time at which the proposal was proposed. + bytes ancillaryData; // Extra data appended to a proposal to enhance the voters information. + } + + // Reference to UMA finder, used to find addresses of other UMA ecosystem contracts. + FinderInterface public immutable finder; + + // Array of all proposals. + Proposal[] public proposals; + + /**************************************** + * EVENTS * + ****************************************/ + + event NewProposal(uint256 indexed id, Transaction[] transactions); + + event ProposalExecuted(uint256 indexed id, uint256 transactionIndex); + event EmergencyExecution(address indexed to, uint256 value, bytes data); + + /** + * @notice Construct the Governor contract. + * @param _finderAddress keeps track of all contracts within the system based on their interfaceName. + * @param _startingId the initial proposal id that the contract will begin incrementing from. + */ + constructor(address _finderAddress, uint256 _startingId) { + finder = FinderInterface(_finderAddress); + _createExclusiveRole(uint256(Roles.Owner), uint256(Roles.Owner), msg.sender); + _createExclusiveRole(uint256(Roles.Proposer), uint256(Roles.Owner), msg.sender); + _createExclusiveRole(uint256(Roles.EmergencyProposer), uint256(Roles.Owner), msg.sender); + + // Ensure the startingId is not set unreasonably high to avoid it being set such that new proposals overwrite + // other storage slots in the contract. + uint256 maxStartingId = 10 ** 18; + require(_startingId <= maxStartingId, "Cannot set startingId larger than 10^18"); + + // Sets the initial length of the array to the startingId. Modifying length directly has been disallowed in solidity 0.6. + assembly { + sstore(proposals.slot, _startingId) + } + } + + /**************************************** + * PROPOSAL ACTIONS * + ****************************************/ + + /** + * @notice Proposes a new governance action. Can only be called by the holder of the Proposer role. + * @param transactions list of transactions that are being proposed. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + */ + function propose( + Transaction[] memory transactions, + bytes memory ancillaryData + ) external nonReentrant onlyRoleHolder(uint256(Roles.Proposer)) { + require(transactions.length > 0, "Empty transactions array"); + uint256 id = proposals.length; + uint256 time = getCurrentTime(); + + // Note: doing all of this array manipulation manually is necessary because directly setting an array of + // structs in storage to an array of structs in memory is currently not implemented in solidity :/. + + // Add a zero-initialized element to the proposals array. + proposals.push(); + + // Initialize the new proposal. + Proposal storage proposal = proposals[id]; + proposal.requestTime = time; + proposal.ancillaryData = ancillaryData; + + // Initialize the transaction array. + for (uint256 i = 0; i < transactions.length; i++) { + require(transactions[i].to != address(0), "The `to` address cannot be 0x0"); + // If the transaction has any data with it the recipient must be a contract, not an EOA. + if (transactions[i].data.length > 0) { + require(transactions[i].to.isContract(), "EOA can't accept tx with data"); + } + proposal.transactions.push(transactions[i]); + } + + bytes32 identifier = AdminIdentifierLib._constructIdentifier(id); + + // Request a vote on this proposal in the DVM. + _getOracle().requestGovernanceAction(identifier, time, ancillaryData); + + emit NewProposal(id, transactions); + } + + /** + * @notice Executes a proposed governance action that has been approved by voters. + * @dev This can be called by any address. Caller is expected to send enough ETH to execute payable transactions. + * @param id unique id for the executed proposal. + * @param transactionIndex unique transaction index for the executed proposal. + */ + function executeProposal(uint256 id, uint256 transactionIndex) external payable nonReentrant { + Proposal storage proposal = proposals[id]; + int256 price = _getOracle().getPrice( + AdminIdentifierLib._constructIdentifier(id), + proposal.requestTime, + proposal.ancillaryData + ); + + Transaction memory transaction = proposal.transactions[transactionIndex]; + + require( + transactionIndex == 0 || proposal.transactions[transactionIndex - 1].to == address(0), + "Previous tx not yet executed" + ); + require(transaction.to != address(0), "Tx already executed"); + require(price != 0, "Proposal was rejected"); + require(msg.value == transaction.value, "Must send exact amount of ETH"); + + // Delete the transaction before execution to avoid any potential re-entrancy issues. + delete proposal.transactions[transactionIndex]; + + require(_executeCall(transaction.to, transaction.value, transaction.data), "Tx execution failed"); + + emit ProposalExecuted(id, transactionIndex); + } + + /** + * @notice Emergency execution method that bypasses the voting system to execute a transaction. + * @dev This can only be called by the EmergencyProposer. + * @param transaction a single transaction to execute. + */ + function emergencyExecute( + Transaction memory transaction + ) external payable nonReentrant onlyRoleHolder(uint256(Roles.EmergencyProposer)) { + require(_executeCall(transaction.to, transaction.value, transaction.data), "Tx execution failed"); + + emit EmergencyExecution(transaction.to, transaction.value, transaction.data); + } + + /** + * @notice Returns the current block timestamp. + * @dev Can be overridden to control contract time. + * @return the current block timestamp. + */ + function getCurrentTime() public view virtual returns (uint256) { + return block.timestamp; + } + + /**************************************** + * GOVERNOR STATE GETTERS * + ****************************************/ + + /** + * @notice Gets the total number of proposals (includes executed and non-executed). + * @return uint256 representing the current number of proposals. + */ + function numProposals() external view returns (uint256) { + return proposals.length; + } + + /** + * @notice Gets the proposal data for a particular id. + * @dev after a proposal is executed, its data will be zeroed out, except for the request time and ancillary data. + * @param id uniquely identify the identity of the proposal. + * @return proposal struct containing transactions[] and requestTime. + */ + function getProposal(uint256 id) external view returns (Proposal memory) { + return proposals[id]; + } + + /**************************************** + * PRIVATE GETTERS AND FUNCTIONS * + ****************************************/ + + // Runs a function call on to, with value eth sent and data payload. + function _executeCall(address to, uint256 value, bytes memory data) private returns (bool) { + // Mostly copied from: + // solhint-disable-next-line max-line-length + // https://github.com/gnosis/safe-contracts/blob/59cfdaebcd8b87a0a32f87b50fead092c10d3a05/contracts/base/Executor.sol#L23-L31 + // solhint-disable-next-line no-inline-assembly + + bool success; + assembly { + let inputData := add(data, 0x20) + let inputDataSize := mload(data) + success := call(gas(), to, value, inputData, inputDataSize, 0, 0) + } + return success; + } + + // Returns the Voting contract address, named "Oracle" in the finder. + function _getOracle() private view returns (OracleGovernanceInterface) { + return OracleGovernanceInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); + } + + // Returns the IdentifierWhitelist contract address, named "IdentifierWhitelist" in the finder. + function _getIdentifierWhitelist() private view returns (IdentifierWhitelistInterface) { + return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/IdentifierWhitelist.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/IdentifierWhitelist.sol new file mode 100644 index 000000000..2461558f3 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/IdentifierWhitelist.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../interfaces/IdentifierWhitelistInterface.sol"; +import "@openzeppelin/contracts-v4/access/Ownable.sol"; + +/** + * @title Stores a whitelist of supported identifiers that the oracle can provide prices for. + */ +contract IdentifierWhitelist is IdentifierWhitelistInterface, Ownable { + /**************************************** + * INTERNAL VARIABLES AND STORAGE * + ****************************************/ + + mapping(bytes32 => bool) private supportedIdentifiers; + + /**************************************** + * EVENTS * + ****************************************/ + + event SupportedIdentifierAdded(bytes32 indexed identifier); + event SupportedIdentifierRemoved(bytes32 indexed identifier); + + /**************************************** + * ADMIN STATE MODIFYING FUNCTIONS * + ****************************************/ + + /** + * @notice Adds the provided identifier as a supported identifier. + * @dev Price requests using this identifier will succeed after this call. + * @param identifier unique UTF-8 representation for the feed being added. Eg: BTC/USD. + */ + function addSupportedIdentifier(bytes32 identifier) external override onlyOwner { + if (!supportedIdentifiers[identifier]) { + supportedIdentifiers[identifier] = true; + emit SupportedIdentifierAdded(identifier); + } + } + + /** + * @notice Removes the identifier from the whitelist. + * @dev Price requests using this identifier will no longer succeed after this call. + * @param identifier unique UTF-8 representation for the feed being removed. Eg: BTC/USD. + */ + function removeSupportedIdentifier(bytes32 identifier) external override onlyOwner { + if (supportedIdentifiers[identifier]) { + supportedIdentifiers[identifier] = false; + emit SupportedIdentifierRemoved(identifier); + } + } + + /**************************************** + * WHITELIST GETTERS FUNCTIONS * + ****************************************/ + + /** + * @notice Checks whether an identifier is on the whitelist. + * @param identifier unique UTF-8 representation for the feed being queried. Eg: BTC/USD. + * @return bool if the identifier is supported (or not). + */ + function isIdentifierSupported(bytes32 identifier) external view override returns (bool) { + return supportedIdentifiers[identifier]; + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Proposer.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Proposer.sol new file mode 100644 index 000000000..1e13fe8eb --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Proposer.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "./Finder.sol"; +import "./Governor.sol"; +import "./Constants.sol"; +import "./Voting.sol"; +import "./AdminIdentifierLib.sol"; +import "../../common/implementation/Lockable.sol"; +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +/** + * @title Proposer contract that allows anyone to make governance proposals with a bond. + */ +contract Proposer is Ownable, Testable, Lockable { + using SafeERC20 for IERC20; + IERC20 public token; + uint256 public bond; + Governor public governor; + Finder public finder; + + struct BondedProposal { + address sender; + // 64 bits to save a storage slot. + uint64 time; + uint256 lockedBond; + } + mapping(uint256 => BondedProposal) public bondedProposals; + + event BondSet(uint256 bond); + event ProposalResolved(uint256 indexed id, bool success); + + /** + * @notice Construct the Proposer contract. + * @param _token the ERC20 token that the bond is paid in. + * @param _bond the bond amount. + * @param _governor the governor contract that this contract makes proposals to. + * @param _finder the finder contract used to look up addresses. + * @param _timer the timer contract to control the output of getCurrentTime(). Set to 0x0 if in production. + */ + constructor(IERC20 _token, uint256 _bond, Governor _governor, Finder _finder, address _timer) Testable(_timer) { + token = _token; + governor = _governor; + finder = _finder; + setBond(_bond); + transferOwnership(address(_governor)); + } + + /** + * @notice Propose a new set of governance transactions for vote. + * @dev Pulls bond from the caller. + * @param transactions list of transactions for the governor to execute. + * @return id the id of the governor proposal. + */ + function propose(Governor.Transaction[] memory transactions) external nonReentrant returns (uint256 id) { + id = governor.numProposals(); + token.safeTransferFrom(msg.sender, address(this), bond); + bondedProposals[id] = BondedProposal({ sender: msg.sender, lockedBond: bond, time: uint64(getCurrentTime()) }); + governor.propose(transactions); + } + + /** + * @notice Resolves a proposal by checking the status of the request in the Voting contract. + * @dev For the resolution to work correctly, this contract must be a registered contract in the DVM. + * @param id proposal id. + */ + function resolveProposal(uint256 id) external nonReentrant { + BondedProposal storage bondedProposal = bondedProposals[id]; + Voting voting = Voting(finder.getImplementationAddress(OracleInterfaces.Oracle)); + require( + voting.hasPrice(AdminIdentifierLib._constructIdentifier(id), bondedProposal.time, ""), + "No price resolved" + ); + if (voting.getPrice(AdminIdentifierLib._constructIdentifier(id), bondedProposal.time, "") != 0) { + token.safeTransfer(bondedProposal.sender, bondedProposal.lockedBond); + emit ProposalResolved(id, true); + } else { + token.safeTransfer(finder.getImplementationAddress(OracleInterfaces.Store), bondedProposal.lockedBond); + emit ProposalResolved(id, false); + } + delete bondedProposals[id]; + } + + /** + * @notice Admin method to set the bond amount. + * @dev Admin is intended to be the governance system, itself. + * @param _bond the new bond. + */ + function setBond(uint256 _bond) public nonReentrant onlyOwner { + bond = _bond; + emit BondSet(_bond); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ProposerV2.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ProposerV2.sol new file mode 100644 index 000000000..8739d87de --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ProposerV2.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "./Finder.sol"; +import "./GovernorV2.sol"; +import "./Constants.sol"; +import "../interfaces/OracleAncillaryInterface.sol"; +import "./AdminIdentifierLib.sol"; +import "../../common/implementation/Lockable.sol"; +import "../../common/implementation/MultiCaller.sol"; +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +/** + * @title Proposer contract that allows anyone to make governance proposals with a bond. + */ +contract ProposerV2 is Ownable, Lockable, MultiCaller { + using SafeERC20 for IERC20; + IERC20 public immutable token; // The ERC20 token that the bond is paid in. + uint256 public bond; // The bond amount for making a proposal. + GovernorV2 public immutable governor; // The governor contract that this contract makes proposals to. + Finder public immutable finder; // Finder contract that stores addresses of UMA system contracts. + + struct BondedProposal { + address sender; + // 64 bits to save a storage slot. + uint64 time; + uint256 lockedBond; + bytes ancillaryData; + } + mapping(uint256 => BondedProposal) public bondedProposals; + + event BondSet(uint256 bond); + event ProposalResolved(uint256 indexed id, bool success); + + /** + * @notice Construct the Proposer contract. + * @param _token the ERC20 token that the bond is paid in. + * @param _bond the bond amount. + * @param _governor the governor contract that this contract makes proposals to. + * @param _finder the finder contract used to look up addresses. + */ + constructor(IERC20 _token, uint256 _bond, GovernorV2 _governor, Finder _finder) { + token = _token; + governor = _governor; + finder = _finder; + setBond(_bond); + transferOwnership(address(_governor)); + } + + /** + * @notice Propose a new set of governance transactions for vote. + * @dev Pulls bond from the caller. + * @param transactions list of transactions for the governor to execute. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + * @return id the id of the governor proposal. + */ + function propose( + GovernorV2.Transaction[] memory transactions, + bytes memory ancillaryData + ) external nonReentrant returns (uint256) { + uint256 id = governor.numProposals(); + token.safeTransferFrom(msg.sender, address(this), bond); + bondedProposals[id] = BondedProposal({ + sender: msg.sender, + lockedBond: bond, + time: uint64(getCurrentTime()), + ancillaryData: ancillaryData + }); + governor.propose(transactions, ancillaryData); + return id; + } + + /** + * @notice Resolves a proposal by checking the status of the request in the Voting contract. + * @dev For the resolution to work correctly, this contract must be a registered contract in the DVM. + * @param id proposal id. + */ + function resolveProposal(uint256 id) external nonReentrant { + BondedProposal memory bondedProposal = bondedProposals[id]; + require(bondedProposal.sender != address(0), "Invalid proposal id"); + OracleAncillaryInterface voting = OracleAncillaryInterface( + finder.getImplementationAddress(OracleInterfaces.Oracle) + ); + bytes32 adminIdentifier = AdminIdentifierLib._constructIdentifier(id); + + require( + voting.hasPrice(adminIdentifier, bondedProposal.time, bondedProposal.ancillaryData), + "No price resolved" + ); + if (voting.getPrice(adminIdentifier, bondedProposal.time, bondedProposal.ancillaryData) != 0) { + token.safeTransfer(bondedProposal.sender, bondedProposal.lockedBond); + emit ProposalResolved(id, true); + } else { + token.safeTransfer(finder.getImplementationAddress(OracleInterfaces.Store), bondedProposal.lockedBond); + emit ProposalResolved(id, false); + } + delete bondedProposals[id]; + } + + /** + * @notice Admin method to set the bond amount. + * @dev Admin is intended to be the governance system itself. + * @param _bond the new bond. + */ + function setBond(uint256 _bond) public nonReentrant onlyOwner { + bond = _bond; + emit BondSet(_bond); + } + + /** + * @notice Returns the current block timestamp. + * @dev Can be overridden to control contract time. + * @return the current block timestamp. + */ + function getCurrentTime() public view virtual returns (uint256) { + return block.timestamp; + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Registry.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Registry.sol new file mode 100644 index 000000000..504c11166 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Registry.sol @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/MultiRole.sol"; +import "../interfaces/RegistryInterface.sol"; + +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; + +/** + * @title Registry for financial contracts and approved financial contract creators. + * @dev Maintains a whitelist of financial contract creators that are allowed + * to register new financial contracts and stores party members of a financial contract. + */ +contract Registry is RegistryInterface, MultiRole { + using SafeMath for uint256; + + /**************************************** + * INTERNAL VARIABLES AND STORAGE * + ****************************************/ + + enum Roles { + Owner, // The owner manages the set of ContractCreators. + ContractCreator // Can register financial contracts. + } + + // This enum is required because a `WasValid` state is required + // to ensure that financial contracts cannot be re-registered. + enum Validity { + Invalid, + Valid + } + + // Local information about a contract. + struct FinancialContract { + Validity valid; + uint128 index; + } + + struct Party { + address[] contracts; // Each financial contract address is stored in this array. + // The address of each financial contract is mapped to its index for constant time look up and deletion. + mapping(address => uint256) contractIndex; + } + + // Array of all contracts that are approved to use the UMA Oracle. + address[] public registeredContracts; + + // Map of financial contract contracts to the associated FinancialContract struct. + mapping(address => FinancialContract) public contractMap; + + // Map each party member to their their associated Party struct. + mapping(address => Party) private partyMap; + + /**************************************** + * EVENTS * + ****************************************/ + + event NewContractRegistered(address indexed contractAddress, address indexed creator, address[] parties); + event PartyAdded(address indexed contractAddress, address indexed party); + event PartyRemoved(address indexed contractAddress, address indexed party); + + /** + * @notice Construct the Registry contract. + */ + constructor() { + _createExclusiveRole(uint256(Roles.Owner), uint256(Roles.Owner), msg.sender); + // Start with no contract creators registered. + _createSharedRole(uint256(Roles.ContractCreator), uint256(Roles.Owner), new address[](0)); + } + + /**************************************** + * REGISTRATION FUNCTIONS * + ****************************************/ + + /** + * @notice Registers a new financial contract. + * @dev Only authorized contract creators can call this method. + * @param parties array of addresses who become parties in the contract. + * @param contractAddress address of the contract against which the parties are registered. + */ + function registerContract( + address[] calldata parties, + address contractAddress + ) external override onlyRoleHolder(uint256(Roles.ContractCreator)) { + FinancialContract storage financialContract = contractMap[contractAddress]; + require(contractMap[contractAddress].valid == Validity.Invalid, "Can only register once"); + + // Store contract address as a registered contract. + registeredContracts.push(contractAddress); + + // No length check necessary because we should never hit (2^127 - 1) contracts. + financialContract.index = uint128(registeredContracts.length.sub(1)); + + // For all parties in the array add them to the contract's parties. + financialContract.valid = Validity.Valid; + for (uint256 i = 0; i < parties.length; i = i.add(1)) { + _addPartyToContract(parties[i], contractAddress); + } + + emit NewContractRegistered(contractAddress, msg.sender, parties); + } + + /** + * @notice Adds a party member to the calling contract. + * @dev msg.sender will be used to determine the contract that this party is added to. + * @param party new party for the calling contract. + */ + function addPartyToContract(address party) external override { + address contractAddress = msg.sender; + require(contractMap[contractAddress].valid == Validity.Valid, "Can only add to valid contract"); + + _addPartyToContract(party, contractAddress); + } + + /** + * @notice Removes a party member from the calling contract. + * @dev msg.sender will be used to determine the contract that this party is removed from. + * @param partyAddress address to be removed from the calling contract. + */ + function removePartyFromContract(address partyAddress) external override { + address contractAddress = msg.sender; + Party storage party = partyMap[partyAddress]; + uint256 numberOfContracts = party.contracts.length; + + require(numberOfContracts != 0, "Party has no contracts"); + require(contractMap[contractAddress].valid == Validity.Valid, "Remove only from valid contract"); + require(isPartyMemberOfContract(partyAddress, contractAddress), "Can only remove existing party"); + + // Index of the current location of the contract to remove. + uint256 deleteIndex = party.contractIndex[contractAddress]; + + // Store the last contract's address to update the lookup map. + address lastContractAddress = party.contracts[numberOfContracts - 1]; + + // Swap the contract to be removed with the last contract. + party.contracts[deleteIndex] = lastContractAddress; + + // Update the lookup index with the new location. + party.contractIndex[lastContractAddress] = deleteIndex; + + // Pop the last contract from the array and update the lookup map. + party.contracts.pop(); + delete party.contractIndex[contractAddress]; + + emit PartyRemoved(contractAddress, partyAddress); + } + + /**************************************** + * REGISTRY STATE GETTERS * + ****************************************/ + + /** + * @notice Returns whether the contract has been registered with the registry. + * @dev If it is registered, it is an authorized participant in the UMA system. + * @param contractAddress address of the financial contract. + * @return bool indicates whether the contract is registered. + */ + function isContractRegistered(address contractAddress) external view override returns (bool) { + return contractMap[contractAddress].valid == Validity.Valid; + } + + /** + * @notice Returns a list of all contracts that are associated with a particular party. + * @param party address of the party. + * @return an array of the contracts the party is registered to. + */ + function getRegisteredContracts(address party) external view override returns (address[] memory) { + return partyMap[party].contracts; + } + + /** + * @notice Returns all registered contracts. + * @return all registered contract addresses within the system. + */ + function getAllRegisteredContracts() external view override returns (address[] memory) { + return registeredContracts; + } + + /** + * @notice checks if an address is a party of a contract. + * @param party party to check. + * @param contractAddress address to check against the party. + * @return bool indicating if the address is a party of the contract. + */ + function isPartyMemberOfContract(address party, address contractAddress) public view override returns (bool) { + uint256 index = partyMap[party].contractIndex[contractAddress]; + return partyMap[party].contracts.length > index && partyMap[party].contracts[index] == contractAddress; + } + + /**************************************** + * INTERNAL FUNCTIONS * + ****************************************/ + + function _addPartyToContract(address party, address contractAddress) internal { + require(!isPartyMemberOfContract(party, contractAddress), "Can only register a party once"); + uint256 contractIndex = partyMap[party].contracts.length; + partyMap[party].contracts.push(contractAddress); + partyMap[party].contractIndex[contractAddress] = contractIndex; + + emit PartyAdded(contractAddress, party); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ResultComputation.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ResultComputation.sol new file mode 100644 index 000000000..8b67f0251 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ResultComputation.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/FixedPoint.sol"; + +/** + * @title Computes vote results. + * @dev The result is the mode of the added votes. Otherwise, the vote is unresolved. + */ +library ResultComputation { + using FixedPoint for FixedPoint.Unsigned; + + /**************************************** + * INTERNAL LIBRARY DATA STRUCTURE * + ****************************************/ + + struct Data { + // Maps price to number of tokens that voted for that price. + mapping(int256 => FixedPoint.Unsigned) voteFrequency; + // The total votes that have been added. + FixedPoint.Unsigned totalVotes; + // The price that is the current mode, i.e., the price with the highest frequency in `voteFrequency`. + int256 currentMode; + } + + /**************************************** + * VOTING FUNCTIONS * + ****************************************/ + + /** + * @notice Adds a new vote to be used when computing the result. + * @param data contains information to which the vote is applied. + * @param votePrice value specified in the vote for the given `numberTokens`. + * @param numberTokens number of tokens that voted on the `votePrice`. + */ + function addVote(Data storage data, int256 votePrice, FixedPoint.Unsigned memory numberTokens) internal { + data.totalVotes = data.totalVotes.add(numberTokens); + data.voteFrequency[votePrice] = data.voteFrequency[votePrice].add(numberTokens); + if ( + votePrice != data.currentMode && + data.voteFrequency[votePrice].isGreaterThan(data.voteFrequency[data.currentMode]) + ) data.currentMode = votePrice; + } + + /**************************************** + * VOTING STATE GETTERS * + ****************************************/ + + /** + * @notice Returns whether the result is resolved, and if so, what value it resolved to. + * @dev `price` should be ignored if `isResolved` is false. + * @param data contains information against which the `minVoteThreshold` is applied. + * @param minVoteThreshold min (exclusive) number of tokens that must have voted for the result to be valid. Can be + * used to enforce a minimum voter participation rate, regardless of how the votes are distributed. + * @return isResolved indicates if the price has been resolved correctly. + * @return price the price that the dvm resolved to. + */ + function getResolvedPrice( + Data storage data, + FixedPoint.Unsigned memory minVoteThreshold + ) internal view returns (bool isResolved, int256 price) { + FixedPoint.Unsigned memory modeThreshold = FixedPoint.fromUnscaledUint(50).div(100); + + if ( + data.totalVotes.isGreaterThan(minVoteThreshold) && + data.voteFrequency[data.currentMode].div(data.totalVotes).isGreaterThan(modeThreshold) + ) { + // `modeThreshold` and `minVoteThreshold` are exceeded, so the current mode is the resolved price. + isResolved = true; + price = data.currentMode; + } else isResolved = false; + } + + /** + * @notice Checks whether a `voteHash` is considered correct. + * @dev Should only be called after a vote is resolved, i.e., via `getResolvedPrice`. + * @param data contains information against which the `voteHash` is checked. + * @param voteHash committed hash submitted by the voter. + * @return bool true if the vote was correct. + */ + function wasVoteCorrect(Data storage data, bytes32 voteHash) internal view returns (bool) { + return voteHash == keccak256(abi.encode(data.currentMode)); + } + + /** + * @notice Gets the total number of tokens whose votes are considered correct. + * @dev Should only be called after a vote is resolved, i.e., via `getResolvedPrice`. + * @param data contains all votes against which the correctly voted tokens are counted. + * @return FixedPoint.Unsigned which indicates the frequency of the correctly voted tokens. + */ + function getTotalCorrectlyVotedTokens(Data storage data) internal view returns (FixedPoint.Unsigned memory) { + return data.voteFrequency[data.currentMode]; + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ResultComputationV2.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ResultComputationV2.sol new file mode 100644 index 000000000..cf7eb8c74 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ResultComputationV2.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +/** + * @title Computes vote results. + * @dev The result is the mode of the added votes. Otherwise, the vote is unresolved. + */ +library ResultComputationV2 { + /**************************************** + * INTERNAL LIBRARY DATA STRUCTURE * + ****************************************/ + + struct Data { + mapping(int256 => uint128) voteFrequency; // Maps price to number of tokens that voted for that price. + uint128 totalVotes; // The total votes that have been added. + int256 currentMode; // The price that is the current mode, i.e., the price with the highest frequency. + } + + /**************************************** + * VOTING FUNCTIONS * + ****************************************/ + + /** + * @notice Adds a new vote to be used when computing the result. + * @param data contains information to which the vote is applied. + * @param votePrice value specified in the vote for the given `numberTokens`. + * @param numberTokens number of tokens that voted on the `votePrice`. + */ + function addVote(Data storage data, int256 votePrice, uint128 numberTokens) internal { + data.totalVotes += numberTokens; + data.voteFrequency[votePrice] += numberTokens; + if (votePrice != data.currentMode && data.voteFrequency[votePrice] > data.voteFrequency[data.currentMode]) + data.currentMode = votePrice; + } + + /**************************************** + * VOTING STATE GETTERS * + ****************************************/ + + /** + * @notice Returns whether the result is resolved, and if so, what value it resolved to. + * @dev `price` should be ignored if `isResolved` is false. + * @param data contains information against which the `minTotalVotes` and `minModalVotes` thresholds are applied. + * @param minTotalVotes min (exclusive) number of tokens that must have voted (in any direction) for the result + * to be valid. Used to enforce a minimum voter participation rate, regardless of how the votes are distributed. + * @param minModalVotes min (exclusive) number of tokens that must have voted for the modal outcome for it to result + * in a resolution. This is used to avoid cases where the mode is a very small plurality. + * @return isResolved indicates if the price has been resolved correctly. + * @return price the price that the dvm resolved to. + */ + function getResolvedPrice( + Data storage data, + uint128 minTotalVotes, + uint128 minModalVotes + ) internal view returns (bool isResolved, int256 price) { + if (data.totalVotes > minTotalVotes && data.voteFrequency[data.currentMode] > minModalVotes) { + isResolved = true; // minTotalVotes and minModalVotes are exceeded, so the resolved price is the mode. + price = data.currentMode; + } + } + + /** + * @notice Checks whether a `voteHash` is considered correct. + * @dev Should only be called after a vote is resolved, i.e., via `getResolvedPrice`. + * @param data contains information against which the `voteHash` is checked. + * @param voteHash committed hash submitted by the voter. + * @return bool true if the vote was correct. + */ + function wasVoteCorrect(Data storage data, bytes32 voteHash) internal view returns (bool) { + return voteHash == keccak256(abi.encode(data.currentMode)); + } + + /** + * @notice Gets the total number of tokens whose votes are considered correct. + * @dev Should only be called after a vote is resolved, i.e., via `getResolvedPrice`. + * @param data contains all votes against which the correctly voted tokens are counted. + * @return uint128 which indicates the frequency of the correctly voted tokens. + */ + function getTotalCorrectlyVotedTokens(Data storage data) internal view returns (uint128) { + return data.voteFrequency[data.currentMode]; + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Staker.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Staker.sol new file mode 100644 index 000000000..aa8e52f09 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Staker.sol @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/Lockable.sol"; +import "../../common/implementation/MultiCaller.sol"; + +import "../../common/interfaces/ExpandedIERC20.sol"; +import "../interfaces/StakerInterface.sol"; + +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "@openzeppelin/contracts-v4/utils/math/SafeCast.sol"; + +/** + * @title Staking contract enabling UMA to be locked up by stakers to earn a pro rata share of a fixed emission rate. + * @dev Handles the staking, unstaking and reward retrieval logic. + */ +abstract contract Staker is StakerInterface, Ownable, Lockable, MultiCaller { + /**************************************** + * STAKING STATE * + ****************************************/ + + // Identifies a "stake" for a given voter. Each staker has an instance of this struct. + struct VoterStake { + uint128 stake; // UMA staked by the staker. + uint128 pendingUnstake; // UMA in unstake cooldown period, waiting to be unstaked. + mapping(uint32 => uint128) pendingStakes; // If a voter stakes during an active reveal, stake is pending. + uint128 rewardsPaidPerToken; // Internal tracker used in the calculation of pro-rata share of rewards. + uint128 outstandingRewards; // Accumulated rewards that have not yet been claimed. + int128 unappliedSlash; // Used to track unapplied slashing in the case of bisected rounds. + uint64 nextIndexToProcess; // The next request index that a staker is susceptible to be slashed on. + uint64 unstakeTime; // Time that a staker can unstake. Used to determine if cooldown has passed. + address delegate; // Address a staker has delegated to. The delegate can commit/reveal/claimRestake rewards. + } + + mapping(address => VoterStake) public voterStakes; // Each voter is mapped to staker struct for their position. + + mapping(address => address) public delegateToStaker; // Mapping of delegates to their delegators (staker). + + uint128 public emissionRate; // Number of UMA emitted per second to incentivize stakers. + + uint128 public cumulativeStake; // Total number of UMA staked within the system. + + uint128 public rewardPerTokenStored; // Tracker used to allocate pro-rata share of rewards to stakers. + + uint64 public unstakeCoolDown; // Delay, in seconds, a staker must wait when trying to unstake their UMA. + + uint64 public lastUpdateTime; // Tracks the last time the reward rate was updated, used in reward allocation. + + ExpandedIERC20 public immutable votingToken; // An instance of the UMA voting token to mint rewards for stakers + + /**************************************** + * EVENTS * + ****************************************/ + + event Staked( + address indexed voter, + address indexed from, + uint128 amount, + uint128 voterStake, + uint128 voterPendingUnstake, + uint128 cumulativeStake + ); + + event RequestedUnstake(address indexed voter, uint128 amount, uint64 unstakeTime, uint128 voterStake); + + event ExecutedUnstake(address indexed voter, uint128 tokensSent, uint128 voterStake); + + event WithdrawnRewards(address indexed voter, address indexed delegate, uint128 tokensWithdrawn); + + event UpdatedReward(address indexed voter, uint128 newReward, uint64 lastUpdateTime); + + event SetNewEmissionRate(uint128 newEmissionRate); + + event SetNewUnstakeCoolDown(uint64 newUnstakeCoolDown); + + event DelegateSet(address indexed delegator, address indexed delegate); + + event DelegatorSet(address indexed delegate, address indexed delegator); + + /** + * @notice Construct the Staker contract + * @param _emissionRate amount of voting tokens that are emitted per second, split pro rata to stakers. + * @param _unstakeCoolDown time that a voter must wait to unstake after requesting to unstake. + * @param _votingToken address of the UMA token contract used to commit votes. + */ + constructor(uint128 _emissionRate, uint64 _unstakeCoolDown, address _votingToken) { + setEmissionRate(_emissionRate); + setUnstakeCoolDown(_unstakeCoolDown); + votingToken = ExpandedIERC20(_votingToken); + } + + /**************************************** + * STAKER FUNCTIONS * + ****************************************/ + + /** + * @notice Pulls tokens from the sender's wallet and stakes them on his behalf. + * @param amount the amount of tokens to stake. + */ + function stake(uint128 amount) external { + _stakeTo(msg.sender, msg.sender, amount); + } + + /** + * @notice Pulls tokens from the sender's wallet and stakes them for the recipient. + * @param recipient the recipient address. + * @param amount the amount of tokens to stake. + */ + function stakeTo(address recipient, uint128 amount) external { + _stakeTo(msg.sender, recipient, amount); + } + + // Pull an amount of votingToken from the from address and stakes them for the recipient address. + // If we are in an active reveal phase the stake amount will be added to the pending stake. + // If not, the stake amount will be added to the stake. + function _stakeTo(address from, address recipient, uint128 amount) internal { + require(amount > 0, "Cannot stake 0"); + + VoterStake storage voterStake = voterStakes[recipient]; + + // If the staker has a cumulative staked balance of 0 then we can shortcut their nextIndexToProcess to + // the most recent index. This means we don't need to traverse requests where the staker was not staked. + // _getStartingIndexForStaker returns the appropriate index to start at. + if (voterStake.stake == 0) voterStake.nextIndexToProcess = _getStartingIndexForStaker(); + _updateTrackers(recipient); + + // Compute pending stakes when needed. + _computePendingStakes(recipient, amount); + + voterStake.stake += amount; + cumulativeStake += amount; + + // Tokens are pulled from the from address and sent to this contract. + // During withdrawAndRestake, from is the same as the address of this contract, so there is no need to transfer. + if (from != address(this)) votingToken.transferFrom(from, address(this), amount); + emit Staked(recipient, from, amount, voterStake.stake, voterStake.pendingUnstake, cumulativeStake); + } + + /** + * @notice Request a certain number of tokens to be unstaked. After the unstake time expires, the user may execute + * the unstake. Tokens requested to unstake are not slashable nor subject to earning rewards. + * This function cannot be called during an active reveal phase. + * Note there is no way to cancel an unstake request, you must wait until after unstakeTime and re-stake. + * @param amount the amount of tokens to request to be unstaked. + */ + function requestUnstake(uint128 amount) external nonReentrant { + require(!_inActiveReveal(), "In an active reveal phase"); + require(amount > 0, "Cannot unstake 0"); + _updateTrackers(msg.sender); + VoterStake storage voterStake = voterStakes[msg.sender]; + + require(voterStake.stake >= amount && voterStake.pendingUnstake == 0, "Bad amount or pending unstake"); + + cumulativeStake -= amount; + voterStake.pendingUnstake = amount; + voterStake.stake -= amount; + voterStake.unstakeTime = uint64(getCurrentTime()) + unstakeCoolDown; + + emit RequestedUnstake(msg.sender, amount, voterStake.unstakeTime, voterStake.stake); + } + + /** + * @notice Execute a previously requested unstake. Requires the unstake time to have passed. + * @dev If a staker requested an unstake and time > unstakeTime then send funds to staker. If unstakeCoolDown is + * set to 0 then the unstake can be executed immediately. + */ + function executeUnstake() external nonReentrant { + VoterStake storage voterStake = voterStakes[msg.sender]; + require( + voterStake.unstakeTime != 0 && (getCurrentTime() >= voterStake.unstakeTime || unstakeCoolDown == 0), + "Unstake time not passed" + ); + uint128 tokensToSend = voterStake.pendingUnstake; + + if (tokensToSend > 0) { + voterStake.pendingUnstake = 0; + voterStake.unstakeTime = 0; + votingToken.transfer(msg.sender, tokensToSend); + } + + emit ExecutedUnstake(msg.sender, tokensToSend, voterStake.stake); + } + + /** + * @notice Send accumulated rewards to the voter. Note that these rewards do not include slashing balance changes. + * @return uint128 the amount of tokens sent to the voter. + */ + function withdrawRewards() external returns (uint128) { + return _withdrawRewards(msg.sender, msg.sender); + } + + // Withdraws rewards for a given voter and sends them to the recipient. + function _withdrawRewards(address voter, address recipient) internal returns (uint128) { + _updateTrackers(voter); + VoterStake storage voterStake = voterStakes[voter]; + + uint128 tokensToMint = voterStake.outstandingRewards; + if (tokensToMint > 0) { + voterStake.outstandingRewards = 0; + require(votingToken.mint(recipient, tokensToMint), "Voting token issuance failed"); + emit WithdrawnRewards(voter, msg.sender, tokensToMint); + } + return tokensToMint; + } + + /** + * @notice Stake accumulated rewards. This is merely a convenience mechanism that combines the voter's withdrawal + * and stake in the same transaction if requested by a delegate or the voter. + * @dev The rewarded tokens simply pass through this contract before being staked on the voter's behalf. + * The balance of the delegate remains unchanged. + * @return uint128 the amount of tokens that the voter is staking. + */ + function withdrawAndRestake() external returns (uint128) { + address voter = getVoterFromDelegate(msg.sender); + uint128 rewards = _withdrawRewards(voter, address(this)); + _stakeTo(address(this), voter, rewards); + return rewards; + } + + /** + * @notice Sets the delegate of a voter. This delegate can vote on behalf of the staker. The staker will still own + * all staked balances, receive rewards and be slashed based on the actions of the delegate. Intended use is using a + * low-security available wallet for voting while keeping access to staked amounts secure by a more secure wallet. + * @param delegate the address of the delegate. + */ + function setDelegate(address delegate) external { + voterStakes[msg.sender].delegate = delegate; + emit DelegateSet(msg.sender, delegate); + } + + /** + * @notice Sets the delegator of a voter. Acts to accept a delegation. The delegate can only vote for the delegator + * if the delegator also selected the delegate to do so (two-way relationship needed). + * @param delegator the address of the delegator. + */ + function setDelegator(address delegator) external { + delegateToStaker[msg.sender] = delegator; + emit DelegatorSet(msg.sender, delegator); + } + + /**************************************** + * OWNER ADMIN FUNCTIONS * + ****************************************/ + + /** + * @notice Set the token's emission rate, the number of voting tokens that are emitted per second. + * @param newEmissionRate the new amount of voting tokens that are emitted per second, split pro rata to stakers. + */ + function setEmissionRate(uint128 newEmissionRate) public onlyOwner { + _updateReward(address(0)); + emissionRate = newEmissionRate; + emit SetNewEmissionRate(newEmissionRate); + } + + /** + * @notice Set the amount of time a voter must wait to unstake after submitting a request to do so. + * @param newUnstakeCoolDown the new duration of the cool down period in seconds. + */ + function setUnstakeCoolDown(uint64 newUnstakeCoolDown) public onlyOwner { + unstakeCoolDown = newUnstakeCoolDown; + emit SetNewUnstakeCoolDown(newUnstakeCoolDown); + } + + // Updates an account internal trackers. + function _updateTrackers(address voter) internal virtual { + _updateReward(voter); + } + + /**************************************** + * VIEW FUNCTIONS * + ****************************************/ + + /** + * @notice Gets the pending stake for a voter for a given round. + * @param voter the voter address. + * @param roundId round id. + * @return uint128 amount of the pending stake. + */ + function getVoterPendingStake(address voter, uint32 roundId) external view returns (uint128) { + return voterStakes[voter].pendingStakes[roundId]; + } + + /** + * @notice Gets the voter from the delegate. + * @param caller caller of the function or the address to check in the mapping between a voter and their delegate. + * @return address voter that corresponds to the delegate. + */ + function getVoterFromDelegate(address caller) public view returns (address) { + address delegator = delegateToStaker[caller]; + // The delegate chose to be a delegate for the staker. + if (delegator != address(0) && voterStakes[delegator].delegate == caller) return delegator; + else return caller; // The staker chose the delegate. + } + + /** + * @notice Determine the number of outstanding token rewards that can be withdrawn by a voter. + * @param voter the address of the voter. + * @return uint256 the outstanding rewards. + */ + function outstandingRewards(address voter) public view returns (uint256) { + VoterStake storage voterStake = voterStakes[voter]; + + return + ((voterStake.stake * (rewardPerToken() - voterStake.rewardsPaidPerToken)) / 1e18) + + voterStake.outstandingRewards; + } + + /** + * @notice Calculate the reward per token based on the last time the reward was updated. + * @return uint256 the reward per token. + */ + function rewardPerToken() public view returns (uint256) { + if (cumulativeStake == 0) return rewardPerTokenStored; + return rewardPerTokenStored + ((getCurrentTime() - lastUpdateTime) * emissionRate * 1e18) / cumulativeStake; + } + + /** + * @notice Returns the total amount of tokens staked by the voter, after applying updateTrackers. Specifically used + * by offchain apps to simulate the cumulative stake + unapplied slashing updates without sending a transaction. + * @param voter the address of the voter. + * @return uint128 the total stake. + */ + function getVoterStakePostUpdate(address voter) external returns (uint128) { + _updateTrackers(voter); + return voterStakes[voter].stake; + } + + /** + * @notice Returns the current block timestamp. + * @dev Can be overridden to control contract time. + * @return the current block timestamp. + */ + function getCurrentTime() public view virtual returns (uint256) { + return block.timestamp; + } + + /**************************************** + * INTERNAL FUNCTIONS * + ****************************************/ + + // This function must be called before any tokens are staked. Update the voter's pending stakes when necessary. + // The contract that inherits from Staker (e.g. VotingV2) must implement this logic by overriding this function. + function _computePendingStakes(address voter, uint128 amount) internal virtual; + + // Add a new stake amount to the voter's pending stake for a specific round id. + function _incrementPendingStake(address voter, uint32 roundId, uint128 amount) internal { + voterStakes[voter].pendingStakes[roundId] += amount; + } + + // Determine if we are in an active reveal phase. This function should be overridden by the child contract. + function _inActiveReveal() internal view virtual returns (bool) { + return false; + } + + // Returns the starting index for a staker. This function should be overridden by the implementing contract. + function _getStartingIndexForStaker() internal virtual returns (uint64) { + return 0; + } + + // Calculate the reward per token based on last time the reward was updated. + function _updateReward(address voter) internal { + uint128 newRewardPerToken = uint128(rewardPerToken()); + rewardPerTokenStored = newRewardPerToken; + lastUpdateTime = uint64(getCurrentTime()); + if (voter != address(0)) { + VoterStake storage voterStake = voterStakes[voter]; + voterStake.outstandingRewards = uint128(outstandingRewards(voter)); + voterStake.rewardsPaidPerToken = newRewardPerToken; + } + emit UpdatedReward(voter, newRewardPerToken, lastUpdateTime); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Store.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Store.sol new file mode 100644 index 000000000..7762123da --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Store.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; +import "../../common/implementation/FixedPoint.sol"; +import "../../common/implementation/MultiRole.sol"; +import "../../common/implementation/Withdrawable.sol"; +import "../../common/implementation/Testable.sol"; +import "../interfaces/StoreInterface.sol"; + +/** + * @title An implementation of Store that can accept Oracle fees in ETH or any arbitrary ERC20 token. + */ +contract Store is StoreInterface, Withdrawable, Testable { + using SafeMath for uint256; + using FixedPoint for FixedPoint.Unsigned; + using FixedPoint for uint256; + using SafeERC20 for IERC20; + + /**************************************** + * INTERNAL VARIABLES AND STORAGE * + ****************************************/ + + enum Roles { + Owner, + Withdrawer + } + + FixedPoint.Unsigned public fixedOracleFeePerSecondPerPfc; // Percentage of 1 E.g., .1 is 10% Oracle fee. + FixedPoint.Unsigned public weeklyDelayFeePerSecondPerPfc; // Percentage of 1 E.g., .1 is 10% weekly delay fee. + + mapping(address => FixedPoint.Unsigned) public finalFees; + uint256 public constant SECONDS_PER_WEEK = 604800; + + /**************************************** + * EVENTS * + ****************************************/ + + event NewFixedOracleFeePerSecondPerPfc(FixedPoint.Unsigned newOracleFee); + event NewWeeklyDelayFeePerSecondPerPfc(FixedPoint.Unsigned newWeeklyDelayFeePerSecondPerPfc); + event NewFinalFee(FixedPoint.Unsigned newFinalFee); + + /** + * @notice Construct the Store contract. + */ + constructor( + FixedPoint.Unsigned memory _fixedOracleFeePerSecondPerPfc, + FixedPoint.Unsigned memory _weeklyDelayFeePerSecondPerPfc, + address _timerAddress + ) Testable(_timerAddress) { + _createExclusiveRole(uint256(Roles.Owner), uint256(Roles.Owner), msg.sender); + _createWithdrawRole(uint256(Roles.Withdrawer), uint256(Roles.Owner), msg.sender); + setFixedOracleFeePerSecondPerPfc(_fixedOracleFeePerSecondPerPfc); + setWeeklyDelayFeePerSecondPerPfc(_weeklyDelayFeePerSecondPerPfc); + } + + /**************************************** + * ORACLE FEE CALCULATION AND PAYMENT * + ****************************************/ + + /** + * @notice Pays Oracle fees in ETH to the store. + * @dev To be used by contracts whose margin currency is ETH. + */ + function payOracleFees() external payable override { + require(msg.value > 0, "Value sent can't be zero"); + } + + /** + * @notice Pays oracle fees in the margin currency, erc20Address, to the store. + * @dev To be used if the margin currency is an ERC20 token rather than ETH. + * @param erc20Address address of the ERC20 token used to pay the fee. + * @param amount number of tokens to transfer. An approval for at least this amount must exist. + */ + function payOracleFeesErc20(address erc20Address, FixedPoint.Unsigned calldata amount) external override { + IERC20 erc20 = IERC20(erc20Address); + require(amount.isGreaterThan(0), "Amount sent can't be zero"); + erc20.safeTransferFrom(msg.sender, address(this), amount.rawValue); + } + + /** + * @notice Computes the regular oracle fees that a contract should pay for a period. + * @dev The late penalty is similar to the regular fee in that is is charged per second over the period between + * startTime and endTime. + * + * The late penalty percentage increases over time as follows: + * + * - 0-1 week since startTime: no late penalty + * + * - 1-2 weeks since startTime: 1x late penalty percentage is applied + * + * - 2-3 weeks since startTime: 2x late penalty percentage is applied + * + * - ... + * + * @param startTime defines the beginning time from which the fee is paid. + * @param endTime end time until which the fee is paid. + * @param pfc "profit from corruption", or the maximum amount of margin currency that a + * token sponsor could extract from the contract through corrupting the price feed in their favor. + * @return regularFee amount owed for the duration from start to end time for the given pfc. + * @return latePenalty penalty percentage, if any, for paying the fee after the deadline. + */ + function computeRegularFee( + uint256 startTime, + uint256 endTime, + FixedPoint.Unsigned calldata pfc + ) external view override returns (FixedPoint.Unsigned memory regularFee, FixedPoint.Unsigned memory latePenalty) { + uint256 timeDiff = endTime.sub(startTime); + + // Multiply by the unscaled `timeDiff` first, to get more accurate results. + regularFee = pfc.mul(timeDiff).mul(fixedOracleFeePerSecondPerPfc); + + // Compute how long ago the start time was to compute the delay penalty. + uint256 paymentDelay = getCurrentTime().sub(startTime); + + // Compute the additional percentage (per second) that will be charged because of the penalty. + // Note: if less than a week has gone by since the startTime, paymentDelay / SECONDS_PER_WEEK will truncate to + // 0, causing no penalty to be charged. + FixedPoint.Unsigned memory penaltyPercentagePerSecond = weeklyDelayFeePerSecondPerPfc.mul( + paymentDelay.div(SECONDS_PER_WEEK) + ); + + // Apply the penaltyPercentagePerSecond to the payment period. + latePenalty = pfc.mul(timeDiff).mul(penaltyPercentagePerSecond); + } + + /** + * @notice Computes the final oracle fees that a contract should pay at settlement. + * @param currency token used to pay the final fee. + * @return finalFee amount due denominated in units of `currency`. + */ + function computeFinalFee(address currency) external view override returns (FixedPoint.Unsigned memory) { + return finalFees[currency]; + } + + /**************************************** + * ADMIN STATE MODIFYING FUNCTIONS * + ****************************************/ + + /** + * @notice Sets a new oracle fee per second. + * @param newFixedOracleFeePerSecondPerPfc new fee per second charged to use the oracle. + */ + function setFixedOracleFeePerSecondPerPfc( + FixedPoint.Unsigned memory newFixedOracleFeePerSecondPerPfc + ) public onlyRoleHolder(uint256(Roles.Owner)) { + // Oracle fees at or over 100% don't make sense. + require(newFixedOracleFeePerSecondPerPfc.isLessThan(1), "Fee must be < 100% per second."); + fixedOracleFeePerSecondPerPfc = newFixedOracleFeePerSecondPerPfc; + emit NewFixedOracleFeePerSecondPerPfc(newFixedOracleFeePerSecondPerPfc); + } + + /** + * @notice Sets a new weekly delay fee. + * @param newWeeklyDelayFeePerSecondPerPfc fee escalation per week of late fee payment. + */ + function setWeeklyDelayFeePerSecondPerPfc( + FixedPoint.Unsigned memory newWeeklyDelayFeePerSecondPerPfc + ) public onlyRoleHolder(uint256(Roles.Owner)) { + require(newWeeklyDelayFeePerSecondPerPfc.isLessThan(1), "weekly delay fee must be < 100%"); + weeklyDelayFeePerSecondPerPfc = newWeeklyDelayFeePerSecondPerPfc; + emit NewWeeklyDelayFeePerSecondPerPfc(newWeeklyDelayFeePerSecondPerPfc); + } + + /** + * @notice Sets a new final fee for a particular currency. + * @param currency defines the token currency used to pay the final fee. + * @param newFinalFee final fee amount. + */ + function setFinalFee( + address currency, + FixedPoint.Unsigned memory newFinalFee + ) public onlyRoleHolder(uint256(Roles.Owner)) { + finalFees[currency] = newFinalFee; + emit NewFinalFee(newFinalFee); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/TokenMigrator.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/TokenMigrator.sol new file mode 100644 index 000000000..0f0790f60 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/TokenMigrator.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/FixedPoint.sol"; +import "../../common/interfaces/ExpandedIERC20.sol"; +import "./VotingToken.sol"; + +/** + * @title Migration contract for VotingTokens. + * @dev Handles migrating token holders from one token to the next. + */ +contract TokenMigrator { + using FixedPoint for FixedPoint.Unsigned; + + /**************************************** + * INTERNAL VARIABLES AND STORAGE * + ****************************************/ + + VotingToken public oldToken; + ExpandedIERC20 public newToken; + + uint256 public snapshotId; + FixedPoint.Unsigned public rate; + + mapping(address => bool) public hasMigrated; + + /** + * @notice Construct the TokenMigrator contract. + * @dev This function triggers the snapshot upon which all migrations will be based. + * @param _rate the number of old tokens it takes to generate one new token. + * @param _oldToken address of the token being migrated from. + * @param _newToken address of the token being migrated to. + */ + constructor(FixedPoint.Unsigned memory _rate, address _oldToken, address _newToken) { + // Prevents division by 0 in migrateTokens(). + // Also it doesn’t make sense to have “0 old tokens equate to 1 new token”. + require(_rate.isGreaterThan(0), "Rate can't be 0"); + rate = _rate; + newToken = ExpandedIERC20(_newToken); + oldToken = VotingToken(_oldToken); + snapshotId = oldToken.snapshot(); + } + + /** + * @notice Migrates the tokenHolder's old tokens to new tokens. + * @dev This function can only be called once per `tokenHolder`. Anyone can call this method + * on behalf of any other token holder since there is no disadvantage to receiving the tokens earlier. + * @param tokenHolder address of the token holder to migrate. + */ + function migrateTokens(address tokenHolder) external { + require(!hasMigrated[tokenHolder], "Already migrated tokens"); + hasMigrated[tokenHolder] = true; + + FixedPoint.Unsigned memory oldBalance = FixedPoint.Unsigned(oldToken.balanceOfAt(tokenHolder, snapshotId)); + + if (!oldBalance.isGreaterThan(0)) { + return; + } + + FixedPoint.Unsigned memory newBalance = oldBalance.div(rate); + require(newToken.mint(tokenHolder, newBalance.rawValue), "Mint failed"); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VoteTiming.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VoteTiming.sol new file mode 100644 index 000000000..6fdfa853a --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VoteTiming.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../interfaces/VotingInterface.sol"; + +/** + * @title Library to compute rounds and phases for an equal length commit-reveal voting cycle. + */ +library VoteTiming { + struct Data { + uint256 phaseLength; + } + + /** + * @notice Initializes the data object. Sets the phase length based on the input. + * @param data reference to the this library's data object. + * @param phaseLength length of voting phase in seconds. + */ + function init(Data storage data, uint256 phaseLength) internal { + // This should have a require message but this results in an internal Solidity error. + require(phaseLength > 0); + data.phaseLength = phaseLength; + } + + /** + * @notice Computes the roundID based off the current time as floor(timestamp/roundLength). + * @dev The round ID depends on the global timestamp but not on the lifetime of the system. + * The consequence is that the initial round ID starts at an arbitrary number (that increments, as expected, for subsequent rounds) instead of zero or one. + * @param data input data object. + * @param currentTime input unix timestamp used to compute the current roundId. + * @return roundId defined as a function of the currentTime and `phaseLength` from `data`. + */ + function computeCurrentRoundId(Data storage data, uint256 currentTime) internal view returns (uint256) { + uint256 roundLength = data.phaseLength * uint256(VotingAncillaryInterface.Phase.NUM_PHASES); + return currentTime / roundLength; + } + + /** + * @notice compute the round end time as a function of the round Id. + * @param data input data object. + * @param roundId uniquely identifies the current round. + * @return timestamp unix time of when the current round will end. + */ + function computeRoundEndTime(Data storage data, uint256 roundId) internal view returns (uint256) { + uint256 roundLength = data.phaseLength * uint256(VotingAncillaryInterface.Phase.NUM_PHASES); + return roundLength * (roundId + 1); + } + + /** + * @notice Computes the current phase based only on the current time. + * @param data input data object. + * @param currentTime input unix timestamp used to compute the current roundId. + * @return current voting phase based on current time and vote phases configuration. + */ + function computeCurrentPhase( + Data storage data, + uint256 currentTime + ) internal view returns (VotingAncillaryInterface.Phase) { + // This employs some hacky casting. We could make this an if-statement if we're worried about type safety. + return + VotingAncillaryInterface.Phase( + (currentTime / data.phaseLength) % uint256(VotingAncillaryInterface.Phase.NUM_PHASES) + ); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Voting.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Voting.sol new file mode 100644 index 000000000..76e4f7d25 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Voting.sol @@ -0,0 +1,977 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/FixedPoint.sol"; +import "../../common/implementation/Testable.sol"; +import "../interfaces/FinderInterface.sol"; +import "../interfaces/OracleInterface.sol"; +import "../interfaces/OracleAncillaryInterface.sol"; +import "../interfaces/VotingInterface.sol"; +import "../interfaces/VotingAncillaryInterface.sol"; +import "../interfaces/IdentifierWhitelistInterface.sol"; +import "./Registry.sol"; +import "./ResultComputation.sol"; +import "./VoteTiming.sol"; +import "./VotingToken.sol"; +import "./Constants.sol"; + +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; +import "@openzeppelin/contracts-v4/utils/cryptography/ECDSA.sol"; + +/** + * @title Voting system for Oracle. + * @dev Handles receiving and resolving price requests via a commit-reveal voting scheme. + */ +contract Voting is + Testable, + Ownable, + OracleInterface, + OracleAncillaryInterface, // Interface to support ancillary data with price requests. + VotingInterface, + VotingAncillaryInterface // Interface to support ancillary data with voting rounds. +{ + using FixedPoint for FixedPoint.Unsigned; + using SafeMath for uint256; + using VoteTiming for VoteTiming.Data; + using ResultComputation for ResultComputation.Data; + + /**************************************** + * VOTING DATA STRUCTURES * + ****************************************/ + + // Identifies a unique price request for which the Oracle will always return the same value. + // Tracks ongoing votes as well as the result of the vote. + struct PriceRequest { + bytes32 identifier; + uint256 time; + // A map containing all votes for this price in various rounds. + mapping(uint256 => VoteInstance) voteInstances; + // If in the past, this was the voting round where this price was resolved. If current or the upcoming round, + // this is the voting round where this price will be voted on, but not necessarily resolved. + uint256 lastVotingRound; + // The index in the `pendingPriceRequests` that references this PriceRequest. A value of UINT_MAX means that + // this PriceRequest is resolved and has been cleaned up from `pendingPriceRequests`. + uint256 index; + bytes ancillaryData; + } + + struct VoteInstance { + // Maps (voterAddress) to their submission. + mapping(address => VoteSubmission) voteSubmissions; + // The data structure containing the computed voting results. + ResultComputation.Data resultComputation; + } + + struct VoteSubmission { + // A bytes32 of `0` indicates no commit or a commit that was already revealed. + bytes32 commit; + // The hash of the value that was revealed. + // Note: this is only used for computation of rewards. + bytes32 revealHash; + } + + struct Round { + uint256 snapshotId; // Voting token snapshot ID for this round. 0 if no snapshot has been taken. + FixedPoint.Unsigned inflationRate; // Inflation rate set for this round. + FixedPoint.Unsigned gatPercentage; // Gat rate set for this round. + uint256 rewardsExpirationTime; // Time that rewards for this round can be claimed until. + } + + // Represents the status a price request has. + enum RequestStatus { + NotRequested, // Was never requested. + Active, // Is being voted on in the current round. + Resolved, // Was resolved in a previous round. + Future // Is scheduled to be voted on in a future round. + } + + // Only used as a return value in view methods -- never stored in the contract. + struct RequestState { + RequestStatus status; + uint256 lastVotingRound; + } + + /**************************************** + * INTERNAL TRACKING * + ****************************************/ + + // Maps round numbers to the rounds. + mapping(uint256 => Round) public rounds; + + // Maps price request IDs to the PriceRequest struct. + mapping(bytes32 => PriceRequest) private priceRequests; + + // Price request ids for price requests that haven't yet been marked as resolved. + // These requests may be for future rounds. + bytes32[] internal pendingPriceRequests; + + VoteTiming.Data public voteTiming; + + // Percentage of the total token supply that must be used in a vote to + // create a valid price resolution. 1 == 100%. + FixedPoint.Unsigned public gatPercentage; + + // Global setting for the rate of inflation per vote. This is the percentage of the snapshotted total supply that + // should be split among the correct voters. + // Note: this value is used to set per-round inflation at the beginning of each round. 1 = 100%. + FixedPoint.Unsigned public inflationRate; + + // Time in seconds from the end of the round in which a price request is + // resolved that voters can still claim their rewards. + uint256 public rewardsExpirationTimeout; + + // Reference to the voting token. + VotingToken public votingToken; + + // Reference to the Finder. + FinderInterface private finder; + + // If non-zero, this contract has been migrated to this address. All voters and + // financial contracts should query the new address only. + address public migratedAddress; + + // Max value of an unsigned integer. + uint256 private constant UINT_MAX = ~uint256(0); + + // Max length in bytes of ancillary data that can be appended to a price request. + // As of December 2020, the current Ethereum gas limit is 12.5 million. This requestPrice function's gas primarily + // comes from computing a Keccak-256 hash in _encodePriceRequest and writing a new PriceRequest to + // storage. We have empirically determined an ancillary data limit of 8192 bytes that keeps this function + // well within the gas limit at ~8 million gas. To learn more about the gas limit and EVM opcode costs go here: + // - https://etherscan.io/chart/gaslimit + // - https://github.com/djrtwo/evm-opcode-gas-costs + uint256 public constant ancillaryBytesLimit = 8192; + + bytes32 public snapshotMessageHash = ECDSA.toEthSignedMessageHash(keccak256(bytes("Sign For Snapshot"))); + + /*************************************** + * EVENTS * + ****************************************/ + + event VoteCommitted( + address indexed voter, + uint256 indexed roundId, + bytes32 indexed identifier, + uint256 time, + bytes ancillaryData + ); + + event EncryptedVote( + address indexed voter, + uint256 indexed roundId, + bytes32 indexed identifier, + uint256 time, + bytes ancillaryData, + bytes encryptedVote + ); + + event VoteRevealed( + address indexed voter, + uint256 indexed roundId, + bytes32 indexed identifier, + uint256 time, + int256 price, + bytes ancillaryData, + uint256 numTokens + ); + + event RewardsRetrieved( + address indexed voter, + uint256 indexed roundId, + bytes32 indexed identifier, + uint256 time, + bytes ancillaryData, + uint256 numTokens + ); + + event PriceRequestAdded(uint256 indexed roundId, bytes32 indexed identifier, uint256 time); + + event PriceResolved( + uint256 indexed roundId, + bytes32 indexed identifier, + uint256 time, + int256 price, + bytes ancillaryData + ); + + /** + * @notice Construct the Voting contract. + * @param _phaseLength length of the commit and reveal phases in seconds. + * @param _gatPercentage of the total token supply that must be used in a vote to create a valid price resolution. + * @param _inflationRate percentage inflation per round used to increase token supply of correct voters. + * @param _rewardsExpirationTimeout timeout, in seconds, within which rewards must be claimed. + * @param _votingToken address of the UMA token contract used to commit votes. + * @param _finder keeps track of all contracts within the system based on their interfaceName. + * @param _timerAddress Contract that stores the current time in a testing environment. + * Must be set to 0x0 for production environments that use live time. + */ + constructor( + uint256 _phaseLength, + FixedPoint.Unsigned memory _gatPercentage, + FixedPoint.Unsigned memory _inflationRate, + uint256 _rewardsExpirationTimeout, + address _votingToken, + address _finder, + address _timerAddress + ) Testable(_timerAddress) { + voteTiming.init(_phaseLength); + require(_gatPercentage.isLessThanOrEqual(1), "GAT percentage must be <= 100%"); + gatPercentage = _gatPercentage; + inflationRate = _inflationRate; + votingToken = VotingToken(_votingToken); + finder = FinderInterface(_finder); + rewardsExpirationTimeout = _rewardsExpirationTimeout; + } + + /*************************************** + MODIFIERS + ****************************************/ + + modifier onlyRegisteredContract() { + if (migratedAddress != address(0)) { + require(msg.sender == migratedAddress, "Caller must be migrated address"); + } else { + Registry registry = Registry(finder.getImplementationAddress(OracleInterfaces.Registry)); + require(registry.isContractRegistered(msg.sender), "Called must be registered"); + } + _; + } + + modifier onlyIfNotMigrated() { + require(migratedAddress == address(0), "Only call this if not migrated"); + _; + } + + /**************************************** + * PRICE REQUEST AND ACCESS FUNCTIONS * + ****************************************/ + + /** + * @notice Enqueues a request (if a request isn't already present) for the given `identifier`, `time` pair. + * @dev Time must be in the past and the identifier must be supported. The length of the ancillary data + * is limited such that this method abides by the EVM transaction gas limit. + * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. + * @param time unix timestamp for the price request. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + */ + function requestPrice( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) public override onlyRegisteredContract { + uint256 blockTime = getCurrentTime(); + require(time <= blockTime, "Can only request in past"); + require(_getIdentifierWhitelist().isIdentifierSupported(identifier), "Unsupported identifier request"); + require(ancillaryData.length <= ancillaryBytesLimit, "Invalid ancillary data"); + + bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); + PriceRequest storage priceRequest = priceRequests[priceRequestId]; + uint256 currentRoundId = voteTiming.computeCurrentRoundId(blockTime); + + RequestStatus requestStatus = _getRequestStatus(priceRequest, currentRoundId); + + if (requestStatus == RequestStatus.NotRequested) { + // Price has never been requested. + // Price requests always go in the next round, so add 1 to the computed current round. + uint256 nextRoundId = currentRoundId.add(1); + + PriceRequest storage newPriceRequest = priceRequests[priceRequestId]; + newPriceRequest.identifier = identifier; + newPriceRequest.time = time; + newPriceRequest.lastVotingRound = nextRoundId; + newPriceRequest.index = pendingPriceRequests.length; + newPriceRequest.ancillaryData = ancillaryData; + + pendingPriceRequests.push(priceRequestId); + emit PriceRequestAdded(nextRoundId, identifier, time); + } + } + + // Overloaded method to enable short term backwards compatibility. Will be deprecated in the next DVM version. + function requestPrice(bytes32 identifier, uint256 time) public override { + requestPrice(identifier, time, ""); + } + + /** + * @notice Whether the price for `identifier` and `time` is available. + * @dev Time must be in the past and the identifier must be supported. + * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. + * @param time unix timestamp of for the price request. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + * @return _hasPrice bool if the DVM has resolved to a price for the given identifier and timestamp. + */ + function hasPrice( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) public view override onlyRegisteredContract returns (bool) { + (bool _hasPrice, , ) = _getPriceOrError(identifier, time, ancillaryData); + return _hasPrice; + } + + // Overloaded method to enable short term backwards compatibility. Will be deprecated in the next DVM version. + function hasPrice(bytes32 identifier, uint256 time) public view override returns (bool) { + return hasPrice(identifier, time, ""); + } + + /** + * @notice Gets the price for `identifier` and `time` if it has already been requested and resolved. + * @dev If the price is not available, the method reverts. + * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. + * @param time unix timestamp of for the price request. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + * @return int256 representing the resolved price for the given identifier and timestamp. + */ + function getPrice( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) public view override onlyRegisteredContract returns (int256) { + (bool _hasPrice, int256 price, string memory message) = _getPriceOrError(identifier, time, ancillaryData); + + // If the price wasn't available, revert with the provided message. + require(_hasPrice, message); + return price; + } + + // Overloaded method to enable short term backwards compatibility. Will be deprecated in the next DVM version. + function getPrice(bytes32 identifier, uint256 time) public view override returns (int256) { + return getPrice(identifier, time, ""); + } + + /** + * @notice Gets the status of a list of price requests, identified by their identifier and time. + * @dev If the status for a particular request is NotRequested, the lastVotingRound will always be 0. + * @param requests array of type PendingRequest which includes an identifier and timestamp for each request. + * @return requestStates a list, in the same order as the input list, giving the status of each of the specified price requests. + */ + function getPriceRequestStatuses( + PendingRequestAncillary[] memory requests + ) public view returns (RequestState[] memory) { + RequestState[] memory requestStates = new RequestState[](requests.length); + uint256 currentRoundId = voteTiming.computeCurrentRoundId(getCurrentTime()); + for (uint256 i = 0; i < requests.length; i++) { + PriceRequest storage priceRequest = _getPriceRequest( + requests[i].identifier, + requests[i].time, + requests[i].ancillaryData + ); + + RequestStatus status = _getRequestStatus(priceRequest, currentRoundId); + + // If it's an active request, its true lastVotingRound is the current one, even if it hasn't been updated. + if (status == RequestStatus.Active) { + requestStates[i].lastVotingRound = currentRoundId; + } else { + requestStates[i].lastVotingRound = priceRequest.lastVotingRound; + } + requestStates[i].status = status; + } + return requestStates; + } + + // Overloaded method to enable short term backwards compatibility. Will be deprecated in the next DVM version. + function getPriceRequestStatuses(PendingRequest[] memory requests) public view returns (RequestState[] memory) { + PendingRequestAncillary[] memory requestsAncillary = new PendingRequestAncillary[](requests.length); + + for (uint256 i = 0; i < requests.length; i++) { + requestsAncillary[i].identifier = requests[i].identifier; + requestsAncillary[i].time = requests[i].time; + requestsAncillary[i].ancillaryData = ""; + } + return getPriceRequestStatuses(requestsAncillary); + } + + /**************************************** + * VOTING FUNCTIONS * + ****************************************/ + + /** + * @notice Commit a vote for a price request for `identifier` at `time`. + * @dev `identifier`, `time` must correspond to a price request that's currently in the commit phase. + * Commits can be changed. + * @dev Since transaction data is public, the salt will be revealed with the vote. While this is the system’s expected behavior, + * voters should never reuse salts. If someone else is able to guess the voted price and knows that a salt will be reused, then + * they can determine the vote pre-reveal. + * @param identifier uniquely identifies the committed vote. EG BTC/USD price pair. + * @param time unix timestamp of the price being voted on. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + * @param hash keccak256 hash of the `price`, `salt`, voter `address`, `time`, current `roundId`, and `identifier`. + */ + function commitVote( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData, + bytes32 hash + ) public override onlyIfNotMigrated { + require(hash != bytes32(0), "Invalid provided hash"); + // Current time is required for all vote timing queries. + uint256 blockTime = getCurrentTime(); + require( + voteTiming.computeCurrentPhase(blockTime) == VotingAncillaryInterface.Phase.Commit, + "Cannot commit in reveal phase" + ); + + // At this point, the computed and last updated round ID should be equal. + uint256 currentRoundId = voteTiming.computeCurrentRoundId(blockTime); + + PriceRequest storage priceRequest = _getPriceRequest(identifier, time, ancillaryData); + require( + _getRequestStatus(priceRequest, currentRoundId) == RequestStatus.Active, + "Cannot commit inactive request" + ); + + priceRequest.lastVotingRound = currentRoundId; + VoteInstance storage voteInstance = priceRequest.voteInstances[currentRoundId]; + voteInstance.voteSubmissions[msg.sender].commit = hash; + + emit VoteCommitted(msg.sender, currentRoundId, identifier, time, ancillaryData); + } + + // Overloaded method to enable short term backwards compatibility. Will be deprecated in the next DVM version. + function commitVote(bytes32 identifier, uint256 time, bytes32 hash) public override onlyIfNotMigrated { + commitVote(identifier, time, "", hash); + } + + /** + * @notice Snapshot the current round's token balances and lock in the inflation rate and GAT. + * @dev This function can be called multiple times, but only the first call per round into this function or `revealVote` + * will create the round snapshot. Any later calls will be a no-op. Will revert unless called during reveal period. + * @param signature signature required to prove caller is an EOA to prevent flash loans from being included in the + * snapshot. + */ + function snapshotCurrentRound( + bytes calldata signature + ) external override(VotingInterface, VotingAncillaryInterface) onlyIfNotMigrated { + uint256 blockTime = getCurrentTime(); + require(voteTiming.computeCurrentPhase(blockTime) == Phase.Reveal, "Only snapshot in reveal phase"); + // Require public snapshot require signature to ensure caller is an EOA. + require(ECDSA.recover(snapshotMessageHash, signature) == msg.sender, "Signature must match sender"); + uint256 roundId = voteTiming.computeCurrentRoundId(blockTime); + _freezeRoundVariables(roundId); + } + + /** + * @notice Reveal a previously committed vote for `identifier` at `time`. + * @dev The revealed `price`, `salt`, `address`, `time`, `roundId`, and `identifier`, must hash to the latest `hash` + * that `commitVote()` was called with. Only the committer can reveal their vote. + * @param identifier voted on in the commit phase. EG BTC/USD price pair. + * @param time specifies the unix timestamp of the price being voted on. + * @param price voted on during the commit phase. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + * @param salt value used to hide the commitment price during the commit phase. + */ + function revealVote( + bytes32 identifier, + uint256 time, + int256 price, + bytes memory ancillaryData, + int256 salt + ) public override onlyIfNotMigrated { + require(voteTiming.computeCurrentPhase(getCurrentTime()) == Phase.Reveal, "Cannot reveal in commit phase"); + // Note: computing the current round is required to disallow people from revealing an old commit after the round is over. + uint256 roundId = voteTiming.computeCurrentRoundId(getCurrentTime()); + + PriceRequest storage priceRequest = _getPriceRequest(identifier, time, ancillaryData); + VoteInstance storage voteInstance = priceRequest.voteInstances[roundId]; + VoteSubmission storage voteSubmission = voteInstance.voteSubmissions[msg.sender]; + + // Scoping to get rid of a stack too deep error. + { + // 0 hashes are disallowed in the commit phase, so they indicate a different error. + // Cannot reveal an uncommitted or previously revealed hash + require(voteSubmission.commit != bytes32(0), "Invalid hash reveal"); + require( + keccak256(abi.encodePacked(price, salt, msg.sender, time, ancillaryData, roundId, identifier)) == + voteSubmission.commit, + "Revealed data != commit hash" + ); + // To protect against flash loans, we require snapshot be validated as EOA. + require(rounds[roundId].snapshotId != 0, "Round has no snapshot"); + } + + // Get the frozen snapshotId + uint256 snapshotId = rounds[roundId].snapshotId; + + delete voteSubmission.commit; + + // Get the voter's snapshotted balance. Since balances are returned pre-scaled by 10**18, we can directly + // initialize the Unsigned value with the returned uint. + FixedPoint.Unsigned memory balance = FixedPoint.Unsigned(votingToken.balanceOfAt(msg.sender, snapshotId)); + + // Set the voter's submission. + voteSubmission.revealHash = keccak256(abi.encode(price)); + + // Add vote to the results. + voteInstance.resultComputation.addVote(price, balance); + + emit VoteRevealed(msg.sender, roundId, identifier, time, price, ancillaryData, balance.rawValue); + } + + // Overloaded method to enable short term backwards compatibility. Will be deprecated in the next DVM version. + function revealVote(bytes32 identifier, uint256 time, int256 price, int256 salt) public override { + revealVote(identifier, time, price, "", salt); + } + + /** + * @notice commits a vote and logs an event with a data blob, typically an encrypted version of the vote + * @dev An encrypted version of the vote is emitted in an event `EncryptedVote` to allow off-chain infrastructure to + * retrieve the commit. The contents of `encryptedVote` are never used on chain: it is purely for convenience. + * @param identifier unique price pair identifier. Eg: BTC/USD price pair. + * @param time unix timestamp of for the price request. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + * @param hash keccak256 hash of the price you want to vote for and a `int256 salt`. + * @param encryptedVote offchain encrypted blob containing the voters amount, time and salt. + */ + function commitAndEmitEncryptedVote( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData, + bytes32 hash, + bytes memory encryptedVote + ) public override { + commitVote(identifier, time, ancillaryData, hash); + + uint256 roundId = voteTiming.computeCurrentRoundId(getCurrentTime()); + emit EncryptedVote(msg.sender, roundId, identifier, time, ancillaryData, encryptedVote); + } + + // Overloaded method to enable short term backwards compatibility. Will be deprecated in the next DVM version. + function commitAndEmitEncryptedVote( + bytes32 identifier, + uint256 time, + bytes32 hash, + bytes memory encryptedVote + ) public override { + commitVote(identifier, time, "", hash); + + commitAndEmitEncryptedVote(identifier, time, "", hash, encryptedVote); + } + + /** + * @notice Submit a batch of commits in a single transaction. + * @dev Using `encryptedVote` is optional. If included then commitment is emitted in an event. + * Look at `project-root/common/Constants.js` for the tested maximum number of + * commitments that can fit in one transaction. + * @param commits struct to encapsulate an `identifier`, `time`, `hash` and optional `encryptedVote`. + */ + function batchCommit(CommitmentAncillary[] memory commits) public override { + for (uint256 i = 0; i < commits.length; i++) { + if (commits[i].encryptedVote.length == 0) { + commitVote(commits[i].identifier, commits[i].time, commits[i].ancillaryData, commits[i].hash); + } else { + commitAndEmitEncryptedVote( + commits[i].identifier, + commits[i].time, + commits[i].ancillaryData, + commits[i].hash, + commits[i].encryptedVote + ); + } + } + } + + // Overloaded method to enable short term backwards compatibility. Will be deprecated in the next DVM version. + function batchCommit(Commitment[] memory commits) public override { + CommitmentAncillary[] memory commitsAncillary = new CommitmentAncillary[](commits.length); + + for (uint256 i = 0; i < commits.length; i++) { + commitsAncillary[i].identifier = commits[i].identifier; + commitsAncillary[i].time = commits[i].time; + commitsAncillary[i].ancillaryData = ""; + commitsAncillary[i].hash = commits[i].hash; + commitsAncillary[i].encryptedVote = commits[i].encryptedVote; + } + batchCommit(commitsAncillary); + } + + /** + * @notice Reveal multiple votes in a single transaction. + * Look at `project-root/common/Constants.js` for the tested maximum number of reveals. + * that can fit in one transaction. + * @dev For more info on reveals, review the comment for `revealVote`. + * @param reveals array of the Reveal struct which contains an identifier, time, price and salt. + */ + function batchReveal(RevealAncillary[] memory reveals) public override { + for (uint256 i = 0; i < reveals.length; i++) { + revealVote( + reveals[i].identifier, + reveals[i].time, + reveals[i].price, + reveals[i].ancillaryData, + reveals[i].salt + ); + } + } + + // Overloaded method to enable short term backwards compatibility. Will be deprecated in the next DVM version. + function batchReveal(Reveal[] memory reveals) public override { + RevealAncillary[] memory revealsAncillary = new RevealAncillary[](reveals.length); + + for (uint256 i = 0; i < reveals.length; i++) { + revealsAncillary[i].identifier = reveals[i].identifier; + revealsAncillary[i].time = reveals[i].time; + revealsAncillary[i].price = reveals[i].price; + revealsAncillary[i].ancillaryData = ""; + revealsAncillary[i].salt = reveals[i].salt; + } + batchReveal(revealsAncillary); + } + + /** + * @notice Retrieves rewards owed for a set of resolved price requests. + * @dev Can only retrieve rewards if calling for a valid round and if the call is done within the timeout threshold + * (not expired). Note that a named return value is used here to avoid a stack to deep error. + * @param voterAddress voter for which rewards will be retrieved. Does not have to be the caller. + * @param roundId the round from which voting rewards will be retrieved from. + * @param toRetrieve array of PendingRequests which rewards are retrieved from. + * @return totalRewardToIssue total amount of rewards returned to the voter. + */ + function retrieveRewards( + address voterAddress, + uint256 roundId, + PendingRequestAncillary[] memory toRetrieve + ) public override returns (FixedPoint.Unsigned memory totalRewardToIssue) { + if (migratedAddress != address(0)) { + require(msg.sender == migratedAddress, "Can only call from migrated"); + } + require(roundId < voteTiming.computeCurrentRoundId(getCurrentTime()), "Invalid roundId"); + + Round storage round = rounds[roundId]; + bool isExpired = getCurrentTime() > round.rewardsExpirationTime; + FixedPoint.Unsigned memory snapshotBalance = FixedPoint.Unsigned( + votingToken.balanceOfAt(voterAddress, round.snapshotId) + ); + + // Compute the total amount of reward that will be issued for each of the votes in the round. + FixedPoint.Unsigned memory snapshotTotalSupply = FixedPoint.Unsigned( + votingToken.totalSupplyAt(round.snapshotId) + ); + FixedPoint.Unsigned memory totalRewardPerVote = round.inflationRate.mul(snapshotTotalSupply); + + // Keep track of the voter's accumulated token reward. + totalRewardToIssue = FixedPoint.Unsigned(0); + + for (uint256 i = 0; i < toRetrieve.length; i++) { + PriceRequest storage priceRequest = _getPriceRequest( + toRetrieve[i].identifier, + toRetrieve[i].time, + toRetrieve[i].ancillaryData + ); + VoteInstance storage voteInstance = priceRequest.voteInstances[priceRequest.lastVotingRound]; + // Only retrieve rewards for votes resolved in same round + require(priceRequest.lastVotingRound == roundId, "Retrieve for votes same round"); + + _resolvePriceRequest(priceRequest, voteInstance); + + if (voteInstance.voteSubmissions[voterAddress].revealHash == 0) { + continue; + } else if (isExpired) { + // Emit a 0 token retrieval on expired rewards. + emit RewardsRetrieved( + voterAddress, + roundId, + toRetrieve[i].identifier, + toRetrieve[i].time, + toRetrieve[i].ancillaryData, + 0 + ); + } else if ( + voteInstance.resultComputation.wasVoteCorrect(voteInstance.voteSubmissions[voterAddress].revealHash) + ) { + // The price was successfully resolved during the voter's last voting round, the voter revealed + // and was correct, so they are eligible for a reward. + // Compute the reward and add to the cumulative reward. + + FixedPoint.Unsigned memory reward = snapshotBalance.mul(totalRewardPerVote).div( + voteInstance.resultComputation.getTotalCorrectlyVotedTokens() + ); + totalRewardToIssue = totalRewardToIssue.add(reward); + + // Emit reward retrieval for this vote. + emit RewardsRetrieved( + voterAddress, + roundId, + toRetrieve[i].identifier, + toRetrieve[i].time, + toRetrieve[i].ancillaryData, + reward.rawValue + ); + } else { + // Emit a 0 token retrieval on incorrect votes. + emit RewardsRetrieved( + voterAddress, + roundId, + toRetrieve[i].identifier, + toRetrieve[i].time, + toRetrieve[i].ancillaryData, + 0 + ); + } + + // Delete the submission to capture any refund and clean up storage. + delete voteInstance.voteSubmissions[voterAddress].revealHash; + } + + // Issue any accumulated rewards. + if (totalRewardToIssue.isGreaterThan(0)) { + require(votingToken.mint(voterAddress, totalRewardToIssue.rawValue), "Voting token issuance failed"); + } + } + + // Overloaded method to enable short term backwards compatibility. Will be deprecated in the next DVM version. + function retrieveRewards( + address voterAddress, + uint256 roundId, + PendingRequest[] memory toRetrieve + ) public override returns (FixedPoint.Unsigned memory) { + PendingRequestAncillary[] memory toRetrieveAncillary = new PendingRequestAncillary[](toRetrieve.length); + + for (uint256 i = 0; i < toRetrieve.length; i++) { + toRetrieveAncillary[i].identifier = toRetrieve[i].identifier; + toRetrieveAncillary[i].time = toRetrieve[i].time; + toRetrieveAncillary[i].ancillaryData = ""; + } + + return retrieveRewards(voterAddress, roundId, toRetrieveAncillary); + } + + /**************************************** + * VOTING GETTER FUNCTIONS * + ****************************************/ + + /** + * @notice Gets the queries that are being voted on this round. + * @return pendingRequests array containing identifiers of type `PendingRequest`. + * and timestamps for all pending requests. + */ + function getPendingRequests() + external + view + override(VotingInterface, VotingAncillaryInterface) + returns (PendingRequestAncillary[] memory) + { + uint256 blockTime = getCurrentTime(); + uint256 currentRoundId = voteTiming.computeCurrentRoundId(blockTime); + + // Solidity memory arrays aren't resizable (and reading storage is expensive). Hence this hackery to filter + // `pendingPriceRequests` only to those requests that have an Active RequestStatus. + PendingRequestAncillary[] memory unresolved = new PendingRequestAncillary[](pendingPriceRequests.length); + uint256 numUnresolved = 0; + + for (uint256 i = 0; i < pendingPriceRequests.length; i++) { + PriceRequest storage priceRequest = priceRequests[pendingPriceRequests[i]]; + if (_getRequestStatus(priceRequest, currentRoundId) == RequestStatus.Active) { + unresolved[numUnresolved] = PendingRequestAncillary({ + identifier: priceRequest.identifier, + time: priceRequest.time, + ancillaryData: priceRequest.ancillaryData + }); + numUnresolved++; + } + } + + PendingRequestAncillary[] memory pendingRequests = new PendingRequestAncillary[](numUnresolved); + for (uint256 i = 0; i < numUnresolved; i++) { + pendingRequests[i] = unresolved[i]; + } + return pendingRequests; + } + + /** + * @notice Returns the current voting phase, as a function of the current time. + * @return Phase to indicate the current phase. Either { Commit, Reveal, NUM_PHASES_PLACEHOLDER }. + */ + function getVotePhase() external view override(VotingInterface, VotingAncillaryInterface) returns (Phase) { + return voteTiming.computeCurrentPhase(getCurrentTime()); + } + + /** + * @notice Returns the current round ID, as a function of the current time. + * @return uint256 representing the unique round ID. + */ + function getCurrentRoundId() external view override(VotingInterface, VotingAncillaryInterface) returns (uint256) { + return voteTiming.computeCurrentRoundId(getCurrentTime()); + } + + /**************************************** + * OWNER ADMIN FUNCTIONS * + ****************************************/ + + /** + * @notice Disables this Voting contract in favor of the migrated one. + * @dev Can only be called by the contract owner. + * @param newVotingAddress the newly migrated contract address. + */ + function setMigrated( + address newVotingAddress + ) external override(VotingInterface, VotingAncillaryInterface) onlyOwner { + migratedAddress = newVotingAddress; + } + + /** + * @notice Resets the inflation rate. Note: this change only applies to rounds that have not yet begun. + * @dev This method is public because calldata structs are not currently supported by solidity. + * @param newInflationRate sets the next round's inflation rate. + */ + function setInflationRate( + FixedPoint.Unsigned memory newInflationRate + ) public override(VotingInterface, VotingAncillaryInterface) onlyOwner { + inflationRate = newInflationRate; + } + + /** + * @notice Resets the Gat percentage. Note: this change only applies to rounds that have not yet begun. + * @dev This method is public because calldata structs are not currently supported by solidity. + * @param newGatPercentage sets the next round's Gat percentage. + */ + function setGatPercentage( + FixedPoint.Unsigned memory newGatPercentage + ) public override(VotingInterface, VotingAncillaryInterface) onlyOwner { + require(newGatPercentage.isLessThan(1), "GAT percentage must be < 100%"); + gatPercentage = newGatPercentage; + } + + /** + * @notice Resets the rewards expiration timeout. + * @dev This change only applies to rounds that have not yet begun. + * @param NewRewardsExpirationTimeout how long a caller can wait before choosing to withdraw their rewards. + */ + function setRewardsExpirationTimeout( + uint256 NewRewardsExpirationTimeout + ) public override(VotingInterface, VotingAncillaryInterface) onlyOwner { + rewardsExpirationTimeout = NewRewardsExpirationTimeout; + } + + /**************************************** + * PRIVATE AND INTERNAL FUNCTIONS * + ****************************************/ + + // Returns the price for a given identifer. Three params are returns: bool if there was an error, int to represent + // the resolved price and a string which is filled with an error message, if there was an error or "". + function _getPriceOrError( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) private view returns (bool, int256, string memory) { + PriceRequest storage priceRequest = _getPriceRequest(identifier, time, ancillaryData); + uint256 currentRoundId = voteTiming.computeCurrentRoundId(getCurrentTime()); + + RequestStatus requestStatus = _getRequestStatus(priceRequest, currentRoundId); + if (requestStatus == RequestStatus.Active) { + return (false, 0, "Current voting round not ended"); + } else if (requestStatus == RequestStatus.Resolved) { + VoteInstance storage voteInstance = priceRequest.voteInstances[priceRequest.lastVotingRound]; + (, int256 resolvedPrice) = voteInstance.resultComputation.getResolvedPrice( + _computeGat(priceRequest.lastVotingRound) + ); + return (true, resolvedPrice, ""); + } else if (requestStatus == RequestStatus.Future) { + return (false, 0, "Price is still to be voted on"); + } else { + return (false, 0, "Price was never requested"); + } + } + + function _getPriceRequest( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) private view returns (PriceRequest storage) { + return priceRequests[_encodePriceRequest(identifier, time, ancillaryData)]; + } + + function _encodePriceRequest( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) private pure returns (bytes32) { + return keccak256(abi.encode(identifier, time, ancillaryData)); + } + + function _freezeRoundVariables(uint256 roundId) private { + Round storage round = rounds[roundId]; + // Only on the first reveal should the snapshot be captured for that round. + if (round.snapshotId == 0) { + // There is no snapshot ID set, so create one. + round.snapshotId = votingToken.snapshot(); + + // Set the round inflation rate to the current global inflation rate. + rounds[roundId].inflationRate = inflationRate; + + // Set the round gat percentage to the current global gat rate. + rounds[roundId].gatPercentage = gatPercentage; + + // Set the rewards expiration time based on end of time of this round and the current global timeout. + rounds[roundId].rewardsExpirationTime = voteTiming.computeRoundEndTime(roundId).add( + rewardsExpirationTimeout + ); + } + } + + function _resolvePriceRequest(PriceRequest storage priceRequest, VoteInstance storage voteInstance) private { + if (priceRequest.index == UINT_MAX) { + return; + } + (bool isResolved, int256 resolvedPrice) = voteInstance.resultComputation.getResolvedPrice( + _computeGat(priceRequest.lastVotingRound) + ); + require(isResolved, "Can't resolve unresolved request"); + + // Delete the resolved price request from pendingPriceRequests. + uint256 lastIndex = pendingPriceRequests.length - 1; + PriceRequest storage lastPriceRequest = priceRequests[pendingPriceRequests[lastIndex]]; + lastPriceRequest.index = priceRequest.index; + pendingPriceRequests[priceRequest.index] = pendingPriceRequests[lastIndex]; + pendingPriceRequests.pop(); + + priceRequest.index = UINT_MAX; + emit PriceResolved( + priceRequest.lastVotingRound, + priceRequest.identifier, + priceRequest.time, + resolvedPrice, + priceRequest.ancillaryData + ); + } + + function _computeGat(uint256 roundId) private view returns (FixedPoint.Unsigned memory) { + uint256 snapshotId = rounds[roundId].snapshotId; + if (snapshotId == 0) { + // No snapshot - return max value to err on the side of caution. + return FixedPoint.Unsigned(UINT_MAX); + } + + // Grab the snapshotted supply from the voting token. It's already scaled by 10**18, so we can directly + // initialize the Unsigned value with the returned uint. + FixedPoint.Unsigned memory snapshottedSupply = FixedPoint.Unsigned(votingToken.totalSupplyAt(snapshotId)); + + // Multiply the total supply at the snapshot by the gatPercentage to get the GAT in number of tokens. + return snapshottedSupply.mul(rounds[roundId].gatPercentage); + } + + function _getRequestStatus( + PriceRequest storage priceRequest, + uint256 currentRoundId + ) private view returns (RequestStatus) { + if (priceRequest.lastVotingRound == 0) { + return RequestStatus.NotRequested; + } else if (priceRequest.lastVotingRound < currentRoundId) { + VoteInstance storage voteInstance = priceRequest.voteInstances[priceRequest.lastVotingRound]; + (bool isResolved, ) = voteInstance.resultComputation.getResolvedPrice( + _computeGat(priceRequest.lastVotingRound) + ); + return isResolved ? RequestStatus.Resolved : RequestStatus.Active; + } else if (priceRequest.lastVotingRound == currentRoundId) { + return RequestStatus.Active; + } else { + // Means than priceRequest.lastVotingRound > currentRoundId + return RequestStatus.Future; + } + } + + function _getIdentifierWhitelist() private view returns (IdentifierWhitelistInterface supportedIdentifiers) { + return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VotingToken.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VotingToken.sol new file mode 100644 index 000000000..428fbf58c --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VotingToken.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/ExpandedERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/extensions/ERC20Snapshot.sol"; + +/** + * @title Ownership of this token allows a voter to respond to price requests. + * @dev Supports snapshotting and allows the Oracle to mint new tokens as rewards. + */ +contract VotingToken is ExpandedERC20, ERC20Snapshot { + /** + * @notice Constructs the VotingToken. + */ + constructor() ExpandedERC20("UMA Voting Token v1", "UMA", 18) ERC20Snapshot() {} + + function decimals() public view virtual override(ERC20, ExpandedERC20) returns (uint8) { + return super.decimals(); + } + + /** + * @notice Creates a new snapshot ID. + * @return uint256 Thew new snapshot ID. + */ + function snapshot() external returns (uint256) { + return _snapshot(); + } + + // _transfer, _mint and _burn are ERC20 internal methods that are overridden by ERC20Snapshot, + // therefore the compiler will complain that VotingToken must override these methods + // because the two base classes (ERC20 and ERC20Snapshot) both define the same functions + + function _transfer(address from, address to, uint256 value) internal override(ERC20) { + super._transfer(from, to, value); + } + + function _mint(address account, uint256 value) internal virtual override(ERC20) { + super._mint(account, value); + } + + function _burn(address account, uint256 value) internal virtual override(ERC20) { + super._burn(account, value); + } + + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual override(ERC20, ERC20Snapshot) { + super._beforeTokenTransfer(from, to, amount); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VotingV2.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VotingV2.sol new file mode 100644 index 000000000..89d882fa1 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VotingV2.sol @@ -0,0 +1,1156 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "@openzeppelin/contracts-v4/utils/math/Math.sol"; + +import "./ResultComputationV2.sol"; +import "./Staker.sol"; +import "./VoteTiming.sol"; +import "./Constants.sol"; + +import "../interfaces/MinimumVotingAncillaryInterface.sol"; +import "../interfaces/FinderInterface.sol"; +import "../interfaces/IdentifierWhitelistInterface.sol"; +import "../interfaces/OracleAncillaryInterface.sol"; +import "../interfaces/OracleGovernanceInterface.sol"; +import "../interfaces/OracleInterface.sol"; +import "../interfaces/VotingV2Interface.sol"; +import "../interfaces/RegistryInterface.sol"; +import "../interfaces/SlashingLibraryInterface.sol"; + +/** + * @title VotingV2 contract for the UMA DVM. + * @dev Handles receiving and resolving price requests via a commit-reveal voting schelling scheme. + */ + +contract VotingV2 is Staker, OracleInterface, OracleAncillaryInterface, OracleGovernanceInterface, VotingV2Interface { + using VoteTiming for VoteTiming.Data; + using ResultComputationV2 for ResultComputationV2.Data; + + /**************************************** + * VOTING DATA STRUCTURES * + ****************************************/ + + // Identifies a unique price request. Tracks ongoing votes as well as the result of the vote. + struct PriceRequest { + uint32 lastVotingRound; // Last round that this price request was voted on. Updated when a request is rolled. + bool isGovernance; // Denotes whether this is a governance request or not. + uint64 time; // Timestamp used when evaluating the request. + uint32 rollCount; // The number of rounds that a price request has rolled. Informs if a request can be deleted. + bytes32 identifier; // Identifier that defines how the voters should resolve the request. + mapping(uint32 => VoteInstance) voteInstances; // A map containing all votes for this price in various rounds. + bytes ancillaryData; // Additional data used to resolve the request. + } + + struct VoteInstance { + mapping(address => VoteSubmission) voteSubmissions; // Maps (voter) to their submission. + ResultComputationV2.Data results; // The data structure containing the computed voting results. + } + + struct VoteSubmission { + bytes32 commit; // A bytes32 of 0 indicates no commit or a commit that was already revealed. + bytes32 revealHash; // The hash of the value that was revealed. This is only used for computation of rewards. + } + + struct Round { + SlashingLibraryInterface slashingLibrary; // Slashing library used to compute voter participation slash at this round. + uint128 minParticipationRequirement; // Minimum staked tokens that must vote to resolve a request. + uint128 minAgreementRequirement; // Minimum staked tokens that must agree on an outcome to resolve a request. + uint128 cumulativeStakeAtRound; // Total staked tokens at the start of the round. + uint32 numberOfRequestsToVote; // The number of requests to vote in this round. + } + + struct SlashingTracker { + uint256 wrongVoteSlashPerToken; // The amount of tokens slashed per token staked for a wrong vote. + uint256 noVoteSlashPerToken; // The amount of tokens slashed per token staked for a no vote. + uint256 totalSlashed; // The total amount of tokens slashed for a given request. + uint256 totalCorrectVotes; // The total number of correct votes for a given request. + uint32 lastVotingRound; // The last round that this request was voted on (when it resolved). + } + + enum VoteParticipation { + DidNotVote, // Voter did not vote. + WrongVote, // Voter voted against the resolved price. + CorrectVote // Voter voted with the resolved price. + } + + // Represents the status a price request has. + enum RequestStatus { + NotRequested, // Was never requested. + Active, // Is being voted on in the current round. + Resolved, // Was resolved in a previous round. + Future, // Is scheduled to be voted on in a future round. + ToDelete // Is scheduled to be deleted. + } + + // Only used as a return value in view methods -- never stored in the contract. + struct RequestState { + RequestStatus status; + uint32 lastVotingRound; + } + + /**************************************** + * VOTING STATE * + ****************************************/ + + uint32 public lastRoundIdProcessed; // The last round pendingPriceRequestsIds were traversed in. + + uint64 public nextPendingIndexToProcess; // Next pendingPriceRequestsIds index to process in lastRoundIdProcessed. + + FinderInterface public immutable finder; // Reference to the UMA Finder contract, used to find other UMA contracts. + + SlashingLibraryInterface public slashingLibrary; // Reference to Slashing Library, used to compute slashing amounts. + + VoteTiming.Data public voteTiming; // Vote timing library used to compute round timing related logic. + + OracleAncillaryInterface public immutable previousVotingContract; // Previous voting contract, if migrated. + + mapping(uint256 => Round) public rounds; // Maps round numbers to the rounds. + + mapping(bytes32 => PriceRequest) public priceRequests; // Maps price request IDs to the PriceRequest struct. + + bytes32[] public resolvedPriceRequestIds; // Array of resolved price requestIds. Used to track resolved requests. + + bytes32[] public pendingPriceRequestsIds; // Array of pending price requestIds. Can be resolved in the future. + + uint32 public maxRolls; // The maximum number of times a request can roll before it is deleted automatically. + + uint32 public maxRequestsPerRound; // The maximum number of requests that can be enqueued in a single round. + + address public migratedAddress; // If non-zero, this contract has been migrated to this address. + + uint128 public gat; // GAT: A minimum number of tokens that must participate to resolve a vote. + + uint64 public spat; // SPAT: Minimum percentage of staked tokens that must agree on the answer to resolve a vote. + + uint64 public constant UINT64_MAX = type(uint64).max; // Max value of an unsigned integer. + + uint256 public constant ANCILLARY_BYTES_LIMIT = 8192; // Max length in bytes of ancillary data. + + /**************************************** + * EVENTS * + ****************************************/ + + event VoteCommitted( + address indexed voter, + address indexed caller, + uint32 roundId, + bytes32 indexed identifier, + uint256 time, + bytes ancillaryData + ); + + event EncryptedVote( + address indexed caller, + uint32 indexed roundId, + bytes32 indexed identifier, + uint256 time, + bytes ancillaryData, + bytes encryptedVote + ); + + event VoteRevealed( + address indexed voter, + address indexed caller, + uint32 roundId, + bytes32 indexed identifier, + uint256 time, + bytes ancillaryData, + int256 price, + uint128 numTokens + ); + + event RequestAdded( + address indexed requester, + uint32 indexed roundId, + bytes32 indexed identifier, + uint256 time, + bytes ancillaryData, + bool isGovernance + ); + + event RequestResolved( + uint32 indexed roundId, + uint256 indexed resolvedPriceRequestIndex, + bytes32 indexed identifier, + uint256 time, + bytes ancillaryData, + int256 price + ); + + event VotingContractMigrated(address newAddress); + + event RequestDeleted(bytes32 indexed identifier, uint256 indexed time, bytes ancillaryData, uint32 rollCount); + + event RequestRolled(bytes32 indexed identifier, uint256 indexed time, bytes ancillaryData, uint32 rollCount); + + event GatAndSpatChanged(uint128 newGat, uint64 newSpat); + + event SlashingLibraryChanged(address newAddress); + + event MaxRollsChanged(uint32 newMaxRolls); + + event MaxRequestsPerRoundChanged(uint32 newMaxRequestsPerRound); + + event VoterSlashApplied(address indexed voter, int128 slashedTokens, uint128 postStake); + + event VoterSlashed(address indexed voter, uint256 indexed requestIndex, int128 slashedTokens); + + /** + * @notice Construct the VotingV2 contract. + * @param _emissionRate amount of voting tokens that are emitted per second, split prorate between stakers. + * @param _unstakeCoolDown time that a voter must wait to unstake after requesting to unstake. + * @param _phaseLength length of the voting phases in seconds. + * @param _maxRolls number of times a vote must roll to be auto deleted by the DVM. + * @param _maxRequestsPerRound maximum number of requests that can be enqueued in a single round. + * @param _gat number of tokens that must participate to resolve a vote. + * @param _spat percentage of staked tokens that must agree on the result to resolve a vote. + * @param _votingToken address of the UMA token contract used to commit votes. + * @param _finder keeps track of all contracts within the system based on their interfaceName. + * @param _slashingLibrary contract used to calculate voting slashing penalties based on voter participation. + * @param _previousVotingContract previous voting contract address. + */ + constructor( + uint128 _emissionRate, + uint64 _unstakeCoolDown, + uint64 _phaseLength, + uint32 _maxRolls, + uint32 _maxRequestsPerRound, + uint128 _gat, + uint64 _spat, + address _votingToken, + address _finder, + address _slashingLibrary, + address _previousVotingContract + ) Staker(_emissionRate, _unstakeCoolDown, _votingToken) { + voteTiming.init(_phaseLength); + finder = FinderInterface(_finder); + previousVotingContract = OracleAncillaryInterface(_previousVotingContract); + setGatAndSpat(_gat, _spat); + setSlashingLibrary(_slashingLibrary); + setMaxRequestPerRound(_maxRequestsPerRound); + setMaxRolls(_maxRolls); + } + + /*************************************** + MODIFIERS + ****************************************/ + + modifier onlyRegisteredContract() { + _requireRegisteredContract(); + _; + } + + modifier onlyIfNotMigrated() { + _requireNotMigrated(); + _; + } + + /**************************************** + * PRICE REQUEST AND ACCESS FUNCTIONS * + ****************************************/ + + /** + * @notice Enqueues a request (if a request isn't already present) for the identifier, time and ancillary data. + * @dev Time must be in the past and the identifier must be supported. The length of the ancillary data is limited. + * @param identifier uniquely identifies the price requested. E.g. BTC/USD (encoded as bytes32) could be requested. + * @param time unix timestamp for the price request. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + */ + function requestPrice( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) public override nonReentrant onlyIfNotMigrated onlyRegisteredContract { + _requestPrice(identifier, time, ancillaryData, false); + } + + /** + * @notice Enqueues a governance action request (if not already present) for identifier, time and ancillary data. + * @dev Only the owner of the Voting contract can call this. In normal operation this is the Governor contract. + * @param identifier uniquely identifies the price requested. E.g. Admin 0 (encoded as bytes32) could be requested. + * @param time unix timestamp for the price request. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + */ + function requestGovernanceAction( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) external override onlyOwner onlyIfNotMigrated { + _requestPrice(identifier, time, ancillaryData, true); + } + + /** + * @notice Enqueues a request (if a request isn't already present) for the identifier, time pair. + * @dev Overloaded method to enable short term backwards compatibility when ancillary data is not included. + * @param identifier uniquely identifies the price requested. E.g. BTC/USD (encoded as bytes32) could be requested. + * @param time unix timestamp for the price request. + */ + function requestPrice(bytes32 identifier, uint256 time) external override { + requestPrice(identifier, time, ""); + } + + // Enqueues a request (if a request isn't already present) for the given identifier, time and ancillary data. + function _requestPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData, bool isGovernance) internal { + require(time <= getCurrentTime(), "Can only request in past"); + require(isGovernance || _getIdentifierWhitelist().isIdentifierSupported(identifier), "Unsupported identifier"); + require(ancillaryData.length <= ANCILLARY_BYTES_LIMIT, "Invalid ancillary data"); + + bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); + PriceRequest storage priceRequest = priceRequests[priceRequestId]; + + // Price has never been requested. + uint32 currentRoundId = getCurrentRoundId(); + if (_getRequestStatus(priceRequest, currentRoundId) == RequestStatus.NotRequested) { + uint32 roundIdToVoteOn = getRoundIdToVoteOnRequest(currentRoundId + 1); + ++rounds[roundIdToVoteOn].numberOfRequestsToVote; + priceRequest.identifier = identifier; + priceRequest.time = uint64(time); + priceRequest.ancillaryData = ancillaryData; + priceRequest.lastVotingRound = roundIdToVoteOn; + if (isGovernance) priceRequest.isGovernance = isGovernance; + + pendingPriceRequestsIds.push(priceRequestId); + emit RequestAdded(msg.sender, roundIdToVoteOn, identifier, time, ancillaryData, isGovernance); + } + } + + /** + * @notice Gets the round ID that a request should be voted on. + * @param targetRoundId round ID to start searching for a round to vote on. + * @return uint32 round ID that a request should be voted on. + */ + function getRoundIdToVoteOnRequest(uint32 targetRoundId) public view returns (uint32) { + while (rounds[targetRoundId].numberOfRequestsToVote >= maxRequestsPerRound) ++targetRoundId; + return targetRoundId; + } + + /** + * @notice Returns whether the price for identifier, time and ancillary data is available. + * @param identifier uniquely identifies the price requested. E.g. BTC/USD (encoded as bytes32) could be requested. + * @param time unix timestamp of the price request. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + * @return bool if the DVM has resolved to a price for the given identifier, timestamp and ancillary data. + */ + function hasPrice( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) public view override onlyRegisteredContract returns (bool) { + (bool _hasPrice, , ) = _getPriceOrError(identifier, time, ancillaryData); + return _hasPrice; + } + + /** + * @notice Whether the price for identifier and time is available. + * @dev Overloaded method to enable short term backwards compatibility when ancillary data is not included. + * @param identifier uniquely identifies the price requested. E.g. BTC/USD (encoded as bytes32) could be requested. + * @param time unix timestamp of the price request. + * @return bool if the DVM has resolved to a price for the given identifier and timestamp. + */ + function hasPrice(bytes32 identifier, uint256 time) external view override returns (bool) { + return hasPrice(identifier, time, ""); + } + + /** + * @notice Gets the price for identifier, time and ancillary data if it has already been requested and resolved. + * @dev If the price is not available, the method reverts. + * @param identifier uniquely identifies the price requested. E.g. BTC/USD (encoded as bytes32) could be requested. + * @param time unix timestamp of the price request. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + * @return int256 representing the resolved price for the given identifier, timestamp and ancillary data. + */ + function getPrice( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) public view override onlyRegisteredContract returns (int256) { + (bool _hasPrice, int256 price, string memory message) = _getPriceOrError(identifier, time, ancillaryData); + + // If the price wasn't available, revert with the provided message. + require(_hasPrice, message); + return price; + } + + /** + * @notice Gets the price for identifier and time if it has already been requested and resolved. + * @dev Overloaded method to enable short term backwards compatibility when ancillary data is not included. + * @dev If the price is not available, the method reverts. + * @param identifier uniquely identifies the price requested. E.g. BTC/USD (encoded as bytes32) could be requested. + * @param time unix timestamp of the price request. + * @return int256 representing the resolved price for the given identifier and timestamp. + */ + function getPrice(bytes32 identifier, uint256 time) external view override returns (int256) { + return getPrice(identifier, time, ""); + } + + /** + * @notice Gets the status of a list of price requests, identified by their identifier, time and ancillary data. + * @dev If the status for a particular request is NotRequested, the lastVotingRound will always be 0. + * @param requests array of pending requests which includes identifier, timestamp & ancillary data for the requests. + * @return requestStates a list, in the same order as the input list, giving the status of the specified requests. + */ + function getPriceRequestStatuses( + PendingRequestAncillary[] memory requests + ) public view returns (RequestState[] memory) { + RequestState[] memory requestStates = new RequestState[](requests.length); + uint32 currentRoundId = getCurrentRoundId(); + for (uint256 i = 0; i < requests.length; i = unsafe_inc(i)) { + PriceRequest storage priceRequest = _getPriceRequest( + requests[i].identifier, + requests[i].time, + requests[i].ancillaryData + ); + + RequestStatus status = _getRequestStatus(priceRequest, currentRoundId); + + // If it's an active request, its true lastVotingRound is the current one, even if it hasn't been updated. + if (status == RequestStatus.Active) requestStates[i].lastVotingRound = currentRoundId; + else requestStates[i].lastVotingRound = priceRequest.lastVotingRound; + requestStates[i].status = status; + } + return requestStates; + } + + /**************************************** + * VOTING FUNCTIONS * + ****************************************/ + + /** + * @notice Commit a vote for a price request for identifier at time. + * @dev identifier, time must correspond to a price request that's currently in the commit phase. + * Commits can be changed. + * @dev Since transaction data is public, the salt will be revealed with the vote. While this is the system’s + * expected behavior, voters should never reuse salts. If someone else is able to guess the voted price and knows + * that a salt will be reused, then they can determine the vote pre-reveal. + * @param identifier uniquely identifies the committed vote. E.g. BTC/USD price pair. + * @param time unix timestamp of the price being voted on. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + * @param hash keccak256 hash of the price, salt, voter address, time, ancillaryData, current roundId, identifier. + */ + function commitVote( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData, + bytes32 hash + ) public override nonReentrant { + uint32 currentRoundId = getCurrentRoundId(); + address voter = getVoterFromDelegate(msg.sender); + _updateTrackers(voter); + + require(hash != bytes32(0), "Invalid commit hash"); + require(getVotePhase() == Phase.Commit, "Cannot commit in reveal phase"); + PriceRequest storage priceRequest = _getPriceRequest(identifier, time, ancillaryData); + require(_getRequestStatus(priceRequest, currentRoundId) == RequestStatus.Active, "Request must be active"); + + priceRequest.voteInstances[currentRoundId].voteSubmissions[voter].commit = hash; + + emit VoteCommitted(voter, msg.sender, currentRoundId, identifier, time, ancillaryData); + } + + /** + * @notice Reveal a previously committed vote for identifier at time. + * @dev The revealed price, salt, voter address, time, ancillaryData, current roundId, identifier must hash to the + * latest hash that commitVote() was called with. Only the committer can reveal their vote. + * @param identifier voted on in the commit phase. E.g. BTC/USD price pair. + * @param time specifies the unix timestamp of the price being voted on. + * @param price voted on during the commit phase. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + * @param salt value used to hide the commitment price during the commit phase. + */ + function revealVote( + bytes32 identifier, + uint256 time, + int256 price, + bytes memory ancillaryData, + int256 salt + ) public override nonReentrant { + uint32 currentRoundId = getCurrentRoundId(); + _freezeRoundVariables(currentRoundId); + VoteInstance storage voteInstance = _getPriceRequest(identifier, time, ancillaryData).voteInstances[ + currentRoundId + ]; + address voter = getVoterFromDelegate(msg.sender); + VoteSubmission storage voteSubmission = voteInstance.voteSubmissions[voter]; + + require(getVotePhase() == Phase.Reveal, "Reveal phase has not started yet"); // Can only reveal in reveal phase. + + // Zero hashes are blocked in commit; they indicate a different error: voter did not commit or already revealed. + require(voteSubmission.commit != bytes32(0), "Invalid hash reveal"); + + // Check that the hash that was committed matches to the one that was revealed. Note that if the voter had + // then they must reveal with the same account they had committed with. + require( + keccak256(abi.encodePacked(price, salt, voter, time, ancillaryData, uint256(currentRoundId), identifier)) == + voteSubmission.commit, + "Revealed data != commit hash" + ); + + delete voteSubmission.commit; // Small gas refund for clearing up storage. + voteSubmission.revealHash = keccak256(abi.encode(price)); // Set the voter's submission. + + // Calculate the voters effective stake for this round as the difference between their stake and pending stake. + // This allows for the voter to have staked during this reveal phase and not consider their pending stake. + uint128 effectiveStake = voterStakes[voter].stake - voterStakes[voter].pendingStakes[currentRoundId]; + voteInstance.results.addVote(price, effectiveStake); // Add vote to the results. + emit VoteRevealed(voter, msg.sender, currentRoundId, identifier, time, ancillaryData, price, effectiveStake); + } + + /** + * @notice Commits a vote and logs an event with a data blob, typically an encrypted version of the vote + * @dev An encrypted version of the vote is emitted in an event EncryptedVote to allow off-chain infrastructure to + * retrieve the commit. The contents of encryptedVote are never used on chain: it is purely for convenience. + * @param identifier unique price pair identifier. E.g. BTC/USD price pair. + * @param time unix timestamp of the price request. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + * @param hash keccak256 hash of the price you want to vote for and a int256 salt. + * @param encryptedVote offchain encrypted blob containing the voter's amount, time and salt. + */ + function commitAndEmitEncryptedVote( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData, + bytes32 hash, + bytes memory encryptedVote + ) public override { + commitVote(identifier, time, ancillaryData, hash); + emit EncryptedVote(msg.sender, getCurrentRoundId(), identifier, time, ancillaryData, encryptedVote); + } + + /**************************************** + * VOTING GETTER FUNCTIONS * + ****************************************/ + + /** + * @notice Gets the requests that are being voted on this round. + * @dev This view method returns requests with Active status that may be ahead of the stored contract state as this + * also filters out requests that would be resolvable or deleted if the resolvable requests were processed with the + * processResolvablePriceRequests() method. + * @return pendingRequests array containing identifiers of type PendingRequestAncillaryAugmented. + */ + function getPendingRequests() public view override returns (PendingRequestAncillaryAugmented[] memory) { + // Solidity memory arrays aren't resizable (and reading storage is expensive). Hence this hackery to filter + // pendingPriceRequestsIds only to those requests that have an Active RequestStatus. + PendingRequestAncillaryAugmented[] memory unresolved = new PendingRequestAncillaryAugmented[]( + pendingPriceRequestsIds.length + ); + uint256 numUnresolved = 0; + uint32 currentRoundId = getCurrentRoundId(); + + for (uint256 i = 0; i < pendingPriceRequestsIds.length; i = unsafe_inc(i)) { + PriceRequest storage priceRequest = priceRequests[pendingPriceRequestsIds[i]]; + if (_getRequestStatus(priceRequest, currentRoundId) == RequestStatus.Active) { + unresolved[numUnresolved] = PendingRequestAncillaryAugmented({ + lastVotingRound: priceRequest.lastVotingRound, + isGovernance: priceRequest.isGovernance, + time: priceRequest.time, + rollCount: _getActualRollCount(priceRequest, currentRoundId), + identifier: priceRequest.identifier, + ancillaryData: priceRequest.ancillaryData + }); + numUnresolved++; + } + } + + PendingRequestAncillaryAugmented[] memory pendingRequests = new PendingRequestAncillaryAugmented[]( + numUnresolved + ); + for (uint256 i = 0; i < numUnresolved; i = unsafe_inc(i)) pendingRequests[i] = unresolved[i]; + + return pendingRequests; + } + + /** + * @notice Checks if there are current active requests. + * @return bool true if there are active requests, false otherwise. + */ + function currentActiveRequests() public view returns (bool) { + uint32 currentRoundId = getCurrentRoundId(); + for (uint256 i = 0; i < pendingPriceRequestsIds.length; i = unsafe_inc(i)) + if (_getRequestStatus(priceRequests[pendingPriceRequestsIds[i]], currentRoundId) == RequestStatus.Active) + return true; + + return false; + } + + /** + * @notice Returns the current voting phase, as a function of the current time. + * @return Phase to indicate the current phase. Either { Commit, Reveal, NUM_PHASES }. + */ + function getVotePhase() public view override returns (Phase) { + return Phase(uint256(voteTiming.computeCurrentPhase(getCurrentTime()))); + } + + /** + * @notice Returns the current round ID, as a function of the current time. + * @return uint32 the unique round ID. + */ + function getCurrentRoundId() public view override returns (uint32) { + return uint32(voteTiming.computeCurrentRoundId(getCurrentTime())); + } + + /** + * @notice Returns the round end time, as a function of the round number. + * @param roundId representing the unique round ID. + * @return uint256 representing the round end time. + */ + function getRoundEndTime(uint256 roundId) external view returns (uint256) { + return voteTiming.computeRoundEndTime(roundId); + } + + /** + * @notice Returns the number of current pending price requests to be voted and the number of resolved price + requests over all time. + * @dev This method might return stale values if the state of the contract has changed since the last time + `processResolvablePriceRequests()` was called. To get the most up-to-date values, call + `getNumberOfPriceRequestsPostUpdate()` instead. + * @return numberPendingPriceRequests the total number of pending prices requests. + * @return numberResolvedPriceRequests the total number of prices resolved over all time. + */ + function getNumberOfPriceRequests() + public + view + returns (uint256 numberPendingPriceRequests, uint256 numberResolvedPriceRequests) + { + return (pendingPriceRequestsIds.length, resolvedPriceRequestIds.length); + } + + /** + * @notice Returns the number of current pending price requests to be voted and the number of resolved price + requests over all time after processing any resolvable price requests. + * @return numberPendingPriceRequests the total number of pending prices requests. + * @return numberResolvedPriceRequests the total number of prices resolved over all time. + */ + function getNumberOfPriceRequestsPostUpdate() + external + returns (uint256 numberPendingPriceRequests, uint256 numberResolvedPriceRequests) + { + processResolvablePriceRequests(); + return getNumberOfPriceRequests(); + } + + /** + * @notice Returns aggregate slashing trackers for a given request index. + * @param requestIndex requestIndex the index of the request to fetch slashing trackers for. + * @return SlashingTracker Tracker object contains the slashed UMA per staked UMA per wrong vote and no vote, the + * total UMA slashed in the round and the total number of correct votes in the round. + */ + function requestSlashingTrackers(uint256 requestIndex) public view returns (SlashingTracker memory) { + PriceRequest storage priceRequest = priceRequests[resolvedPriceRequestIds[requestIndex]]; + uint32 lastVotingRound = priceRequest.lastVotingRound; + VoteInstance storage voteInstance = priceRequest.voteInstances[lastVotingRound]; + + uint256 totalVotes = voteInstance.results.totalVotes; + uint256 totalCorrectVotes = voteInstance.results.getTotalCorrectlyVotedTokens(); + uint256 totalStaked = rounds[lastVotingRound].cumulativeStakeAtRound; + + (uint256 wrongVoteSlash, uint256 noVoteSlash) = rounds[lastVotingRound].slashingLibrary.calcSlashing( + totalStaked, + totalVotes, + totalCorrectVotes, + requestIndex, + priceRequest.isGovernance + ); + + uint256 totalSlashed = ((noVoteSlash * (totalStaked - totalVotes)) + + (wrongVoteSlash * (totalVotes - totalCorrectVotes))) / 1e18; + + return SlashingTracker(wrongVoteSlash, noVoteSlash, totalSlashed, totalCorrectVotes, lastVotingRound); + } + + /** + * @notice Returns the voter's participation in the vote for a given request index. + * @param requestIndex requestIndex the index of the request to fetch slashing trackers for. + * @param lastVotingRound the round to get voter participation for. + * @param voter the voter to get participation for. + * @return VoteParticipation enum representing the voter's participation in the vote. + */ + function getVoterParticipation( + uint256 requestIndex, + uint32 lastVotingRound, + address voter + ) public view returns (VoteParticipation) { + VoteInstance storage voteInstance = priceRequests[resolvedPriceRequestIds[requestIndex]].voteInstances[ + lastVotingRound + ]; + bytes32 revealHash = voteInstance.voteSubmissions[voter].revealHash; + if (revealHash == bytes32(0)) return VoteParticipation.DidNotVote; + if (voteInstance.results.wasVoteCorrect(revealHash)) return VoteParticipation.CorrectVote; + return VoteParticipation.WrongVote; + } + + /**************************************** + * OWNER ADMIN FUNCTIONS * + ****************************************/ + + /** + * @notice Disables this Voting contract in favor of the migrated one. + * @dev Can only be called by the contract owner. + * @param newVotingAddress the newly migrated contract address. + */ + function setMigrated(address newVotingAddress) external override onlyOwner { + migratedAddress = newVotingAddress; + emit VotingContractMigrated(newVotingAddress); + } + + /** + * @notice Sets the maximum number of rounds to roll a request can have before the DVM auto deletes it. + * @dev Can only be called by the contract owner. + * @param newMaxRolls the new number of rounds to roll a request before the DVM auto deletes it. + */ + function setMaxRolls(uint32 newMaxRolls) public override onlyOwner { + // Changes to max rolls can impact unresolved requests. To protect against this process requests first. + processResolvablePriceRequests(); + maxRolls = newMaxRolls; + emit MaxRollsChanged(newMaxRolls); + } + + /** + * @notice Sets the maximum number of requests that can be made in a single round. Used to bound the maximum + * sequential slashing that can be applied within a single round. + * @dev Can only be called by the contract owner. + * @param newMaxRequestsPerRound the new maximum number of requests that can be made in a single round. + */ + function setMaxRequestPerRound(uint32 newMaxRequestsPerRound) public override onlyOwner { + require(newMaxRequestsPerRound > 0); + maxRequestsPerRound = newMaxRequestsPerRound; + emit MaxRequestsPerRoundChanged(newMaxRequestsPerRound); + } + + /** + * @notice Resets the GAT number and SPAT percentage. GAT is the minimum number of tokens that must participate in a + * vote for it to resolve (quorum number). SPAT is the minimum percentage of tokens that must agree on a result + * for it to resolve (percentage of staked tokens) This change only applies to subsequent rounds. + * @param newGat sets the next round's GAT and going forward. + * @param newSpat sets the next round's SPAT and going forward. + */ + function setGatAndSpat(uint128 newGat, uint64 newSpat) public override onlyOwner { + require(newGat < votingToken.totalSupply() && newGat > 0); + require(newSpat > 0 && newSpat < 1e18); + gat = newGat; + spat = newSpat; + + emit GatAndSpatChanged(newGat, newSpat); + } + + /** + * @notice Changes the slashing library used by this contract. + * @param _newSlashingLibrary new slashing library address. + */ + function setSlashingLibrary(address _newSlashingLibrary) public override onlyOwner { + slashingLibrary = SlashingLibraryInterface(_newSlashingLibrary); + emit SlashingLibraryChanged(_newSlashingLibrary); + } + + /**************************************** + * STAKING FUNCTIONS * + ****************************************/ + + /** + * @notice Updates the voter's trackers for staking and slashing. Applies all unapplied slashing to given staker. + * @dev Can be called by anyone, but it is not necessary for the contract to function is run the other functions. + * @param voter address of the voter to update the trackers for. + */ + function updateTrackers(address voter) external { + _updateTrackers(voter); + } + + /** + * @notice Updates the voter's trackers for staking and voting, specifying a maximum number of resolved requests to + * traverse. This function can be used in place of updateTrackers to process the trackers in batches, hence avoiding + * potential issues if the number of elements to be processed is large and the associated gas cost is too high. + * @param voter address of the voter to update the trackers for. + * @param maxTraversals maximum number of resolved requests to traverse in this call. + */ + function updateTrackersRange(address voter, uint64 maxTraversals) external { + processResolvablePriceRequests(); + _updateAccountSlashingTrackers(voter, maxTraversals); + } + + // Updates the global and selected wallet's trackers for staking and voting. Note that the order of these calls is + // very important due to the interplay between slashing and inactive/active liquidity. + function _updateTrackers(address voter) internal override { + processResolvablePriceRequests(); + _updateAccountSlashingTrackers(voter, UINT64_MAX); + super._updateTrackers(voter); + } + + /** + * @notice Process and resolve all resolvable price requests. This function traverses all pending price requests and + * resolves them if they are resolvable. It also rolls and deletes requests, if required. + */ + function processResolvablePriceRequests() public { + _processResolvablePriceRequests(UINT64_MAX); + } + + /** + * @notice Process and resolve all resolvable price requests. This function traverses all pending price requests and + * resolves them if they are resolvable. It also rolls and deletes requests, if required. This function can be used + * in place of processResolvablePriceRequests to process the requests in batches, hence avoiding potential issues if + * the number of elements to be processed is large and the associated gas cost is too high. + * @param maxTraversals maximum number of resolved requests to traverse in this call. + */ + function processResolvablePriceRequestsRange(uint64 maxTraversals) external { + _processResolvablePriceRequests(maxTraversals); + } + + // Starting index for a staker is the first value that nextIndexToProcess is set to and defines the first index that + // a staker is suspectable to receiving slashing on. This is set to current length of the resolvedPriceRequestIds. + // Note first call processResolvablePriceRequests to ensure that the resolvedPriceRequestIds array is up to date. + function _getStartingIndexForStaker() internal override returns (uint64) { + processResolvablePriceRequests(); + return SafeCast.toUint64(resolvedPriceRequestIds.length); + } + + // Checks if we are in an active voting reveal phase (currently revealing votes). This impacts if a new staker's + // stake should be activated immediately or if it should be frozen until the end of the reveal phase. + function _inActiveReveal() internal view override returns (bool) { + return (currentActiveRequests() && getVotePhase() == Phase.Reveal); + } + + // This function must be called before any tokens are staked. It updates the voter's pending stakes to reflect the + // new amount to stake. These updates are only made if we are in an active reveal. This is required to appropriately + // calculate a voter's trackers and avoid slashing them for amounts staked during an active reveal phase. + function _computePendingStakes(address voter, uint128 amount) internal override { + if (_inActiveReveal()) { + uint32 currentRoundId = getCurrentRoundId(); + // Freeze round variables to prevent cumulativeActiveStakeAtRound from changing based on the stakes during + // the active reveal phase. This will happen if the first action within the reveal is someone staking. + _freezeRoundVariables(currentRoundId); + // Increment pending stake for voter by amount. With the omission of stake from cumulativeActiveStakeAtRound + // for this round, ensure that the pending stakes is not included in the slashing calculation for this round. + _incrementPendingStake(voter, currentRoundId, amount); + } + } + + // Updates the slashing trackers of a given account based on previous voting activity. This traverses all resolved + // requests for each voter and for each request checks if the voter voted correctly or not. Based on the voters + // voting activity the voters balance is updated accordingly. The caller can provide a maxTraversals parameter to + // limit the number of resolved requests to traverse in this call to bound the gas used. Note each iteration of + // this function re-uses a fresh slash variable to produce useful logs on the amount a voter is slashed. + function _updateAccountSlashingTrackers(address voter, uint64 maxTraversals) internal { + VoterStake storage voterStake = voterStakes[voter]; + uint64 requestIndex = voterStake.nextIndexToProcess; // Traverse all requests from the last considered request. + + // Traverse all elements within the resolvedPriceRequestIds array and update the voter's trackers according to + // their voting activity. Bound the number of iterations to the maxTraversals parameter to cap the gas used. + while (requestIndex < resolvedPriceRequestIds.length && maxTraversals > 0) { + maxTraversals = unsafe_dec_64(maxTraversals); // reduce the number of traversals left & re-use the prop. + + // Get the slashing for this request. This comes from the slashing library and informs to the voter slash. + SlashingTracker memory trackers = requestSlashingTrackers(requestIndex); + + // Use the effective stake as the difference between the current stake and pending stake. The staker will + //have a pending stake if they staked during an active reveal for the voting round in question. + uint256 effectiveStake = voterStake.stake - voterStake.pendingStakes[trackers.lastVotingRound]; + int256 slash; // The amount to slash the voter by for this request. Reset on each entry to emit useful logs. + + // Get the voter participation for this request. This informs if the voter voted correctly or not. + VoteParticipation participation = getVoterParticipation(requestIndex, trackers.lastVotingRound, voter); + + // The voter did not reveal or did not commit. Slash at noVote rate. + if (participation == VoteParticipation.DidNotVote) + slash = -int256(Math.ceilDiv(effectiveStake * trackers.noVoteSlashPerToken, 1e18)); + + // The voter did not vote with the majority. Slash at wrongVote rate. + else if (participation == VoteParticipation.WrongVote) + slash = -int256(Math.ceilDiv(effectiveStake * trackers.wrongVoteSlashPerToken, 1e18)); + + // Else, the voter voted correctly. Receive a pro-rate share of the other voters slash. + else slash = int256((effectiveStake * trackers.totalSlashed) / trackers.totalCorrectVotes); + + emit VoterSlashed(voter, requestIndex, int128(slash)); + voterStake.unappliedSlash += int128(slash); + + // If the next round is different to the current considered round, apply the slash to the voter. + if (isNextRequestRoundDifferent(requestIndex)) _applySlashToVoter(voterStake, voter); + + requestIndex = unsafe_inc_64(requestIndex); // Increment the request index. + } + + // Set the account's nextIndexToProcess to the requestIndex so the next entry starts where we left off. + voterStake.nextIndexToProcess = requestIndex; + } + + // Applies a given slash to a given voter's stake. In the event the sum of the slash and the voter's stake is less + // than 0, the voter's stake is set to 0 to prevent the voter's stake from going negative. unappliedSlash tracked + // all slashing the staker has received but not yet applied to their stake. Apply it then set it to zero. + function _applySlashToVoter(VoterStake storage voterStake, address voter) internal { + if (voterStake.unappliedSlash + int128(voterStake.stake) > 0) + voterStake.stake = uint128(int128(voterStake.stake) + voterStake.unappliedSlash); + else voterStake.stake = 0; + emit VoterSlashApplied(voter, voterStake.unappliedSlash, voterStake.stake); + voterStake.unappliedSlash = 0; + } + + // Checks if the next round (index+1) is different to the current round (index). + function isNextRequestRoundDifferent(uint64 index) internal view returns (bool) { + if (index + 1 >= resolvedPriceRequestIds.length) return true; + + return + priceRequests[resolvedPriceRequestIds[index]].lastVotingRound != + priceRequests[resolvedPriceRequestIds[index + 1]].lastVotingRound; + } + + /**************************************** + * MIGRATION SUPPORT FUNCTIONS * + ****************************************/ + + /** + * @notice Enable retrieval of rewards on a previously migrated away from voting contract. This function is intended + * on being removed from future versions of the Voting contract and aims to solve a short term migration pain point. + * @param voter voter for which rewards will be retrieved. Does not have to be the caller. + * @param roundId the round from which voting rewards will be retrieved from. + * @param toRetrieve array of PendingRequests which rewards are retrieved from. + * @return uint256 the amount of rewards. + */ + function retrieveRewardsOnMigratedVotingContract( + address voter, + uint256 roundId, + MinimumVotingAncillaryInterface.PendingRequestAncillary[] memory toRetrieve + ) external returns (uint256) { + uint256 rewards = MinimumVotingAncillaryInterface(address(previousVotingContract)) + .retrieveRewards(voter, roundId, toRetrieve) + .rawValue; + return rewards; + } + + /**************************************** + * PRIVATE AND INTERNAL FUNCTIONS * + ****************************************/ + + // Deletes a request from the pending requests array, based on index. Swap and pop. + function _removeRequestFromPendingPriceRequestsIds(uint64 pendingRequestIndex) internal { + pendingPriceRequestsIds[pendingRequestIndex] = pendingPriceRequestsIds[pendingPriceRequestsIds.length - 1]; + pendingPriceRequestsIds.pop(); + } + + // Returns the price for a given identifier. Three params are returns: bool if there was an error, int to represent + // the resolved price and a string which is filled with an error message, if there was an error or "". + // This method considers actual request status that might be ahead of the stored contract state that gets updated + // only after processResolvablePriceRequests() is called. + function _getPriceOrError( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) internal view returns (bool, int256, string memory) { + PriceRequest storage priceRequest = _getPriceRequest(identifier, time, ancillaryData); + uint32 currentRoundId = getCurrentRoundId(); + RequestStatus requestStatus = _getRequestStatus(priceRequest, currentRoundId); + + if (requestStatus == RequestStatus.Active) return (false, 0, "Current voting round not ended"); + if (requestStatus == RequestStatus.Resolved) { + VoteInstance storage voteInstance = priceRequest.voteInstances[priceRequest.lastVotingRound]; + (, int256 resolvedPrice) = _getResolvedPrice(voteInstance, priceRequest.lastVotingRound); + return (true, resolvedPrice, ""); + } + + if (requestStatus == RequestStatus.Future) return (false, 0, "Price is still to be voted on"); + if (requestStatus == RequestStatus.ToDelete) return (false, 0, "Price will be deleted"); + (bool previouslyResolved, int256 previousPrice) = _getPriceFromPreviousVotingContract( + identifier, + time, + ancillaryData + ); + if (previouslyResolved) return (true, previousPrice, ""); + return (false, 0, "Price was never requested"); + } + + // Check the previousVotingContract to see if a given price request was resolved. + // Returns true or false, and the resolved price or zero, depending on whether it was found or not. + function _getPriceFromPreviousVotingContract( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) private view returns (bool, int256) { + if (address(previousVotingContract) == address(0)) return (false, 0); + if (previousVotingContract.hasPrice(identifier, time, ancillaryData)) + return (true, previousVotingContract.getPrice(identifier, time, ancillaryData)); + return (false, 0); + } + + // Returns a price request object for a given identifier, time and ancillary data. + function _getPriceRequest( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) private view returns (PriceRequest storage) { + return priceRequests[_encodePriceRequest(identifier, time, ancillaryData)]; + } + + // Returns an encoded bytes32 representing a price request. Used when storing/referencing price requests. + function _encodePriceRequest( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) private pure returns (bytes32) { + return keccak256(abi.encode(identifier, time, ancillaryData)); + } + + // Stores ("freezes") variables that should not shift within an active voting round. Called on reveal but only makes + // a state change if and only if the this is the first reveal. + function _freezeRoundVariables(uint256 roundId) private { + // Only freeze the round if this is the first request in the round. + if (rounds[roundId].minParticipationRequirement == 0) { + rounds[roundId].slashingLibrary = slashingLibrary; + + // The minimum required participation for a vote to settle within this round is the GAT (fixed number). + rounds[roundId].minParticipationRequirement = gat; + + // The minimum votes on the modal outcome for the vote to settle within this round is the SPAT (percentage). + rounds[roundId].minAgreementRequirement = uint128((spat * uint256(cumulativeStake)) / 1e18); + rounds[roundId].cumulativeStakeAtRound = cumulativeStake; // Store the cumulativeStake to work slashing. + } + } + + // Traverse pending price requests and resolve any that are resolvable. If requests are rollable (they did not + // resolve in the previous round and are to be voted in a subsequent round) then roll them. If requests can be + // deleted (they have been rolled up to the maxRolls counter) then delete them. The caller can pass in maxTraversals + // to limit the number of requests that are resolved in a single call to bound the total gas used by this function. + // Note that the resolved index is stores for each round. This means that only the first caller of this function + // per round needs to traverse the pending requests. After that subsequent calls to this are a no-op for that round. + function _processResolvablePriceRequests(uint64 maxTraversals) private { + uint32 currentRoundId = getCurrentRoundId(); + + // Load in the last resolved index for this round to continue off from where the last caller left. + uint64 requestIndex = lastRoundIdProcessed == currentRoundId ? nextPendingIndexToProcess : 0; + // Traverse pendingPriceRequestsIds array and update the requests status according to the state of the request + //(i.e settle, roll or delete request). Bound iterations to the maxTraversals parameter to cap the gas used. + while (requestIndex < pendingPriceRequestsIds.length && maxTraversals > 0) { + maxTraversals = unsafe_dec_64(maxTraversals); + PriceRequest storage request = priceRequests[pendingPriceRequestsIds[requestIndex]]; + + // If the last voting round is greater than or equal to the current round then this request is currently + // being voted on or is enqueued for the next round. In this case, skip it and increment the request index. + if (request.lastVotingRound >= currentRoundId) { + requestIndex = unsafe_inc_64(requestIndex); + continue; // Continue to the next request. + } + + // Else, we are dealing with a request that can either be: a) deleted, b) rolled or c) resolved. + VoteInstance storage voteInstance = request.voteInstances[request.lastVotingRound]; + (bool isResolvable, int256 resolvedPrice) = _getResolvedPrice(voteInstance, request.lastVotingRound); + + if (isResolvable) { + // If resolvable, resolve. This involves a) moving the requestId from pendingPriceRequestsIds array to + // resolvedPriceRequestIds array and b) removing requestId from pendingPriceRequestsIds. Don't need to + // increment requestIndex as from pendingPriceRequestsIds amounts to decreasing the while loop bound. + resolvedPriceRequestIds.push(pendingPriceRequestsIds[requestIndex]); + _removeRequestFromPendingPriceRequestsIds(requestIndex); + emit RequestResolved( + request.lastVotingRound, + resolvedPriceRequestIds.length - 1, + request.identifier, + request.time, + request.ancillaryData, + resolvedPrice + ); + continue; // Continue to the next request. + } + // If not resolvable, but the round has passed its voting round, then it must be deleted or rolled. First, + // increment the rollCount. Use the difference between the current round and the last voting round to + // accommodate the contract not being touched for any number of rounds during the roll. + request.rollCount += currentRoundId - request.lastVotingRound; + + // If the roll count exceeds the threshold and the request is not governance then it is deletable. + if (_shouldDeleteRequest(request.rollCount, request.isGovernance)) { + emit RequestDeleted(request.identifier, request.time, request.ancillaryData, request.rollCount); + delete priceRequests[pendingPriceRequestsIds[requestIndex]]; + _removeRequestFromPendingPriceRequestsIds(requestIndex); + continue; + } + // Else, the request should be rolled. This involves only moving forward the lastVotingRound. + request.lastVotingRound = getRoundIdToVoteOnRequest(currentRoundId); + ++rounds[request.lastVotingRound].numberOfRequestsToVote; + emit RequestRolled(request.identifier, request.time, request.ancillaryData, request.rollCount); + requestIndex = unsafe_inc_64(requestIndex); + } + + lastRoundIdProcessed = currentRoundId; // Store the roundId that was processed. + nextPendingIndexToProcess = requestIndex; // Store the index traversed up to for this round. + } + + // Returns a price request status. A request is either: NotRequested, Active, Resolved, Future or ToDelete. + function _getRequestStatus( + PriceRequest storage priceRequest, + uint32 currentRoundId + ) private view returns (RequestStatus) { + if (priceRequest.lastVotingRound == 0) return RequestStatus.NotRequested; + if (priceRequest.lastVotingRound < currentRoundId) { + // Check if the request has already been resolved + VoteInstance storage voteInstance = priceRequest.voteInstances[priceRequest.lastVotingRound]; + (bool isResolved, ) = _getResolvedPrice(voteInstance, priceRequest.lastVotingRound); + if (isResolved) return RequestStatus.Resolved; + if (_shouldDeleteRequest(_getActualRollCount(priceRequest, currentRoundId), priceRequest.isGovernance)) + return RequestStatus.ToDelete; + return RequestStatus.Active; + } + if (priceRequest.lastVotingRound == currentRoundId) return RequestStatus.Active; + + return RequestStatus.Future; // Means than priceRequest.lastVotingRound > currentRoundId + } + + function _getResolvedPrice( + VoteInstance storage voteInstance, + uint256 lastVotingRound + ) internal view returns (bool isResolved, int256 price) { + return + voteInstance.results.getResolvedPrice( + rounds[lastVotingRound].minParticipationRequirement, + rounds[lastVotingRound].minAgreementRequirement + ); + } + + // Gas optimized uint256 increment. + function unsafe_inc(uint256 x) internal pure returns (uint256) { + unchecked { + return x + 1; + } + } + + // Gas optimized uint64 increment. + function unsafe_inc_64(uint64 x) internal pure returns (uint64) { + unchecked { + return x + 1; + } + } + + // Gas optimized uint64 decrement. + function unsafe_dec_64(uint64 x) internal pure returns (uint64) { + unchecked { + return x - 1; + } + } + + // Returns the registered identifier whitelist, stored in the finder. + function _getIdentifierWhitelist() private view returns (IdentifierWhitelistInterface) { + return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); + } + + // Reverts if the contract has been migrated. Used in a modifier, defined as a private function for gas savings. + function _requireNotMigrated() private view { + require(migratedAddress == address(0), "Contract migrated"); + } + + // Enforces that a calling contract is registered. + function _requireRegisteredContract() private view { + RegistryInterface registry = RegistryInterface(finder.getImplementationAddress(OracleInterfaces.Registry)); + require(registry.isContractRegistered(msg.sender) || msg.sender == migratedAddress, "Caller not registered"); + } + + // Checks if a request should be deleted. A non-gevernance request should be deleted if it has been rolled more than + // the maxRolls. + function _shouldDeleteRequest(uint256 rollCount, bool isGovernance) private view returns (bool) { + return rollCount > maxRolls && !isGovernance; + } + + // Returns the actual roll count of a request. This is the roll count plus the number of rounds that have passed + // since the last voting round. + function _getActualRollCount( + PriceRequest storage priceRequest, + uint32 currentRoundId + ) private view returns (uint32) { + if (currentRoundId <= priceRequest.lastVotingRound) return priceRequest.rollCount; + return priceRequest.rollCount + currentRoundId - priceRequest.lastVotingRound; + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/EmergencyProposerTest.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/EmergencyProposerTest.sol new file mode 100644 index 000000000..218793404 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/EmergencyProposerTest.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "../EmergencyProposer.sol"; +import "../../../common/implementation/Testable.sol"; + +contract EmergencyProposerTest is EmergencyProposer, Testable { + constructor( + IERC20 _token, + uint256 _quorum, + GovernorV2 _governor, + address _executor, + address _timerAddress, + uint64 _minimumWaitTime + ) EmergencyProposer(_token, _quorum, _governor, _executor, _minimumWaitTime) Testable(_timerAddress) {} + + function getCurrentTime() public view override(EmergencyProposer, Testable) returns (uint256) { + return Testable.getCurrentTime(); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/GovernorTest.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/GovernorTest.sol new file mode 100644 index 000000000..429e38b0a --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/GovernorTest.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../Governor.sol"; +import "../AdminIdentifierLib.sol"; + +// GovernorTest exposes internal methods in the Governor for testing. +contract GovernorTest is Governor { + constructor(address _timerAddress) Governor(address(0), 0, _timerAddress) {} + + function addPrefix(bytes32 input, bytes32 prefix, uint256 prefixLength) external pure returns (bytes32) { + return AdminIdentifierLib._addPrefix(input, prefix, prefixLength); + } + + function uintToUtf8(uint256 v) external pure returns (bytes32 ret) { + return AdminIdentifierLib._uintToUtf8(v); + } + + function constructIdentifier(uint256 id) external pure returns (bytes32 identifier) { + return AdminIdentifierLib._constructIdentifier(id); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/GovernorV2Test.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/GovernorV2Test.sol new file mode 100644 index 000000000..4ca10abda --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/GovernorV2Test.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../GovernorV2.sol"; +import "../../../common/implementation/Testable.sol"; + +contract GovernorV2Test is GovernorV2, Testable { + constructor( + address _finderAddress, + uint256 _startingId, + address _timerAddress + ) GovernorV2(_finderAddress, _startingId) Testable(_timerAddress) {} + + function getCurrentTime() public view override(GovernorV2, Testable) returns (uint256) { + return Testable.getCurrentTime(); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/MockAdministratee.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/MockAdministratee.sol new file mode 100644 index 000000000..dd9745df9 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/MockAdministratee.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../interfaces/AdministrateeInterface.sol"; + +// A mock implementation of AdministrateeInterface, taking the place of a financial contract. +contract MockAdministratee is AdministrateeInterface { + uint256 public timesRemargined; + uint256 public timesEmergencyShutdown; + + function remargin() external override { + timesRemargined++; + } + + function emergencyShutdown() external override { + timesEmergencyShutdown++; + } + + function pfc() external pure override returns (FixedPoint.Unsigned memory) { + return FixedPoint.fromUnscaledUint(0); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/PriceIdentifierSlashingLibaryTest.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/PriceIdentifierSlashingLibaryTest.sol new file mode 100644 index 000000000..ecd74c7ba --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/PriceIdentifierSlashingLibaryTest.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "../../interfaces/SlashingLibraryInterface.sol"; +import "../VotingV2.sol"; + +/** + * @title Slashing Library contract that uses the price identifier to calculate the amount of tokens to slash. + */ + +contract PriceIdentifierSlashingLibaryTest is SlashingLibraryInterface { + VotingV2 public voting; + + bytes32 public constant whiteListedIdentifier = "SAFE_NO_VOTE"; + + uint256 public constant slashPerToken = 0.0016e18; + + constructor(address _votingV2Address) { + voting = VotingV2(_votingV2Address); + } + + function calcWrongVoteSlashPerToken( + uint256 totalStaked, + uint256 totalVotes, + uint256 totalCorrectVotes, + uint256 priceRequestIndex + ) public pure returns (uint256) {} + + function calcWrongVoteSlashPerTokenGovernance( + uint256 totalStaked, + uint256 totalVotes, + uint256 totalCorrectVotes, + uint256 priceRequestIndex + ) public pure returns (uint256) {} + + function calcNoVoteSlashPerToken( + uint256 totalStaked, + uint256 totalVotes, + uint256 totalCorrectVotes, + uint256 priceRequestIndex + ) public pure returns (uint256) {} + + function calcSlashing( + uint256 totalStaked, + uint256 totalVotes, + uint256 totalCorrectVotes, + uint256 priceRequestIndex, + bool isGovernance + ) external view returns (uint256 wrongVoteSlashPerToken, uint256 noVoteSlashPerToken) { + bytes32 priceRequestIdentifier = voting.resolvedPriceRequestIds(priceRequestIndex); + (, , , , bytes32 identifier, ) = voting.priceRequests(priceRequestIdentifier); + + // If the identifier is whiteListedIdentifier, then no tokens are slashed for no vote. + uint256 noVoteSlashPerToken = identifier == whiteListedIdentifier ? 0 : slashPerToken; + + // If it's a governance price request, then no tokens are slashed for wrong vote. + uint256 wrongVoteSlashPerToken = isGovernance ? 0 : slashPerToken; + + return (isGovernance ? 0 : slashPerToken, noVoteSlashPerToken); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ProposerV2Test.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ProposerV2Test.sol new file mode 100644 index 000000000..aeb27cae2 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ProposerV2Test.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../ProposerV2.sol"; +import "../AdminIdentifierLib.sol"; +import "../../../common/implementation/Testable.sol"; + +contract ProposerV2Test is ProposerV2, Testable { + constructor( + IERC20 _token, + uint256 _bond, + GovernorV2 _governor, + Finder _finder, + address _timerAddress + ) ProposerV2(_token, _bond, _governor, _finder) Testable(_timerAddress) {} + + function getCurrentTime() public view override(ProposerV2, Testable) returns (uint256) { + return Testable.getCurrentTime(); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/PunitiveSlashingLibraryTest.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/PunitiveSlashingLibraryTest.sol new file mode 100644 index 000000000..e0166b0f0 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/PunitiveSlashingLibraryTest.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "../../interfaces/SlashingLibraryInterface.sol"; + +contract PunitiveSlashingLibraryTest is SlashingLibraryInterface { + function calcWrongVoteSlashPerToken( + uint256 totalStaked, + uint256 totalVotes, + uint256 totalCorrectVotes, + uint256 priceRequestIndex + ) public pure returns (uint256) { + return 0.99e18; + } + + function calcWrongVoteSlashPerTokenGovernance( + uint256 totalStaked, + uint256 totalVotes, + uint256 totalCorrectVotes, + uint256 priceRequestIndex + ) public pure returns (uint256) { + return 0.99e18; + } + + function calcNoVoteSlashPerToken( + uint256 totalStaked, + uint256 totalVotes, + uint256 totalCorrectVotes, + uint256 priceRequestIndex + ) public pure returns (uint256) { + return 0.99e18; + } + + function calcSlashing( + uint256 totalStaked, + uint256 totalVotes, + uint256 totalCorrectVotes, + uint256 priceRequestIndex, + bool isGovernance + ) external pure returns (uint256 wrongVoteSlashPerToken, uint256 noVoteSlashPerToken) { + return ( + isGovernance + ? calcWrongVoteSlashPerTokenGovernance(totalStaked, totalVotes, totalCorrectVotes, priceRequestIndex) + : calcWrongVoteSlashPerToken(totalStaked, totalVotes, totalCorrectVotes, priceRequestIndex), + calcNoVoteSlashPerToken(totalStaked, totalVotes, totalCorrectVotes, priceRequestIndex) + ); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ResultComputationTest.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ResultComputationTest.sol new file mode 100644 index 000000000..ff780591d --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ResultComputationTest.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../ResultComputation.sol"; +import "../../../common/implementation/FixedPoint.sol"; + +// Wraps the library ResultComputation for testing purposes. +contract ResultComputationTest { + using ResultComputation for ResultComputation.Data; + + ResultComputation.Data public data; + + function wrapAddVote(int256 votePrice, uint256 numberTokens) external { + data.addVote(votePrice, FixedPoint.Unsigned(numberTokens)); + } + + function wrapGetResolvedPrice(uint256 minVoteThreshold) external view returns (bool isResolved, int256 price) { + return data.getResolvedPrice(FixedPoint.Unsigned(minVoteThreshold)); + } + + function wrapWasVoteCorrect(bytes32 revealHash) external view returns (bool) { + return data.wasVoteCorrect(revealHash); + } + + function wrapGetTotalCorrectlyVotedTokens() external view returns (uint256) { + return data.getTotalCorrectlyVotedTokens().rawValue; + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/StakerTest.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/StakerTest.sol new file mode 100644 index 000000000..66eb1b816 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/StakerTest.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../Staker.sol"; +import "../../../common/implementation/Testable.sol"; + +// Version of the Staker contract used in tests so time can be controlled. +abstract contract StakerControlledTiming is Staker, Testable { + constructor( + uint128 _emissionRate, + uint64 _unstakeCoolDown, + address _votingToken, + address _timerAddress + ) Staker(_emissionRate, _unstakeCoolDown, _votingToken) Testable(_timerAddress) {} + + function getCurrentTime() public view virtual override(Staker, Testable) returns (uint256) { + return Testable.getCurrentTime(); + } +} + +contract StakerTest is StakerControlledTiming { + constructor( + uint128 _emissionRate, + uint64 _unstakeCoolDown, + address _votingToken, + address _timer + ) StakerControlledTiming(_emissionRate, _unstakeCoolDown, _votingToken, _timer) {} + + function applySlashingToCumulativeStaked(address voter, int128 amount) public { + _updateTrackers(voter); // apply any unaccumulated rewards before modifying the staked balances. + require(int128(cumulativeStake) + amount >= 0, "Cumulative staked cannot be negative"); + voterStakes[voter].stake = uint128(int128(voterStakes[voter].stake) + amount); + } + + function _inActiveReveal() internal view override returns (bool) { + return false; + } + + function _getStartingIndexForStaker() internal view override returns (uint64) { + return 0; + } + + function _computePendingStakes(address wallet, uint128 amount) internal override {} +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VoteTimingTest.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VoteTimingTest.sol new file mode 100644 index 000000000..afa0d0ca5 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VoteTimingTest.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../interfaces/VotingInterface.sol"; +import "../VoteTiming.sol"; + +// Wraps the library VoteTiming for testing purposes. +contract VoteTimingTest { + using VoteTiming for VoteTiming.Data; + + VoteTiming.Data public voteTiming; + + constructor(uint256 phaseLength) { + wrapInit(phaseLength); + } + + function wrapComputeCurrentRoundId(uint256 currentTime) external view returns (uint256) { + return voteTiming.computeCurrentRoundId(currentTime); + } + + function wrapComputeCurrentPhase(uint256 currentTime) external view returns (VotingAncillaryInterface.Phase) { + return voteTiming.computeCurrentPhase(currentTime); + } + + function wrapInit(uint256 phaseLength) public { + voteTiming.init(phaseLength); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VotingTest.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VotingTest.sol new file mode 100644 index 000000000..9b68a7a00 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VotingTest.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../Voting.sol"; +import "../../../common/implementation/FixedPoint.sol"; + +// Test contract used to access internal variables in the Voting contract. +contract VotingTest is Voting { + constructor( + uint256 _phaseLength, + FixedPoint.Unsigned memory _gatPercentage, + FixedPoint.Unsigned memory _inflationRate, + uint256 _rewardsExpirationTimeout, + address _votingToken, + address _finder, + address _timerAddress + ) + Voting( + _phaseLength, + _gatPercentage, + _inflationRate, + _rewardsExpirationTimeout, + _votingToken, + _finder, + _timerAddress + ) + {} + + function getPendingPriceRequestsArray() external view returns (bytes32[] memory) { + return pendingPriceRequests; + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VotingV2Test.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VotingV2Test.sol new file mode 100644 index 000000000..0a014c3f4 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VotingV2Test.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "../VotingV2.sol"; +import "../../../common/implementation/Testable.sol"; + +// Test contract used to manage the time for the contract in tests. +contract VotingV2ControllableTiming is VotingV2, Testable { + constructor( + uint128 _emissionRate, + uint64 _unstakeCoolDown, + uint64 _phaseLength, + uint32 _maxRolls, + uint32 _maxRequestsPerRound, + uint128 _gat, + uint64 _spat, + address _votingToken, + address _finder, + address _slashingLibrary, + address _previousVotingContract, + address _timerAddress + ) + VotingV2( + _emissionRate, + _unstakeCoolDown, + _phaseLength, + _maxRolls, + _maxRequestsPerRound, + _gat, + _spat, + _votingToken, + _finder, + _slashingLibrary, + _previousVotingContract + ) + Testable(_timerAddress) + {} + + function getCurrentTime() public view override(Staker, Testable) returns (uint256) { + return Testable.getCurrentTime(); + } + + function commitVote(bytes32 identifier, uint256 time, bytes32 hash) external virtual { + commitVote(identifier, time, "", hash); + } + + function revealVote(bytes32 identifier, uint256 time, int256 price, int256 salt) external virtual { + revealVote(identifier, time, price, "", salt); + } + + function commitAndEmitEncryptedVote( + bytes32 identifier, + uint256 time, + bytes32 hash, + bytes memory encryptedVote + ) public { + commitAndEmitEncryptedVote(identifier, time, "", hash, encryptedVote); + } + + function getPendingPriceRequestsArray() external view returns (bytes32[] memory) { + return pendingPriceRequestsIds; + } + + function getPriceRequestStatuses(PendingRequest[] memory requests) external view returns (RequestState[] memory) { + PendingRequestAncillary[] memory requestsAncillary = new PendingRequestAncillary[](requests.length); + + for (uint256 i = 0; i < requests.length; i = unsafe_inc(i)) { + requestsAncillary[i].identifier = requests[i].identifier; + requestsAncillary[i].time = requests[i].time; + requestsAncillary[i].ancillaryData = ""; + } + return getPriceRequestStatuses(requestsAncillary); + } +} + +// Test contract used to access internal variables in the Voting contract. +contract VotingV2Test is VotingV2ControllableTiming { + constructor( + uint128 _emissionRate, + uint64 _unstakeCoolDown, + uint64 _phaseLength, + uint32 _maxRolls, + uint32 _maxRequestsPerRound, + uint128 _gat, + uint64 _spat, + address _votingToken, + address _finder, + address _slashingLibrary, + address _previousVotingContract, + address _timerAddress + ) + VotingV2ControllableTiming( + _emissionRate, + _unstakeCoolDown, + _phaseLength, + _maxRolls, + _maxRequestsPerRound, + _gat, + _spat, + _votingToken, + _finder, + _slashingLibrary, + _previousVotingContract, + _timerAddress + ) + {} +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ZeroedSlashingLibaryTest.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ZeroedSlashingLibaryTest.sol new file mode 100644 index 000000000..d44b659fc --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ZeroedSlashingLibaryTest.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "../../interfaces/SlashingLibraryInterface.sol"; + +/** + * @title Slashing Library contract that executes no slashing for any actions. Used in tests. + */ + +contract ZeroedSlashingSlashingLibraryTest is SlashingLibraryInterface { + function calcWrongVoteSlashPerToken( + uint256 totalStaked, + uint256 totalVotes, + uint256 totalCorrectVotes, + uint256 priceRequestIndex + ) public pure returns (uint256) { + return 0; + } + + function calcWrongVoteSlashPerTokenGovernance( + uint256 totalStaked, + uint256 totalVotes, + uint256 totalCorrectVotes, + uint256 priceRequestIndex + ) public pure returns (uint256) { + return 0; + } + + function calcNoVoteSlashPerToken( + uint256 totalStaked, + uint256 totalVotes, + uint256 totalCorrectVotes, + uint256 priceRequestIndex + ) public pure returns (uint256) { + return 0; + } + + function calcSlashing( + uint256 totalStaked, + uint256 totalVotes, + uint256 totalCorrectVotes, + uint256 priceRequestIndex, + bool isGovernance + ) external pure returns (uint256 wrongVoteSlashPerToken, uint256 noVoteSlashPerToken) { + return ( + isGovernance + ? calcWrongVoteSlashPerTokenGovernance(totalStaked, totalVotes, totalCorrectVotes, priceRequestIndex) + : calcWrongVoteSlashPerToken(totalStaked, totalVotes, totalCorrectVotes, priceRequestIndex), + calcNoVoteSlashPerToken(totalStaked, totalVotes, totalCorrectVotes, priceRequestIndex) + ); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/AdministrateeInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/AdministrateeInterface.sol new file mode 100644 index 000000000..96e70bb4d --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/AdministrateeInterface.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/FixedPoint.sol"; + +/** + * @title Interface that all financial contracts expose to the admin. + */ +interface AdministrateeInterface { + /** + * @notice Initiates the shutdown process, in case of an emergency. + */ + function emergencyShutdown() external; + + /** + * @notice A core contract method called independently or as a part of other financial contract transactions. + * @dev It pays fees and moves money between margin accounts to make sure they reflect the NAV of the contract. + */ + function remargin() external; + + /** + * @notice Gets the current profit from corruption for this contract in terms of the collateral currency. + * @dev This is equivalent to the collateral pool available from which to pay fees. Therefore, derived contracts are + * expected to implement this so that pay-fee methods can correctly compute the owed fees as a % of PfC. + * @return pfc value for equal to the current profit from corruption denominated in collateral currency. + */ + function pfc() external view returns (FixedPoint.Unsigned memory); +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/FinderInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/FinderInterface.sol new file mode 100644 index 000000000..8f544d418 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/FinderInterface.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +/** + * @title Provides addresses of the live contracts implementing certain interfaces. + * @dev Examples are the Oracle or Store interfaces. + */ +interface FinderInterface { + /** + * @notice Updates the address of the contract that implements `interfaceName`. + * @param interfaceName bytes32 encoding of the interface name that is either changed or registered. + * @param implementationAddress address of the deployed contract that implements the interface. + */ + function changeImplementationAddress(bytes32 interfaceName, address implementationAddress) external; + + /** + * @notice Gets the address of the contract that implements the given `interfaceName`. + * @param interfaceName queried interface. + * @return implementationAddress address of the deployed contract that implements the interface. + */ + function getImplementationAddress(bytes32 interfaceName) external view returns (address); +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol new file mode 100644 index 000000000..f56e205ed --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +/** + * @title Interface for whitelists of supported identifiers that the oracle can provide prices for. + */ +interface IdentifierWhitelistInterface { + /** + * @notice Adds the provided identifier as a supported identifier. + * @dev Price requests using this identifier will succeed after this call. + * @param identifier bytes32 encoding of the string identifier. Eg: BTC/USD. + */ + function addSupportedIdentifier(bytes32 identifier) external; + + /** + * @notice Removes the identifier from the whitelist. + * @dev Price requests using this identifier will no longer succeed after this call. + * @param identifier bytes32 encoding of the string identifier. Eg: BTC/USD. + */ + function removeSupportedIdentifier(bytes32 identifier) external; + + /** + * @notice Checks whether an identifier is on the whitelist. + * @param identifier bytes32 encoding of the string identifier. Eg: BTC/USD. + * @return bool if the identifier is supported (or not). + */ + function isIdentifierSupported(bytes32 identifier) external view returns (bool); +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/MinimumVotingAncillaryInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/MinimumVotingAncillaryInterface.sol new file mode 100644 index 000000000..1b662f35b --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/MinimumVotingAncillaryInterface.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +interface MinimumVotingAncillaryInterface { + struct Unsigned { + uint256 rawValue; + } + + struct PendingRequestAncillary { + bytes32 identifier; + uint256 time; + bytes ancillaryData; + } + + function retrieveRewards( + address voterAddress, + uint256 roundId, + PendingRequestAncillary[] memory toRetrieve + ) external returns (Unsigned memory); +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleAncillaryInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleAncillaryInterface.sol new file mode 100644 index 000000000..e5055955f --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleAncillaryInterface.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +/** + * @title Financial contract facing Oracle interface. + * @dev Interface used by financial contracts to interact with the Oracle. Voters will use a different interface. + */ +abstract contract OracleAncillaryInterface { + /** + * @notice Enqueues a request (if a request isn't already present) for the given `identifier`, `time` pair. + * @dev Time must be in the past and the identifier must be supported. + * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + * @param time unix timestamp for the price request. + */ + + function requestPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData) public virtual; + + /** + * @notice Whether the price for `identifier` and `time` is available. + * @dev Time must be in the past and the identifier must be supported. + * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. + * @param time unix timestamp for the price request. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + * @return bool if the DVM has resolved to a price for the given identifier and timestamp. + */ + function hasPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData) public view virtual returns (bool); + + /** + * @notice Gets the price for `identifier` and `time` if it has already been requested and resolved. + * @dev If the price is not available, the method reverts. + * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. + * @param time unix timestamp for the price request. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + * @return int256 representing the resolved price for the given identifier and timestamp. + */ + + function getPrice( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) public view virtual returns (int256); +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleGovernanceInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleGovernanceInterface.sol new file mode 100644 index 000000000..0bd7dd965 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleGovernanceInterface.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "./OracleInterface.sol"; +import "./OracleAncillaryInterface.sol"; + +/** + * @title Financial contract facing extending the Oracle interface with governance actions. + * @dev Interface used by financial contracts to interact with the Oracle extending governance actions. Voters will use a different interface. + */ +abstract contract OracleGovernanceInterface is OracleInterface, OracleAncillaryInterface { + /** + * @notice Enqueues a request (if a request isn't already present) for the given `identifier`, `time` pair. + * @dev Time must be in the past and the identifier must be supported. + * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + * @param time unix timestamp for the price request. + */ + function requestGovernanceAction(bytes32 identifier, uint256 time, bytes memory ancillaryData) external virtual; +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleInterface.sol new file mode 100644 index 000000000..610c27c5d --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleInterface.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +/** + * @title Financial contract facing Oracle interface. + * @dev Interface used by financial contracts to interact with the Oracle. Voters will use a different interface. + */ +abstract contract OracleInterface { + /** + * @notice Enqueues a request (if a request isn't already present) for the given `identifier`, `time` pair. + * @dev Time must be in the past and the identifier must be supported. + * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. + * @param time unix timestamp for the price request. + */ + function requestPrice(bytes32 identifier, uint256 time) external virtual; + + /** + * @notice Whether the price for `identifier` and `time` is available. + * @dev Time must be in the past and the identifier must be supported. + * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. + * @param time unix timestamp for the price request. + * @return bool if the DVM has resolved to a price for the given identifier and timestamp. + */ + function hasPrice(bytes32 identifier, uint256 time) external view virtual returns (bool); + + /** + * @notice Gets the price for `identifier` and `time` if it has already been requested and resolved. + * @dev If the price is not available, the method reverts. + * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. + * @param time unix timestamp for the price request. + * @return int256 representing the resolved price for the given identifier and timestamp. + */ + function getPrice(bytes32 identifier, uint256 time) external view virtual returns (int256); +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/RegistryInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/RegistryInterface.sol new file mode 100644 index 000000000..4bf923a4f --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/RegistryInterface.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +/** + * @title Interface for a registry of contracts and contract creators. + */ +interface RegistryInterface { + /** + * @notice Registers a new contract. + * @dev Only authorized contract creators can call this method. + * @param parties an array of addresses who become parties in the contract. + * @param contractAddress defines the address of the deployed contract. + */ + function registerContract(address[] calldata parties, address contractAddress) external; + + /** + * @notice Returns whether the contract has been registered with the registry. + * @dev If it is registered, it is an authorized participant in the UMA system. + * @param contractAddress address of the contract. + * @return bool indicates whether the contract is registered. + */ + function isContractRegistered(address contractAddress) external view returns (bool); + + /** + * @notice Returns a list of all contracts that are associated with a particular party. + * @param party address of the party. + * @return an array of the contracts the party is registered to. + */ + function getRegisteredContracts(address party) external view returns (address[] memory); + + /** + * @notice Returns all registered contracts. + * @return all registered contract addresses within the system. + */ + function getAllRegisteredContracts() external view returns (address[] memory); + + /** + * @notice Adds a party to the calling contract. + * @dev msg.sender must be the contract to which the party member is added. + * @param party address to be added to the contract. + */ + function addPartyToContract(address party) external; + + /** + * @notice Removes a party member to the calling contract. + * @dev msg.sender must be the contract to which the party member is added. + * @param party address to be removed from the contract. + */ + function removePartyFromContract(address party) external; + + /** + * @notice checks if an address is a party in a contract. + * @param party party to check. + * @param contractAddress address to check against the party. + * @return bool indicating if the address is a party of the contract. + */ + function isPartyMemberOfContract(address party, address contractAddress) external view returns (bool); +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/SlashingLibraryInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/SlashingLibraryInterface.sol new file mode 100644 index 000000000..3f898abc4 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/SlashingLibraryInterface.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +interface SlashingLibraryInterface { + /** + * @notice Calculates the wrong vote slash per token. + * @param totalStaked The total amount of tokens staked. + * @param totalVotes The total amount of votes. + * @param totalCorrectVotes The total amount of correct votes. + * @param priceRequestIndex The price request index within the resolvedPriceRequestIds array. + * @return uint256 The amount of tokens to slash per token staked. + */ + function calcWrongVoteSlashPerToken( + uint256 totalStaked, + uint256 totalVotes, + uint256 totalCorrectVotes, + uint256 priceRequestIndex + ) external view returns (uint256); + + /** + * @notice Calculates the wrong vote slash per token for governance requests. + * @param totalStaked The total amount of tokens staked. + * @param totalVotes The total amount of votes. + * @param totalCorrectVotes The total amount of correct votes. + * @param priceRequestIndex The price request index within the resolvedPriceRequestIds array. + * @return uint256 The amount of tokens to slash per token staked. + */ + function calcWrongVoteSlashPerTokenGovernance( + uint256 totalStaked, + uint256 totalVotes, + uint256 totalCorrectVotes, + uint256 priceRequestIndex + ) external view returns (uint256); + + /** + * @notice Calculates the no vote slash per token. + * @param totalStaked The total amount of tokens staked. + * @param totalVotes The total amount of votes. + * @param totalCorrectVotes The total amount of correct votes. + * @param priceRequestIndex The price request index within the resolvedPriceRequestIds array. + * @return uint256 The amount of tokens to slash per token staked. + */ + function calcNoVoteSlashPerToken( + uint256 totalStaked, + uint256 totalVotes, + uint256 totalCorrectVotes, + uint256 priceRequestIndex + ) external view returns (uint256); + + /** + * @notice Calculates all slashing trackers in one go to decrease cross-contract calls needed. + * @param totalStaked The total amount of tokens staked. + * @param totalVotes The total amount of votes. + * @param totalCorrectVotes The total amount of correct votes. + * @param priceRequestIndex The price request index within the resolvedPriceRequestIds array. + * @param isGovernance Whether the request is a governance request. + * @return wrongVoteSlashPerToken The amount of tokens to slash for voting wrong. + * @return noVoteSlashPerToken The amount of tokens to slash for not voting. + */ + function calcSlashing( + uint256 totalStaked, + uint256 totalVotes, + uint256 totalCorrectVotes, + uint256 priceRequestIndex, + bool isGovernance + ) external view returns (uint256 wrongVoteSlashPerToken, uint256 noVoteSlashPerToken); +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/StakerInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/StakerInterface.sol new file mode 100644 index 000000000..f7d348a71 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/StakerInterface.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "../implementation/VotingToken.sol"; +import "../../common/interfaces/ExpandedIERC20.sol"; + +interface StakerInterface { + function votingToken() external returns (ExpandedIERC20); + + function stake(uint128 amount) external; + + function requestUnstake(uint128 amount) external; + + function executeUnstake() external; + + function withdrawRewards() external returns (uint128); + + function withdrawAndRestake() external returns (uint128); + + function setEmissionRate(uint128 newEmissionRate) external; + + function setUnstakeCoolDown(uint64 newUnstakeCoolDown) external; + + /** + * @notice Sets the delegate of a voter. This delegate can vote on behalf of the staker. The staker will still own + * all staked balances, receive rewards and be slashed based on the actions of the delegate. Intended use is using a + * low-security available wallet for voting while keeping access to staked amounts secure by a more secure wallet. + * @param delegate the address of the delegate. + */ + function setDelegate(address delegate) external virtual; + + /** + * @notice Sets the delegator of a voter. Acts to accept a delegation. The delegate can only vote for the delegator + * if the delegator also selected the delegate to do so (two-way relationship needed). + * @param delegator the address of the delegator. + */ + function setDelegator(address delegator) external virtual; +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/StoreInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/StoreInterface.sol new file mode 100644 index 000000000..74c3bfd63 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/StoreInterface.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "../../common/implementation/FixedPoint.sol"; + +/** + * @title Interface that allows financial contracts to pay oracle fees for their use of the system. + */ +interface StoreInterface { + /** + * @notice Pays Oracle fees in ETH to the store. + * @dev To be used by contracts whose margin currency is ETH. + */ + function payOracleFees() external payable; + + /** + * @notice Pays oracle fees in the margin currency, erc20Address, to the store. + * @dev To be used if the margin currency is an ERC20 token rather than ETH. + * @param erc20Address address of the ERC20 token used to pay the fee. + * @param amount number of tokens to transfer. An approval for at least this amount must exist. + */ + function payOracleFeesErc20(address erc20Address, FixedPoint.Unsigned calldata amount) external; + + /** + * @notice Computes the regular oracle fees that a contract should pay for a period. + * @param startTime defines the beginning time from which the fee is paid. + * @param endTime end time until which the fee is paid. + * @param pfc "profit from corruption", or the maximum amount of margin currency that a + * token sponsor could extract from the contract through corrupting the price feed in their favor. + * @return regularFee amount owed for the duration from start to end time for the given pfc. + * @return latePenalty for paying the fee after the deadline. + */ + function computeRegularFee( + uint256 startTime, + uint256 endTime, + FixedPoint.Unsigned calldata pfc + ) external view returns (FixedPoint.Unsigned memory regularFee, FixedPoint.Unsigned memory latePenalty); + + /** + * @notice Computes the final oracle fees that a contract should pay at settlement. + * @param currency token used to pay the final fee. + * @return finalFee amount due. + */ + function computeFinalFee(address currency) external view returns (FixedPoint.Unsigned memory); +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingAncillaryInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingAncillaryInterface.sol new file mode 100644 index 000000000..5b0487b05 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingAncillaryInterface.sol @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/FixedPoint.sol"; + +/** + * @title Interface that voters must use to Vote on price request resolutions. + */ +abstract contract VotingAncillaryInterface { + struct PendingRequestAncillary { + bytes32 identifier; + uint256 time; + bytes ancillaryData; + } + + // Captures the necessary data for making a commitment. + // Used as a parameter when making batch commitments. + // Not used as a data structure for storage. + struct CommitmentAncillary { + bytes32 identifier; + uint256 time; + bytes ancillaryData; + bytes32 hash; + bytes encryptedVote; + } + + // Captures the necessary data for revealing a vote. + // Used as a parameter when making batch reveals. + // Not used as a data structure for storage. + struct RevealAncillary { + bytes32 identifier; + uint256 time; + int256 price; + bytes ancillaryData; + int256 salt; + } + + // Note: the phases must be in order. Meaning the first enum value must be the first phase, etc. + // `NUM_PHASES` is to get the number of phases. It isn't an actual phase, and it should always be last. + enum Phase { + Commit, + Reveal, + NUM_PHASES + } + + /** + * @notice Commit a vote for a price request for `identifier` at `time`. + * @dev `identifier`, `time` must correspond to a price request that's currently in the commit phase. + * Commits can be changed. + * @dev Since transaction data is public, the salt will be revealed with the vote. While this is the system’s expected behavior, + * voters should never reuse salts. If someone else is able to guess the voted price and knows that a salt will be reused, then + * they can determine the vote pre-reveal. + * @param identifier uniquely identifies the committed vote. E.G. BTC/USD price pair. + * @param time unix timestamp of the price being voted on. + * @param hash keccak256 hash of the `price`, `salt`, voter `address`, `time`, current `roundId`, and `identifier`. + */ + function commitVote(bytes32 identifier, uint256 time, bytes memory ancillaryData, bytes32 hash) public virtual; + + /** + * @notice Submit a batch of commits in a single transaction. + * @dev Using `encryptedVote` is optional. If included then commitment is stored on chain. + * Look at `project-root/common/Constants.js` for the tested maximum number of + * commitments that can fit in one transaction. + * @param commits array of structs that encapsulate an `identifier`, `time`, `hash` and optional `encryptedVote`. + */ + function batchCommit(CommitmentAncillary[] memory commits) public virtual; + + /** + * @notice commits a vote and logs an event with a data blob, typically an encrypted version of the vote + * @dev An encrypted version of the vote is emitted in an event `EncryptedVote` to allow off-chain infrastructure to + * retrieve the commit. The contents of `encryptedVote` are never used on chain: it is purely for convenience. + * @param identifier unique price pair identifier. E.g. BTC/USD price pair. + * @param time unix timestamp of for the price request. + * @param hash keccak256 hash of the price you want to vote for and a `int256 salt`. + * @param encryptedVote offchain encrypted blob containing the voters amount, time and salt. + */ + function commitAndEmitEncryptedVote( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData, + bytes32 hash, + bytes memory encryptedVote + ) public virtual; + + /** + * @notice snapshot the current round's token balances and lock in the inflation rate and GAT. + * @dev This function can be called multiple times but each round will only every have one snapshot at the + * time of calling `_freezeRoundVariables`. + * @param signature signature required to prove caller is an EOA to prevent flash loans from being included in the + * snapshot. + */ + function snapshotCurrentRound(bytes calldata signature) external virtual; + + /** + * @notice Reveal a previously committed vote for `identifier` at `time`. + * @dev The revealed `price`, `salt`, `address`, `time`, `roundId`, and `identifier`, must hash to the latest `hash` + * that `commitVote()` was called with. Only the committer can reveal their vote. + * @param identifier voted on in the commit phase. EG BTC/USD price pair. + * @param time specifies the unix timestamp of the price is being voted on. + * @param price voted on during the commit phase. + * @param salt value used to hide the commitment price during the commit phase. + */ + function revealVote( + bytes32 identifier, + uint256 time, + int256 price, + bytes memory ancillaryData, + int256 salt + ) public virtual; + + /** + * @notice Reveal multiple votes in a single transaction. + * Look at `project-root/common/Constants.js` for the tested maximum number of reveals. + * that can fit in one transaction. + * @dev For more information on reveals, review the comment for `revealVote`. + * @param reveals array of the Reveal struct which contains an identifier, time, price and salt. + */ + function batchReveal(RevealAncillary[] memory reveals) public virtual; + + /** + * @notice Gets the queries that are being voted on this round. + * @return pendingRequests `PendingRequest` array containing identifiers + * and timestamps for all pending requests. + */ + function getPendingRequests() external view virtual returns (PendingRequestAncillary[] memory); + + /** + * @notice Returns the current voting phase, as a function of the current time. + * @return Phase to indicate the current phase. Either { Commit, Reveal, NUM_PHASES }. + */ + function getVotePhase() external view virtual returns (Phase); + + /** + * @notice Returns the current round ID, as a function of the current time. + * @return uint256 representing the unique round ID. + */ + function getCurrentRoundId() external view virtual returns (uint256); + + /** + * @notice Retrieves rewards owed for a set of resolved price requests. + * @dev Can only retrieve rewards if calling for a valid round and if the + * call is done within the timeout threshold (not expired). + * @param voterAddress voter for which rewards will be retrieved. Does not have to be the caller. + * @param roundId the round from which voting rewards will be retrieved from. + * @param toRetrieve array of PendingRequests which rewards are retrieved from. + * @return total amount of rewards returned to the voter. + */ + function retrieveRewards( + address voterAddress, + uint256 roundId, + PendingRequestAncillary[] memory toRetrieve + ) public virtual returns (FixedPoint.Unsigned memory); + + // Voting Owner functions. + + /** + * @notice Disables this Voting contract in favor of the migrated one. + * @dev Can only be called by the contract owner. + * @param newVotingAddress the newly migrated contract address. + */ + function setMigrated(address newVotingAddress) external virtual; + + /** + * @notice Resets the inflation rate. Note: this change only applies to rounds that have not yet begun. + * @dev This method is public because calldata structs are not currently supported by solidity. + * @param newInflationRate sets the next round's inflation rate. + */ + function setInflationRate(FixedPoint.Unsigned memory newInflationRate) public virtual; + + /** + * @notice Resets the Gat percentage. Note: this change only applies to rounds that have not yet begun. + * @dev This method is public because calldata structs are not currently supported by solidity. + * @param newGatPercentage sets the next round's Gat percentage. + */ + function setGatPercentage(FixedPoint.Unsigned memory newGatPercentage) public virtual; + + /** + * @notice Resets the rewards expiration timeout. + * @dev This change only applies to rounds that have not yet begun. + * @param NewRewardsExpirationTimeout how long a caller can wait before choosing to withdraw their rewards. + */ + function setRewardsExpirationTimeout(uint256 NewRewardsExpirationTimeout) public virtual; +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingInterface.sol new file mode 100644 index 000000000..93837ed42 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingInterface.sol @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/FixedPoint.sol"; +import "./VotingAncillaryInterface.sol"; + +/** + * @title Interface that voters must use to Vote on price request resolutions. + */ +abstract contract VotingInterface { + struct PendingRequest { + bytes32 identifier; + uint256 time; + } + + // Captures the necessary data for making a commitment. + // Used as a parameter when making batch commitments. + // Not used as a data structure for storage. + struct Commitment { + bytes32 identifier; + uint256 time; + bytes32 hash; + bytes encryptedVote; + } + + // Captures the necessary data for revealing a vote. + // Used as a parameter when making batch reveals. + // Not used as a data structure for storage. + struct Reveal { + bytes32 identifier; + uint256 time; + int256 price; + int256 salt; + } + + /** + * @notice Commit a vote for a price request for `identifier` at `time`. + * @dev `identifier`, `time` must correspond to a price request that's currently in the commit phase. + * Commits can be changed. + * @dev Since transaction data is public, the salt will be revealed with the vote. While this is the system’s expected behavior, + * voters should never reuse salts. If someone else is able to guess the voted price and knows that a salt will be reused, then + * they can determine the vote pre-reveal. + * @param identifier uniquely identifies the committed vote. EG BTC/USD price pair. + * @param time unix timestamp of the price being voted on. + * @param hash keccak256 hash of the `price`, `salt`, voter `address`, `time`, current `roundId`, and `identifier`. + */ + function commitVote(bytes32 identifier, uint256 time, bytes32 hash) external virtual; + + /** + * @notice Submit a batch of commits in a single transaction. + * @dev Using `encryptedVote` is optional. If included then commitment is stored on chain. + * Look at `project-root/common/Constants.js` for the tested maximum number of + * commitments that can fit in one transaction. + * @param commits array of structs that encapsulate an `identifier`, `time`, `hash` and optional `encryptedVote`. + */ + function batchCommit(Commitment[] memory commits) public virtual; + + /** + * @notice commits a vote and logs an event with a data blob, typically an encrypted version of the vote + * @dev An encrypted version of the vote is emitted in an event `EncryptedVote` to allow off-chain infrastructure to + * retrieve the commit. The contents of `encryptedVote` are never used on chain: it is purely for convenience. + * @param identifier unique price pair identifier. Eg: BTC/USD price pair. + * @param time unix timestamp of for the price request. + * @param hash keccak256 hash of the price you want to vote for and a `int256 salt`. + * @param encryptedVote offchain encrypted blob containing the voters amount, time and salt. + */ + function commitAndEmitEncryptedVote( + bytes32 identifier, + uint256 time, + bytes32 hash, + bytes memory encryptedVote + ) public virtual; + + /** + * @notice snapshot the current round's token balances and lock in the inflation rate and GAT. + * @dev This function can be called multiple times but each round will only every have one snapshot at the + * time of calling `_freezeRoundVariables`. + * @param signature signature required to prove caller is an EOA to prevent flash loans from being included in the + * snapshot. + */ + function snapshotCurrentRound(bytes calldata signature) external virtual; + + /** + * @notice Reveal a previously committed vote for `identifier` at `time`. + * @dev The revealed `price`, `salt`, `address`, `time`, `roundId`, and `identifier`, must hash to the latest `hash` + * that `commitVote()` was called with. Only the committer can reveal their vote. + * @param identifier voted on in the commit phase. EG BTC/USD price pair. + * @param time specifies the unix timestamp of the price is being voted on. + * @param price voted on during the commit phase. + * @param salt value used to hide the commitment price during the commit phase. + */ + function revealVote(bytes32 identifier, uint256 time, int256 price, int256 salt) public virtual; + + /** + * @notice Reveal multiple votes in a single transaction. + * Look at `project-root/common/Constants.js` for the tested maximum number of reveals. + * that can fit in one transaction. + * @dev For more information on reveals, review the comment for `revealVote`. + * @param reveals array of the Reveal struct which contains an identifier, time, price and salt. + */ + function batchReveal(Reveal[] memory reveals) public virtual; + + /** + * @notice Gets the queries that are being voted on this round. + * @return pendingRequests `PendingRequest` array containing identifiers + * and timestamps for all pending requests. + */ + function getPendingRequests() + external + view + virtual + returns (VotingAncillaryInterface.PendingRequestAncillary[] memory); + + /** + * @notice Returns the current voting phase, as a function of the current time. + * @return Phase to indicate the current phase. Either { Commit, Reveal, NUM_PHASES }. + */ + function getVotePhase() external view virtual returns (VotingAncillaryInterface.Phase); + + /** + * @notice Returns the current round ID, as a function of the current time. + * @return uint256 representing the unique round ID. + */ + function getCurrentRoundId() external view virtual returns (uint256); + + /** + * @notice Retrieves rewards owed for a set of resolved price requests. + * @dev Can only retrieve rewards if calling for a valid round and if the + * call is done within the timeout threshold (not expired). + * @param voterAddress voter for which rewards will be retrieved. Does not have to be the caller. + * @param roundId the round from which voting rewards will be retrieved from. + * @param toRetrieve array of PendingRequests which rewards are retrieved from. + * @return total amount of rewards returned to the voter. + */ + function retrieveRewards( + address voterAddress, + uint256 roundId, + PendingRequest[] memory toRetrieve + ) public virtual returns (FixedPoint.Unsigned memory); + + // Voting Owner functions. + + /** + * @notice Disables this Voting contract in favor of the migrated one. + * @dev Can only be called by the contract owner. + * @param newVotingAddress the newly migrated contract address. + */ + function setMigrated(address newVotingAddress) external virtual; + + /** + * @notice Resets the inflation rate. Note: this change only applies to rounds that have not yet begun. + * @dev This method is public because calldata structs are not currently supported by solidity. + * @param newInflationRate sets the next round's inflation rate. + */ + function setInflationRate(FixedPoint.Unsigned memory newInflationRate) public virtual; + + /** + * @notice Resets the Gat percentage. Note: this change only applies to rounds that have not yet begun. + * @dev This method is public because calldata structs are not currently supported by solidity. + * @param newGatPercentage sets the next round's Gat percentage. + */ + function setGatPercentage(FixedPoint.Unsigned memory newGatPercentage) public virtual; + + /** + * @notice Resets the rewards expiration timeout. + * @dev This change only applies to rounds that have not yet begun. + * @param NewRewardsExpirationTimeout how long a caller can wait before choosing to withdraw their rewards. + */ + function setRewardsExpirationTimeout(uint256 NewRewardsExpirationTimeout) public virtual; +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingV2Interface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingV2Interface.sol new file mode 100644 index 000000000..8c127058d --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingV2Interface.sol @@ -0,0 +1,187 @@ +// TODO: add staking/snapshot interfaces to this interface file. + +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +/** + * @title Interface that voters must use to Vote on price request resolutions. + */ +abstract contract VotingV2Interface { + struct PendingRequest { + bytes32 identifier; + uint256 time; + } + + struct PendingRequestAncillary { + bytes32 identifier; + uint256 time; + bytes ancillaryData; + } + + struct PendingRequestAncillaryAugmented { + uint32 lastVotingRound; + bool isGovernance; + uint64 time; + uint32 rollCount; + bytes32 identifier; + bytes ancillaryData; + } + + // Captures the necessary data for making a commitment. + // Used as a parameter when making batch commitments. + // Not used as a data structure for storage. + struct Commitment { + bytes32 identifier; + uint256 time; + bytes32 hash; + bytes encryptedVote; + } + + // Captures the necessary data for revealing a vote. + // Used as a parameter when making batch reveals. + // Not used as a data structure for storage. + struct Reveal { + bytes32 identifier; + uint256 time; + int256 price; + int256 salt; + } + + // Captures the necessary data for making a commitment. + // Used as a parameter when making batch commitments. + // Not used as a data structure for storage. + struct CommitmentAncillary { + bytes32 identifier; + uint256 time; + bytes ancillaryData; + bytes32 hash; + bytes encryptedVote; + } + + // Captures the necessary data for revealing a vote. + // Used as a parameter when making batch reveals. + // Not used as a data structure for storage. + struct RevealAncillary { + bytes32 identifier; + uint256 time; + int256 price; + bytes ancillaryData; + int256 salt; + } + + // Note: the phases must be in order. Meaning the first enum value must be the first phase, etc. + // `NUM_PHASES` is to get the number of phases. It isn't an actual phase, and it should always be last. + enum Phase { + Commit, + Reveal, + NUM_PHASES + } + + /** + * @notice Commit a vote for a price request for `identifier` at `time`. + * @dev `identifier`, `time` must correspond to a price request that's currently in the commit phase. + * Commits can be changed. + * @dev Since transaction data is public, the salt will be revealed with the vote. While this is the system’s expected behavior, + * voters should never reuse salts. If someone else is able to guess the voted price and knows that a salt will be reused, then + * they can determine the vote pre-reveal. + * @param identifier uniquely identifies the committed vote. EG BTC/USD price pair. + * @param time unix timestamp of the price being voted on. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + * @param hash keccak256 hash of the `price`, `salt`, voter `address`, `time`, current `roundId`, and `identifier`. + */ + function commitVote(bytes32 identifier, uint256 time, bytes memory ancillaryData, bytes32 hash) public virtual; + + /** + * @notice commits a vote and logs an event with a data blob, typically an encrypted version of the vote + * @dev An encrypted version of the vote is emitted in an event `EncryptedVote` to allow off-chain infrastructure to + * retrieve the commit. The contents of `encryptedVote` are never used on chain: it is purely for convenience. + * @param identifier unique price pair identifier. Eg: BTC/USD price pair. + * @param time unix timestamp of for the price request. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + * @param hash keccak256 hash of the price you want to vote for and a `int256 salt`. + * @param encryptedVote offchain encrypted blob containing the voters amount, time and salt. + */ + function commitAndEmitEncryptedVote( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData, + bytes32 hash, + bytes memory encryptedVote + ) external virtual; + + /** + * @notice Reveal a previously committed vote for `identifier` at `time`. + * @dev The revealed `price`, `salt`, `address`, `time`, `roundId`, and `identifier`, must hash to the latest `hash` + * that `commitVote()` was called with. Only the committer can reveal their vote. + * @param identifier voted on in the commit phase. EG BTC/USD price pair. + * @param time specifies the unix timestamp of the price is being voted on. + * @param price voted on during the commit phase. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + * @param salt value used to hide the commitment price during the commit phase. + */ + function revealVote( + bytes32 identifier, + uint256 time, + int256 price, + bytes memory ancillaryData, + int256 salt + ) public virtual; + + /** + * @notice Gets the requests that are being voted on this round. + * @return pendingRequests array containing identifiers of type PendingRequestAncillaryAugmented. + */ + function getPendingRequests() external virtual returns (PendingRequestAncillaryAugmented[] memory); + + /** + * @notice Returns the current voting phase, as a function of the current time. + * @return Phase to indicate the current phase. Either { Commit, Reveal, NUM_PHASES }. + */ + function getVotePhase() external view virtual returns (Phase); + + /** + * @notice Returns the current round ID, as a function of the current time. + * @return uint256 representing the unique round ID. + */ + function getCurrentRoundId() external view virtual returns (uint32); + + // Voting Owner functions. + + /** + * @notice Disables this Voting contract in favor of the migrated one. + * @dev Can only be called by the contract owner. + * @param newVotingAddress the newly migrated contract address. + */ + function setMigrated(address newVotingAddress) external virtual; + + /** + * @notice Sets the maximum number of rounds to roll a request can have before the DVM auto deletes it. + * @dev Can only be called by the contract owner. + * @param newMaxRolls the new number of rounds to roll a request before the DVM auto deletes it. + */ + function setMaxRolls(uint32 newMaxRolls) external virtual; + + /** + * @notice Sets the maximum number of requests that can be made in a single round. Used to bound the maximum + * sequential slashing that can be applied within a single round. + * @dev Can only be called by the contract owner. + * @param newMaxRequestsPerRound the new maximum number of requests that can be made in a single round. + */ + function setMaxRequestPerRound(uint32 newMaxRequestsPerRound) external virtual; + + /** + * @notice Resets the GAT number and SPAT percentage. The GAT is the minimum number of tokens that must participate + * in a vote for it to resolve (quorum number). The SPAT is is the minimum percentage of tokens that must agree + * in a vote for it to resolve (percentage of staked tokens) Note: this change only applies to rounds that + * have not yet begun. + * @param newGat sets the next round's GAT and going forward. + * @param newSpat sets the next round's SPAT and going forward. + */ + function setGatAndSpat(uint128 newGat, uint64 newSpat) external virtual; + + /** + * @notice Changes the slashing library used by this contract. + * @param _newSlashingLibrary new slashing library address. + */ + function setSlashingLibrary(address _newSlashingLibrary) external virtual; +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracle.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracle.sol new file mode 100644 index 000000000..33fc2882f --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracle.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/Testable.sol"; +import "../interfaces/OracleInterface.sol"; +import "../interfaces/IdentifierWhitelistInterface.sol"; +import "../interfaces/FinderInterface.sol"; +import "../implementation/Constants.sol"; + +// A mock oracle used for testing. +contract MockOracle is OracleInterface, Testable { + // Represents an available price. Have to keep a separate bool to allow for price=0. + struct Price { + bool isAvailable; + int256 price; + // Time the verified price became available. + uint256 verifiedTime; + } + + // The two structs below are used in an array and mapping to keep track of prices that have been requested but are + // not yet available. + struct QueryIndex { + bool isValid; + uint256 index; + } + + // Represents a (identifier, time) point that has been queried. + struct QueryPoint { + bytes32 identifier; + uint256 time; + } + + // Reference to the Finder. + FinderInterface private finder; + + // Maps request IDs to their resolved Price structs. + mapping(bytes32 => Price) private verifiedPrices; + + // Maps request IDs to their pending QueryIndex structs. + mapping(bytes32 => QueryIndex) private queryIndices; + + // Array of pending QueryPoint structs. + QueryPoint[] private requestedPrices; + + event PriceRequestAdded( + address indexed requester, + bytes32 indexed identifier, + uint256 time, + bytes32 indexed requestId + ); + event PushedPrice( + address indexed pusher, + bytes32 indexed identifier, + uint256 time, + int256 price, + bytes32 indexed requestId + ); + + constructor(address _finderAddress, address _timerAddress) Testable(_timerAddress) { + finder = FinderInterface(_finderAddress); + } + + // Enqueues a request (if a request isn't already present) for the given (identifier, time) pair. + + function requestPrice(bytes32 identifier, uint256 time) public override { + require(_getIdentifierWhitelist().isIdentifierSupported(identifier)); + bytes32 requestId = _encodePriceRequest(identifier, time); + Price storage lookup = verifiedPrices[requestId]; + if (!lookup.isAvailable && !queryIndices[requestId].isValid) { + // New query, enqueue it for review. + queryIndices[requestId] = QueryIndex(true, requestedPrices.length); + requestedPrices.push(QueryPoint(identifier, time)); + emit PriceRequestAdded(msg.sender, identifier, time, requestId); + } + } + + // Pushes the verified price for a requested query. + function pushPrice(bytes32 identifier, uint256 time, int256 price) public { + bytes32 requestId = _encodePriceRequest(identifier, time); + verifiedPrices[requestId] = Price(true, price, getCurrentTime()); + + QueryIndex storage queryIndex = queryIndices[requestId]; + require(queryIndex.isValid, "Can't push prices that haven't been requested"); + // Delete from the array. Instead of shifting the queries over, replace the contents of `indexToReplace` with + // the contents of the last index (unless it is the last index). + uint256 indexToReplace = queryIndex.index; + delete queryIndices[requestId]; + uint256 lastIndex = requestedPrices.length - 1; + if (lastIndex != indexToReplace) { + QueryPoint storage queryToCopy = requestedPrices[lastIndex]; + queryIndices[_encodePriceRequest(queryToCopy.identifier, queryToCopy.time)].index = indexToReplace; + requestedPrices[indexToReplace] = queryToCopy; + } + requestedPrices.pop(); + + emit PushedPrice(msg.sender, identifier, time, price, requestId); + } + + // Wrapper function to push the verified price by request ID. + function pushPriceByRequestId(bytes32 requestId, int256 price) external { + QueryPoint memory queryPoint = getRequestParameters(requestId); + pushPrice(queryPoint.identifier, queryPoint.time, price); + } + + // Checks whether a price has been resolved. + function hasPrice(bytes32 identifier, uint256 time) public view override returns (bool) { + Price storage lookup = verifiedPrices[_encodePriceRequest(identifier, time)]; + return lookup.isAvailable; + } + + // Gets a price that has already been resolved. + function getPrice(bytes32 identifier, uint256 time) public view override returns (int256) { + Price storage lookup = verifiedPrices[_encodePriceRequest(identifier, time)]; + require(lookup.isAvailable); + return lookup.price; + } + + // Gets the queries that still need verified prices. + function getPendingQueries() external view returns (QueryPoint[] memory) { + return requestedPrices; + } + + // Gets the request parameters by request ID. + function getRequestParameters(bytes32 requestId) public view returns (QueryPoint memory) { + QueryIndex storage queryIndex = queryIndices[requestId]; + require(queryIndex.isValid, "Request ID not found"); + return requestedPrices[queryIndex.index]; + } + + function _getIdentifierWhitelist() private view returns (IdentifierWhitelistInterface supportedIdentifiers) { + return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); + } + + // Returns an encoded bytes32 representing a price request ID. Used when storing/referencing price requests. + function _encodePriceRequest(bytes32 identifier, uint256 time) internal pure returns (bytes32) { + return keccak256(abi.encode(identifier, time)); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleAncillary.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleAncillary.sol new file mode 100644 index 000000000..0b8ee5b3e --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleAncillary.sol @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/Testable.sol"; +import "../interfaces/OracleAncillaryInterface.sol"; +import "../interfaces/IdentifierWhitelistInterface.sol"; +import "../interfaces/FinderInterface.sol"; +import "../implementation/Constants.sol"; + +// A mock oracle used for testing. +contract MockOracleAncillary is OracleAncillaryInterface, Testable { + // Represents an available price. Have to keep a separate bool to allow for price=0. + struct Price { + bool isAvailable; + int256 price; + // Time the verified price became available. + uint256 verifiedTime; + } + + // The two structs below are used in an array and mapping to keep track of prices that have been requested but are + // not yet available. + struct QueryIndex { + bool isValid; + uint256 index; + } + + // Represents a (identifier, time, ancillary data) point that has been queried. + struct QueryPoint { + bytes32 identifier; + uint256 time; + bytes ancillaryData; + } + + // Reference to the Finder. + FinderInterface private finder; + + // Maps request IDs to their resolved Price structs. + mapping(bytes32 => Price) internal verifiedPrices; + + // Maps request IDs to their pending QueryIndex structs. + mapping(bytes32 => QueryIndex) internal queryIndices; + + // Array of pending QueryPoint structs. + QueryPoint[] internal requestedPrices; + + event PriceRequestAdded( + address indexed requester, + bytes32 indexed identifier, + uint256 time, + bytes ancillaryData, + bytes32 indexed requestId + ); + event PushedPrice( + address indexed pusher, + bytes32 indexed identifier, + uint256 time, + bytes ancillaryData, + int256 price, + bytes32 indexed requestId + ); + + constructor(address _finderAddress, address _timerAddress) Testable(_timerAddress) { + finder = FinderInterface(_finderAddress); + } + + // Enqueues a request (if a request isn't already present) for the given identifier, time and ancillary data. + + function requestPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData) public override { + require(_getIdentifierWhitelist().isIdentifierSupported(identifier)); + bytes32 requestId = _encodePriceRequest(identifier, time, ancillaryData); + Price storage lookup = verifiedPrices[requestId]; + if (!lookup.isAvailable && !queryIndices[requestId].isValid) { + // New query, enqueue it for review. + queryIndices[requestId] = QueryIndex(true, requestedPrices.length); + requestedPrices.push(QueryPoint(identifier, time, ancillaryData)); + emit PriceRequestAdded(msg.sender, identifier, time, ancillaryData, requestId); + } + } + + // Pushes the verified price for a requested query. + function pushPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData, int256 price) public { + bytes32 requestId = _encodePriceRequest(identifier, time, ancillaryData); + verifiedPrices[requestId] = Price(true, price, getCurrentTime()); + + QueryIndex storage queryIndex = queryIndices[requestId]; + require(queryIndex.isValid, "Can't push prices that haven't been requested"); + // Delete from the array. Instead of shifting the queries over, replace the contents of `indexToReplace` with + // the contents of the last index (unless it is the last index). + uint256 indexToReplace = queryIndex.index; + delete queryIndices[requestId]; + uint256 lastIndex = requestedPrices.length - 1; + if (lastIndex != indexToReplace) { + QueryPoint storage queryToCopy = requestedPrices[lastIndex]; + queryIndices[_encodePriceRequest(queryToCopy.identifier, queryToCopy.time, queryToCopy.ancillaryData)] + .index = indexToReplace; + requestedPrices[indexToReplace] = queryToCopy; + } + requestedPrices.pop(); + + emit PushedPrice(msg.sender, identifier, time, ancillaryData, price, requestId); + } + + // Wrapper function to push the verified price by request ID. + function pushPriceByRequestId(bytes32 requestId, int256 price) external { + QueryPoint memory queryPoint = getRequestParameters(requestId); + pushPrice(queryPoint.identifier, queryPoint.time, queryPoint.ancillaryData, price); + } + + // Checks whether a price has been resolved. + function hasPrice( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) public view override returns (bool) { + Price storage lookup = verifiedPrices[_encodePriceRequest(identifier, time, ancillaryData)]; + return lookup.isAvailable; + } + + // Gets a price that has already been resolved. + function getPrice( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) public view override returns (int256) { + Price storage lookup = verifiedPrices[_encodePriceRequest(identifier, time, ancillaryData)]; + require(lookup.isAvailable); + return lookup.price; + } + + // Gets the queries that still need verified prices. + function getPendingQueries() external view returns (QueryPoint[] memory) { + return requestedPrices; + } + + // Gets the request parameters by request ID. + function getRequestParameters(bytes32 requestId) public view returns (QueryPoint memory) { + QueryIndex storage queryIndex = queryIndices[requestId]; + require(queryIndex.isValid, "Request ID not found"); + return requestedPrices[queryIndex.index]; + } + + function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface supportedIdentifiers) { + return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); + } + + // Returns an encoded bytes32 representing a price request ID. Used when storing/referencing price requests. + function _encodePriceRequest( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) internal pure returns (bytes32) { + return keccak256(abi.encode(identifier, time, ancillaryData)); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleCombined.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleCombined.sol new file mode 100644 index 000000000..0b313816a --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleCombined.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "./MockOracleAncillary.sol"; + +// A mock oracle used for testing. Allows both ancillary and non-ancillary methods to be called. +contract MockOracleCombined is MockOracleAncillary { + constructor(address _finderAddress, address _timerAddress) MockOracleAncillary(_finderAddress, _timerAddress) {} + + // Enqueues a request (if a request isn't already present) for the given (identifier, time) pair. + function requestPrice(bytes32 identifier, uint256 time) public { + requestPrice(identifier, time, ""); + } + + // Pushes the verified price for a requested query. + function pushPrice(bytes32 identifier, uint256 time, int256 price) external { + pushPrice(identifier, time, "", price); + } + + // Checks whether a price has been resolved. + function hasPrice(bytes32 identifier, uint256 time) public view returns (bool) { + return hasPrice(identifier, time, ""); + } + + // Gets a price that has already been resolved. + function getPrice(bytes32 identifier, uint256 time) public view returns (int256) { + return getPrice(identifier, time, ""); + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleGovernance.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleGovernance.sol new file mode 100644 index 000000000..8d49a1744 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleGovernance.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../interfaces/OracleGovernanceInterface.sol"; +import "./MockOracleAncillary.sol"; + +// A mock oracle used for testing. +contract MockOracleGovernance is MockOracleAncillary { + constructor(address _finderAddress, address _timerAddress) MockOracleAncillary(_finderAddress, _timerAddress) {} + + // Enqueues a governance request (if a request isn't already present) for the given (identifier, time) pair. + function requestGovernanceAction(bytes32 identifier, uint256 time, bytes memory ancillaryData) public { + _requestPrice(identifier, time, ancillaryData, true); + } + + function _requestPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData, bool isGovernance) internal { + require(isGovernance || _getIdentifierWhitelist().isIdentifierSupported(identifier)); + bytes32 requestId = _encodePriceRequest(identifier, time, ancillaryData); + Price storage lookup = verifiedPrices[requestId]; + if (!lookup.isAvailable && !queryIndices[requestId].isValid) { + // New query, enqueue it for review. + queryIndices[requestId] = QueryIndex(true, requestedPrices.length); + QueryPoint memory queryPoint = QueryPoint(identifier, time, ancillaryData); + requestedPrices.push(queryPoint); + emit PriceRequestAdded(msg.sender, identifier, time, ancillaryData, requestId); + } + } +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/test/VotingAncillaryInterfaceTest.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/test/VotingAncillaryInterfaceTest.sol new file mode 100644 index 000000000..b5fdbb18e --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/test/VotingAncillaryInterfaceTest.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/FixedPoint.sol"; +import "../../common/implementation/Testable.sol"; +import "../interfaces/OracleAncillaryInterface.sol"; +import "../interfaces/VotingAncillaryInterface.sol"; + +// A mock oracle used for testing. Exports the voting & oracle interfaces and events that contain ancillary data. +abstract contract VotingAncillaryInterfaceTesting is OracleAncillaryInterface, VotingAncillaryInterface, Testable { + using FixedPoint for FixedPoint.Unsigned; + + // Events, data structures and functions not exported in the base interfaces, used for testing. + event VoteCommitted( + address indexed voter, + uint256 indexed roundId, + bytes32 indexed identifier, + uint256 time, + bytes ancillaryData + ); + + event EncryptedVote( + address indexed voter, + uint256 indexed roundId, + bytes32 indexed identifier, + uint256 time, + bytes ancillaryData, + bytes encryptedVote + ); + + event VoteRevealed( + address indexed voter, + uint256 indexed roundId, + bytes32 indexed identifier, + uint256 time, + int256 price, + bytes ancillaryData, + uint256 numTokens + ); + + event RewardsRetrieved( + address indexed voter, + uint256 indexed roundId, + bytes32 indexed identifier, + uint256 time, + bytes ancillaryData, + uint256 numTokens + ); + + event PriceRequestAdded(uint256 indexed roundId, bytes32 indexed identifier, uint256 time); + + event PriceResolved( + uint256 indexed roundId, + bytes32 indexed identifier, + uint256 time, + int256 price, + bytes ancillaryData + ); + + struct Round { + uint256 snapshotId; // Voting token snapshot ID for this round. 0 if no snapshot has been taken. + FixedPoint.Unsigned inflationRate; // Inflation rate set for this round. + FixedPoint.Unsigned gatPercentage; // Gat rate set for this round. + uint256 rewardsExpirationTime; // Time that rewards for this round can be claimed until. + } + + // Represents the status a price request has. + enum RequestStatus { + NotRequested, // Was never requested. + Active, // Is being voted on in the current round. + Resolved, // Was resolved in a previous round. + Future // Is scheduled to be voted on in a future round. + } + + // Only used as a return value in view methods -- never stored in the contract. + struct RequestState { + RequestStatus status; + uint256 lastVotingRound; + } + + function rounds(uint256 roundId) public view virtual returns (Round memory); + + function getPriceRequestStatuses( + VotingAncillaryInterface.PendingRequestAncillary[] memory requests + ) public view virtual returns (RequestState[] memory); + + function getPendingPriceRequestsArray() external view virtual returns (bytes32[] memory); +} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/test/VotingInterfaceTest.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/test/VotingInterfaceTest.sol new file mode 100644 index 000000000..a9b0b92e8 --- /dev/null +++ b/contracts/external/uma/core/contracts/data-verification-mechanism/test/VotingInterfaceTest.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/FixedPoint.sol"; +import "../../common/implementation/Testable.sol"; +import "../interfaces/OracleInterface.sol"; +import "../interfaces/VotingInterface.sol"; + +// A mock oracle used for testing. Exports the voting & oracle interfaces and events that contain no ancillary data. +abstract contract VotingInterfaceTesting is OracleInterface, VotingInterface, Testable { + using FixedPoint for FixedPoint.Unsigned; + + // Events, data structures and functions not exported in the base interfaces, used for testing. + event VoteCommitted( + address indexed voter, + uint256 indexed roundId, + bytes32 indexed identifier, + uint256 time, + bytes ancillaryData + ); + + event EncryptedVote( + address indexed voter, + uint256 indexed roundId, + bytes32 indexed identifier, + uint256 time, + bytes ancillaryData, + bytes encryptedVote + ); + + event VoteRevealed( + address indexed voter, + uint256 indexed roundId, + bytes32 indexed identifier, + uint256 time, + int256 price, + bytes ancillaryData, + uint256 numTokens + ); + + event RewardsRetrieved( + address indexed voter, + uint256 indexed roundId, + bytes32 indexed identifier, + uint256 time, + bytes ancillaryData, + uint256 numTokens + ); + + event PriceRequestAdded(uint256 indexed roundId, bytes32 indexed identifier, uint256 time); + + event PriceResolved( + uint256 indexed roundId, + bytes32 indexed identifier, + uint256 time, + int256 price, + bytes ancillaryData + ); + + struct Round { + uint256 snapshotId; // Voting token snapshot ID for this round. 0 if no snapshot has been taken. + FixedPoint.Unsigned inflationRate; // Inflation rate set for this round. + FixedPoint.Unsigned gatPercentage; // Gat rate set for this round. + uint256 rewardsExpirationTime; // Time that rewards for this round can be claimed until. + } + + // Represents the status a price request has. + enum RequestStatus { + NotRequested, // Was never requested. + Active, // Is being voted on in the current round. + Resolved, // Was resolved in a previous round. + Future // Is scheduled to be voted on in a future round. + } + + // Only used as a return value in view methods -- never stored in the contract. + struct RequestState { + RequestStatus status; + uint256 lastVotingRound; + } + + function rounds(uint256 roundId) public view virtual returns (Round memory); + + function getPriceRequestStatuses( + VotingInterface.PendingRequest[] memory requests + ) public view virtual returns (RequestState[] memory); + + function getPendingPriceRequestsArray() external view virtual returns (bytes32[] memory); +} diff --git a/contracts/external/uma/core/contracts/external/README.md b/contracts/external/uma/core/contracts/external/README.md new file mode 100644 index 000000000..8c35978d2 --- /dev/null +++ b/contracts/external/uma/core/contracts/external/README.md @@ -0,0 +1,3 @@ +# External contracts + +All contracts in this folder were originally copied from an external source and any changes are documented in the `CHANGELOG.md` for each respective sub folder. diff --git a/contracts/external/uma/core/contracts/external/avm/AVM_CrossDomainEnabled.sol b/contracts/external/uma/core/contracts/external/avm/AVM_CrossDomainEnabled.sol new file mode 100644 index 000000000..9d26452d1 --- /dev/null +++ b/contracts/external/uma/core/contracts/external/avm/AVM_CrossDomainEnabled.sol @@ -0,0 +1,39 @@ +// Copied logic from https://github.com/makerdao/arbitrum-dai-bridge/blob/34acc39bc6f3a2da0a837ea3c5dbc634ec61c7de/contracts/l2/L2CrossDomainEnabled.sol +// with a change to the solidity version. + +pragma solidity ^0.8.0; + +import "./interfaces/ArbSys.sol"; + +abstract contract AVM_CrossDomainEnabled { + event SentCrossDomainMessage(address indexed from, address indexed to, uint256 indexed id, bytes data); + + modifier onlyFromCrossDomainAccount(address l1Counterpart) { + require(msg.sender == applyL1ToL2Alias(l1Counterpart), "ONLY_COUNTERPART_GATEWAY"); + _; + } + + uint160 constant offset = uint160(0x1111000000000000000000000000000000001111); + + // l1 addresses are transformed during l1->l2 calls. See https://developer.offchainlabs.com/docs/l1_l2_messages#address-aliasing for more information. + function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) { + l2Address = address(uint160(l1Address) + offset); + } + + // Sends a message to L1 via the ArbSys contract. See https://developer.offchainlabs.com/docs/arbsys. + // After the Arbitrum chain advances some set amount of time, ArbOS gathers all outgoing messages, Merklizes them, + // and publishes the root as an OutboxEntry in the chain's outbox. Note that this happens "automatically"; + // i.e., it requires no additional action from the user. After the Outbox entry is published on the L1 chain, + // the user (or anybody) can compute the Merkle proof of inclusion of their outgoing message. Anytime after the + // dispute window passes (~7 days), any user can execute the L1 message by calling Outbox.executeTransaction; + // if it reverts, it can be re-executed any number of times and with no upper time-bound. + // To read more about the L2 --> L1 lifecycle, see: https://developer.offchainlabs.com/docs/l1_l2_messages#explanation. + function sendCrossDomainMessage(address user, address to, bytes memory data) internal returns (uint256) { + // note: this method doesn't support sending ether to L1 together with a call + uint256 id = ArbSys(address(100)).sendTxToL1(to, data); + + emit SentCrossDomainMessage(user, to, id, data); + + return id; + } +} diff --git a/contracts/external/uma/core/contracts/external/avm/Arbitrum_CrossDomainEnabled.sol b/contracts/external/uma/core/contracts/external/avm/Arbitrum_CrossDomainEnabled.sol new file mode 100644 index 000000000..fd1d7020e --- /dev/null +++ b/contracts/external/uma/core/contracts/external/avm/Arbitrum_CrossDomainEnabled.sol @@ -0,0 +1,66 @@ +// Copied logic from https://github.com/makerdao/arbitrum-dai-bridge/blob/34acc39bc6f3a2da0a837ea3c5dbc634ec61c7de/contracts/l1/L1CrossDomainEnabled.sol +// with a change to the solidity version. +pragma solidity ^0.8.0; + +import "../../external/avm/interfaces/iArbitrum_Inbox.sol"; +import "../../external/avm/interfaces/iArbitrum_Outbox.sol"; + +abstract contract Arbitrum_CrossDomainEnabled { + iArbitrum_Inbox public immutable inbox; + + /** + * @param _inbox Contract that sends generalized messages to the Arbitrum chain. + */ + constructor(address _inbox) { + inbox = iArbitrum_Inbox(_inbox); + } + + // More details about retryable ticket parameters here: https://developer.offchainlabs.com/docs/l1_l2_messages#parameters + // This function will not apply aliassing to the `user` address on L2. + // Note: If `l1CallValue > 0`, then this contract must contain at least that much ETH to send as msg.value to the + // inbox. + function sendTxToL2NoAliassing( + address target, // Address where transaction will initiate on L2. + address user, // Address where excess gas is credited on L2. + uint256 l1CallValue, // msg.value deposited to `user` on L2. + uint256 maxSubmissionCost, // Amount of ETH allocated to pay for base submission fee. The user is charged this + // fee to cover the storage costs of keeping their retryable ticket's calldata in the retry buffer. This should + // also cover the `l2CallValue`, but we set that to 0. This amount is proportional to the size of `data`. + uint256 maxGas, // Gas limit for immediate L2 execution attempt. + uint256 gasPriceBid, // L2 gas price bid for immediate L2 execution attempt. + bytes memory data // ABI encoded data to send to target. + ) internal returns (uint256) { + // createRetryableTicket API: https://developer.offchainlabs.com/docs/sol_contract_docs/md_docs/arb-bridge-eth/bridge/inbox#createretryableticketaddress-destaddr-uint256-l2callvalue-uint256-maxsubmissioncost-address-excessfeerefundaddress-address-callvaluerefundaddress-uint256-maxgas-uint256-gaspricebid-bytes-data-%E2%86%92-uint256-external + // - address destAddr: destination L2 contract address + // - uint256 l2CallValue: call value for retryable L2 message + // - uint256 maxSubmissionCost: Max gas deducted from user's L2 balance to cover base submission fee + // - address excessFeeRefundAddress: maxgas x gasprice - execution cost gets credited here on L2 + // - address callValueRefundAddress: l2CallValue gets credited here on L2 if retryable txn times out or gets cancelled + // - uint256 maxGas: Max gas deducted from user's L2 balance to cover L2 execution + // - uint256 gasPriceBid: price bid for L2 execution + // - bytes data: ABI encoded data of L2 message + uint256 seqNum = inbox.createRetryableTicketNoRefundAliasRewrite{ value: l1CallValue }( + target, + 0, // we always assume that l2CallValue = 0 + maxSubmissionCost, + user, + user, + maxGas, + gasPriceBid, + data + ); + return seqNum; + } + + // Copied mostly from: https://github.com/makerdao/arbitrum-dai-bridge/blob/34acc39bc6f3a2da0a837ea3c5dbc634ec61c7de/contracts/l1/L1CrossDomainEnabled.sol#L31 + modifier onlyFromCrossDomainAccount(address l2Counterpart) { + // a message coming from the counterpart gateway was executed by the bridge + IBridge bridge = IBridge(inbox.bridge()); + require(msg.sender == address(bridge), "NOT_FROM_BRIDGE"); + + // and the outbox reports that the L2 address of the sender is the counterpart gateway + address l2ToL1Sender = iArbitrum_Outbox(bridge.activeOutbox()).l2ToL1Sender(); + require(l2ToL1Sender == l2Counterpart, "ONLY_COUNTERPART_GATEWAY"); + _; + } +} diff --git a/contracts/external/uma/core/contracts/external/avm/Arbitrum_Messenger.sol b/contracts/external/uma/core/contracts/external/avm/Arbitrum_Messenger.sol new file mode 100644 index 000000000..5fe2d350a --- /dev/null +++ b/contracts/external/uma/core/contracts/external/avm/Arbitrum_Messenger.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "./Arbitrum_CrossDomainEnabled.sol"; +import "@openzeppelin/contracts-v4/access/Ownable.sol"; + +/** + * @notice Sends cross chain messages Arbitrum L2 network. + * @dev This contract's owner should be set to the BridgeAdmin deployed on the same L1 network so that only the + * BridgeAdmin can call cross-chain administrative functions on the L2 DepositBox via this messenger. + * @dev This address will be the sender of any L1 --> L2 retryable tickets, so it should be set as the cross domain + * owner for L2 contracts that expect to receive cross domain messages. + */ +contract Arbitrum_Messenger is Ownable, Arbitrum_CrossDomainEnabled { + event RelayedMessage( + address indexed from, + address indexed to, + uint256 indexed seqNum, + address userToRefund, + uint256 l1CallValue, + uint256 gasLimit, + uint256 gasPrice, + uint256 maxSubmissionCost, + bytes data + ); + + /** + * @param _inbox Contract that sends generalized messages to the Arbitrum chain. + */ + constructor(address _inbox) Arbitrum_CrossDomainEnabled(_inbox) {} + + /** + * @notice Sends a message to an account on L2. If this message reverts on l2 for any reason it can either be + * resent on L1, or redeemed on L2 manually. To learn more see how "retryable tickets" work on Arbitrum + * https://developer.offchainlabs.com/docs/l1_l2_messages#parameters + * @param target The intended recipient on L2. + * @param userToRefund User on L2 to refund extra fees to. + * @param l1CallValue Amount of ETH deposited to `target` contract on L2. Used to pay for L2 submission fee and + * l2CallValue. This will usually be > 0. + * @param gasLimit The gasLimit for the receipt of the message on L2. + * @param gasPrice Gas price bid for L2 execution. + * @param maxSubmissionCost: Max gas deducted from user's L2 balance to cover base submission fee. + * This amount is proportional to the size of `data`. + * @param message The data to send to the target (usually calldata to a function with + * `onlyFromCrossDomainAccount()`) + */ + function relayMessage( + address target, + address userToRefund, + uint256 l1CallValue, + uint256 gasLimit, + uint256 gasPrice, + uint256 maxSubmissionCost, + bytes memory message + ) external payable onlyOwner { + // Since we know the L2 target's address in advance, we don't need to alias an L1 address. + uint256 seqNumber = sendTxToL2NoAliassing( + target, + userToRefund, + l1CallValue, + maxSubmissionCost, + gasLimit, + gasPrice, + message + ); + emit RelayedMessage( + msg.sender, + target, + seqNumber, + userToRefund, + l1CallValue, + gasLimit, + gasPrice, + maxSubmissionCost, + message + ); + } +} diff --git a/contracts/external/uma/core/contracts/external/avm/CHANGELOG.md b/contracts/external/uma/core/contracts/external/avm/CHANGELOG.md new file mode 100644 index 000000000..41a7b5f74 --- /dev/null +++ b/contracts/external/uma/core/contracts/external/avm/CHANGELOG.md @@ -0,0 +1,25 @@ +# Change Log + +Any modifications to original source code can be found in this document. Original sources are also listed here. + +## interfaces + +- **[iArbitrum_Inbox.sol](https://github.com/makerdao/arbitrum-dai-bridge/blob/7f1b47ef65a43f1696c5f1681109daac127d9c95/contracts/arbitrum/IInbox.sol):** + + - Bumped solidity version to >= 0.8.x + - Removed functions from interface that are not used by `ArbitrumCrossDomainEnabled.sol` or `AVM_CrossDomainEnabled.sol` + - Removed IMessageProvider inheritance. + +- **[iArbitrum_Outbox.sol](https://github.com/OffchainLabs/arbitrum-tutorials/blob/4761fa1ba1f1eca95e8c03f24f1442ed5aecd8bd/packages/arb-shared-dependencies/contracts/Outbox.sol):** + + - Bumped solidity version to >= 0.8.x + - Removed functions from interface that are not used by `ArbitrumCrossDomainEnabled.sol` or `AVM_CrossDomainEnabled.sol` + +- **[ArbSys.sol](https://github.com/makerdao/arbitrum-dai-bridge/blob/54a2109a97c5b1504824c6317d358e2d2733b5a3/contracts/arbitrum/ArbSys.sol):** + + - Bumped solidity version to >= 0.8.x + +## abstract contracts + +- **[AVM_CrossDomainEnabled.sol](https://github.com/makerdao/arbitrum-dai-bridge/blob/34acc39bc6f3a2da0a837ea3c5dbc634ec61c7de/contracts/l2/L2CrossDomainEnabled.sol):** + - Bumped solidity version to >= 0.8.x diff --git a/contracts/external/uma/core/contracts/external/avm/interfaces/ArbSys.sol b/contracts/external/uma/core/contracts/external/avm/interfaces/ArbSys.sol new file mode 100644 index 000000000..3c0a67a6e --- /dev/null +++ b/contracts/external/uma/core/contracts/external/avm/interfaces/ArbSys.sol @@ -0,0 +1,79 @@ +// Copied logic from https://github.com/makerdao/arbitrum-dai-bridge/blob/54a2109a97c5b1504824c6317d358e2d2733b5a3/contracts/arbitrum/ArbSys.sol +// with changes only to the solidity version and comments. + +pragma solidity ^0.8.0; + +/** + * @notice Precompiled contract that exists in every Arbitrum chain at address(100), + * 0x0000000000000000000000000000000000000064. Exposes a variety of system-level functionality. ArbSys provides + * systems functionality useful to some Arbitrum contracts. Any contract running on an Arbitrum Chain can call the + * chain's ArbSys. + */ +interface ArbSys { + /** + * @notice Get internal version number identifying an ArbOS build + * @return version number as int + */ + function arbOSVersion() external pure returns (uint256); + + function arbChainID() external view returns (uint256); + + /** + * @notice Get Arbitrum block number (distinct from L1 block number; Arbitrum genesis block has block number 0) + * @return block number as int + */ + function arbBlockNumber() external view returns (uint256); + + /** + * @notice Send given amount of Eth to dest from sender. + * This is a convenience function, which is equivalent to calling sendTxToL1 with empty calldataForL1. + * @param destination recipient address on L1 + * @return unique identifier for this L2-to-L1 transaction. + */ + function withdrawEth(address destination) external payable returns (uint256); + + /** + * @notice Send a transaction to L1 + * @param destination recipient address on L1 + * @param calldataForL1 (optional) calldata for L1 contract call + * @return a unique identifier for this L2-to-L1 transaction. + */ + function sendTxToL1(address destination, bytes calldata calldataForL1) external payable returns (uint256); + + /** + * @notice get the number of transactions issued by the given external account or the account sequence number of the given contract + * @param account target account + * @return the number of transactions issued by the given external account or the account sequence number of the given contract + */ + function getTransactionCount(address account) external view returns (uint256); + + /** + * @notice get the value of target L2 storage slot + * This function is only callable from address 0 to prevent contracts from being able to call it + * @param account target account + * @param index target index of storage slot + * @return stotage value for the given account at the given index + */ + function getStorageAt(address account, uint256 index) external view returns (uint256); + + /** + * @notice check if current call is coming from l1 + * @return true if the caller of this was called directly from L1 + */ + function isTopLevelCall() external view returns (bool); + + event EthWithdrawal(address indexed destAddr, uint256 amount); + + event L2ToL1Transaction( + address caller, + address indexed destination, + uint256 indexed uniqueId, + uint256 indexed batchNumber, + uint256 indexInBatch, + uint256 arbBlockNum, + uint256 ethBlockNum, + uint256 timestamp, + uint256 callvalue, + bytes data + ); +} diff --git a/contracts/external/uma/core/contracts/external/avm/interfaces/iArbitrum_Inbox.sol b/contracts/external/uma/core/contracts/external/avm/interfaces/iArbitrum_Inbox.sol new file mode 100644 index 000000000..ad4d4b505 --- /dev/null +++ b/contracts/external/uma/core/contracts/external/avm/interfaces/iArbitrum_Inbox.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.0; + +interface IBridge { + function activeOutbox() external view returns (address); +} + +interface iArbitrum_Inbox { + // Retryable tickets are the Arbitrum protocol’s canonical method for passing generalized messages from Ethereum to + // Arbitrum. A retryable ticket is an L2 message encoded and delivered by L1; if gas is provided, it will be executed + // immediately. If no gas is provided or the execution reverts, it will be placed in the L2 retry buffer, + // where any user can re-execute for some fixed period (roughly one week). + // Retryable tickets are created by calling Inbox.createRetryableTicket. + // More details here: https://developer.offchainlabs.com/docs/l1_l2_messages#ethereum-to-arbitrum-retryable-tickets + function createRetryableTicketNoRefundAliasRewrite( + address destAddr, + uint256 l2CallValue, + uint256 maxSubmissionCost, + address excessFeeRefundAddress, + address callValueRefundAddress, + uint256 maxGas, + uint256 gasPriceBid, + bytes calldata data + ) external payable returns (uint256); + + function bridge() external view returns (address); +} diff --git a/contracts/external/uma/core/contracts/external/avm/interfaces/iArbitrum_Outbox.sol b/contracts/external/uma/core/contracts/external/avm/interfaces/iArbitrum_Outbox.sol new file mode 100644 index 000000000..1e0eb4cdb --- /dev/null +++ b/contracts/external/uma/core/contracts/external/avm/interfaces/iArbitrum_Outbox.sol @@ -0,0 +1,26 @@ +// Copied logic from https://github.com/OffchainLabs/arbitrum-tutorials/blob/4761fa1ba1f1eca95e8c03f24f1442ed5aecd8bd/packages/arb-shared-dependencies/contracts/Outbox.sol +// with changes only to the solidity version. + +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2021, Offchain Labs, Inc. + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +pragma solidity ^0.8.0; + +interface iArbitrum_Outbox { + function l2ToL1Sender() external view returns (address); +} diff --git a/contracts/external/uma/core/contracts/external/boba/BobaAddressManager.sol b/contracts/external/uma/core/contracts/external/boba/BobaAddressManager.sol new file mode 100644 index 000000000..e754081a5 --- /dev/null +++ b/contracts/external/uma/core/contracts/external/boba/BobaAddressManager.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +abstract contract BobaAddressManager { + /** + * Retrieves the address associated with a given name. + * @param _name Name to retrieve an address for. + * @return Address associated with the given name. + */ + function getAddress(string memory _name) external view virtual returns (address); +} diff --git a/contracts/external/uma/core/contracts/external/chainbridge/Bridge.sol b/contracts/external/uma/core/contracts/external/chainbridge/Bridge.sol new file mode 100644 index 000000000..115ffed9a --- /dev/null +++ b/contracts/external/uma/core/contracts/external/chainbridge/Bridge.sol @@ -0,0 +1,451 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/access/AccessControl.sol"; +import "@openzeppelin/contracts-v4/security/Pausable.sol"; +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; +import "./interfaces/IDepositExecute.sol"; +import "./interfaces/IBridge.sol"; +import "./interfaces/IERCHandler.sol"; +import "./interfaces/IGenericHandler.sol"; + +/** + @title Facilitates deposits, creation and votiing of deposit proposals, and deposit executions. + @author ChainSafe Systems. + */ +contract Bridge is Pausable, AccessControl { + using SafeMath for uint256; + + uint8 public _chainID; + uint256 public _relayerThreshold; + uint256 public _totalRelayers; + uint256 public _totalProposals; + uint256 public _fee; + uint256 public _expiry; + + enum Vote { + No, + Yes + } + + enum ProposalStatus { + Inactive, + Active, + Passed, + Executed, + Cancelled + } + + struct Proposal { + bytes32 _resourceID; + bytes32 _dataHash; + address[] _yesVotes; + address[] _noVotes; + ProposalStatus _status; + uint256 _proposedBlock; + } + + // destinationChainID => number of deposits + mapping(uint8 => uint64) public _depositCounts; + // resourceID => handler address + mapping(bytes32 => address) public _resourceIDToHandlerAddress; + // depositNonce => destinationChainID => bytes + mapping(uint64 => mapping(uint8 => bytes)) public _depositRecords; + // destinationChainID + depositNonce => dataHash => Proposal + mapping(uint72 => mapping(bytes32 => Proposal)) public _proposals; + // destinationChainID + depositNonce => dataHash => relayerAddress => bool + mapping(uint72 => mapping(bytes32 => mapping(address => bool))) public _hasVotedOnProposal; + + event RelayerThresholdChanged(uint256 indexed newThreshold); + event RelayerAdded(address indexed relayer); + event RelayerRemoved(address indexed relayer); + event Deposit(uint8 indexed destinationChainID, bytes32 indexed resourceID, uint64 indexed depositNonce); + event ProposalEvent( + uint8 indexed originChainID, + uint64 indexed depositNonce, + ProposalStatus indexed status, + bytes32 resourceID, + bytes32 dataHash + ); + + event ProposalVote( + uint8 indexed originChainID, + uint64 indexed depositNonce, + ProposalStatus indexed status, + bytes32 resourceID + ); + + bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE"); + + modifier onlyAdmin() { + _onlyAdmin(); + _; + } + + modifier onlyAdminOrRelayer() { + _onlyAdminOrRelayer(); + _; + } + + modifier onlyRelayers() { + _onlyRelayers(); + _; + } + + function _onlyAdminOrRelayer() private view { + require( + hasRole(DEFAULT_ADMIN_ROLE, msg.sender) || hasRole(RELAYER_ROLE, msg.sender), + "sender is not relayer or admin" + ); + } + + function _onlyAdmin() private view { + require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "sender doesn't have admin role"); + } + + function _onlyRelayers() private view { + require(hasRole(RELAYER_ROLE, msg.sender), "sender doesn't have relayer role"); + } + + /** + @notice Initializes Bridge, creates and grants {msg.sender} the admin role, + creates and grants {initialRelayers} the relayer role. + @param chainID ID of chain the Bridge contract exists on. + @param initialRelayers Addresses that should be initially granted the relayer role. + @param initialRelayerThreshold Number of votes needed for a deposit proposal to be considered passed. + */ + constructor( + uint8 chainID, + address[] memory initialRelayers, + uint256 initialRelayerThreshold, + uint256 fee, + uint256 expiry + ) { + _chainID = chainID; + _relayerThreshold = initialRelayerThreshold; + _fee = fee; + _expiry = expiry; + + _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); + _setRoleAdmin(RELAYER_ROLE, DEFAULT_ADMIN_ROLE); + + for (uint256 i; i < initialRelayers.length; i++) { + grantRole(RELAYER_ROLE, initialRelayers[i]); + _totalRelayers++; + } + } + + /** + @notice Returns true if {relayer} has the relayer role. + @param relayer Address to check. + */ + function isRelayer(address relayer) external view returns (bool) { + return hasRole(RELAYER_ROLE, relayer); + } + + /** + @notice Removes admin role from {msg.sender} and grants it to {newAdmin}. + @notice Only callable by an address that currently has the admin role. + @param newAdmin Address that admin role will be granted to. + */ + function renounceAdmin(address newAdmin) external onlyAdmin { + grantRole(DEFAULT_ADMIN_ROLE, newAdmin); + renounceRole(DEFAULT_ADMIN_ROLE, msg.sender); + } + + /** + @notice Pauses deposits, proposal creation and voting, and deposit executions. + @notice Only callable by an address that currently has the admin role. + */ + function adminPauseTransfers() external onlyAdmin { + _pause(); + } + + /** + @notice Unpauses deposits, proposal creation and voting, and deposit executions. + @notice Only callable by an address that currently has the admin role. + */ + function adminUnpauseTransfers() external onlyAdmin { + _unpause(); + } + + /** + @notice Modifies the number of votes required for a proposal to be considered passed. + @notice Only callable by an address that currently has the admin role. + @param newThreshold Value {_relayerThreshold} will be changed to. + @notice Emits {RelayerThresholdChanged} event. + */ + function adminChangeRelayerThreshold(uint256 newThreshold) external onlyAdmin { + _relayerThreshold = newThreshold; + emit RelayerThresholdChanged(newThreshold); + } + + /** + @notice Grants {relayerAddress} the relayer role and increases {_totalRelayer} count. + @notice Only callable by an address that currently has the admin role. + @param relayerAddress Address of relayer to be added. + @notice Emits {RelayerAdded} event. + */ + function adminAddRelayer(address relayerAddress) external onlyAdmin { + require(!hasRole(RELAYER_ROLE, relayerAddress), "addr already has relayer role!"); + grantRole(RELAYER_ROLE, relayerAddress); + emit RelayerAdded(relayerAddress); + _totalRelayers++; + } + + /** + @notice Removes relayer role for {relayerAddress} and decreases {_totalRelayer} count. + @notice Only callable by an address that currently has the admin role. + @param relayerAddress Address of relayer to be removed. + @notice Emits {RelayerRemoved} event. + */ + function adminRemoveRelayer(address relayerAddress) external onlyAdmin { + require(hasRole(RELAYER_ROLE, relayerAddress), "addr doesn't have relayer role!"); + revokeRole(RELAYER_ROLE, relayerAddress); + emit RelayerRemoved(relayerAddress); + _totalRelayers--; + } + + /** + @notice Sets a new resource for handler contracts that use the IERCHandler interface, + and maps the {handlerAddress} to {resourceID} in {_resourceIDToHandlerAddress}. + @notice Only callable by an address that currently has the admin role. + @param handlerAddress Address of handler resource will be set for. + @param resourceID ResourceID to be used when making deposits. + @param tokenAddress Address of contract to be called when a deposit is made and a deposited is executed. + */ + function adminSetResource(address handlerAddress, bytes32 resourceID, address tokenAddress) external onlyAdmin { + _resourceIDToHandlerAddress[resourceID] = handlerAddress; + IERCHandler handler = IERCHandler(handlerAddress); + handler.setResource(resourceID, tokenAddress); + } + + /** + @notice Sets a new resource for handler contracts that use the IGenericHandler interface, + and maps the {handlerAddress} to {resourceID} in {_resourceIDToHandlerAddress}. + @notice Only callable by an address that currently has the admin role. + @param handlerAddress Address of handler resource will be set for. + @param resourceID ResourceID to be used when making deposits. + @param contractAddress Address of contract to be called when a deposit is made and a deposited is executed. + */ + function adminSetGenericResource( + address handlerAddress, + bytes32 resourceID, + address contractAddress, + bytes4 depositFunctionSig, + bytes4 executeFunctionSig + ) external onlyAdmin { + _resourceIDToHandlerAddress[resourceID] = handlerAddress; + IGenericHandler handler = IGenericHandler(handlerAddress); + handler.setResource(resourceID, contractAddress, depositFunctionSig, executeFunctionSig); + } + + /** + @notice Sets a resource as burnable for handler contracts that use the IERCHandler interface. + @notice Only callable by an address that currently has the admin role. + @param handlerAddress Address of handler resource will be set for. + @param tokenAddress Address of contract to be called when a deposit is made and a deposited is executed. + */ + function adminSetBurnable(address handlerAddress, address tokenAddress) external onlyAdmin { + IERCHandler handler = IERCHandler(handlerAddress); + handler.setBurnable(tokenAddress); + } + + /** + @notice Returns a proposal. + @param originChainID Chain ID deposit originated from. + @param depositNonce ID of proposal generated by proposal's origin Bridge contract. + @param dataHash Hash of data to be provided when deposit proposal is executed. + @return Proposal which consists of: + - _dataHash Hash of data to be provided when deposit proposal is executed. + - _yesVotes Number of votes in favor of proposal. + - _noVotes Number of votes against proposal. + - _status Current status of proposal. + */ + function getProposal( + uint8 originChainID, + uint64 depositNonce, + bytes32 dataHash + ) external view returns (Proposal memory) { + uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(originChainID); + return _proposals[nonceAndID][dataHash]; + } + + /** + @notice Changes deposit fee. + @notice Only callable by admin. + @param newFee Value {_fee} will be updated to. + */ + function adminChangeFee(uint256 newFee) external onlyAdmin { + require(_fee != newFee, "Current fee is equal to new fee"); + _fee = newFee; + } + + /** + @notice Used to manually withdraw funds from ERC safes. + @param handlerAddress Address of handler to withdraw from. + @param tokenAddress Address of token to withdraw. + @param recipient Address to withdraw tokens to. + @param amountOrTokenID Either the amount of ERC20 tokens or the ERC721 token ID to withdraw. + */ + function adminWithdraw( + address handlerAddress, + address tokenAddress, + address recipient, + uint256 amountOrTokenID + ) external onlyAdmin { + IERCHandler handler = IERCHandler(handlerAddress); + handler.withdraw(tokenAddress, recipient, amountOrTokenID); + } + + /** + @notice Initiates a transfer using a specified handler contract. + @notice Only callable when Bridge is not paused. + @param destinationChainID ID of chain deposit will be bridged to. + @param resourceID ResourceID used to find address of handler to be used for deposit. + @param data Additional data to be passed to specified handler. + @notice Emits {Deposit} event. + */ + function deposit(uint8 destinationChainID, bytes32 resourceID, bytes calldata data) external payable whenNotPaused { + require(msg.value == _fee, "Incorrect fee supplied"); + + address handler = _resourceIDToHandlerAddress[resourceID]; + require(handler != address(0), "resourceID not mapped to handler"); + + uint64 depositNonce = ++_depositCounts[destinationChainID]; + _depositRecords[depositNonce][destinationChainID] = data; + + IDepositExecute depositHandler = IDepositExecute(handler); + depositHandler.deposit(resourceID, destinationChainID, depositNonce, msg.sender, data); + + emit Deposit(destinationChainID, resourceID, depositNonce); + } + + /** + @notice When called, {msg.sender} will be marked as voting in favor of proposal. + @notice Only callable by relayers when Bridge is not paused. + @param chainID ID of chain deposit originated from. + @param depositNonce ID of deposited generated by origin Bridge contract. + @param dataHash Hash of data provided when deposit was made. + @notice Proposal must not have already been passed or executed. + @notice {msg.sender} must not have already voted on proposal. + @notice Emits {ProposalEvent} event with status indicating the proposal status. + @notice Emits {ProposalVote} event. + */ + function voteProposal( + uint8 chainID, + uint64 depositNonce, + bytes32 resourceID, + bytes32 dataHash + ) external onlyRelayers whenNotPaused { + uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(chainID); + Proposal storage proposal = _proposals[nonceAndID][dataHash]; + + require(_resourceIDToHandlerAddress[resourceID] != address(0), "no handler for resourceID"); + require(uint256(proposal._status) <= 1, "proposal already passed/executed/cancelled"); + require(!_hasVotedOnProposal[nonceAndID][dataHash][msg.sender], "relayer already voted"); + + if (uint256(proposal._status) == 0) { + ++_totalProposals; + _proposals[nonceAndID][dataHash] = Proposal({ + _resourceID: resourceID, + _dataHash: dataHash, + _yesVotes: new address[](1), + _noVotes: new address[](0), + _status: ProposalStatus.Active, + _proposedBlock: block.number + }); + + proposal._yesVotes[0] = msg.sender; + emit ProposalEvent(chainID, depositNonce, ProposalStatus.Active, resourceID, dataHash); + } else { + if (block.number.sub(proposal._proposedBlock) > _expiry) { + // if the number of blocks that has passed since this proposal was + // submitted exceeds the expiry threshold set, cancel the proposal + proposal._status = ProposalStatus.Cancelled; + emit ProposalEvent(chainID, depositNonce, ProposalStatus.Cancelled, resourceID, dataHash); + } else { + require(dataHash == proposal._dataHash, "datahash mismatch"); + proposal._yesVotes.push(msg.sender); + } + } + if (proposal._status != ProposalStatus.Cancelled) { + _hasVotedOnProposal[nonceAndID][dataHash][msg.sender] = true; + emit ProposalVote(chainID, depositNonce, proposal._status, resourceID); + + // If _depositThreshold is set to 1, then auto finalize + // or if _relayerThreshold has been exceeded + if (_relayerThreshold <= 1 || proposal._yesVotes.length >= _relayerThreshold) { + proposal._status = ProposalStatus.Passed; + + emit ProposalEvent(chainID, depositNonce, ProposalStatus.Passed, resourceID, dataHash); + } + } + } + + /** + @notice Executes a deposit proposal that is considered passed using a specified handler contract. + @notice Only callable by relayers when Bridge is not paused. + @param chainID ID of chain deposit originated from. + @param depositNonce ID of deposited generated by origin Bridge contract. + @param dataHash Hash of data originally provided when deposit was made. + @notice Proposal must be past expiry threshold. + @notice Emits {ProposalEvent} event with status {Cancelled}. + */ + function cancelProposal(uint8 chainID, uint64 depositNonce, bytes32 dataHash) public onlyAdminOrRelayer { + uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(chainID); + Proposal storage proposal = _proposals[nonceAndID][dataHash]; + + require(proposal._status != ProposalStatus.Cancelled, "Proposal already cancelled"); + require(block.number.sub(proposal._proposedBlock) > _expiry, "Proposal not at expiry threshold"); + + proposal._status = ProposalStatus.Cancelled; + emit ProposalEvent(chainID, depositNonce, ProposalStatus.Cancelled, proposal._resourceID, proposal._dataHash); + } + + /** + @notice Executes a deposit proposal that is considered passed using a specified handler contract. + @notice Only callable by relayers when Bridge is not paused. + @param chainID ID of chain deposit originated from. + @param resourceID ResourceID to be used when making deposits. + @param depositNonce ID of deposited generated by origin Bridge contract. + @param data Data originally provided when deposit was made. + @notice Proposal must have Passed status. + @notice Hash of {data} must equal proposal's {dataHash}. + @notice Emits {ProposalEvent} event with status {Executed}. + */ + function executeProposal( + uint8 chainID, + uint64 depositNonce, + bytes calldata data, + bytes32 resourceID + ) external onlyRelayers whenNotPaused { + address handler = _resourceIDToHandlerAddress[resourceID]; + uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(chainID); + bytes32 dataHash = keccak256(abi.encodePacked(handler, data)); + Proposal storage proposal = _proposals[nonceAndID][dataHash]; + + require(proposal._status != ProposalStatus.Inactive, "proposal is not active"); + require(proposal._status == ProposalStatus.Passed, "proposal already transferred"); + require(dataHash == proposal._dataHash, "data doesn't match datahash"); + + proposal._status = ProposalStatus.Executed; + + IDepositExecute depositHandler = IDepositExecute(_resourceIDToHandlerAddress[proposal._resourceID]); + depositHandler.executeProposal(proposal._resourceID, data); + + emit ProposalEvent(chainID, depositNonce, proposal._status, proposal._resourceID, proposal._dataHash); + } + + /** + @notice Transfers eth in the contract to the specified addresses. The parameters addrs and amounts are mapped 1-1. + This means that the address at index 0 for addrs will receive the amount (in WEI) from amounts at index 0. + @param addrs Array of addresses to transfer {amounts} to. + @param amounts Array of amonuts to transfer to {addrs}. + */ + function transferFunds(address payable[] calldata addrs, uint256[] calldata amounts) external onlyAdmin { + for (uint256 i = 0; i < addrs.length; i++) { + addrs[i].transfer(amounts[i]); + } + } +} diff --git a/contracts/external/uma/core/contracts/external/chainbridge/CHANGELOG.md b/contracts/external/uma/core/contracts/external/chainbridge/CHANGELOG.md new file mode 100644 index 000000000..edfdcb588 --- /dev/null +++ b/contracts/external/uma/core/contracts/external/chainbridge/CHANGELOG.md @@ -0,0 +1,26 @@ +# Change Log + +Any modifications to original source code can be found in this document. Original sources are also listed here. + +## chainbridge + +- **[Bridge.sol](https://github.com/ChainSafe/chainbridge-solidity/blob/849db5657b8ce7c340a8847078de87d3a9e421f1/contracts/Bridge.sol):** + - Changed version from from `0.6.4` --> `0.8` + - Changed imported `Pausable` and `SafeMath` contracts to `@openzeppelin/contracts` implementations. + +## handlers + +- **[GenericHandler.sol](https://github.com/ChainSafe/chainbridge-solidity/blob/849db5657b8ce7c340a8847078de87d3a9e421f1/contracts/handlers/GenericHandler.sol):** + - Changed version from from `0.6.4` --> `0.8` + +## interfaces + +- **[IBridge.sol](https://github.com/ChainSafe/chainbridge-solidity/blob/849db5657b8ce7c340a8847078de87d3a9e421f1/contracts/interfaces/IBridge.sol):** + - Changed version from from `0.6.4` --> `0.8` + - Added `deposit(uint8 destinationChainID, bytes32 resourceID, bytes data)` to interface so that `SinkOracle` and `SourceOracle` contracts can access it. +- **[IDepositExecute.sol](https://github.com/ChainSafe/chainbridge-solidity/blob/849db5657b8ce7c340a8847078de87d3a9e421f1/contracts/interfaces/IDepositExecute.sol):** + - Changed version from from `0.6.4` --> `0.8` +- **[IERCHandler.sol](https://github.com/ChainSafe/chainbridge-solidity/blob/849db5657b8ce7c340a8847078de87d3a9e421f1/contracts/interfaces/IERCHandler.sol):** + - Changed version from from `0.6.4` --> `0.8` +- **[IGenericHandler.sol](https://github.com/ChainSafe/chainbridge-solidity/blob/849db5657b8ce7c340a8847078de87d3a9e421f1/contracts/interfaces/IGenericHandler.sol):** + - Changed version from from `0.6.4` --> `0.8` diff --git a/contracts/external/uma/core/contracts/external/chainbridge/handlers/GenericHandler.sol b/contracts/external/uma/core/contracts/external/chainbridge/handlers/GenericHandler.sol new file mode 100644 index 000000000..f15c820a3 --- /dev/null +++ b/contracts/external/uma/core/contracts/external/chainbridge/handlers/GenericHandler.sol @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../interfaces/IGenericHandler.sol"; + +/** + @title Handles generic deposits and deposit executions. + @author ChainSafe Systems. + @notice This contract is intended to be used with the Bridge contract. + */ +contract GenericHandler is IGenericHandler { + address public _bridgeAddress; + + struct DepositRecord { + uint8 _destinationChainID; + address _depositer; + bytes32 _resourceID; + bytes _metaData; + } + + // depositNonce => Deposit Record + mapping(uint8 => mapping(uint64 => DepositRecord)) public _depositRecords; + + // resourceID => contract address + mapping(bytes32 => address) public _resourceIDToContractAddress; + + // contract address => resourceID + mapping(address => bytes32) public _contractAddressToResourceID; + + // contract address => deposit function signature + mapping(address => bytes4) public _contractAddressToDepositFunctionSignature; + + // contract address => execute proposal function signature + mapping(address => bytes4) public _contractAddressToExecuteFunctionSignature; + + // token contract address => is whitelisted + mapping(address => bool) public _contractWhitelist; + + modifier onlyBridge() { + _onlyBridge(); + _; + } + + function _onlyBridge() private view { + require(msg.sender == _bridgeAddress, "sender must be bridge contract"); + } + + /** + @param bridgeAddress Contract address of previously deployed Bridge. + @param initialResourceIDs Resource IDs used to identify a specific contract address. + These are the Resource IDs this contract will initially support. + @param initialContractAddresses These are the addresses the {initialResourceIDs} will point to, and are the contracts that will be + called to perform deposit and execution calls. + @param initialDepositFunctionSignatures These are the function signatures {initialContractAddresses} will point to, + and are the function that will be called when executing {deposit} + @param initialExecuteFunctionSignatures These are the function signatures {initialContractAddresses} will point to, + and are the function that will be called when executing {executeProposal} + + @dev {initialResourceIDs}, {initialContractAddresses}, {initialDepositFunctionSignatures}, + and {initialExecuteFunctionSignatures} must all have the same length. Also, + values must be ordered in the way that that index x of any mentioned array + must be intended for value x of any other array, e.g. {initialContractAddresses}[0] + is the intended address for {initialDepositFunctionSignatures}[0]. + */ + constructor( + address bridgeAddress, + bytes32[] memory initialResourceIDs, + address[] memory initialContractAddresses, + bytes4[] memory initialDepositFunctionSignatures, + bytes4[] memory initialExecuteFunctionSignatures + ) { + require( + initialResourceIDs.length == initialContractAddresses.length, + "initialResourceIDs and initialContractAddresses len mismatch" + ); + + require( + initialContractAddresses.length == initialDepositFunctionSignatures.length, + "provided contract addresses and function signatures len mismatch" + ); + + require( + initialDepositFunctionSignatures.length == initialExecuteFunctionSignatures.length, + "provided deposit and execute function signatures len mismatch" + ); + + _bridgeAddress = bridgeAddress; + + for (uint256 i = 0; i < initialResourceIDs.length; i++) { + _setResource( + initialResourceIDs[i], + initialContractAddresses[i], + initialDepositFunctionSignatures[i], + initialExecuteFunctionSignatures[i] + ); + } + } + + /** + @param depositNonce This ID will have been generated by the Bridge contract. + @param destId ID of chain deposit will be bridged to. + @return DepositRecord which consists of: + - _destinationChainID ChainID deposited tokens are intended to end up on. + - _resourceID ResourceID used when {deposit} was executed. + - _depositer Address that initially called {deposit} in the Bridge contract. + - _metaData Data to be passed to method executed in corresponding {resourceID} contract. + */ + function getDepositRecord(uint64 depositNonce, uint8 destId) external view returns (DepositRecord memory) { + return _depositRecords[destId][depositNonce]; + } + + /** + @notice First verifies {_resourceIDToContractAddress}[{resourceID}] and + {_contractAddressToResourceID}[{contractAddress}] are not already set, + then sets {_resourceIDToContractAddress} with {contractAddress}, + {_contractAddressToResourceID} with {resourceID}, + {_contractAddressToDepositFunctionSignature} with {depositFunctionSig}, + {_contractAddressToExecuteFunctionSignature} with {executeFunctionSig}, + and {_contractWhitelist} to true for {contractAddress}. + @param resourceID ResourceID to be used when making deposits. + @param contractAddress Address of contract to be called when a deposit is made and a deposited is executed. + @param depositFunctionSig Function signature of method to be called in {contractAddress} when a deposit is made. + @param executeFunctionSig Function signature of method to be called in {contractAddress} when a deposit is executed. + */ + function setResource( + bytes32 resourceID, + address contractAddress, + bytes4 depositFunctionSig, + bytes4 executeFunctionSig + ) external override onlyBridge { + _setResource(resourceID, contractAddress, depositFunctionSig, executeFunctionSig); + } + + /** + @notice A deposit is initiatied by making a deposit in the Bridge contract. + @param destinationChainID Chain ID deposit is expected to be bridged to. + @param depositNonce This value is generated as an ID by the Bridge contract. + @param depositer Address of account making the deposit in the Bridge contract. + @notice {contractAddress} is required to be whitelisted + @notice If {_contractAddressToDepositFunctionSignature}[{contractAddress}] is set, + {metaData} is expected to consist of needed function arguments. + */ + function deposit( + bytes32 resourceID, + uint8 destinationChainID, + uint64 depositNonce, + address depositer, + bytes calldata + ) external onlyBridge { + bytes32 lenMetadata; + bytes memory metadata; + + assembly { + // Load length of metadata from data + 64 + lenMetadata := calldataload(0xC4) + // Load free memory pointer + metadata := mload(0x40) + + mstore(0x40, add(0x20, add(metadata, lenMetadata))) + + // func sig (4) + destinationChainId (padded to 32) + depositNonce (32) + depositor (32) + + // bytes length (32) + resourceId (32) + length (32) = 0xC4 + + calldatacopy( + metadata, // copy to metadata + 0xC4, // copy from calldata after metadata length declaration @0xC4 + sub(calldatasize(), 0xC4) // copy size (calldatasize - (0xC4 + the space metaData takes up)) + ) + } + + address contractAddress = _resourceIDToContractAddress[resourceID]; + require(_contractWhitelist[contractAddress], "provided contractAddress is not whitelisted"); + + bytes4 sig = _contractAddressToDepositFunctionSignature[contractAddress]; + if (sig != bytes4(0)) { + bytes memory callData = abi.encodePacked(sig, metadata); + (bool success, ) = contractAddress.call(callData); + require(success, "delegatecall to contractAddress failed"); + } + + _depositRecords[destinationChainID][depositNonce] = DepositRecord( + destinationChainID, + depositer, + resourceID, + metadata + ); + } + + /** + @notice Proposal execution should be initiated when a proposal is finalized in the Bridge contract. + @notice {contractAddress} is required to be whitelisted + @notice If {_contractAddressToExecuteFunctionSignature}[{contractAddress}] is set, + {metaData} is expected to consist of needed function arguments. + */ + function executeProposal(bytes32 resourceID, bytes calldata) external onlyBridge { + bytes memory metaData; + assembly { + // metadata has variable length + // load free memory pointer to store metadata + metaData := mload(0x40) + // first 32 bytes of variable length in storage refer to length + let lenMeta := calldataload(0x64) + mstore(0x40, add(0x60, add(metaData, lenMeta))) + + // in the calldata, metadata is stored @0x64 after accounting for function signature, and 2 previous params + calldatacopy( + metaData, // copy to metaData + 0x64, // copy from calldata after data length declaration at 0x64 + sub(calldatasize(), 0x64) // copy size (calldatasize - 0x64) + ) + } + + address contractAddress = _resourceIDToContractAddress[resourceID]; + require(_contractWhitelist[contractAddress], "provided contractAddress is not whitelisted"); + + bytes4 sig = _contractAddressToExecuteFunctionSignature[contractAddress]; + if (sig != bytes4(0)) { + bytes memory callData = abi.encodePacked(sig, metaData); + (bool success, ) = contractAddress.call(callData); + require(success, "delegatecall to contractAddress failed"); + } + } + + function _setResource( + bytes32 resourceID, + address contractAddress, + bytes4 depositFunctionSig, + bytes4 executeFunctionSig + ) internal { + _resourceIDToContractAddress[resourceID] = contractAddress; + _contractAddressToResourceID[contractAddress] = resourceID; + _contractAddressToDepositFunctionSignature[contractAddress] = depositFunctionSig; + _contractAddressToExecuteFunctionSignature[contractAddress] = executeFunctionSig; + + _contractWhitelist[contractAddress] = true; + } +} diff --git a/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IBridge.sol b/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IBridge.sol new file mode 100644 index 000000000..1106eeaa1 --- /dev/null +++ b/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IBridge.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +/** + @title Interface for Bridge contract. + @author ChainSafe Systems. + */ +interface IBridge { + /** + @notice Exposing getter for {_chainID} instead of forcing the use of call. + @return uint8 The {_chainID} that is currently set for the Bridge contract. + */ + function _chainID() external returns (uint8); + + function deposit(uint8 destinationChainID, bytes32 resourceID, bytes calldata data) external; +} diff --git a/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IDepositExecute.sol b/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IDepositExecute.sol new file mode 100644 index 000000000..bc8df3b9c --- /dev/null +++ b/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IDepositExecute.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +/** + @title Interface for handler contracts that support deposits and deposit executions. + @author ChainSafe Systems. + */ +interface IDepositExecute { + /** + @notice It is intended that deposit are made using the Bridge contract. + @param destinationChainID Chain ID deposit is expected to be bridged to. + @param depositNonce This value is generated as an ID by the Bridge contract. + @param depositer Address of account making the deposit in the Bridge contract. + @param data Consists of additional data needed for a specific deposit. + */ + function deposit( + bytes32 resourceID, + uint8 destinationChainID, + uint64 depositNonce, + address depositer, + bytes calldata data + ) external; + + /** + @notice It is intended that proposals are executed by the Bridge contract. + @param data Consists of additional data needed for a specific deposit execution. + */ + function executeProposal(bytes32 resourceID, bytes calldata data) external; +} diff --git a/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IERCHandler.sol b/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IERCHandler.sol new file mode 100644 index 000000000..045af10a7 --- /dev/null +++ b/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IERCHandler.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +/** + @title Interface to be used with handlers that support ERC20s and ERC721s. + @author ChainSafe Systems. + */ +interface IERCHandler { + /** + @notice Correlates {resourceID} with {contractAddress}. + @param resourceID ResourceID to be used when making deposits. + @param contractAddress Address of contract to be called when a deposit is made and a deposited is executed. + */ + function setResource(bytes32 resourceID, address contractAddress) external; + + /** + @notice Marks {contractAddress} as mintable/burnable. + @param contractAddress Address of contract to be used when making or executing deposits. + */ + function setBurnable(address contractAddress) external; + + /** + @notice Used to manually release funds from ERC safes. + @param tokenAddress Address of token contract to release. + @param recipient Address to release tokens to. + @param amountOrTokenID Either the amount of ERC20 tokens or the ERC721 token ID to release. + */ + function withdraw(address tokenAddress, address recipient, uint256 amountOrTokenID) external; +} diff --git a/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IGenericHandler.sol b/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IGenericHandler.sol new file mode 100644 index 000000000..9c4f59fcd --- /dev/null +++ b/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IGenericHandler.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +/** + @title Interface for handler that handles generic deposits and deposit executions. + @dev Copied directly from here: https://github.com/ChainSafe/chainbridge-solidity/releases/tag/v1.0.0. + @author ChainSafe Systems. + */ +interface IGenericHandler { + /** + @notice Correlates {resourceID} with {contractAddress}, {depositFunctionSig}, and {executeFunctionSig}. + @param resourceID ResourceID to be used when making deposits. + @param contractAddress Address of contract to be called when a deposit is made and a deposited is executed. + @param depositFunctionSig Function signature of method to be called in {contractAddress} when a deposit is made. + @param executeFunctionSig Function signature of method to be called in {contractAddress} when a deposit is executed. + */ + function setResource( + bytes32 resourceID, + address contractAddress, + bytes4 depositFunctionSig, + bytes4 executeFunctionSig + ) external; +} diff --git a/contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL1StandardBridge.sol b/contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL1StandardBridge.sol new file mode 100644 index 000000000..0483c28ae --- /dev/null +++ b/contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL1StandardBridge.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import { Address } from "@openzeppelin/contracts-v4/utils/Address.sol"; +import { SafeERC20 } from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +/** + * @title L1StandardBridge + * @dev The L1 ETH and ERC20 Bridge is a contract which stores deposited L1 funds and standard + * tokens that are in use on L2. It synchronizes a corresponding L2 Bridge, informing it of deposits + * and listening to it for newly finalized withdrawals. + * + */ +interface OptimismL1StandardBridge { + function depositETH(uint32 _l2Gas, bytes calldata _data) external payable; + + function depositERC20( + address _l1Token, + address _l2Token, + uint256 _amount, + uint32 _l2Gas, + bytes calldata _data + ) external; +} diff --git a/contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL2StandardBridge.sol b/contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL2StandardBridge.sol new file mode 100644 index 000000000..0a453170f --- /dev/null +++ b/contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL2StandardBridge.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @title L2StandardBridge + * @dev The L2 Standard bridge is a contract which works together with the L1 Standard bridge to + * enable ETH and ERC20 transitions between L1 and L2. + * This contract acts as a minter for new tokens when it hears about deposits into the L1 Standard + * bridge. + * This contract also acts as a burner of the tokens intended for withdrawal, informing the L1 + * bridge to release L1 funds. + */ +contract OptimismL2StandardBridge { + /******************************** + * External Contract References * + ********************************/ + + address public l1TokenBridge; +} diff --git a/contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL2StandardERC20.sol b/contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL2StandardERC20.sol new file mode 100644 index 000000000..a13dfccd9 --- /dev/null +++ b/contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL2StandardERC20.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ERC20 } from "@openzeppelin/contracts-v4/token/ERC20/ERC20.sol"; + +abstract contract OptimismL2StandardERC20 is ERC20 { + address public l1Token; + address public l2Bridge; +} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/EmergencyShutdownable.sol b/contracts/external/uma/core/contracts/financial-templates/common/EmergencyShutdownable.sol new file mode 100644 index 000000000..0304614cc --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/common/EmergencyShutdownable.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; + +/** + * @title EmergencyShutdownable contract. + * @notice Any contract that inherits this contract will have an emergency shutdown timestamp state variable. + * This contract provides modifiers that can be used by children contracts to determine if the contract is + * in the shutdown state. The child contract is expected to implement the logic that happens + * once a shutdown occurs. + */ + +abstract contract EmergencyShutdownable { + using SafeMath for uint256; + + /**************************************** + * EMERGENCY SHUTDOWN DATA STRUCTURES * + ****************************************/ + + // Timestamp used in case of emergency shutdown. 0 if no shutdown has been triggered. + uint256 public emergencyShutdownTimestamp; + + /**************************************** + * MODIFIERS * + ****************************************/ + + modifier notEmergencyShutdown() { + _notEmergencyShutdown(); + _; + } + + modifier isEmergencyShutdown() { + _isEmergencyShutdown(); + _; + } + + /**************************************** + * EXTERNAL FUNCTIONS * + ****************************************/ + + constructor() { + emergencyShutdownTimestamp = 0; + } + + /**************************************** + * INTERNAL FUNCTIONS * + ****************************************/ + + function _notEmergencyShutdown() internal view { + // Note: removed require string to save bytecode. + require(emergencyShutdownTimestamp == 0); + } + + function _isEmergencyShutdown() internal view { + // Note: removed require string to save bytecode. + require(emergencyShutdownTimestamp != 0); + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/FeePayer.sol b/contracts/external/uma/core/contracts/financial-templates/common/FeePayer.sol new file mode 100644 index 000000000..3adaa935f --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/common/FeePayer.sol @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +import "../../common/implementation/Lockable.sol"; +import "../../common/implementation/FixedPoint.sol"; +import "../../common/implementation/Testable.sol"; + +import "../../data-verification-mechanism/interfaces/StoreInterface.sol"; +import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; +import "../../data-verification-mechanism/interfaces/AdministrateeInterface.sol"; +import "../../data-verification-mechanism/implementation/Constants.sol"; + +/** + * @title FeePayer contract. + * @notice Provides fee payment functionality for the ExpiringMultiParty contract. + * contract is abstract as each derived contract that inherits `FeePayer` must implement `pfc()`. + */ + +abstract contract FeePayer is AdministrateeInterface, Testable, Lockable { + using SafeMath for uint256; + using FixedPoint for FixedPoint.Unsigned; + using SafeERC20 for IERC20; + + /**************************************** + * FEE PAYER DATA STRUCTURES * + ****************************************/ + + // The collateral currency used to back the positions in this contract. + IERC20 public collateralCurrency; + + // Finder contract used to look up addresses for UMA system contracts. + FinderInterface public finder; + + // Tracks the last block time when the fees were paid. + uint256 private lastPaymentTime; + + // Tracks the cumulative fees that have been paid by the contract for use by derived contracts. + // The multiplier starts at 1, and is updated by computing cumulativeFeeMultiplier * (1 - effectiveFee). + // Put another way, the cumulativeFeeMultiplier is (1 - effectiveFee1) * (1 - effectiveFee2) ... + // For example: + // The cumulativeFeeMultiplier should start at 1. + // If a 1% fee is charged, the multiplier should update to .99. + // If another 1% fee is charged, the multiplier should be 0.99^2 (0.9801). + FixedPoint.Unsigned public cumulativeFeeMultiplier; + + /**************************************** + * EVENTS * + ****************************************/ + + event RegularFeesPaid(uint256 indexed regularFee, uint256 indexed lateFee); + event FinalFeesPaid(uint256 indexed amount); + + /**************************************** + * MODIFIERS * + ****************************************/ + + // modifier that calls payRegularFees(). + modifier fees() virtual { + // Note: the regular fee is applied on every fee-accruing transaction, where the total change is simply the + // regular fee applied linearly since the last update. This implies that the compounding rate depends on the + // frequency of update transactions that have this modifier, and it never reaches the ideal of continuous + // compounding. This approximate-compounding pattern is common in the Ethereum ecosystem because of the + // complexity of compounding data on-chain. + payRegularFees(); + _; + } + + /** + * @notice Constructs the FeePayer contract. Called by child contracts. + * @param _collateralAddress ERC20 token that is used as the underlying collateral for the synthetic. + * @param _finderAddress UMA protocol Finder used to discover other protocol contracts. + * @param _timerAddress Contract that stores the current time in a testing environment. + * Must be set to 0x0 for production environments that use live time. + */ + constructor(address _collateralAddress, address _finderAddress, address _timerAddress) Testable(_timerAddress) { + collateralCurrency = IERC20(_collateralAddress); + finder = FinderInterface(_finderAddress); + lastPaymentTime = getCurrentTime(); + cumulativeFeeMultiplier = FixedPoint.fromUnscaledUint(1); + } + + /**************************************** + * FEE PAYMENT FUNCTIONS * + ****************************************/ + + /** + * @notice Pays UMA DVM regular fees (as a % of the collateral pool) to the Store contract. + * @dev These must be paid periodically for the life of the contract. If the contract has not paid its regular fee + * in a week or more then a late penalty is applied which is sent to the caller. If the amount of + * fees owed are greater than the pfc, then this will pay as much as possible from the available collateral. + * An event is only fired if the fees charged are greater than 0. + * @return totalPaid Amount of collateral that the contract paid (sum of the amount paid to the Store and caller). + * This returns 0 and exit early if there is no pfc, fees were already paid during the current block, or the fee rate is 0. + */ + function payRegularFees() public nonReentrant returns (FixedPoint.Unsigned memory) { + uint256 time = getCurrentTime(); + FixedPoint.Unsigned memory collateralPool = _pfc(); + + // Fetch the regular fees, late penalty and the max possible to pay given the current collateral within the contract. + ( + FixedPoint.Unsigned memory regularFee, + FixedPoint.Unsigned memory latePenalty, + FixedPoint.Unsigned memory totalPaid + ) = getOutstandingRegularFees(time); + lastPaymentTime = time; + + // If there are no fees to pay then exit early. + if (totalPaid.isEqual(0)) { + return totalPaid; + } + + emit RegularFeesPaid(regularFee.rawValue, latePenalty.rawValue); + + _adjustCumulativeFeeMultiplier(totalPaid, collateralPool); + + if (regularFee.isGreaterThan(0)) { + StoreInterface store = _getStore(); + collateralCurrency.safeIncreaseAllowance(address(store), regularFee.rawValue); + store.payOracleFeesErc20(address(collateralCurrency), regularFee); + } + + if (latePenalty.isGreaterThan(0)) { + collateralCurrency.safeTransfer(msg.sender, latePenalty.rawValue); + } + return totalPaid; + } + + /** + * @notice Fetch any regular fees that the contract has pending but has not yet paid. If the fees to be paid are more + * than the total collateral within the contract then the totalPaid returned is full contract collateral amount. + * @dev This returns 0 and exit early if there is no pfc, fees were already paid during the current block, or the fee rate is 0. + * @return regularFee outstanding unpaid regular fee. + * @return latePenalty outstanding unpaid late fee for being late in previous fee payments. + * @return totalPaid Amount of collateral that the contract paid (sum of the amount paid to the Store and caller). + */ + function getOutstandingRegularFees( + uint256 time + ) + public + view + returns ( + FixedPoint.Unsigned memory regularFee, + FixedPoint.Unsigned memory latePenalty, + FixedPoint.Unsigned memory totalPaid + ) + { + StoreInterface store = _getStore(); + FixedPoint.Unsigned memory collateralPool = _pfc(); + + // Exit early if there is no collateral or if fees were already paid during this block. + if (collateralPool.isEqual(0) || lastPaymentTime == time) { + return (regularFee, latePenalty, totalPaid); + } + + (regularFee, latePenalty) = store.computeRegularFee(lastPaymentTime, time, collateralPool); + + totalPaid = regularFee.add(latePenalty); + if (totalPaid.isEqual(0)) { + return (regularFee, latePenalty, totalPaid); + } + // If the effective fees paid as a % of the pfc is > 100%, then we need to reduce it and make the contract pay + // as much of the fee that it can (up to 100% of its pfc). We'll reduce the late penalty first and then the + // regular fee, which has the effect of paying the store first, followed by the caller if there is any fee remaining. + if (totalPaid.isGreaterThan(collateralPool)) { + FixedPoint.Unsigned memory deficit = totalPaid.sub(collateralPool); + FixedPoint.Unsigned memory latePenaltyReduction = FixedPoint.min(latePenalty, deficit); + latePenalty = latePenalty.sub(latePenaltyReduction); + deficit = deficit.sub(latePenaltyReduction); + regularFee = regularFee.sub(FixedPoint.min(regularFee, deficit)); + totalPaid = collateralPool; + } + } + + /** + * @notice Gets the current profit from corruption for this contract in terms of the collateral currency. + * @dev This is equivalent to the collateral pool available from which to pay fees. Therefore, derived contracts are + * expected to implement this so that pay-fee methods can correctly compute the owed fees as a % of PfC. + * @return pfc value for equal to the current profit from corruption denominated in collateral currency. + */ + function pfc() external view override nonReentrantView returns (FixedPoint.Unsigned memory) { + return _pfc(); + } + + /** + * @notice Removes excess collateral balance not counted in the PfC by distributing it out pro-rata to all sponsors. + * @dev Multiplying the `cumulativeFeeMultiplier` by the ratio of non-PfC-collateral :: PfC-collateral effectively + * pays all sponsors a pro-rata portion of the excess collateral. + * @dev This will revert if PfC is 0 and this contract's collateral balance > 0. + */ + function gulp() external nonReentrant { + _gulp(); + } + + /**************************************** + * INTERNAL FUNCTIONS * + ****************************************/ + + // Pays UMA Oracle final fees of `amount` in `collateralCurrency` to the Store contract. Final fee is a flat fee + // charged for each price request. If payer is the contract, adjusts internal bookkeeping variables. If payer is not + // the contract, pulls in `amount` of collateral currency. + function _payFinalFees(address payer, FixedPoint.Unsigned memory amount) internal { + if (amount.isEqual(0)) { + return; + } + + if (payer != address(this)) { + // If the payer is not the contract pull the collateral from the payer. + collateralCurrency.safeTransferFrom(payer, address(this), amount.rawValue); + } else { + // If the payer is the contract, adjust the cumulativeFeeMultiplier to compensate. + FixedPoint.Unsigned memory collateralPool = _pfc(); + + // The final fee must be < available collateral or the fee will be larger than 100%. + // Note: revert reason removed to save bytecode. + require(collateralPool.isGreaterThan(amount)); + + _adjustCumulativeFeeMultiplier(amount, collateralPool); + } + + emit FinalFeesPaid(amount.rawValue); + + StoreInterface store = _getStore(); + collateralCurrency.safeIncreaseAllowance(address(store), amount.rawValue); + store.payOracleFeesErc20(address(collateralCurrency), amount); + } + + function _gulp() internal { + FixedPoint.Unsigned memory currentPfc = _pfc(); + FixedPoint.Unsigned memory currentBalance = FixedPoint.Unsigned(collateralCurrency.balanceOf(address(this))); + if (currentPfc.isLessThan(currentBalance)) { + cumulativeFeeMultiplier = cumulativeFeeMultiplier.mul(currentBalance.div(currentPfc)); + } + } + + function _pfc() internal view virtual returns (FixedPoint.Unsigned memory); + + function _getStore() internal view returns (StoreInterface) { + return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store)); + } + + function _computeFinalFees() internal view returns (FixedPoint.Unsigned memory finalFees) { + StoreInterface store = _getStore(); + return store.computeFinalFee(address(collateralCurrency)); + } + + // Returns the user's collateral minus any fees that have been subtracted since it was originally + // deposited into the contract. Note: if the contract has paid fees since it was deployed, the raw + // value should be larger than the returned value. + function _getFeeAdjustedCollateral( + FixedPoint.Unsigned memory rawCollateral + ) internal view returns (FixedPoint.Unsigned memory collateral) { + return rawCollateral.mul(cumulativeFeeMultiplier); + } + + // Returns the user's collateral minus any pending fees that have yet to be subtracted. + function _getPendingRegularFeeAdjustedCollateral( + FixedPoint.Unsigned memory rawCollateral + ) internal view returns (FixedPoint.Unsigned memory) { + (, , FixedPoint.Unsigned memory currentTotalOutstandingRegularFees) = getOutstandingRegularFees( + getCurrentTime() + ); + if (currentTotalOutstandingRegularFees.isEqual(FixedPoint.fromUnscaledUint(0))) return rawCollateral; + + // Calculate the total outstanding regular fee as a fraction of the total contract PFC. + FixedPoint.Unsigned memory effectiveOutstandingFee = currentTotalOutstandingRegularFees.divCeil(_pfc()); + + // Scale as rawCollateral* (1 - effectiveOutstandingFee) to apply the pro-rata amount to the regular fee. + return rawCollateral.mul(FixedPoint.fromUnscaledUint(1).sub(effectiveOutstandingFee)); + } + + // Converts a user-readable collateral value into a raw value that accounts for already-assessed fees. If any fees + // have been taken from this contract in the past, then the raw value will be larger than the user-readable value. + function _convertToRawCollateral( + FixedPoint.Unsigned memory collateral + ) internal view returns (FixedPoint.Unsigned memory rawCollateral) { + return collateral.div(cumulativeFeeMultiplier); + } + + // Decrease rawCollateral by a fee-adjusted collateralToRemove amount. Fee adjustment scales up collateralToRemove + // by dividing it by cumulativeFeeMultiplier. There is potential for this quotient to be floored, therefore + // rawCollateral is decreased by less than expected. Because this method is usually called in conjunction with an + // actual removal of collateral from this contract, return the fee-adjusted amount that the rawCollateral is + // decreased by so that the caller can minimize error between collateral removed and rawCollateral debited. + function _removeCollateral( + FixedPoint.Unsigned storage rawCollateral, + FixedPoint.Unsigned memory collateralToRemove + ) internal returns (FixedPoint.Unsigned memory removedCollateral) { + FixedPoint.Unsigned memory initialBalance = _getFeeAdjustedCollateral(rawCollateral); + FixedPoint.Unsigned memory adjustedCollateral = _convertToRawCollateral(collateralToRemove); + rawCollateral.rawValue = rawCollateral.sub(adjustedCollateral).rawValue; + removedCollateral = initialBalance.sub(_getFeeAdjustedCollateral(rawCollateral)); + } + + // Increase rawCollateral by a fee-adjusted collateralToAdd amount. Fee adjustment scales up collateralToAdd + // by dividing it by cumulativeFeeMultiplier. There is potential for this quotient to be floored, therefore + // rawCollateral is increased by less than expected. Because this method is usually called in conjunction with an + // actual addition of collateral to this contract, return the fee-adjusted amount that the rawCollateral is + // increased by so that the caller can minimize error between collateral added and rawCollateral credited. + // NOTE: This return value exists only for the sake of symmetry with _removeCollateral. We don't actually use it + // because we are OK if more collateral is stored in the contract than is represented by rawTotalPositionCollateral. + function _addCollateral( + FixedPoint.Unsigned storage rawCollateral, + FixedPoint.Unsigned memory collateralToAdd + ) internal returns (FixedPoint.Unsigned memory addedCollateral) { + FixedPoint.Unsigned memory initialBalance = _getFeeAdjustedCollateral(rawCollateral); + FixedPoint.Unsigned memory adjustedCollateral = _convertToRawCollateral(collateralToAdd); + rawCollateral.rawValue = rawCollateral.add(adjustedCollateral).rawValue; + addedCollateral = _getFeeAdjustedCollateral(rawCollateral).sub(initialBalance); + } + + // Scale the cumulativeFeeMultiplier by the ratio of fees paid to the current available collateral. + function _adjustCumulativeFeeMultiplier( + FixedPoint.Unsigned memory amount, + FixedPoint.Unsigned memory currentPfc + ) internal { + FixedPoint.Unsigned memory effectiveFee = amount.divCeil(currentPfc); + cumulativeFeeMultiplier = cumulativeFeeMultiplier.mul(FixedPoint.fromUnscaledUint(1).sub(effectiveFee)); + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/FundingRateApplier.sol b/contracts/external/uma/core/contracts/financial-templates/common/FundingRateApplier.sol new file mode 100644 index 000000000..f3ebc8d8c --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/common/FundingRateApplier.sol @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/utils/math/SafeCast.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +import "../../common/implementation/Lockable.sol"; +import "../../common/implementation/FixedPoint.sol"; +import "../../common/implementation/Testable.sol"; +import "../../common/implementation/AncillaryData.sol"; + +import "../../data-verification-mechanism/implementation/Constants.sol"; +import "../../optimistic-oracle-v2/interfaces/OptimisticOracleInterface.sol"; +import "../perpetual-multiparty/ConfigStoreInterface.sol"; + +import "./EmergencyShutdownable.sol"; +import "./FeePayer.sol"; + +/** + * @title FundingRateApplier contract. + * @notice Provides funding rate payment functionality for the Perpetual contract. + */ + +abstract contract FundingRateApplier is EmergencyShutdownable, FeePayer { + using FixedPoint for FixedPoint.Unsigned; + using FixedPoint for FixedPoint.Signed; + using SafeERC20 for IERC20; + using SafeMath for uint256; + + /**************************************** + * FUNDING RATE APPLIER DATA STRUCTURES * + ****************************************/ + + struct FundingRate { + // Current funding rate value. + FixedPoint.Signed rate; + // Identifier to retrieve the funding rate. + bytes32 identifier; + // Tracks the cumulative funding payments that have been paid to the sponsors. + // The multiplier starts at 1, and is updated by computing cumulativeFundingRateMultiplier * (1 + effectivePayment). + // Put another way, the cumulativeFeeMultiplier is (1 + effectivePayment1) * (1 + effectivePayment2) ... + // For example: + // The cumulativeFundingRateMultiplier should start at 1. + // If a 1% funding payment is paid to sponsors, the multiplier should update to 1.01. + // If another 1% fee is charged, the multiplier should be 1.01^2 (1.0201). + FixedPoint.Unsigned cumulativeMultiplier; + // Most recent time that the funding rate was updated. + uint256 updateTime; + // Most recent time that the funding rate was applied and changed the cumulative multiplier. + uint256 applicationTime; + // The time for the active (if it exists) funding rate proposal. 0 otherwise. + uint256 proposalTime; + } + + FundingRate public fundingRate; + + // Remote config store managed an owner. + ConfigStoreInterface public configStore; + + /**************************************** + * EVENTS * + ****************************************/ + + event FundingRateUpdated(int256 newFundingRate, uint256 indexed updateTime, uint256 reward); + + /**************************************** + * MODIFIERS * + ****************************************/ + + // This is overridden to both pay fees (which is done by applyFundingRate()) and apply the funding rate. + modifier fees() override { + // Note: the funding rate is applied on every fee-accruing transaction, where the total change is simply the + // rate applied linearly since the last update. This implies that the compounding rate depends on the frequency + // of update transactions that have this modifier, and it never reaches the ideal of continuous compounding. + // This approximate-compounding pattern is common in the Ethereum ecosystem because of the complexity of + // compounding data on-chain. + applyFundingRate(); + _; + } + + // Note: this modifier is intended to be used if the caller intends to _only_ pay regular fees. + modifier paysRegularFees() { + payRegularFees(); + _; + } + + /** + * @notice Constructs the FundingRateApplier contract. Called by child contracts. + * @param _fundingRateIdentifier identifier that tracks the funding rate of this contract. + * @param _collateralAddress address of the collateral token. + * @param _finderAddress Finder used to discover financial-product-related contracts. + * @param _configStoreAddress address of the remote configuration store managed by an external owner. + * @param _tokenScaling initial scaling to apply to the token value (i.e. scales the tracking index). + * @param _timerAddress address of the timer contract in test envs, otherwise 0x0. + */ + constructor( + bytes32 _fundingRateIdentifier, + address _collateralAddress, + address _finderAddress, + address _configStoreAddress, + FixedPoint.Unsigned memory _tokenScaling, + address _timerAddress + ) FeePayer(_collateralAddress, _finderAddress, _timerAddress) EmergencyShutdownable() { + uint256 currentTime = getCurrentTime(); + fundingRate.updateTime = currentTime; + fundingRate.applicationTime = currentTime; + + // Seed the cumulative multiplier with the token scaling, from which it will be scaled as funding rates are + // applied over time. + fundingRate.cumulativeMultiplier = _tokenScaling; + + fundingRate.identifier = _fundingRateIdentifier; + configStore = ConfigStoreInterface(_configStoreAddress); + } + + /** + * @notice This method takes 3 distinct actions: + * 1. Pays out regular fees. + * 2. If possible, resolves the outstanding funding rate proposal, pulling the result in and paying out the rewards. + * 3. Applies the prevailing funding rate over the most recent period. + */ + function applyFundingRate() public paysRegularFees nonReentrant { + _applyEffectiveFundingRate(); + } + + /** + * @notice Proposes a new funding rate. Proposer receives a reward if correct. + * @param rate funding rate being proposed. + * @param timestamp time at which the funding rate was computed. + */ + function proposeFundingRate( + FixedPoint.Signed memory rate, + uint256 timestamp + ) external fees nonReentrant returns (FixedPoint.Unsigned memory totalBond) { + require(fundingRate.proposalTime == 0); + _validateFundingRate(rate); + + // Timestamp must be after the last funding rate update time, within the last 30 minutes. + uint256 currentTime = getCurrentTime(); + uint256 updateTime = fundingRate.updateTime; + require(timestamp > updateTime && timestamp >= currentTime.sub(_getConfig().proposalTimePastLimit)); + + // Set the proposal time in order to allow this contract to track this request. + fundingRate.proposalTime = timestamp; + + OptimisticOracleInterface optimisticOracle = _getOptimisticOracle(); + + // Set up optimistic oracle. + bytes32 identifier = fundingRate.identifier; + bytes memory ancillaryData = _getAncillaryData(); + // Note: requestPrice will revert if `timestamp` is less than the current block timestamp. + optimisticOracle.requestPrice(identifier, timestamp, ancillaryData, collateralCurrency, 0); + totalBond = FixedPoint.Unsigned( + optimisticOracle.setBond( + identifier, + timestamp, + ancillaryData, + _pfc().mul(_getConfig().proposerBondPercentage).rawValue + ) + ); + + // Pull bond from caller and send to optimistic oracle. + if (totalBond.isGreaterThan(0)) { + collateralCurrency.safeTransferFrom(msg.sender, address(this), totalBond.rawValue); + collateralCurrency.safeIncreaseAllowance(address(optimisticOracle), totalBond.rawValue); + } + + optimisticOracle.proposePriceFor( + msg.sender, + address(this), + identifier, + timestamp, + ancillaryData, + rate.rawValue + ); + } + + // Returns a token amount scaled by the current funding rate multiplier. + // Note: if the contract has paid fees since it was deployed, the raw value should be larger than the returned value. + function _getFundingRateAppliedTokenDebt( + FixedPoint.Unsigned memory rawTokenDebt + ) internal view returns (FixedPoint.Unsigned memory tokenDebt) { + return rawTokenDebt.mul(fundingRate.cumulativeMultiplier); + } + + function _getOptimisticOracle() internal view returns (OptimisticOracleInterface) { + return OptimisticOracleInterface(finder.getImplementationAddress(OracleInterfaces.OptimisticOracle)); + } + + function _getConfig() internal returns (ConfigStoreInterface.ConfigSettings memory) { + return configStore.updateAndGetCurrentConfig(); + } + + function _updateFundingRate() internal { + uint256 proposalTime = fundingRate.proposalTime; + // If there is no pending proposal then do nothing. Otherwise check to see if we can update the funding rate. + if (proposalTime != 0) { + // Attempt to update the funding rate. + OptimisticOracleInterface optimisticOracle = _getOptimisticOracle(); + bytes32 identifier = fundingRate.identifier; + bytes memory ancillaryData = _getAncillaryData(); + + // Try to get the price from the optimistic oracle. This call will revert if the request has not resolved + // yet. If the request has not resolved yet, then we need to do additional checks to see if we should + // "forget" the pending proposal and allow new proposals to update the funding rate. + try optimisticOracle.settleAndGetPrice(identifier, proposalTime, ancillaryData) returns (int256 price) { + // If successful, determine if the funding rate state needs to be updated. + // If the request is more recent than the last update then we should update it. + uint256 lastUpdateTime = fundingRate.updateTime; + if (proposalTime >= lastUpdateTime) { + // Update funding rates + fundingRate.rate = FixedPoint.Signed(price); + fundingRate.updateTime = proposalTime; + + // If there was no dispute, send a reward. + FixedPoint.Unsigned memory reward = FixedPoint.fromUnscaledUint(0); + OptimisticOracleInterface.Request memory request = optimisticOracle.getRequest( + address(this), + identifier, + proposalTime, + ancillaryData + ); + if (request.disputer == address(0)) { + reward = _pfc().mul(_getConfig().rewardRatePerSecond).mul(proposalTime.sub(lastUpdateTime)); + if (reward.isGreaterThan(0)) { + _adjustCumulativeFeeMultiplier(reward, _pfc()); + collateralCurrency.safeTransfer(request.proposer, reward.rawValue); + } + } + + // This event will only be emitted after the fundingRate struct's "updateTime" has been set + // to the latest proposal's proposalTime, indicating that the proposal has been published. + // So, it suffices to just emit fundingRate.updateTime here. + emit FundingRateUpdated(fundingRate.rate.rawValue, fundingRate.updateTime, reward.rawValue); + } + + // Set proposal time to 0 since this proposal has now been resolved. + fundingRate.proposalTime = 0; + } catch { + // Stop tracking and allow other proposals to come in if: + // - The requester address is empty, indicating that the Oracle does not know about this funding rate + // request. This is possible if the Oracle is replaced while the price request is still pending. + // - The request has been disputed. + OptimisticOracleInterface.Request memory request = optimisticOracle.getRequest( + address(this), + identifier, + proposalTime, + ancillaryData + ); + if (request.disputer != address(0) || request.proposer == address(0)) { + fundingRate.proposalTime = 0; + } + } + } + } + + // Constraining the range of funding rates limits the PfC for any dishonest proposer and enhances the + // perpetual's security. For example, let's examine the case where the max and min funding rates + // are equivalent to +/- 500%/year. This 1000% funding rate range allows a 8.6% profit from corruption for a + // proposer who can deter honest proposers for 74 hours: + // 1000%/year / 360 days / 24 hours * 74 hours max attack time = ~ 8.6%. + // How would attack work? Imagine that the market is very volatile currently and that the "true" funding + // rate for the next 74 hours is -500%, but a dishonest proposer successfully proposes a rate of +500% + // (after a two hour liveness) and disputes honest proposers for the next 72 hours. This results in a funding + // rate error of 1000% for 74 hours, until the DVM can set the funding rate back to its correct value. + function _validateFundingRate(FixedPoint.Signed memory rate) internal { + require( + rate.isLessThanOrEqual(_getConfig().maxFundingRate) && + rate.isGreaterThanOrEqual(_getConfig().minFundingRate) + ); + } + + // Fetches a funding rate from the Store, determines the period over which to compute an effective fee, + // and multiplies the current multiplier by the effective fee. + // A funding rate < 1 will reduce the multiplier, and a funding rate of > 1 will increase the multiplier. + // Note: 1 is set as the neutral rate because there are no negative numbers in FixedPoint, so we decide to treat + // values < 1 as "negative". + function _applyEffectiveFundingRate() internal { + // If contract is emergency shutdown, then the funding rate multiplier should no longer change. + if (emergencyShutdownTimestamp != 0) { + return; + } + + uint256 currentTime = getCurrentTime(); + uint256 paymentPeriod = currentTime.sub(fundingRate.applicationTime); + + _updateFundingRate(); // Update the funding rate if there is a resolved proposal. + fundingRate.cumulativeMultiplier = _calculateEffectiveFundingRate( + paymentPeriod, + fundingRate.rate, + fundingRate.cumulativeMultiplier + ); + + fundingRate.applicationTime = currentTime; + } + + function _calculateEffectiveFundingRate( + uint256 paymentPeriodSeconds, + FixedPoint.Signed memory fundingRatePerSecond, + FixedPoint.Unsigned memory currentCumulativeFundingRateMultiplier + ) internal pure returns (FixedPoint.Unsigned memory newCumulativeFundingRateMultiplier) { + // Note: this method uses named return variables to save a little bytecode. + + // The overall formula that this function is performing: + // newCumulativeFundingRateMultiplier = + // (1 + (fundingRatePerSecond * paymentPeriodSeconds)) * currentCumulativeFundingRateMultiplier. + FixedPoint.Signed memory ONE = FixedPoint.fromUnscaledInt(1); + + // Multiply the per-second rate over the number of seconds that have elapsed to get the period rate. + FixedPoint.Signed memory periodRate = fundingRatePerSecond.mul(SafeCast.toInt256(paymentPeriodSeconds)); + + // Add one to create the multiplier to scale the existing fee multiplier. + FixedPoint.Signed memory signedPeriodMultiplier = ONE.add(periodRate); + + // Max with 0 to ensure the multiplier isn't negative, then cast to an Unsigned. + FixedPoint.Unsigned memory unsignedPeriodMultiplier = FixedPoint.fromSigned( + FixedPoint.max(signedPeriodMultiplier, FixedPoint.fromUnscaledInt(0)) + ); + + // Multiply the existing cumulative funding rate multiplier by the computed period multiplier to get the new + // cumulative funding rate multiplier. + newCumulativeFundingRateMultiplier = currentCumulativeFundingRateMultiplier.mul(unsignedPeriodMultiplier); + } + + /** + * @dev We do not need to check that the ancillary data length is less than the hardcoded max length in the + * OptimisticOracle because the length of the ancillary data is fixed in this function. + */ + function _getAncillaryData() internal view returns (bytes memory) { + // When ancillary data is passed to the optimistic oracle, it should be tagged with the token address + // whose funding rate it's trying to get so that financial contracts can re-use the same identifier for + // multiple funding rate products. + return AncillaryData.appendKeyValueAddress("", "tokenAddress", _getTokenAddress()); + } + + function _getTokenAddress() internal view virtual returns (address); +} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/SyntheticToken.sol b/contracts/external/uma/core/contracts/financial-templates/common/SyntheticToken.sol new file mode 100644 index 000000000..20dac683e --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/common/SyntheticToken.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; +import "../../common/implementation/ExpandedERC20.sol"; +import "../../common/implementation/Lockable.sol"; + +/** + * @title Burnable and mintable ERC20. + * @dev The contract deployer will initially be the only minter, burner and owner capable of adding new roles. + */ + +contract SyntheticToken is ExpandedERC20, Lockable { + /** + * @notice Constructs the SyntheticToken. + * @param tokenName The name which describes the new token. + * @param tokenSymbol The ticker abbreviation of the name. Ideally < 5 chars. + * @param tokenDecimals The number of decimals to define token precision. + */ + constructor( + string memory tokenName, + string memory tokenSymbol, + uint8 tokenDecimals + ) ExpandedERC20(tokenName, tokenSymbol, tokenDecimals) nonReentrant() {} + + /** + * @notice Add Minter role to account. + * @dev The caller must have the Owner role. + * @param account The address to which the Minter role is added. + */ + function addMinter(address account) external override nonReentrant { + addMember(uint256(Roles.Minter), account); + } + + /** + * @notice Remove Minter role from account. + * @dev The caller must have the Owner role. + * @param account The address from which the Minter role is removed. + */ + function removeMinter(address account) external nonReentrant { + removeMember(uint256(Roles.Minter), account); + } + + /** + * @notice Add Burner role to account. + * @dev The caller must have the Owner role. + * @param account The address to which the Burner role is added. + */ + function addBurner(address account) external override nonReentrant { + addMember(uint256(Roles.Burner), account); + } + + /** + * @notice Removes Burner role from account. + * @dev The caller must have the Owner role. + * @param account The address from which the Burner role is removed. + */ + function removeBurner(address account) external nonReentrant { + removeMember(uint256(Roles.Burner), account); + } + + /** + * @notice Reset Owner role to account. + * @dev The caller must have the Owner role. + * @param account The new holder of the Owner role. + */ + function resetOwner(address account) external override nonReentrant { + resetMember(uint256(Roles.Owner), account); + } + + /** + * @notice Checks if a given account holds the Minter role. + * @param account The address which is checked for the Minter role. + * @return bool True if the provided account is a Minter. + */ + function isMinter(address account) public view nonReentrantView returns (bool) { + return holdsRole(uint256(Roles.Minter), account); + } + + /** + * @notice Checks if a given account holds the Burner role. + * @param account The address which is checked for the Burner role. + * @return bool True if the provided account is a Burner. + */ + function isBurner(address account) public view nonReentrantView returns (bool) { + return holdsRole(uint256(Roles.Burner), account); + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/TokenFactory.sol b/contracts/external/uma/core/contracts/financial-templates/common/TokenFactory.sol new file mode 100644 index 000000000..b7909e660 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/common/TokenFactory.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "./SyntheticToken.sol"; +import "../../common/interfaces/ExpandedIERC20.sol"; +import "../../common/implementation/Lockable.sol"; + +/** + * @title Factory for creating new mintable and burnable tokens. + */ + +contract TokenFactory is Lockable { + /** + * @notice Create a new token and return it to the caller. + * @dev The caller will become the only minter and burner and the new owner capable of assigning the roles. + * @param tokenName used to describe the new token. + * @param tokenSymbol short ticker abbreviation of the name. Ideally < 5 chars. + * @param tokenDecimals used to define the precision used in the token's numerical representation. + * @return newToken an instance of the newly created token interface. + */ + function createToken( + string calldata tokenName, + string calldata tokenSymbol, + uint8 tokenDecimals + ) external nonReentrant returns (ExpandedIERC20 newToken) { + SyntheticToken mintableToken = new SyntheticToken(tokenName, tokenSymbol, tokenDecimals); + mintableToken.resetOwner(msg.sender); + newToken = ExpandedIERC20(address(mintableToken)); + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/WETH9.sol b/contracts/external/uma/core/contracts/financial-templates/common/WETH9.sol new file mode 100644 index 000000000..02dc9fb8c --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/common/WETH9.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +// Copied from the verified code from Etherscan: +// https://etherscan.io/address/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2#code +// And then updated for Solidity version 0.6. Specific changes: +// * Change `function() public` into `receive() external` and `fallback() external` +// * Change `this.balance` to `address(this).balance` +// * Ran prettier +contract WETH9 { + string public name = "Wrapped Ether"; + string public symbol = "WETH"; + uint8 public decimals = 18; + + event Approval(address indexed src, address indexed guy, uint256 wad); + event Transfer(address indexed src, address indexed dst, uint256 wad); + event Deposit(address indexed dst, uint256 wad); + event Withdrawal(address indexed src, uint256 wad); + + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + + receive() external payable { + deposit(); + } + + fallback() external payable { + deposit(); + } + + function deposit() public payable { + balanceOf[msg.sender] += msg.value; + emit Deposit(msg.sender, msg.value); + } + + function withdraw(uint256 wad) public { + require(balanceOf[msg.sender] >= wad); + balanceOf[msg.sender] -= wad; + payable(msg.sender).transfer(wad); + emit Withdrawal(msg.sender, wad); + } + + function totalSupply() public view returns (uint256) { + return address(this).balance; + } + + function approve(address guy, uint256 wad) public returns (bool) { + allowance[msg.sender][guy] = wad; + emit Approval(msg.sender, guy, wad); + return true; + } + + function transfer(address dst, uint256 wad) public returns (bool) { + return transferFrom(msg.sender, dst, wad); + } + + function transferFrom(address src, address dst, uint256 wad) public returns (bool) { + require(balanceOf[src] >= wad); + + if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) { + require(allowance[src][msg.sender] >= wad); + allowance[src][msg.sender] -= wad; + } + + balanceOf[src] -= wad; + balanceOf[dst] += wad; + + emit Transfer(src, dst, wad); + + return true; + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/CoveredCallFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/CoveredCallFinancialProductLibrary.sol new file mode 100644 index 000000000..103989865 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/CoveredCallFinancialProductLibrary.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; +import "./FinancialProductLibrary.sol"; +import "../../../../common/implementation/Lockable.sol"; + +/** + * @title Covered Call Financial Product Library. + * @notice Adds custom price transformation logic to modify the behavior of the expiring multi party contract. The + * contract holds say 1 WETH in collateral and pays out a portion of that, at expiry, if ETHUSD is above a set strike. If + * ETHUSD is below that strike, the contract pays out 0. The fraction paid out if above the strike is defined by + * (oraclePrice - strikePrice) / oraclePrice; + * Example: expiry is DEC 31. Strike is $400. Each token is backed by 1 WETH. + * If ETHUSD = $600 at expiry, the call is $200 in the money, and the contract pays out 0.333 WETH (worth $200). + * If ETHUSD = $800 at expiry, the call is $400 in the money, and the contract pays out 0.5 WETH (worth $400). + * If ETHUSD =< $400 at expiry, the call is out of the money, and the contract pays out 0 WETH. + */ +contract CoveredCallFinancialProductLibrary is FinancialProductLibrary, Lockable { + using FixedPoint for FixedPoint.Unsigned; + + mapping(address => FixedPoint.Unsigned) private financialProductStrikes; + + /** + * @notice Enables any address to set the strike price for an associated financial product. + * @param financialProduct address of the financial product. + * @param strikePrice the strike price for the covered call to be applied to the financial product. + * @dev Note: a) Any address can set the initial strike price b) A strike price cannot be 0. + * c) A strike price can only be set once to prevent the deployer from changing the strike after the fact. + * d) For safety, a strike price should be set before depositing any synthetic tokens in a liquidity pool. + * e) financialProduct must expose an expirationTimestamp method. + */ + function setFinancialProductStrike( + address financialProduct, + FixedPoint.Unsigned memory strikePrice + ) public nonReentrant { + require(strikePrice.isGreaterThan(0), "Cant set 0 strike"); + require(financialProductStrikes[financialProduct].isEqual(0), "Strike already set"); + require(ExpiringContractInterface(financialProduct).expirationTimestamp() != 0, "Invalid EMP contract"); + financialProductStrikes[financialProduct] = strikePrice; + } + + /** + * @notice Returns the strike price associated with a given financial product address. + * @param financialProduct address of the financial product. + * @return strikePrice for the associated financial product. + */ + function getStrikeForFinancialProduct( + address financialProduct + ) public view nonReentrantView returns (FixedPoint.Unsigned memory) { + return financialProductStrikes[financialProduct]; + } + + /** + * @notice Returns a transformed price by applying the call option payout structure. + * @param oraclePrice price from the oracle to be transformed. + * @param requestTime timestamp the oraclePrice was requested at. + * @return transformedPrice the input oracle price with the price transformation logic applied to it. + */ + function transformPrice( + FixedPoint.Unsigned memory oraclePrice, + uint256 requestTime + ) public view override nonReentrantView returns (FixedPoint.Unsigned memory) { + FixedPoint.Unsigned memory strike = financialProductStrikes[msg.sender]; + require(strike.isGreaterThan(0), "Caller has no strike"); + // If price request is made before expiry, return 1. Thus we can keep the contract 100% collateralized with + // each token backed 1:1 by collateral currency. + if (requestTime < ExpiringContractInterface(msg.sender).expirationTimestamp()) { + return FixedPoint.fromUnscaledUint(1); + } + if (oraclePrice.isLessThanOrEqual(strike)) { + return FixedPoint.fromUnscaledUint(0); + } else { + // Token expires to be worth the fraction of a collateral token that's in the money. + // eg if ETHUSD is $500 and strike is $400, token is redeemable for 100/500 = 0.2 WETH (worth $100). + // Note: oraclePrice cannot be 0 here because it would always satisfy the if above because 0 <= x is always + // true. + return (oraclePrice.sub(strike)).div(oraclePrice); + } + } + + /** + * @notice Returns a transformed collateral requirement by applying the covered call payout structure. + * @return transformedCollateralRequirement the input collateral requirement with the transformation logic applied to it. + */ + function transformCollateralRequirement( + FixedPoint.Unsigned memory, + FixedPoint.Unsigned memory + ) public view override nonReentrantView returns (FixedPoint.Unsigned memory) { + FixedPoint.Unsigned memory strike = financialProductStrikes[msg.sender]; + require(strike.isGreaterThan(0), "Caller has no strike"); + + // Always return 1 because option must be collateralized by 1 token. + return FixedPoint.fromUnscaledUint(1); + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/FinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/FinancialProductLibrary.sol new file mode 100644 index 000000000..ac7de39b0 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/FinancialProductLibrary.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; +import "../../../../common/implementation/FixedPoint.sol"; + +interface ExpiringContractInterface { + function expirationTimestamp() external view returns (uint256); +} + +/** + * @title Financial product library contract + * @notice Provides price and collateral requirement transformation interfaces that can be overridden by custom + * Financial product library implementations. + */ +abstract contract FinancialProductLibrary { + using FixedPoint for FixedPoint.Unsigned; + + /** + * @notice Transforms a given oracle price using the financial product libraries transformation logic. + * @param oraclePrice input price returned by the DVM to be transformed. + * @return transformedOraclePrice input oraclePrice with the transformation function applied. + */ + function transformPrice( + FixedPoint.Unsigned memory oraclePrice, + uint256 + ) public view virtual returns (FixedPoint.Unsigned memory) { + return oraclePrice; + } + + /** + * @notice Transforms a given collateral requirement using the financial product libraries transformation logic. + * @param collateralRequirement input collateral requirement to be transformed. + * @return transformedCollateralRequirement input collateral requirement with the transformation function applied. + */ + function transformCollateralRequirement( + FixedPoint.Unsigned memory, + FixedPoint.Unsigned memory collateralRequirement + ) public view virtual returns (FixedPoint.Unsigned memory) { + return collateralRequirement; + } + + /** + * @notice Transforms a given price identifier using the financial product libraries transformation logic. + * @param priceIdentifier input price identifier defined for the financial contract. + * @return transformedPriceIdentifier input price identifier with the transformation function applied. + */ + function transformPriceIdentifier(bytes32 priceIdentifier, uint256) public view virtual returns (bytes32) { + return priceIdentifier; + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/KpiOptionsFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/KpiOptionsFinancialProductLibrary.sol new file mode 100644 index 000000000..66550eacf --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/KpiOptionsFinancialProductLibrary.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; +import "./FinancialProductLibrary.sol"; +import "../../../../common/implementation/Lockable.sol"; + +/** + * @title KPI Options Financial Product Library + * @notice Adds custom tranformation logic to modify the price and collateral requirement behavior of the expiring multi party contract. + * If a price request is made pre-expiry, the price should always be set to 2 and the collateral requirement should be set to 1. + * Post-expiry, the collateral requirement is left as 1 and the price is left unchanged. + */ +contract KpiOptionsFinancialProductLibrary is FinancialProductLibrary, Lockable { + /** + * @notice Returns a transformed price for pre-expiry price requests. + * @param oraclePrice price from the oracle to be transformed. + * @param requestTime timestamp the oraclePrice was requested at. + * @return transformedPrice the input oracle price with the price transformation logic applied to it. + */ + function transformPrice( + FixedPoint.Unsigned memory oraclePrice, + uint256 requestTime + ) public view override nonReentrantView returns (FixedPoint.Unsigned memory) { + // If price request is made before expiry, return 2. Thus we can keep the contract 100% collateralized with + // each token backed 1:2 by collateral currency. Post-expiry, leave unchanged. + if (requestTime < ExpiringContractInterface(msg.sender).expirationTimestamp()) { + return FixedPoint.fromUnscaledUint(2); + } else { + return oraclePrice; + } + } + + /** + * @notice Returns a transformed collateral requirement that is set to be equivalent to 2 tokens pre-expiry. + * @return transformedCollateralRequirement the input collateral requirement with the transformation logic applied to it. + */ + function transformCollateralRequirement( + FixedPoint.Unsigned memory, + FixedPoint.Unsigned memory + ) public view override nonReentrantView returns (FixedPoint.Unsigned memory) { + // Always return 1. + return FixedPoint.fromUnscaledUint(1); + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/PostExpirationIdentifierTransformationFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/PostExpirationIdentifierTransformationFinancialProductLibrary.sol new file mode 100644 index 000000000..a8359d9f1 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/PostExpirationIdentifierTransformationFinancialProductLibrary.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; +import "./FinancialProductLibrary.sol"; +import "../../../../common/implementation/Lockable.sol"; + +/** + * @title Post-Expiration Identifier Transformation Financial Product Library + * @notice Adds custom identifier transformation to enable a financial contract to use two different identifiers, depending + * on when a price request is made. If the request is made at or after expiration then a transformation is made to the identifier + * & if it is before expiration then the original identifier is returned. This library enables self referential + * TWAP identifier to be used on synthetics pre-expiration, in conjunction with a separate identifier at expiration. + */ +contract PostExpirationIdentifierTransformationFinancialProductLibrary is FinancialProductLibrary, Lockable { + mapping(address => bytes32) financialProductTransformedIdentifiers; + + /** + * @notice Enables the deployer of the library to set the transformed identifier for an associated financial product. + * @param financialProduct address of the financial product. + * @param transformedIdentifier the identifier for the financial product to be used if the contract is post expiration. + * @dev Note: a) Any address can set identifier transformations b) The identifier can't be set to blank. c) A + * transformed price can only be set once to prevent the deployer from changing it after the fact. d) financialProduct + * must expose an expirationTimestamp method. + */ + function setFinancialProductTransformedIdentifier( + address financialProduct, + bytes32 transformedIdentifier + ) public nonReentrant { + require(transformedIdentifier != "", "Cant set to empty transformation"); + require(financialProductTransformedIdentifiers[financialProduct] == "", "Transformation already set"); + require(ExpiringContractInterface(financialProduct).expirationTimestamp() != 0, "Invalid EMP contract"); + financialProductTransformedIdentifiers[financialProduct] = transformedIdentifier; + } + + /** + * @notice Returns the transformed identifier associated with a given financial product address. + * @param financialProduct address of the financial product. + * @return transformed identifier for the associated financial product. + */ + function getTransformedIdentifierForFinancialProduct( + address financialProduct + ) public view nonReentrantView returns (bytes32) { + return financialProductTransformedIdentifiers[financialProduct]; + } + + /** + * @notice Returns a transformed price identifier if the contract is post-expiration and no transformation if pre. + * @param identifier input price identifier to be transformed. + * @param requestTime timestamp the identifier is to be used at. + * @return transformedPriceIdentifier the input price identifier with the transformation logic applied to it. + */ + function transformPriceIdentifier( + bytes32 identifier, + uint256 requestTime + ) public view override nonReentrantView returns (bytes32) { + require(financialProductTransformedIdentifiers[msg.sender] != "", "Caller has no transformation"); + // If the request time is after contract expiration then return the transformed identifier. Else, return the + // original price identifier. + if (requestTime < ExpiringContractInterface(msg.sender).expirationTimestamp()) { + return identifier; + } else { + return financialProductTransformedIdentifiers[msg.sender]; + } + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/PreExpirationIdentifierTransformationFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/PreExpirationIdentifierTransformationFinancialProductLibrary.sol new file mode 100644 index 000000000..81f28a4b9 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/PreExpirationIdentifierTransformationFinancialProductLibrary.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; +import "./FinancialProductLibrary.sol"; +import "../../../../common/implementation/Lockable.sol"; + +/** + * @title Pre-Expiration Identifier Transformation Financial Product Library + * @notice Adds custom identifier transformation to enable a financial contract to use two different identifiers, depending + * on when a price request is made. If the request is made before expiration then a transformation is made to the identifier + * & if it is at or after expiration then the original identifier is returned. This library enables self referential + * TWAP identifier to be used on synthetics pre-expiration, in conjunction with a separate identifier at expiration. + */ +contract PreExpirationIdentifierTransformationFinancialProductLibrary is FinancialProductLibrary, Lockable { + mapping(address => bytes32) financialProductTransformedIdentifiers; + + /** + * @notice Enables the deployer of the library to set the transformed identifier for an associated financial product. + * @param financialProduct address of the financial product. + * @param transformedIdentifier the identifier for the financial product to be used if the contract is pre expiration. + * @dev Note: a) Any address can set identifier transformations b) The identifier can't be set to blank. c) A + * transformed price can only be set once to prevent the deployer from changing it after the fact. d) financialProduct + * must expose an expirationTimestamp method. + */ + function setFinancialProductTransformedIdentifier( + address financialProduct, + bytes32 transformedIdentifier + ) public nonReentrant { + require(transformedIdentifier != "", "Cant set to empty transformation"); + require(financialProductTransformedIdentifiers[financialProduct] == "", "Transformation already set"); + require(ExpiringContractInterface(financialProduct).expirationTimestamp() != 0, "Invalid EMP contract"); + financialProductTransformedIdentifiers[financialProduct] = transformedIdentifier; + } + + /** + * @notice Returns the transformed identifier associated with a given financial product address. + * @param financialProduct address of the financial product. + * @return transformed identifier for the associated financial product. + */ + function getTransformedIdentifierForFinancialProduct( + address financialProduct + ) public view nonReentrantView returns (bytes32) { + return financialProductTransformedIdentifiers[financialProduct]; + } + + /** + * @notice Returns a transformed price identifier if the contract is pre-expiration and no transformation if post. + * @param identifier input price identifier to be transformed. + * @param requestTime timestamp the identifier is to be used at. + * @return transformedPriceIdentifier the input price identifier with the transformation logic applied to it. + */ + function transformPriceIdentifier( + bytes32 identifier, + uint256 requestTime + ) public view override nonReentrantView returns (bytes32) { + require(financialProductTransformedIdentifiers[msg.sender] != "", "Caller has no transformation"); + // If the request time is before contract expiration then return the transformed identifier. Else, return the + // original price identifier. + if (requestTime < ExpiringContractInterface(msg.sender).expirationTimestamp()) { + return financialProductTransformedIdentifiers[msg.sender]; + } else { + return identifier; + } + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/StructuredNoteFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/StructuredNoteFinancialProductLibrary.sol new file mode 100644 index 000000000..3afa17855 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/StructuredNoteFinancialProductLibrary.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; +import "./FinancialProductLibrary.sol"; +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "../../../../common/implementation/Lockable.sol"; + +/** + * @title Structured Note Financial Product Library + * @notice Adds custom price transformation logic to modify the behavior of the expiring multi party contract. The + * contract holds say 1 WETH in collateral and pays out that 1 WETH if, at expiry, ETHUSD is below a set strike. If + * ETHUSD is above that strike, the contract pays out a given dollar amount of ETH. + * Example: expiry is DEC 31. Strike is $400. Each token is backed by 1 WETH + * If ETHUSD < $400 at expiry, token is redeemed for 1 ETH. + * If ETHUSD >= $400 at expiry, token is redeemed for $400 worth of ETH, as determined by the DVM. + */ +contract StructuredNoteFinancialProductLibrary is FinancialProductLibrary, Ownable, Lockable { + using FixedPoint for FixedPoint.Unsigned; + + mapping(address => FixedPoint.Unsigned) financialProductStrikes; + + /** + * @notice Enables the deployer of the library to set the strike price for an associated financial product. + * @param financialProduct address of the financial product. + * @param strikePrice the strike price for the structured note to be applied to the financial product. + * @dev Note: a) Only the owner (deployer) of this library can set new strike prices b) A strike price cannot be 0. + * c) A strike price can only be set once to prevent the deployer from changing the strike after the fact. + * d) financialProduct must exposes an expirationTimestamp method. + */ + function setFinancialProductStrike( + address financialProduct, + FixedPoint.Unsigned memory strikePrice + ) public onlyOwner nonReentrant { + require(strikePrice.isGreaterThan(0), "Cant set 0 strike"); + require(financialProductStrikes[financialProduct].isEqual(0), "Strike already set"); + require(ExpiringContractInterface(financialProduct).expirationTimestamp() != 0, "Invalid EMP contract"); + financialProductStrikes[financialProduct] = strikePrice; + } + + /** + * @notice Returns the strike price associated with a given financial product address. + * @param financialProduct address of the financial product. + * @return strikePrice for the associated financial product. + */ + function getStrikeForFinancialProduct( + address financialProduct + ) public view nonReentrantView returns (FixedPoint.Unsigned memory) { + return financialProductStrikes[financialProduct]; + } + + /** + * @notice Returns a transformed price by applying the structured note payout structure. + * @param oraclePrice price from the oracle to be transformed. + * @param requestTime timestamp the oraclePrice was requested at. + * @return transformedPrice the input oracle price with the price transformation logic applied to it. + */ + function transformPrice( + FixedPoint.Unsigned memory oraclePrice, + uint256 requestTime + ) public view override nonReentrantView returns (FixedPoint.Unsigned memory) { + FixedPoint.Unsigned memory strike = financialProductStrikes[msg.sender]; + require(strike.isGreaterThan(0), "Caller has no strike"); + // If price request is made before expiry, return 1. Thus we can keep the contract 100% collateralized with + // each token backed 1:1 by collateral currency. + if (requestTime < ExpiringContractInterface(msg.sender).expirationTimestamp()) { + return FixedPoint.fromUnscaledUint(1); + } + if (oraclePrice.isLessThan(strike)) { + return FixedPoint.fromUnscaledUint(1); + } else { + // Token expires to be worth strike $ worth of collateral. + // eg if ETHUSD is $500 and strike is $400, token is redeemable for 400/500 = 0.8 WETH. + return strike.div(oraclePrice); + } + } + + /** + * @notice Returns a transformed collateral requirement by applying the structured note payout structure. If the price + * of the structured note is greater than the strike then the collateral requirement scales down accordingly. + * @param oraclePrice price from the oracle to transform the collateral requirement. + * @param collateralRequirement financial products collateral requirement to be scaled according to price and strike. + * @return transformedCollateralRequirement the input collateral requirement with the transformation logic applied to it. + */ + function transformCollateralRequirement( + FixedPoint.Unsigned memory oraclePrice, + FixedPoint.Unsigned memory collateralRequirement + ) public view override nonReentrantView returns (FixedPoint.Unsigned memory) { + FixedPoint.Unsigned memory strike = financialProductStrikes[msg.sender]; + require(strike.isGreaterThan(0), "Caller has no strike"); + // If the price is less than the strike than the original collateral requirement is used. + if (oraclePrice.isLessThan(strike)) { + return collateralRequirement; + } else { + // If the price is more than the strike then the collateral requirement is scaled by the strike. For example + // a strike of $400 and a CR of 1.2 would yield: + // ETHUSD = $350, payout is 1 WETH. CR is multiplied by 1. resulting CR = 1.2 + // ETHUSD = $400, payout is 1 WETH. CR is multiplied by 1. resulting CR = 1.2 + // ETHUSD = $425, payout is 0.941 WETH (worth $400). CR is multiplied by 0.941. resulting CR = 1.1292 + // ETHUSD = $500, payout is 0.8 WETH (worth $400). CR multiplied by 0.8. resulting CR = 0.96 + return collateralRequirement.mul(strike.div(oraclePrice)); + } + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/BinaryOptionLongShortPairFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/BinaryOptionLongShortPairFinancialProductLibrary.sol new file mode 100644 index 000000000..341dbca63 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/BinaryOptionLongShortPairFinancialProductLibrary.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; + +import "./LongShortPairFinancialProductLibrary.sol"; +import "../../../../common/implementation/Lockable.sol"; + +/** + * @title Binary Option Long Short Pair Financial Product Library. + * @notice Adds settlement logic to binary option LSPs. Binary options settle with all collateral allocated to + * either the long or short side, depending on the settlement price. They can be used to make prediction markets or any + * kind of binary bet. Settlement is defined using a strike price which informs which side of the bet was correct. If + * settlement price is greater or equal to the strike then all value is sent to the long side. Otherwise, all value + * is sent to the short side. The settlement price could be a scalar (like the price of ETH) or a binary bet with + * settlement being 0 or 1 depending on the outcome. + */ +contract BinaryOptionLongShortPairFinancialProductLibrary is LongShortPairFinancialProductLibrary, Lockable { + using FixedPoint for FixedPoint.Unsigned; + using SafeMath for uint256; + + struct BinaryLongShortPairParameters { + bool isSet; + int256 strikePrice; + } + + mapping(address => BinaryLongShortPairParameters) public longShortPairParameters; + + /** + * @notice Enables any address to set the strike price for an associated binary option. + * @param longShortPair address of the LSP. + * @param strikePrice the strike price for the binary option. + * @dev Note: a) Any address can set the initial strike price b) A strike can be 0. + * c) A strike price can only be set once to prevent the deployer from changing the strike after the fact. + * d) For safety, a strike price should be set before depositing any synthetic tokens in a liquidity pool. + * e) longShortPair must expose an expirationTimestamp method to validate it is correctly deployed. + */ + function setLongShortPairParameters(address longShortPair, int256 strikePrice) public nonReentrant { + require(ExpiringContractInterface(longShortPair).expirationTimestamp() != 0, "Invalid LSP address"); + require(!longShortPairParameters[longShortPair].isSet, "Parameters already set"); + + longShortPairParameters[longShortPair] = BinaryLongShortPairParameters({ + isSet: true, + strikePrice: strikePrice + }); + } + + /** + * @notice Returns a number between 0 and 1e18 to indicate how much collateral each long and short token are entitled + * to per collateralPerPair. + * @param expiryPrice price from the optimistic oracle for the LSP price identifier. + * @return expiryPercentLong to indicate how much collateral should be sent between long and short tokens. + */ + function percentageLongCollateralAtExpiry( + int256 expiryPrice + ) public view override nonReentrantView returns (uint256) { + BinaryLongShortPairParameters memory params = longShortPairParameters[msg.sender]; + require(params.isSet, "Params not set for calling LSP"); + + if (expiryPrice >= params.strikePrice) return FixedPoint.fromUnscaledUint(1).rawValue; + else return FixedPoint.fromUnscaledUint(0).rawValue; + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/CappedYieldDollarLongShortPairFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/CappedYieldDollarLongShortPairFinancialProductLibrary.sol new file mode 100644 index 000000000..d5227baf2 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/CappedYieldDollarLongShortPairFinancialProductLibrary.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/utils/math/Math.sol"; + +import "./LongShortPairFinancialProductLibrary.sol"; +import "../../../../common/implementation/Lockable.sol"; + +/** + * @title Capped Yield Dollar Long Short Pair Financial Product Library + * @notice Adds settlement logic to create Yield Dollar LSPs. A range bond is the combination of a Yield dollar and short + * put option enabling the token sponsor to issue structured products to unlock DeFi treasuries. This library is like + * a Range Bond, but with no embedded call option. + * A Capped Yield Dollar is defined as = Yield Dollar - Put Option. In order for the Capped Yield Dollar to be fully + * collateralized and non-liquidatable, there is a low price for the collateral token below which the Capped Yield Dollar + * will be worth < $1. + * Numerically this is found using: + * N = Notional of bond + * P = price of token + * T = number of tokens + * R1 = low price range + * C = collateral per pair, should be N/R1 + * T = min(1,(R1/P)*C) + * If you want a yield dollar denominated as N = $1, you should set C to 1/R1. In that case, T = min(1,1/P). + * - At any price below the low price range (R1) the long side effectively holds a fixed number of collateral equal to + * collateralPerPair from the LSP with the value of expiryPercentLong = 1. This is the max payout in collateral. + * - Any price equal to or above R1 gives a payout equivalent to a yield dollar (bond) of notional N. In this range the + * expiryPercentLong shifts to keep the payout in dollar terms equal to the bond notional. + * With this equation, the contract deployer does not need to specify the bond notional N. The notional can be calculated + * by taking R1*collateralPerPair from the LSP. + */ +contract CappedYieldDollarLongShortPairFinancialProductLibrary is LongShortPairFinancialProductLibrary, Lockable { + using FixedPoint for FixedPoint.Unsigned; + + mapping(address => uint256) public lowPriceRanges; + + /** + * @notice Enables any address to set the low price range for an associated financial product. + * @param longShortPair address of the LSP contract. + * @param lowPriceRange low price range below which the payout transforms from a yield dollar to a short put option. + * @dev above the lowPriceRange the contract will payout a fixed amount of + * lowPriceRange*collateralPerPair (i.e the "notional" of the yield dollar). + * @dev Note: a) Any address can set these parameters b) existing LSP parameters for address not set. + * c) low price range can only be set once to prevent the deployer from changing the parameters after the fact. + * d) For safety, a low price range should be set before depositing any synthetic tokens in a liquidity pool. + * e) longShortPair must expose an expirationTimestamp method to validate it is correctly deployed. + */ + function setLongShortPairParameters(address longShortPair, uint256 lowPriceRange) public nonReentrant { + require(ExpiringContractInterface(longShortPair).expirationTimestamp() != 0, "Invalid LSP address"); + require(lowPriceRanges[longShortPair] == 0, "Parameters already set"); + + lowPriceRanges[longShortPair] = lowPriceRange; + } + + /** + * @notice Returns a number between 0 and 1e18 to indicate how much collateral each long and short token are + * entitled to per collateralPerPair. + * @param expiryPrice price from the optimistic oracle for the LSP price identifier. + * @return expiryPercentLong to indicate how much collateral should be sent between long and short tokens. + */ + function percentageLongCollateralAtExpiry( + int256 expiryPrice + ) public view override nonReentrantView returns (uint256) { + uint256 contractLowPriceRange = lowPriceRanges[msg.sender]; + require(contractLowPriceRange != 0, "Params not set for calling LSP"); + + // This function returns a value between 0 and 1e18 to be used in conjunction with the LSP collateralPerPair + // that allocates collateral between the short and long tokens on expiry. This can be simplified by considering + // the price in two discrete ranges: 1) below the low price range, 2) above the low price range. + uint256 positiveExpiryPrice = expiryPrice > 0 ? uint256(expiryPrice) : 0; + + // For expiry prices below lower bound, return expiryPercentLong = 1 (full position) + if (positiveExpiryPrice <= contractLowPriceRange) return FixedPoint.fromUnscaledUint(1).rawValue; + // For expiry prices above lower bound. For example, if the lower bound of Sushi is $4, collateral per pair + // is 0.25, and the expiry price is $12, the payout will be (4/12)*0.25, or .08333 Sushi. With Sushi at $12, + // .08333 Sushi is equal to $1. + return FixedPoint.Unsigned(contractLowPriceRange).div(FixedPoint.Unsigned(positiveExpiryPrice)).rawValue; + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/CoveredCallLongShortPairFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/CoveredCallLongShortPairFinancialProductLibrary.sol new file mode 100644 index 000000000..85ba17856 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/CoveredCallLongShortPairFinancialProductLibrary.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; + +import "./LongShortPairFinancialProductLibrary.sol"; +import "../../../../common/implementation/Lockable.sol"; + +/** + * @title Covered call Long Short Pair Financial Product Library. + * @notice Adds settlement logic to create covered call LSPs. The contract will payout a scaled amount of collateral + * depending on where the settlement price lands relative to the call's strike price. If the settlement is below the + * strike price then longs expire worthless. If the settlement is above the strike then the payout is the fraction above + * the strike defined by (expiryPrice - strikePrice) / expiryPrice. For example, consider a covered call option + * collateralized in ETH, with a strike a price of 3000. + * - If the price is less than 3000 then the each long is worth 0 and each short is worth collateralPerPair. + * - If the price is more than 3000 then each long is worth the fraction of collateralPerPair that was in the money and + * each short is worth the remaining collateralPerPair. + * - Say settlement price is 3500. Then expiryPercentLong = (3500 - 3000) / 3500 = 0.143. The value of this 0.143 ETH + * is worth 0.143*3500=500 which is the percentage of the collateralPerPair that was above the strike price. + */ +contract CoveredCallLongShortPairFinancialProductLibrary is LongShortPairFinancialProductLibrary, Lockable { + using FixedPoint for FixedPoint.Unsigned; + using SafeMath for uint256; + + mapping(address => uint256) public longShortPairStrikePrices; + + /** + * @notice Enables any address to set the strike price for an associated LSP. + * @param longShortPair address of the LSP. + * @param strikePrice the strike price for the covered call for the associated LSP. + * @dev Note: a) Any address can set the initial strike price b) A strike price cannot be 0. + * c) A strike price can only be set once to prevent the deployer from changing the strike after the fact. + * d) For safety, a strike price should be set before depositing any synthetic tokens in a liquidity pool. + * e) longShortPair must expose an expirationTimestamp method to validate it is correctly deployed. + */ + function setLongShortPairParameters(address longShortPair, uint256 strikePrice) public nonReentrant { + require(ExpiringContractInterface(longShortPair).expirationTimestamp() != 0, "Invalid LSP address"); + require(longShortPairStrikePrices[longShortPair] == 0, "Parameters already set"); + + longShortPairStrikePrices[longShortPair] = strikePrice; + } + + /** + * @notice Returns a number between 0 and 1e18 to indicate how much collateral each long and short token are entitled + * to per collateralPerPair. + * @param expiryPrice price from the optimistic oracle for the LSP price identifier. + * @return expiryPercentLong to indicate how much collateral should be sent between long and short tokens. + */ + function percentageLongCollateralAtExpiry( + int256 expiryPrice + ) public view override nonReentrantView returns (uint256) { + uint256 contractStrikePrice = longShortPairStrikePrices[msg.sender]; + require(contractStrikePrice != 0, "Params not set for calling LSP"); + + // If the expiry price is less than the strike price then the long options expire worthless (out of the money). + // Note we do not consider negative expiry prices in this call option implementation. + if (expiryPrice < 0 || uint256(expiryPrice) < contractStrikePrice) + return FixedPoint.fromUnscaledUint(0).rawValue; + + // Else, token expires to be worth the fraction of a collateral token that's in the money. eg if ETH is $3500 + // and strike is $3000, long token is redeemable for (3500-3000)/3500 = 0.143 WETH which is worth $500 and the + // short token is worth the remaining 0.8. This is strictly < 1, tending to 1 as the expiry tends to infinity. + return + (FixedPoint.Unsigned(uint256(expiryPrice)).sub(FixedPoint.Unsigned(contractStrikePrice))) + .div(FixedPoint.Unsigned(uint256(expiryPrice))) + .rawValue; + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/FlooredLinearLongShortPairFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/FlooredLinearLongShortPairFinancialProductLibrary.sol new file mode 100644 index 000000000..b81e6b2e7 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/FlooredLinearLongShortPairFinancialProductLibrary.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/utils/math/SignedSafeMath.sol"; + +import "./LongShortPairFinancialProductLibrary.sol"; +import "../../../../common/implementation/Lockable.sol"; + +/** + * @title Floored Linear Long Short Pair Financial Product Library. + * @notice Adds settlement logic to create floored linear LSPs. The contract will payout a scaled amount of collateral + * depending on where the settlement price lands within a price range between a lowerBound and an upperBound with + * configured minimum floorPercentage payout for all prices below lowerBound. If settlement price is within the price + * range then the expiryPercentLong is defined by (expiryPrice - lowerBound) / (upperBound - lowerBound) * + * (1 - floorPercentage) + floorPercentage. This number represents the amount of collateral from the collateralPerPair + * that will be sent to the long side. If the price is at or higher than the upperBound then expiryPercentLong = 1. + * If the price is at or lower than the lowerBound then expiryPercentLong = floorPercentage. For example, consider a + * floored linear LSP on the price of ETH collateralized in USDC with an upperBound = 4000, lowerBound = 2000 and + * floorPercentage = 20% with a collateralPerPair of 1000 (i.e each pair of long and shorts is worth 1000 USDC). At + * settlement the expiryPercentLong would equal 1 (each long worth 1000 and short worth 0) if ETH price was >= 4000 and + * it would equal 0.2 if <= 2000 (each long is worth minimum 200 and each short is worth maximum 800). If between the + * two (say 3500) then expiryPercentLong = (3500 - 2000) / (4000 - 2000) * (1 - 0.2) + 0.2 = 0.8. Therefore each long + * is worth 800 and each short is worth 200. + */ +contract FlooredLinearLongShortPairFinancialProductLibrary is LongShortPairFinancialProductLibrary, Lockable { + using FixedPoint for FixedPoint.Unsigned; + using SignedSafeMath for int256; + + struct LinearLongShortPairParameters { + int256 upperBound; + int256 lowerBound; + uint256 floorPercentage; + } + + mapping(address => LinearLongShortPairParameters) public longShortPairParameters; + + /** + * @notice Enables any address to set the parameters for an associated financial product. + * @param longShortPair address of the LSP contract. + * @param upperBound the upper price that the linear LSP will operate within. + * @param lowerBound the lower price that the linear LSP will operate within. + * @param floorPercentage the lowest possible payout percentage from collateralPerPair to each long token expressed + * with 1e18 decimals. E.g., a 20% floor percentage should be expressed as 200000000000000000. + * @dev Note: a) Any address can set these parameters b) existing LSP parameters for address not set. + * c) upperBound > lowerBound. + * d) floorPercentage <= 1e18 (no need to check >= 0 as floorPercentage is unsigned). + * e) parameters can only be set once to prevent the deployer from changing the parameters after the fact. + * f) For safety, parameters should be set before depositing any synthetic tokens in a liquidity pool. + * g) longShortPair must expose an expirationTimestamp method to validate it is correctly deployed. + */ + function setLongShortPairParameters( + address longShortPair, + int256 upperBound, + int256 lowerBound, + uint256 floorPercentage + ) public nonReentrant { + require(ExpiringContractInterface(longShortPair).expirationTimestamp() != 0, "Invalid LSP address"); + require(upperBound > lowerBound, "Invalid bounds"); + require(floorPercentage <= 1e18, "Invalid floor percentage"); + + LinearLongShortPairParameters memory params = longShortPairParameters[longShortPair]; + require(params.upperBound == 0 && params.lowerBound == 0, "Parameters already set"); + + longShortPairParameters[longShortPair] = LinearLongShortPairParameters({ + upperBound: upperBound, + lowerBound: lowerBound, + floorPercentage: floorPercentage + }); + } + + /** + * @notice Returns a number between floorPercentage and 1e18 to indicate how much collateral each long and short + * token is entitled to per collateralPerPair. + * @param expiryPrice price from the optimistic oracle for the LSP price identifier. + * @return expiryPercentLong to indicate how much collateral should be sent between long and short tokens. + */ + function percentageLongCollateralAtExpiry( + int256 expiryPrice + ) public view override nonReentrantView returns (uint256) { + LinearLongShortPairParameters memory params = longShortPairParameters[msg.sender]; + require(params.upperBound != 0 || params.lowerBound != 0, "Params not set for calling LSP"); + + if (expiryPrice >= params.upperBound) return 1e18; + + if (expiryPrice <= params.lowerBound) return params.floorPercentage; + + // if not exceeding bounds, expiryPercentLong = (expiryPrice - lowerBound) / (upperBound - lowerBound) * + // (1 - floorPercentage) + floorPercentage + return + FixedPoint + .Unsigned(uint256(expiryPrice - params.lowerBound)) + .div(FixedPoint.Unsigned(uint256(params.upperBound - params.lowerBound))) + .mul(FixedPoint.Unsigned(1e18 - params.floorPercentage)) + .add(FixedPoint.Unsigned(params.floorPercentage)) + .rawValue; + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/LinearLongShortPairFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/LinearLongShortPairFinancialProductLibrary.sol new file mode 100644 index 000000000..ff7e5cf5e --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/LinearLongShortPairFinancialProductLibrary.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/utils/math/SignedSafeMath.sol"; + +import "./LongShortPairFinancialProductLibrary.sol"; +import "../../../../common/implementation/Lockable.sol"; + +/** + * @title Linear Long Short Pair Financial Product Library. + * @notice Adds settlement logic to create linear LSPs. The contract will payout a scaled amount of collateral + * depending on where the settlement price lands within a price range between an upperBound and a lowerBound. If + * settlement price is within the price range then the expiryPercentLong is defined by + * (expiryPrice - lowerBound) / (upperBound - lowerBound). This number represent the amount of collateral from the + * collateralPerPair that will be sent to the long and short side. If the price is higher than the upperBound then + * expiryPercentLong = 1. if the price is lower than the lower bound then expiryPercentLong = 0. For example, consider + * a linear LSP on the price of ETH collateralized in USDC with an upperBound = 4000 and lowerBound = 2000 with a + * collateralPerPair of 1000 (i.e each pair of long and shorts is worth 1000 USDC). At settlement the expiryPercentLong + * would equal 1 (each long worth 1000 and short worth 0) if ETH price was > 4000 and it would equal 0 if < 2000 + * (each long is worthless and each short is worth 1000). If between the two (say 3500) then expiryPercentLong + * = (3500 - 2000) / (4000 - 2000) = 0.75. Therefore each long is worth 750 and each short is worth 250. + */ +contract LinearLongShortPairFinancialProductLibrary is LongShortPairFinancialProductLibrary, Lockable { + using FixedPoint for FixedPoint.Unsigned; + using SignedSafeMath for int256; + + struct LinearLongShortPairParameters { + int256 upperBound; + int256 lowerBound; + } + + mapping(address => LinearLongShortPairParameters) public longShortPairParameters; + + /** + * @notice Enables any address to set the parameters for an associated financial product. + * @param longShortPair address of the LSP contract. + * @param upperBound the upper price that the linear LSP will operate within. + * @param lowerBound the lower price that the linear LSP will operate within. + * @dev Note: a) Any address can set these parameters b) existing LSP parameters for address not set. + * c) upperBound > lowerBound. + * d) parameters can only be set once to prevent the deployer from changing the parameters after the fact. + * e) For safety, parameters should be set before depositing any synthetic tokens in a liquidity pool. + * f) longShortPair must expose an expirationTimestamp method to validate it is correctly deployed. + */ + function setLongShortPairParameters( + address longShortPair, + int256 upperBound, + int256 lowerBound + ) public nonReentrant { + require(ExpiringContractInterface(longShortPair).expirationTimestamp() != 0, "Invalid LSP address"); + require(upperBound > lowerBound, "Invalid bounds"); + + LinearLongShortPairParameters memory params = longShortPairParameters[longShortPair]; + require(params.upperBound == 0 && params.lowerBound == 0, "Parameters already set"); + + longShortPairParameters[longShortPair] = LinearLongShortPairParameters({ + upperBound: upperBound, + lowerBound: lowerBound + }); + } + + /** + * @notice Returns a number between 0 and 1e18 to indicate how much collateral each long and short token is entitled + * to per collateralPerPair. + * @param expiryPrice price from the optimistic oracle for the LSP price identifier. + * @return expiryPercentLong to indicate how much collateral should be sent between long and short tokens. + */ + function percentageLongCollateralAtExpiry( + int256 expiryPrice + ) public view override nonReentrantView returns (uint256) { + LinearLongShortPairParameters memory params = longShortPairParameters[msg.sender]; + require(params.upperBound != 0 || params.lowerBound != 0, "Params not set for calling LSP"); + + if (expiryPrice >= params.upperBound) return FixedPoint.fromUnscaledUint(1).rawValue; + + if (expiryPrice <= params.lowerBound) return FixedPoint.fromUnscaledUint(0).rawValue; + + // if not exceeding bounds, expiryPercentLong = (expiryPrice - lowerBound) / (upperBound - lowerBound) + return + FixedPoint + .Unsigned(uint256(expiryPrice - params.lowerBound)) + .div(FixedPoint.Unsigned(uint256(params.upperBound - params.lowerBound))) + .rawValue; + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/LongShortPairFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/LongShortPairFinancialProductLibrary.sol new file mode 100644 index 000000000..c9b705382 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/LongShortPairFinancialProductLibrary.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; +import "../../../../common/implementation/FixedPoint.sol"; +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; + +interface ExpiringContractInterface { + function expirationTimestamp() external view returns (uint256); +} + +abstract contract LongShortPairFinancialProductLibrary { + function percentageLongCollateralAtExpiry(int256 expiryPrice) public view virtual returns (uint256); +} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/RangeBondLongShortPairFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/RangeBondLongShortPairFinancialProductLibrary.sol new file mode 100644 index 000000000..9b183e2da --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/RangeBondLongShortPairFinancialProductLibrary.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/utils/math/SignedSafeMath.sol"; +import "@openzeppelin/contracts-v4/utils/math/Math.sol"; + +import "./LongShortPairFinancialProductLibrary.sol"; +import "../../../../common/implementation/Lockable.sol"; + +/** + * @title Range Bond Long Short Pair Financial Product Library + * @notice Adds settlement logic to create range bond LSPs. A range bond is the combination of a Yield dollar, short put + * option and long call option enabling the token sponsor to issue structured products to unlock DeFi treasuries. + * A range bond is defined as = Yield Dollar - Put Option + Call option. Numerically this is found using: + * N = Notional of bond + * P = price of token + * T = number of tokens + * R1 = low price range + * R2 = high price range + * T = min(N/P,N/R1) + max((N/R2*(P-R2))/P,0) + * - At any price below the low price range (R1) the long side effectively holds a fixed number of collateral equal to + * collateralPerPair from the LSP with the value of expiryPercentLong = 1. This is the max payout in collateral. + * - Any price between R1 and R2 gives a payout equivalent to a yield dollar (bond) of notional N. In this range the + * expiryPercentLong shifts to keep the payout in dollar terms equal to the bond notional. + * - At any price above R2 the long holders are entitled to a fixed, minimum number of collateral equal to N/R2 with a + * expiryPercentLong=(N/R2)/collateralPerPair. + * The expression for the number of tokens paid out to the long side (T above) can be algebraically simplified, + * transformed to remove the notional and reframed to express the expiryPercentLong as [min(max(1/R2,1/P),1/R1)]/(1/R1) + * With this equation, the contract deployer does not need to specify the bond notional N. The notional can be calculated + * by taking R1*collateralPerPair from the LSP. + */ +contract RangeBondLongShortPairFinancialProductLibrary is LongShortPairFinancialProductLibrary, Lockable { + using FixedPoint for FixedPoint.Unsigned; + using SignedSafeMath for int256; + + struct RangeBondLongShortPairParameters { + uint256 highPriceRange; + uint256 lowPriceRange; + } + + mapping(address => RangeBondLongShortPairParameters) public longShortPairParameters; + + /** + * @notice Enables any address to set the parameters price for an associated financial product. + * @param longShortPair address of the LSP contract. + * @param highPriceRange high price range after which the payout transforms from a yield dollar to a call option. + * @param lowPriceRange low price range below which the payout transforms from a yield dollar to a short put option. + * @dev between highPriceRange and lowPriceRange the contract will payout a fixed amount of + * lowPriceRange*collateralPerPair (i.e the "notional" of the yield dollar). + * @dev Note: a) Any address can set these parameters b) existing LSP parameters for address not set. + * c) highPriceRange > lowPriceRange. + * d) parameters price can only be set once to prevent the deployer from changing the parameters after the fact. + * e) For safety, a parameters should be set before depositing any synthetic tokens in a liquidity pool. + * f) longShortPair must expose an expirationTimestamp method to validate it is correctly deployed. + */ + function setLongShortPairParameters( + address longShortPair, + uint256 highPriceRange, + uint256 lowPriceRange + ) public nonReentrant { + require(ExpiringContractInterface(longShortPair).expirationTimestamp() != 0, "Invalid LSP address"); + + require(highPriceRange > lowPriceRange, "Invalid bounds"); + + RangeBondLongShortPairParameters memory params = longShortPairParameters[longShortPair]; + require(params.highPriceRange == 0 && params.lowPriceRange == 0, "Parameters already set"); + + longShortPairParameters[longShortPair] = RangeBondLongShortPairParameters({ + highPriceRange: highPriceRange, + lowPriceRange: lowPriceRange + }); + } + + /** + * @notice Returns a number between 0 and 1e18 to indicate how much collateral each long and short token are + * entitled to per collateralPerPair. + * @param expiryPrice price from the optimistic oracle for the LSP price identifier. + * @return expiryPercentLong to indicate how much collateral should be sent between long and short tokens. + */ + function percentageLongCollateralAtExpiry( + int256 expiryPrice + ) public view override nonReentrantView returns (uint256) { + RangeBondLongShortPairParameters memory params = longShortPairParameters[msg.sender]; + require(params.highPriceRange != 0 || params.lowPriceRange != 0, "Params not set for calling LSP"); + + // This function returns a value between 0 and 1e18 to be used in conjunction with the LSP collateralPerPair + // that allocates collateral between the short and long tokens on expiry. This can be simplified by considering + // the price in three discrete ranges: 1) below the low price range, 2) between low and high range and 3) above + // the high price range. + uint256 positiveExpiryPrice = expiryPrice > 0 ? uint256(expiryPrice) : 0; + + // 1) The long position is entitled to the full position in this range. The short token is worth 0. + if (positiveExpiryPrice <= params.lowPriceRange) return FixedPoint.fromUnscaledUint(1).rawValue; + // 2) Within the range, the long position is entitled to the bond notional value which is equal to + // (collateral tokens * lowPriceRange). + if (positiveExpiryPrice <= params.highPriceRange) + return FixedPoint.Unsigned(params.lowPriceRange).div(FixedPoint.Unsigned(positiveExpiryPrice)).rawValue; + // 3) Above the range, the long position is entitled to a fixed number of tokens. + return FixedPoint.Unsigned(params.lowPriceRange).div(FixedPoint.Unsigned(params.highPriceRange)).rawValue; + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/SimpleSuccessTokenLongShortPairFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/SimpleSuccessTokenLongShortPairFinancialProductLibrary.sol new file mode 100644 index 000000000..97bf4a515 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/SimpleSuccessTokenLongShortPairFinancialProductLibrary.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "./LongShortPairFinancialProductLibrary.sol"; +import "../../../../common/implementation/Lockable.sol"; + +/** + * @title Simple Success Token Long Short Pair Financial Product Library. + * @notice Adds settlement logic to create success token LSPs. A success token pays out 50% of collateral as a + * floor, with the remaining 50% functioning like an embedded covered call. + * If the settlement is below the strike price then longs are worth 50% of collateral. + * If the settlement is above the strike then the payout is equal to: + * 0.5 + (0.5 * (expiryPrice - strikePrice) / expiryPrice) + * For example, consider a covered call option collateralized in SUSHI, with a strike a price of $20, + * and collateralPerPair of 2. + * - If the price is less than $20 then the each long is worth 0.5 collateralPerPair and each short is worth 0.5 + * collateralPerPair. i.e., each long is worth 1 SUSHI (calls expire worthless). + * - If the price is more than $20 then each long is worth 0.5 collateralPerPair plus 0.5 times the fraction of + * collateralPerPair that was in the money, and each short is worth the remaining collateralPerPair. + * - Say settlement price is $30. Then expiryPercentLong = 0.5 + (0.5 * (30 - 20) / 30) = 0.6667. + * If the collateralPerPair is 2, that means the long payout is 0.6667*2 = 1.3333 $SUSHI, which at a settlement + * price of $30 is worth $40. This is equivalent to the value of 1 $SUSHI plus the value of the $20 strike + * embedded call. + */ +contract SimpleSuccessTokenLongShortPairFinancialProductLibrary is LongShortPairFinancialProductLibrary, Lockable { + using FixedPoint for FixedPoint.Unsigned; + + mapping(address => uint256) public longShortPairStrikePrices; + uint256 basePercentage = 500000000000000000; // 0.5 with 18 decimals + uint256 variablePercentage = uint256(1000000000000000000) - basePercentage; + + /** + * @notice Enables any address to set the strike price for an associated LSP. + * @param longShortPair address of the LSP. + * @param strikePrice the strike price for the covered call for the associated LSP. + * @dev Note: a) Any address can set the initial strike price b) A strike price cannot be 0. + * c) A strike price can only be set once to prevent the deployer from changing the strike after the fact. + * d) For safety, a strike price should be set before depositing any synthetic tokens in a liquidity pool. + * e) longShortPair must expose an expirationTimestamp method to validate it is correctly deployed. + */ + function setLongShortPairParameters(address longShortPair, uint256 strikePrice) public nonReentrant { + require(ExpiringContractInterface(longShortPair).expirationTimestamp() != 0, "Invalid LSP address"); + require(longShortPairStrikePrices[longShortPair] == 0, "Parameters already set"); + + longShortPairStrikePrices[longShortPair] = strikePrice; + } + + /** + * @notice Returns a number between 0 and 1e18 to indicate how much collateral each long and short token are entitled + * to per collateralPerPair. + * @param expiryPrice price from the optimistic oracle for the LSP price identifier. + * @return expiryPercentLong to indicate how much collateral should be sent between long and short tokens. + */ + function percentageLongCollateralAtExpiry( + int256 expiryPrice + ) public view override nonReentrantView returns (uint256) { + uint256 contractStrikePrice = longShortPairStrikePrices[msg.sender]; + require(contractStrikePrice != 0, "Params not set for calling LSP"); + + // If the expiry price is less than the strike price then the long options expire worthless (out of the money). + // In this case, return of value of 50% (half of collateral goes to long) + // Note we do not consider negative expiry prices in this call option implementation. + uint256 positiveExpiryPrice = expiryPrice > 0 ? uint256(expiryPrice) : 0; + if (positiveExpiryPrice == 0 || uint256(positiveExpiryPrice) <= contractStrikePrice) return basePercentage; + + // Else, token expires to be worth the 0.5 of the collateral plus 0.5 * the fraction of a collateral token + // that's in the money. + // eg if SUSHI is $30 and strike is $20, long token is redeemable for 0.5 + 0.5*(30-20)/30 = 0.6667% which if the + // collateralPerPair is 2, is worth 1.3333 $SUSHI, which is worth $40 if 1 $SUSHI is worth $30. + // This return value is strictly < 1, tending to 1 as the expiryPrice tends to infinity. + return + ( + FixedPoint.Unsigned(basePercentage).add( + FixedPoint + .Unsigned(variablePercentage) + .mul( + FixedPoint.Unsigned(uint256(positiveExpiryPrice)).sub( + FixedPoint.Unsigned(contractStrikePrice) + ) + ) + .div(FixedPoint.Unsigned(uint256(positiveExpiryPrice))) + ) + ).rawValue; + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/SuccessTokenLongShortPairFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/SuccessTokenLongShortPairFinancialProductLibrary.sol new file mode 100644 index 000000000..f65da1306 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/SuccessTokenLongShortPairFinancialProductLibrary.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "./LongShortPairFinancialProductLibrary.sol"; +import "../../../../common/implementation/Lockable.sol"; + +/** + * @title Success Token Long Short Pair Financial Product Library. + * @notice Adds settlement logic to create success token LSPs. A success token pays out a fixed amount of + * collateral as a floor, with the remaining amount functioning like an embedded option. The embedded + * option in this case uses payout logic that resembles a covered call. I.e., the token expires to be worth + * basePercentage + (1 - basePercentage) * (expiryPrice - strikePrice). + */ +contract SuccessTokenLongShortPairFinancialProductLibrary is LongShortPairFinancialProductLibrary, Lockable { + using FixedPoint for FixedPoint.Unsigned; + + struct SuccessTokenLongShortPairParameters { + uint256 strikePrice; + uint256 basePercentage; + } + + mapping(address => SuccessTokenLongShortPairParameters) public longShortPairParameters; + + /** + * @notice Enables any address to set the strike price for an associated LSP. + * @param longShortPair address of the LSP. + * @param strikePrice the strike price for the covered call for the associated LSP. + * @param basePercentage the base percentage of collateral per pair paid out to long tokens, expressed + * with 1e18 decimals. E.g., a 50% base percentage should be expressed 500000000000000000, or 0.5 with + * 1e18 decimals. The base percentage cannot be set to 0. + * @dev Note: a) Any address can set the initial strike price b) A strike price cannot be 0. + * c) A strike price can only be set once to prevent the deployer from changing the strike after the fact. + * d) For safety, a strike price should be set before depositing any synthetic tokens in a liquidity pool. + * e) longShortPair must expose an expirationTimestamp method to validate it is correctly deployed. + */ + function setLongShortPairParameters( + address longShortPair, + uint256 strikePrice, + uint256 basePercentage + ) public nonReentrant { + require(ExpiringContractInterface(longShortPair).expirationTimestamp() != 0, "Invalid LSP address"); + SuccessTokenLongShortPairParameters memory params = longShortPairParameters[longShortPair]; + require(params.strikePrice == 0 && params.basePercentage == 0, "Parameters already set"); + require(strikePrice != 0 && basePercentage != 0, "Base percentage and strike price cannot be set to 0"); + + longShortPairParameters[longShortPair] = SuccessTokenLongShortPairParameters({ + strikePrice: strikePrice, + basePercentage: basePercentage + }); + } + + /** + * @notice Returns a number between 0 and 1e18 to indicate how much collateral each long and short token are entitled + * to per collateralPerPair. + * @param expiryPrice price from the optimistic oracle for the LSP price identifier. + * @return expiryPercentLong to indicate how much collateral should be sent between long and short tokens. + */ + function percentageLongCollateralAtExpiry( + int256 expiryPrice + ) public view override nonReentrantView returns (uint256) { + SuccessTokenLongShortPairParameters memory params = longShortPairParameters[msg.sender]; + require(params.strikePrice != 0 || params.basePercentage != 0, "Params not set for calling LSP"); + + // If the expiry price is less than the strike price then the long options expire worthless (out of the money). + // In this case, return of value of the base percentage to the long tokenholders. + // Note we do not consider negative expiry prices in this implementation. + uint256 positiveExpiryPrice = expiryPrice > 0 ? uint256(expiryPrice) : 0; + if (positiveExpiryPrice == 0 || positiveExpiryPrice <= params.strikePrice) return params.basePercentage; + + // Else, token expires to be worth basePercentage + (1 - basePercentage) * (expiryPrice - strikePrice). + // E.g., if base percentage is 50%, $TOKEN is $30, and strike is $20, long token is redeemable for + // 0.5 + 0.5*(30-20)/30 = 0.6667%, which if the collateralPerPair is 2, is worth 1.3333 $TOKEN, which is + // worth $40 if 1 $TOKEN is worth $30. This return value is strictly < 1. The return value tends to 1 as + // the expiryPrice tends to infinity. Due to rounding down and precision errors, this may return a very + // slightly smaller value than expected. + return + ( + FixedPoint.Unsigned(params.basePercentage).add( + FixedPoint + .Unsigned(1e18 - params.basePercentage) + .mul(FixedPoint.Unsigned(positiveExpiryPrice).sub(FixedPoint.Unsigned(params.strikePrice))) + .div(FixedPoint.Unsigned(positiveExpiryPrice)) + ) + ).rawValue; + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiParty.sol b/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiParty.sol new file mode 100644 index 000000000..3d8a6a9ef --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiParty.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "./Liquidatable.sol"; + +/** + * @title Expiring Multi Party. + * @notice Convenient wrapper for Liquidatable. + */ +contract ExpiringMultiParty is Liquidatable { + /** + * @notice Constructs the ExpiringMultiParty contract. + * @param params struct to define input parameters for construction of Liquidatable. Some params + * are fed directly into the PricelessPositionManager's constructor within the inheritance tree. + */ + constructor( + ConstructorParams memory params + ) Liquidatable(params) // Note: since there is no logic here, there is no need to add a re-entrancy guard. + { + + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiPartyCreator.sol b/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiPartyCreator.sol new file mode 100644 index 000000000..8bd26a90e --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiPartyCreator.sol @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/interfaces/ExpandedIERC20.sol"; +import "../../common/interfaces/IERC20Standard.sol"; +import "../../data-verification-mechanism/implementation/ContractCreator.sol"; +import "../../common/implementation/Testable.sol"; +import "../../common/implementation/AddressWhitelist.sol"; +import "../../common/implementation/Lockable.sol"; +import "../common/TokenFactory.sol"; +import "../common/SyntheticToken.sol"; +import "./ExpiringMultiPartyLib.sol"; + +/** + * @title Expiring Multi Party Contract creator. + * @notice Factory contract to create and register new instances of expiring multiparty contracts. + * Responsible for constraining the parameters used to construct a new EMP. This creator contains a number of constraints + * that are applied to newly created expiring multi party contract. These constraints can evolve over time and are + * initially constrained to conservative values in this first iteration. Technically there is nothing in the + * ExpiringMultiParty contract requiring these constraints. However, because `createExpiringMultiParty()` is intended + * to be the only way to create valid financial contracts that are registered with the DVM (via _registerContract), + we can enforce deployment configurations here. + */ +contract ExpiringMultiPartyCreator is ContractCreator, Testable, Lockable { + using FixedPoint for FixedPoint.Unsigned; + + /**************************************** + * EMP CREATOR DATA STRUCTURES * + ****************************************/ + + struct Params { + uint256 expirationTimestamp; + address collateralAddress; + bytes32 priceFeedIdentifier; + string syntheticName; + string syntheticSymbol; + FixedPoint.Unsigned collateralRequirement; + FixedPoint.Unsigned disputeBondPercentage; + FixedPoint.Unsigned sponsorDisputeRewardPercentage; + FixedPoint.Unsigned disputerDisputeRewardPercentage; + FixedPoint.Unsigned minSponsorTokens; + uint256 withdrawalLiveness; + uint256 liquidationLiveness; + address financialProductLibraryAddress; + } + // Address of TokenFactory used to create a new synthetic token. + address public tokenFactoryAddress; + + event CreatedExpiringMultiParty(address indexed expiringMultiPartyAddress, address indexed deployerAddress); + + /** + * @notice Constructs the ExpiringMultiPartyCreator contract. + * @param _finderAddress UMA protocol Finder used to discover other protocol contracts. + * @param _tokenFactoryAddress ERC20 token factory used to deploy synthetic token instances. + * @param _timerAddress Contract that stores the current time in a testing environment. + */ + constructor( + address _finderAddress, + address _tokenFactoryAddress, + address _timerAddress + ) ContractCreator(_finderAddress) Testable(_timerAddress) nonReentrant() { + tokenFactoryAddress = _tokenFactoryAddress; + } + + /** + * @notice Creates an instance of expiring multi party and registers it within the registry. + * @param params is a `ConstructorParams` object from ExpiringMultiParty. + * @return address of the deployed ExpiringMultiParty contract. + */ + function createExpiringMultiParty(Params memory params) public nonReentrant returns (address) { + // Create a new synthetic token using the params. + require(bytes(params.syntheticName).length != 0, "Missing synthetic name"); + require(bytes(params.syntheticSymbol).length != 0, "Missing synthetic symbol"); + TokenFactory tf = TokenFactory(tokenFactoryAddress); + + // If the collateral token does not have a `decimals()` method, then a default precision of 18 will be + // applied to the newly created synthetic token. + uint8 syntheticDecimals = _getSyntheticDecimals(params.collateralAddress); + ExpandedIERC20 tokenCurrency = tf.createToken(params.syntheticName, params.syntheticSymbol, syntheticDecimals); + address derivative = ExpiringMultiPartyLib.deploy(_convertParams(params, tokenCurrency)); + + // Give permissions to new derivative contract and then hand over ownership. + tokenCurrency.addMinter(derivative); + tokenCurrency.addBurner(derivative); + tokenCurrency.resetOwner(derivative); + + _registerContract(new address[](0), derivative); + + emit CreatedExpiringMultiParty(derivative, msg.sender); + + return derivative; + } + + /**************************************** + * PRIVATE FUNCTIONS * + ****************************************/ + + // Converts createExpiringMultiParty params to ExpiringMultiParty constructor params. + function _convertParams( + Params memory params, + ExpandedIERC20 newTokenCurrency + ) private view returns (ExpiringMultiParty.ConstructorParams memory constructorParams) { + // Known from creator deployment. + constructorParams.finderAddress = finderAddress; + constructorParams.timerAddress = timerAddress; + + // Enforce configuration constraints. + require(params.withdrawalLiveness != 0, "Withdrawal liveness cannot be 0"); + require(params.liquidationLiveness != 0, "Liquidation liveness cannot be 0"); + require(params.expirationTimestamp > block.timestamp, "Invalid expiration time"); + _requireWhitelistedCollateral(params.collateralAddress); + + // We don't want EMP deployers to be able to intentionally or unintentionally set + // liveness periods that could induce arithmetic overflow, but we also don't want + // to be opinionated about what livenesses are "correct", so we will somewhat + // arbitrarily set the liveness upper bound to 100 years (5200 weeks). In practice, liveness + // periods even greater than a few days would make the EMP unusable for most users. + require(params.withdrawalLiveness < 5200 weeks, "Withdrawal liveness too large"); + require(params.liquidationLiveness < 5200 weeks, "Liquidation liveness too large"); + + // Input from function call. + constructorParams.tokenAddress = address(newTokenCurrency); + constructorParams.expirationTimestamp = params.expirationTimestamp; + constructorParams.collateralAddress = params.collateralAddress; + constructorParams.priceFeedIdentifier = params.priceFeedIdentifier; + constructorParams.collateralRequirement = params.collateralRequirement; + constructorParams.disputeBondPercentage = params.disputeBondPercentage; + constructorParams.sponsorDisputeRewardPercentage = params.sponsorDisputeRewardPercentage; + constructorParams.disputerDisputeRewardPercentage = params.disputerDisputeRewardPercentage; + constructorParams.minSponsorTokens = params.minSponsorTokens; + constructorParams.withdrawalLiveness = params.withdrawalLiveness; + constructorParams.liquidationLiveness = params.liquidationLiveness; + constructorParams.financialProductLibraryAddress = params.financialProductLibraryAddress; + } + + // IERC20Standard.decimals() will revert if the collateral contract has not implemented the decimals() method, + // which is possible since the method is only an OPTIONAL method in the ERC20 standard: + // https://eips.ethereum.org/EIPS/eip-20#methods. + function _getSyntheticDecimals(address _collateralAddress) public view returns (uint8 decimals) { + try IERC20Standard(_collateralAddress).decimals() returns (uint8 _decimals) { + return _decimals; + } catch { + return 18; + } + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiPartyLib.sol b/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiPartyLib.sol new file mode 100644 index 000000000..9ad551fd1 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiPartyLib.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "./ExpiringMultiParty.sol"; + +/** + * @title Provides convenient Expiring Multi Party contract utilities. + * @dev Using this library to deploy EMP's allows calling contracts to avoid importing the full EMP bytecode. + */ +library ExpiringMultiPartyLib { + /** + * @notice Returns address of new EMP deployed with given `params` configuration. + * @dev Caller will need to register new EMP with the Registry to begin requesting prices. Caller is also + * responsible for enforcing constraints on `params`. + * @param params is a `ConstructorParams` object from ExpiringMultiParty. + * @return address of the deployed ExpiringMultiParty contract + */ + function deploy(ExpiringMultiParty.ConstructorParams memory params) public returns (address) { + ExpiringMultiParty derivative = new ExpiringMultiParty(params); + return address(derivative); + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/Liquidatable.sol b/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/Liquidatable.sol new file mode 100644 index 000000000..29320e9e2 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/Liquidatable.sol @@ -0,0 +1,634 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/utils/Address.sol"; + +import "./PricelessPositionManager.sol"; + +import "../../common/implementation/FixedPoint.sol"; + +/** + * @title Liquidatable + * @notice Adds logic to a position-managing contract that enables callers to liquidate an undercollateralized position. + * @dev The liquidation has a liveness period before expiring successfully, during which someone can "dispute" the + * liquidation, which sends a price request to the relevant Oracle to settle the final collateralization ratio based on + * a DVM price. The contract enforces dispute rewards in order to incentivize disputers to correctly dispute false + * liquidations and compensate position sponsors who had their position incorrectly liquidated. Importantly, a + * prospective disputer must deposit a dispute bond that they can lose in the case of an unsuccessful dispute. + * NOTE: this contract does _not_ work with ERC777 collateral currencies or any others that call into the receiver on + * transfer(). Using an ERC777 token would allow a user to maliciously grief other participants (while also losing + * money themselves). + */ +contract Liquidatable is PricelessPositionManager { + using FixedPoint for FixedPoint.Unsigned; + using SafeMath for uint256; + using SafeERC20 for IERC20; + using SafeERC20 for ExpandedIERC20; + using Address for address; + + /**************************************** + * LIQUIDATION DATA STRUCTURES * + ****************************************/ + + // Because of the check in withdrawable(), the order of these enum values should not change. + enum Status { + Uninitialized, + NotDisputed, + Disputed, + DisputeSucceeded, + DisputeFailed + } + + struct LiquidationData { + // Following variables set upon creation of liquidation: + address sponsor; // Address of the liquidated position's sponsor + address liquidator; // Address who created this liquidation + Status state; // Liquidated (and expired or not), Pending a Dispute, or Dispute has resolved + uint256 liquidationTime; // Time when liquidation is initiated, needed to get price from Oracle + // Following variables determined by the position that is being liquidated: + FixedPoint.Unsigned tokensOutstanding; // Synthetic tokens required to be burned by liquidator to initiate dispute + FixedPoint.Unsigned lockedCollateral; // Collateral locked by contract and released upon expiry or post-dispute + // Amount of collateral being liquidated, which could be different from + // lockedCollateral if there were pending withdrawals at the time of liquidation + FixedPoint.Unsigned liquidatedCollateral; + // Unit value (starts at 1) that is used to track the fees per unit of collateral over the course of the liquidation. + FixedPoint.Unsigned rawUnitCollateral; + // Following variable set upon initiation of a dispute: + address disputer; // Person who is disputing a liquidation + // Following variable set upon a resolution of a dispute: + FixedPoint.Unsigned settlementPrice; // Final price as determined by an Oracle following a dispute + FixedPoint.Unsigned finalFee; + } + + // Define the contract's constructor parameters as a struct to enable more variables to be specified. + // This is required to enable more params, over and above Solidity's limits. + struct ConstructorParams { + // Params for PricelessPositionManager only. + uint256 expirationTimestamp; + uint256 withdrawalLiveness; + address collateralAddress; + address tokenAddress; + address finderAddress; + address timerAddress; + address financialProductLibraryAddress; + bytes32 priceFeedIdentifier; + FixedPoint.Unsigned minSponsorTokens; + // Params specifically for Liquidatable. + uint256 liquidationLiveness; + FixedPoint.Unsigned collateralRequirement; + FixedPoint.Unsigned disputeBondPercentage; + FixedPoint.Unsigned sponsorDisputeRewardPercentage; + FixedPoint.Unsigned disputerDisputeRewardPercentage; + } + + // This struct is used in the `withdrawLiquidation` method that disperses liquidation and dispute rewards. + // `payToX` stores the total collateral to withdraw from the contract to pay X. This value might differ + // from `paidToX` due to precision loss between accounting for the `rawCollateral` versus the + // fee-adjusted collateral. These variables are stored within a struct to avoid the stack too deep error. + struct RewardsData { + FixedPoint.Unsigned payToSponsor; + FixedPoint.Unsigned payToLiquidator; + FixedPoint.Unsigned payToDisputer; + FixedPoint.Unsigned paidToSponsor; + FixedPoint.Unsigned paidToLiquidator; + FixedPoint.Unsigned paidToDisputer; + } + + // Liquidations are unique by ID per sponsor + mapping(address => LiquidationData[]) public liquidations; + + // Total collateral in liquidation. + FixedPoint.Unsigned public rawLiquidationCollateral; + + // Immutable contract parameters: + // Amount of time for pending liquidation before expiry. + // !!Note: The lower the liquidation liveness value, the more risk incurred by sponsors. + // Extremely low liveness values increase the chance that opportunistic invalid liquidations + // expire without dispute, thereby decreasing the usability for sponsors and increasing the risk + // for the contract as a whole. An insolvent contract is extremely risky for any sponsor or synthetic + // token holder for the contract. + uint256 public liquidationLiveness; + // Required collateral:TRV ratio for a position to be considered sufficiently collateralized. + FixedPoint.Unsigned public collateralRequirement; + // Percent of a Liquidation/Position's lockedCollateral to be deposited by a potential disputer + // Represented as a multiplier, for example 1.5e18 = "150%" and 0.05e18 = "5%" + FixedPoint.Unsigned public disputeBondPercentage; + // Percent of oraclePrice paid to sponsor in the Disputed state (i.e. following a successful dispute) + // Represented as a multiplier, see above. + FixedPoint.Unsigned public sponsorDisputeRewardPercentage; + // Percent of oraclePrice paid to disputer in the Disputed state (i.e. following a successful dispute) + // Represented as a multiplier, see above. + FixedPoint.Unsigned public disputerDisputeRewardPercentage; + + /**************************************** + * EVENTS * + ****************************************/ + + event LiquidationCreated( + address indexed sponsor, + address indexed liquidator, + uint256 indexed liquidationId, + uint256 tokensOutstanding, + uint256 lockedCollateral, + uint256 liquidatedCollateral, + uint256 liquidationTime + ); + event LiquidationDisputed( + address indexed sponsor, + address indexed liquidator, + address indexed disputer, + uint256 liquidationId, + uint256 disputeBondAmount + ); + event DisputeSettled( + address indexed caller, + address indexed sponsor, + address indexed liquidator, + address disputer, + uint256 liquidationId, + bool disputeSucceeded + ); + event LiquidationWithdrawn( + address indexed caller, + uint256 paidToLiquidator, + uint256 paidToDisputer, + uint256 paidToSponsor, + Status indexed liquidationStatus, + uint256 settlementPrice + ); + + /**************************************** + * MODIFIERS * + ****************************************/ + + modifier disputable(uint256 liquidationId, address sponsor) { + _disputable(liquidationId, sponsor); + _; + } + + modifier withdrawable(uint256 liquidationId, address sponsor) { + _withdrawable(liquidationId, sponsor); + _; + } + + /** + * @notice Constructs the liquidatable contract. + * @param params struct to define input parameters for construction of Liquidatable. Some params + * are fed directly into the PricelessPositionManager's constructor within the inheritance tree. + */ + constructor( + ConstructorParams memory params + ) + PricelessPositionManager( + params.expirationTimestamp, + params.withdrawalLiveness, + params.collateralAddress, + params.tokenAddress, + params.finderAddress, + params.priceFeedIdentifier, + params.minSponsorTokens, + params.timerAddress, + params.financialProductLibraryAddress + ) + nonReentrant() + { + require(params.collateralRequirement.isGreaterThan(1)); + require(params.sponsorDisputeRewardPercentage.add(params.disputerDisputeRewardPercentage).isLessThan(1)); + + // Set liquidatable specific variables. + liquidationLiveness = params.liquidationLiveness; + collateralRequirement = params.collateralRequirement; + disputeBondPercentage = params.disputeBondPercentage; + sponsorDisputeRewardPercentage = params.sponsorDisputeRewardPercentage; + disputerDisputeRewardPercentage = params.disputerDisputeRewardPercentage; + } + + /**************************************** + * LIQUIDATION FUNCTIONS * + ****************************************/ + + /** + * @notice Liquidates the sponsor's position if the caller has enough + * synthetic tokens to retire the position's outstanding tokens. Liquidations above + * a minimum size also reset an ongoing "slow withdrawal"'s liveness. + * @dev This method generates an ID that will uniquely identify liquidation for the sponsor. This contract must be + * approved to spend at least `tokensLiquidated` of `tokenCurrency` and at least `finalFeeBond` of `collateralCurrency`. + * @dev This contract must have the Burner role for the `tokenCurrency`. + * @param sponsor address of the sponsor to liquidate. + * @param minCollateralPerToken abort the liquidation if the position's collateral per token is below this value. + * @param maxCollateralPerToken abort the liquidation if the position's collateral per token exceeds this value. + * @param maxTokensToLiquidate max number of tokens to liquidate. + * @param deadline abort the liquidation if the transaction is mined after this timestamp. + * @return liquidationId ID of the newly created liquidation. + * @return tokensLiquidated amount of synthetic tokens removed and liquidated from the `sponsor`'s position. + * @return finalFeeBond amount of collateral to be posted by liquidator and returned if not disputed successfully. + */ + function createLiquidation( + address sponsor, + FixedPoint.Unsigned calldata minCollateralPerToken, + FixedPoint.Unsigned calldata maxCollateralPerToken, + FixedPoint.Unsigned calldata maxTokensToLiquidate, + uint256 deadline + ) + external + fees + onlyPreExpiration + nonReentrant + returns ( + uint256 liquidationId, + FixedPoint.Unsigned memory tokensLiquidated, + FixedPoint.Unsigned memory finalFeeBond + ) + { + // Check that this transaction was mined pre-deadline. + require(getCurrentTime() <= deadline, "Mined after deadline"); + + // Retrieve Position data for sponsor + PositionData storage positionToLiquidate = _getPositionData(sponsor); + + tokensLiquidated = FixedPoint.min(maxTokensToLiquidate, positionToLiquidate.tokensOutstanding); + require(tokensLiquidated.isGreaterThan(0)); + + // Starting values for the Position being liquidated. If withdrawal request amount is > position's collateral, + // then set this to 0, otherwise set it to (startCollateral - withdrawal request amount). + FixedPoint.Unsigned memory startCollateral = _getFeeAdjustedCollateral(positionToLiquidate.rawCollateral); + FixedPoint.Unsigned memory startCollateralNetOfWithdrawal = FixedPoint.fromUnscaledUint(0); + if (positionToLiquidate.withdrawalRequestAmount.isLessThanOrEqual(startCollateral)) { + startCollateralNetOfWithdrawal = startCollateral.sub(positionToLiquidate.withdrawalRequestAmount); + } + + // Scoping to get rid of a stack too deep error. + { + FixedPoint.Unsigned memory startTokens = positionToLiquidate.tokensOutstanding; + + // The Position's collateralization ratio must be between [minCollateralPerToken, maxCollateralPerToken]. + // maxCollateralPerToken >= startCollateralNetOfWithdrawal / startTokens. + require( + maxCollateralPerToken.mul(startTokens).isGreaterThanOrEqual(startCollateralNetOfWithdrawal), + "CR is more than max liq. price" + ); + // minCollateralPerToken >= startCollateralNetOfWithdrawal / startTokens. + require( + minCollateralPerToken.mul(startTokens).isLessThanOrEqual(startCollateralNetOfWithdrawal), + "CR is less than min liq. price" + ); + } + + // Compute final fee at time of liquidation. + finalFeeBond = _computeFinalFees(); + + // These will be populated within the scope below. + FixedPoint.Unsigned memory lockedCollateral; + FixedPoint.Unsigned memory liquidatedCollateral; + + // Scoping to get rid of a stack too deep error. + { + FixedPoint.Unsigned memory ratio = tokensLiquidated.div(positionToLiquidate.tokensOutstanding); + + // The actual amount of collateral that gets moved to the liquidation. + lockedCollateral = startCollateral.mul(ratio); + + // For purposes of disputes, it's actually this liquidatedCollateral value that's used. This value is net of + // withdrawal requests. + liquidatedCollateral = startCollateralNetOfWithdrawal.mul(ratio); + + // Part of the withdrawal request is also removed. Ideally: + // liquidatedCollateral + withdrawalAmountToRemove = lockedCollateral. + FixedPoint.Unsigned memory withdrawalAmountToRemove = positionToLiquidate.withdrawalRequestAmount.mul( + ratio + ); + _reduceSponsorPosition(sponsor, tokensLiquidated, lockedCollateral, withdrawalAmountToRemove); + } + + // Add to the global liquidation collateral count. + _addCollateral(rawLiquidationCollateral, lockedCollateral.add(finalFeeBond)); + + // Construct liquidation object. + // Note: All dispute-related values are zeroed out until a dispute occurs. liquidationId is the index of the new + // LiquidationData that is pushed into the array, which is equal to the current length of the array pre-push. + liquidationId = liquidations[sponsor].length; + liquidations[sponsor].push( + LiquidationData({ + sponsor: sponsor, + liquidator: msg.sender, + state: Status.NotDisputed, + liquidationTime: getCurrentTime(), + tokensOutstanding: tokensLiquidated, + lockedCollateral: lockedCollateral, + liquidatedCollateral: liquidatedCollateral, + rawUnitCollateral: _convertToRawCollateral(FixedPoint.fromUnscaledUint(1)), + disputer: address(0), + settlementPrice: FixedPoint.fromUnscaledUint(0), + finalFee: finalFeeBond + }) + ); + + // If this liquidation is a subsequent liquidation on the position, and the liquidation size is larger than + // some "griefing threshold", then re-set the liveness. This enables a liquidation against a withdraw request to be + // "dragged out" if the position is very large and liquidators need time to gather funds. The griefing threshold + // is enforced so that liquidations for trivially small # of tokens cannot drag out an honest sponsor's slow withdrawal. + + // We arbitrarily set the "griefing threshold" to `minSponsorTokens` because it is the only parameter + // denominated in token currency units and we can avoid adding another parameter. + FixedPoint.Unsigned memory griefingThreshold = minSponsorTokens; + if ( + positionToLiquidate.withdrawalRequestPassTimestamp > 0 && // The position is undergoing a slow withdrawal. + positionToLiquidate.withdrawalRequestPassTimestamp > getCurrentTime() && // The slow withdrawal has not yet expired. + tokensLiquidated.isGreaterThanOrEqual(griefingThreshold) // The liquidated token count is above a "griefing threshold". + ) { + positionToLiquidate.withdrawalRequestPassTimestamp = getCurrentTime().add(withdrawalLiveness); + } + + emit LiquidationCreated( + sponsor, + msg.sender, + liquidationId, + tokensLiquidated.rawValue, + lockedCollateral.rawValue, + liquidatedCollateral.rawValue, + getCurrentTime() + ); + + // Destroy tokens + tokenCurrency.safeTransferFrom(msg.sender, address(this), tokensLiquidated.rawValue); + tokenCurrency.burn(tokensLiquidated.rawValue); + + // Pull final fee from liquidator. + collateralCurrency.safeTransferFrom(msg.sender, address(this), finalFeeBond.rawValue); + } + + /** + * @notice Disputes a liquidation, if the caller has enough collateral to post a dispute bond + * and pay a fixed final fee charged on each price request. + * @dev Can only dispute a liquidation before the liquidation expires and if there are no other pending disputes. + * This contract must be approved to spend at least the dispute bond amount of `collateralCurrency`. This dispute + * bond amount is calculated from `disputeBondPercentage` times the collateral in the liquidation. + * @param liquidationId of the disputed liquidation. + * @param sponsor the address of the sponsor whose liquidation is being disputed. + * @return totalPaid amount of collateral charged to disputer (i.e. final fee bond + dispute bond). + */ + function dispute( + uint256 liquidationId, + address sponsor + ) external disputable(liquidationId, sponsor) fees nonReentrant returns (FixedPoint.Unsigned memory totalPaid) { + LiquidationData storage disputedLiquidation = _getLiquidationData(sponsor, liquidationId); + + // Multiply by the unit collateral so the dispute bond is a percentage of the locked collateral after fees. + FixedPoint.Unsigned memory disputeBondAmount = disputedLiquidation + .lockedCollateral + .mul(disputeBondPercentage) + .mul(_getFeeAdjustedCollateral(disputedLiquidation.rawUnitCollateral)); + _addCollateral(rawLiquidationCollateral, disputeBondAmount); + + // Request a price from DVM. Liquidation is pending dispute until DVM returns a price. + disputedLiquidation.state = Status.Disputed; + disputedLiquidation.disputer = msg.sender; + + // Enqueue a request with the DVM. + _requestOraclePriceLiquidation(disputedLiquidation.liquidationTime); + + emit LiquidationDisputed( + sponsor, + disputedLiquidation.liquidator, + msg.sender, + liquidationId, + disputeBondAmount.rawValue + ); + totalPaid = disputeBondAmount.add(disputedLiquidation.finalFee); + + // Pay the final fee for requesting price from the DVM. + _payFinalFees(msg.sender, disputedLiquidation.finalFee); + + // Transfer the dispute bond amount from the caller to this contract. + collateralCurrency.safeTransferFrom(msg.sender, address(this), disputeBondAmount.rawValue); + } + + /** + * @notice After a dispute has settled or after a non-disputed liquidation has expired, + * anyone can call this method to disperse payments to the sponsor, liquidator, and disdputer. + * @dev If the dispute SUCCEEDED: the sponsor, liquidator, and disputer are eligible for payment. + * If the dispute FAILED: only the liquidator can receive payment. + * This method will revert if rewards have already been dispersed. + * @param liquidationId uniquely identifies the sponsor's liquidation. + * @param sponsor address of the sponsor associated with the liquidation. + * @return data about rewards paid out. + */ + function withdrawLiquidation( + uint256 liquidationId, + address sponsor + ) public withdrawable(liquidationId, sponsor) fees nonReentrant returns (RewardsData memory) { + LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId); + + // Settles the liquidation if necessary. This call will revert if the price has not resolved yet. + _settle(liquidationId, sponsor); + + // Calculate rewards as a function of the TRV. + // Note: all payouts are scaled by the unit collateral value so all payouts are charged the fees pro rata. + FixedPoint.Unsigned memory feeAttenuation = _getFeeAdjustedCollateral(liquidation.rawUnitCollateral); + FixedPoint.Unsigned memory settlementPrice = liquidation.settlementPrice; + FixedPoint.Unsigned memory tokenRedemptionValue = liquidation.tokensOutstanding.mul(settlementPrice).mul( + feeAttenuation + ); + FixedPoint.Unsigned memory collateral = liquidation.lockedCollateral.mul(feeAttenuation); + FixedPoint.Unsigned memory disputerDisputeReward = disputerDisputeRewardPercentage.mul(tokenRedemptionValue); + FixedPoint.Unsigned memory sponsorDisputeReward = sponsorDisputeRewardPercentage.mul(tokenRedemptionValue); + FixedPoint.Unsigned memory disputeBondAmount = collateral.mul(disputeBondPercentage); + FixedPoint.Unsigned memory finalFee = liquidation.finalFee.mul(feeAttenuation); + + // There are three main outcome states: either the dispute succeeded, failed or was not updated. + // Based on the state, different parties of a liquidation receive different amounts. + // After assigning rewards based on the liquidation status, decrease the total collateral held in this contract + // by the amount to pay each party. The actual amounts withdrawn might differ if _removeCollateral causes + // precision loss. + RewardsData memory rewards; + if (liquidation.state == Status.DisputeSucceeded) { + // If the dispute is successful then all three users should receive rewards: + + // Pay DISPUTER: disputer reward + dispute bond + returned final fee + rewards.payToDisputer = disputerDisputeReward.add(disputeBondAmount).add(finalFee); + + // Pay SPONSOR: remaining collateral (collateral - TRV) + sponsor reward + rewards.payToSponsor = sponsorDisputeReward.add(collateral.sub(tokenRedemptionValue)); + + // Pay LIQUIDATOR: TRV - dispute reward - sponsor reward + // If TRV > Collateral, then subtract rewards from collateral + // NOTE: `payToLiquidator` should never be below zero since we enforce that + // (sponsorDisputePct+disputerDisputePct) <= 1 in the constructor when these params are set. + rewards.payToLiquidator = tokenRedemptionValue.sub(sponsorDisputeReward).sub(disputerDisputeReward); + + // Transfer rewards and debit collateral + rewards.paidToLiquidator = _removeCollateral(rawLiquidationCollateral, rewards.payToLiquidator); + rewards.paidToSponsor = _removeCollateral(rawLiquidationCollateral, rewards.payToSponsor); + rewards.paidToDisputer = _removeCollateral(rawLiquidationCollateral, rewards.payToDisputer); + + collateralCurrency.safeTransfer(liquidation.disputer, rewards.paidToDisputer.rawValue); + collateralCurrency.safeTransfer(liquidation.liquidator, rewards.paidToLiquidator.rawValue); + collateralCurrency.safeTransfer(liquidation.sponsor, rewards.paidToSponsor.rawValue); + + // In the case of a failed dispute only the liquidator can withdraw. + } else if (liquidation.state == Status.DisputeFailed) { + // Pay LIQUIDATOR: collateral + dispute bond + returned final fee + rewards.payToLiquidator = collateral.add(disputeBondAmount).add(finalFee); + + // Transfer rewards and debit collateral + rewards.paidToLiquidator = _removeCollateral(rawLiquidationCollateral, rewards.payToLiquidator); + + collateralCurrency.safeTransfer(liquidation.liquidator, rewards.paidToLiquidator.rawValue); + + // If the state is pre-dispute but time has passed liveness then there was no dispute. We represent this + // state as a dispute failed and the liquidator can withdraw. + } else if (liquidation.state == Status.NotDisputed) { + // Pay LIQUIDATOR: collateral + returned final fee + rewards.payToLiquidator = collateral.add(finalFee); + + // Transfer rewards and debit collateral + rewards.paidToLiquidator = _removeCollateral(rawLiquidationCollateral, rewards.payToLiquidator); + + collateralCurrency.safeTransfer(liquidation.liquidator, rewards.paidToLiquidator.rawValue); + } + + emit LiquidationWithdrawn( + msg.sender, + rewards.paidToLiquidator.rawValue, + rewards.paidToDisputer.rawValue, + rewards.paidToSponsor.rawValue, + liquidation.state, + settlementPrice.rawValue + ); + + // Free up space after collateral is withdrawn by removing the liquidation object from the array. + delete liquidations[sponsor][liquidationId]; + + return rewards; + } + + /** + * @notice Gets all liquidation information for a given sponsor address. + * @param sponsor address of the position sponsor. + * @return liquidationData array of all liquidation information for the given sponsor address. + */ + function getLiquidations( + address sponsor + ) external view nonReentrantView returns (LiquidationData[] memory liquidationData) { + return liquidations[sponsor]; + } + + /** + * @notice Accessor method to calculate a transformed collateral requirement using the finanical product library + specified during contract deployment. If no library was provided then no modification to the collateral requirement is done. + * @param price input price used as an input to transform the collateral requirement. + * @return transformedCollateralRequirement collateral requirement with transformation applied to it. + * @dev This method should never revert. + */ + function transformCollateralRequirement( + FixedPoint.Unsigned memory price + ) public view nonReentrantView returns (FixedPoint.Unsigned memory) { + return _transformCollateralRequirement(price); + } + + /**************************************** + * INTERNAL FUNCTIONS * + ****************************************/ + + // This settles a liquidation if it is in the Disputed state. If not, it will immediately return. + // If the liquidation is in the Disputed state, but a price is not available, this will revert. + function _settle(uint256 liquidationId, address sponsor) internal { + LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId); + + // Settlement only happens when state == Disputed and will only happen once per liquidation. + // If this liquidation is not ready to be settled, this method should return immediately. + if (liquidation.state != Status.Disputed) { + return; + } + + // Get the returned price from the oracle. If this has not yet resolved will revert. + liquidation.settlementPrice = _getOraclePriceLiquidation(liquidation.liquidationTime); + + // Find the value of the tokens in the underlying collateral. + FixedPoint.Unsigned memory tokenRedemptionValue = liquidation.tokensOutstanding.mul( + liquidation.settlementPrice + ); + + // The required collateral is the value of the tokens in underlying * required collateral ratio. The Transform + // Collateral requirement method applies a from the financial Product library to change the scaled the collateral + // requirement based on the settlement price. If no library was specified when deploying the emp then this makes no change. + FixedPoint.Unsigned memory requiredCollateral = tokenRedemptionValue.mul( + _transformCollateralRequirement(liquidation.settlementPrice) + ); + + // If the position has more than the required collateral it is solvent and the dispute is valid(liquidation is invalid) + // Note that this check uses the liquidatedCollateral not the lockedCollateral as this considers withdrawals. + bool disputeSucceeded = liquidation.liquidatedCollateral.isGreaterThanOrEqual(requiredCollateral); + liquidation.state = disputeSucceeded ? Status.DisputeSucceeded : Status.DisputeFailed; + + emit DisputeSettled( + msg.sender, + sponsor, + liquidation.liquidator, + liquidation.disputer, + liquidationId, + disputeSucceeded + ); + } + + function _pfc() internal view override returns (FixedPoint.Unsigned memory) { + return super._pfc().add(_getFeeAdjustedCollateral(rawLiquidationCollateral)); + } + + function _getLiquidationData( + address sponsor, + uint256 liquidationId + ) internal view returns (LiquidationData storage liquidation) { + LiquidationData[] storage liquidationArray = liquidations[sponsor]; + + // Revert if the caller is attempting to access an invalid liquidation + // (one that has never been created or one has never been initialized). + require( + liquidationId < liquidationArray.length && liquidationArray[liquidationId].state != Status.Uninitialized, + "Invalid liquidation ID" + ); + return liquidationArray[liquidationId]; + } + + function _getLiquidationExpiry(LiquidationData storage liquidation) internal view returns (uint256) { + return liquidation.liquidationTime.add(liquidationLiveness); + } + + // These internal functions are supposed to act identically to modifiers, but re-used modifiers + // unnecessarily increase contract bytecode size. + // source: https://blog.polymath.network/solidity-tips-and-tricks-to-save-gas-and-reduce-bytecode-size-c44580b218e6 + function _disputable(uint256 liquidationId, address sponsor) internal view { + LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId); + require( + (getCurrentTime() < _getLiquidationExpiry(liquidation)) && (liquidation.state == Status.NotDisputed), + "Liquidation not disputable" + ); + } + + function _withdrawable(uint256 liquidationId, address sponsor) internal view { + LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId); + Status state = liquidation.state; + + // Must be disputed or the liquidation has passed expiry. + require( + (state > Status.NotDisputed) || + ((_getLiquidationExpiry(liquidation) <= getCurrentTime()) && (state == Status.NotDisputed)), + "Liquidation not withdrawable" + ); + } + + function _transformCollateralRequirement( + FixedPoint.Unsigned memory price + ) internal view returns (FixedPoint.Unsigned memory) { + if (!address(financialProductLibrary).isContract()) return collateralRequirement; + try financialProductLibrary.transformCollateralRequirement(price, collateralRequirement) returns ( + FixedPoint.Unsigned memory transformedCollateralRequirement + ) { + return transformedCollateralRequirement; + } catch { + return collateralRequirement; + } + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/PricelessPositionManager.sol b/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/PricelessPositionManager.sol new file mode 100644 index 000000000..cb90445e1 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/PricelessPositionManager.sol @@ -0,0 +1,985 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/utils/Address.sol"; + +import "../../common/implementation/FixedPoint.sol"; +import "../../common/interfaces/ExpandedIERC20.sol"; +import "../../common/interfaces/IERC20Standard.sol"; + +import "../../data-verification-mechanism/interfaces/OracleInterface.sol"; +import "../../optimistic-oracle-v2/interfaces/OptimisticOracleInterface.sol"; +import "../../data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol"; +import "../../data-verification-mechanism/implementation/Constants.sol"; + +import "../common/FeePayer.sol"; +import "../common/financial-product-libraries/expiring-multiparty-libraries/FinancialProductLibrary.sol"; + +/** + * @title Financial contract with priceless position management. + * @notice Handles positions for multiple sponsors in an optimistic (i.e., priceless) way without relying + * on a price feed. On construction, deploys a new ERC20, managed by this contract, that is the synthetic token. + */ + +contract PricelessPositionManager is FeePayer { + using SafeMath for uint256; + using FixedPoint for FixedPoint.Unsigned; + using SafeERC20 for IERC20; + using SafeERC20 for ExpandedIERC20; + using Address for address; + + /**************************************** + * PRICELESS POSITION DATA STRUCTURES * + ****************************************/ + + // Stores the state of the PricelessPositionManager. Set on expiration, emergency shutdown, or settlement. + enum ContractState { + Open, + ExpiredPriceRequested, + ExpiredPriceReceived + } + ContractState public contractState; + + // Represents a single sponsor's position. All collateral is held by this contract. + // This struct acts as bookkeeping for how much of that collateral is allocated to each sponsor. + struct PositionData { + FixedPoint.Unsigned tokensOutstanding; + // Tracks pending withdrawal requests. A withdrawal request is pending if `withdrawalRequestPassTimestamp != 0`. + uint256 withdrawalRequestPassTimestamp; + FixedPoint.Unsigned withdrawalRequestAmount; + // Raw collateral value. This value should never be accessed directly -- always use _getFeeAdjustedCollateral(). + // To add or remove collateral, use _addCollateral() and _removeCollateral(). + FixedPoint.Unsigned rawCollateral; + // Tracks pending transfer position requests. A transfer position request is pending if `transferPositionRequestPassTimestamp != 0`. + uint256 transferPositionRequestPassTimestamp; + } + + // Maps sponsor addresses to their positions. Each sponsor can have only one position. + mapping(address => PositionData) public positions; + + // Keep track of the total collateral and tokens across all positions to enable calculating the + // global collateralization ratio without iterating over all positions. + FixedPoint.Unsigned public totalTokensOutstanding; + + // Similar to the rawCollateral in PositionData, this value should not be used directly. + // _getFeeAdjustedCollateral(), _addCollateral() and _removeCollateral() must be used to access and adjust. + FixedPoint.Unsigned public rawTotalPositionCollateral; + + // Synthetic token created by this contract. + ExpandedIERC20 public tokenCurrency; + + // Unique identifier for DVM price feed ticker. + bytes32 public priceIdentifier; + // Time that this contract expires. Should not change post-construction unless an emergency shutdown occurs. + uint256 public expirationTimestamp; + // Time that has to elapse for a withdrawal request to be considered passed, if no liquidations occur. + // !!Note: The lower the withdrawal liveness value, the more risk incurred by the contract. + // Extremely low liveness values increase the chance that opportunistic invalid withdrawal requests + // expire without liquidation, thereby increasing the insolvency risk for the contract as a whole. An insolvent + // contract is extremely risky for any sponsor or synthetic token holder for the contract. + uint256 public withdrawalLiveness; + + // Minimum number of tokens in a sponsor's position. + FixedPoint.Unsigned public minSponsorTokens; + + // The expiry price pulled from the DVM. + FixedPoint.Unsigned public expiryPrice; + + // Instance of FinancialProductLibrary to provide custom price and collateral requirement transformations to extend + // the functionality of the EMP to support a wider range of financial products. + FinancialProductLibrary public financialProductLibrary; + + /**************************************** + * EVENTS * + ****************************************/ + + event RequestTransferPosition(address indexed oldSponsor); + event RequestTransferPositionExecuted(address indexed oldSponsor, address indexed newSponsor); + event RequestTransferPositionCanceled(address indexed oldSponsor); + event Deposit(address indexed sponsor, uint256 indexed collateralAmount); + event Withdrawal(address indexed sponsor, uint256 indexed collateralAmount); + event RequestWithdrawal(address indexed sponsor, uint256 indexed collateralAmount); + event RequestWithdrawalExecuted(address indexed sponsor, uint256 indexed collateralAmount); + event RequestWithdrawalCanceled(address indexed sponsor, uint256 indexed collateralAmount); + event PositionCreated(address indexed sponsor, uint256 indexed collateralAmount, uint256 indexed tokenAmount); + event NewSponsor(address indexed sponsor); + event EndedSponsorPosition(address indexed sponsor); + event Repay(address indexed sponsor, uint256 indexed numTokensRepaid, uint256 indexed newTokenCount); + event Redeem(address indexed sponsor, uint256 indexed collateralAmount, uint256 indexed tokenAmount); + event ContractExpired(address indexed caller); + event SettleExpiredPosition( + address indexed caller, + uint256 indexed collateralReturned, + uint256 indexed tokensBurned + ); + event EmergencyShutdown(address indexed caller, uint256 originalExpirationTimestamp, uint256 shutdownTimestamp); + + /**************************************** + * MODIFIERS * + ****************************************/ + + modifier onlyPreExpiration() { + _onlyPreExpiration(); + _; + } + + modifier onlyPostExpiration() { + _onlyPostExpiration(); + _; + } + + modifier onlyCollateralizedPosition(address sponsor) { + _onlyCollateralizedPosition(sponsor); + _; + } + + // Check that the current state of the pricelessPositionManager is Open. + // This prevents multiple calls to `expire` and `EmergencyShutdown` post expiration. + modifier onlyOpenState() { + _onlyOpenState(); + _; + } + + modifier noPendingWithdrawal(address sponsor) { + _positionHasNoPendingWithdrawal(sponsor); + _; + } + + /** + * @notice Construct the PricelessPositionManager + * @dev Deployer of this contract should consider carefully which parties have ability to mint and burn + * the synthetic tokens referenced by `_tokenAddress`. This contract's security assumes that no external accounts + * can mint new tokens, which could be used to steal all of this contract's locked collateral. + * We recommend to only use synthetic token contracts whose sole Owner role (the role capable of adding & removing roles) + * is assigned to this contract, whose sole Minter role is assigned to this contract, and whose + * total supply is 0 prior to construction of this contract. + * @param _expirationTimestamp unix timestamp of when the contract will expire. + * @param _withdrawalLiveness liveness delay, in seconds, for pending withdrawals. + * @param _collateralAddress ERC20 token used as collateral for all positions. + * @param _tokenAddress ERC20 token used as synthetic token. + * @param _finderAddress UMA protocol Finder used to discover other protocol contracts. + * @param _priceIdentifier registered in the DVM for the synthetic. + * @param _minSponsorTokens minimum number of tokens that must exist at any time in a position. + * @param _timerAddress Contract that stores the current time in a testing environment. + * Must be set to 0x0 for production environments that use live time. + * @param _financialProductLibraryAddress Contract providing contract state transformations. + */ + constructor( + uint256 _expirationTimestamp, + uint256 _withdrawalLiveness, + address _collateralAddress, + address _tokenAddress, + address _finderAddress, + bytes32 _priceIdentifier, + FixedPoint.Unsigned memory _minSponsorTokens, + address _timerAddress, + address _financialProductLibraryAddress + ) FeePayer(_collateralAddress, _finderAddress, _timerAddress) nonReentrant() { + require(_expirationTimestamp > getCurrentTime()); + require(_getIdentifierWhitelist().isIdentifierSupported(_priceIdentifier)); + + expirationTimestamp = _expirationTimestamp; + withdrawalLiveness = _withdrawalLiveness; + tokenCurrency = ExpandedIERC20(_tokenAddress); + minSponsorTokens = _minSponsorTokens; + priceIdentifier = _priceIdentifier; + + // Initialize the financialProductLibrary at the provided address. + financialProductLibrary = FinancialProductLibrary(_financialProductLibraryAddress); + } + + /**************************************** + * POSITION FUNCTIONS * + ****************************************/ + + /** + * @notice Requests to transfer ownership of the caller's current position to a new sponsor address. + * Once the request liveness is passed, the sponsor can execute the transfer and specify the new sponsor. + * @dev The liveness length is the same as the withdrawal liveness. + */ + function requestTransferPosition() public onlyPreExpiration nonReentrant { + PositionData storage positionData = _getPositionData(msg.sender); + require(positionData.transferPositionRequestPassTimestamp == 0); + + // Make sure the proposed expiration of this request is not post-expiry. + uint256 requestPassTime = getCurrentTime().add(withdrawalLiveness); + require(requestPassTime < expirationTimestamp); + + // Update the position object for the user. + positionData.transferPositionRequestPassTimestamp = requestPassTime; + + emit RequestTransferPosition(msg.sender); + } + + /** + * @notice After a passed transfer position request (i.e., by a call to `requestTransferPosition` and waiting + * `withdrawalLiveness`), transfers ownership of the caller's current position to `newSponsorAddress`. + * @dev Transferring positions can only occur if the recipient does not already have a position. + * @param newSponsorAddress is the address to which the position will be transferred. + */ + function transferPositionPassedRequest( + address newSponsorAddress + ) public onlyPreExpiration noPendingWithdrawal(msg.sender) nonReentrant { + require( + _getFeeAdjustedCollateral(positions[newSponsorAddress].rawCollateral).isEqual( + FixedPoint.fromUnscaledUint(0) + ) + ); + PositionData storage positionData = _getPositionData(msg.sender); + require( + positionData.transferPositionRequestPassTimestamp != 0 && + positionData.transferPositionRequestPassTimestamp <= getCurrentTime() + ); + + // Reset transfer request. + positionData.transferPositionRequestPassTimestamp = 0; + + positions[newSponsorAddress] = positionData; + delete positions[msg.sender]; + + emit RequestTransferPositionExecuted(msg.sender, newSponsorAddress); + emit NewSponsor(newSponsorAddress); + emit EndedSponsorPosition(msg.sender); + } + + /** + * @notice Cancels a pending transfer position request. + */ + function cancelTransferPosition() external onlyPreExpiration nonReentrant { + PositionData storage positionData = _getPositionData(msg.sender); + require(positionData.transferPositionRequestPassTimestamp != 0); + + emit RequestTransferPositionCanceled(msg.sender); + + // Reset withdrawal request. + positionData.transferPositionRequestPassTimestamp = 0; + } + + /** + * @notice Transfers `collateralAmount` of `collateralCurrency` into the specified sponsor's position. + * @dev Increases the collateralization level of a position after creation. This contract must be approved to spend + * at least `collateralAmount` of `collateralCurrency`. + * @param sponsor the sponsor to credit the deposit to. + * @param collateralAmount total amount of collateral tokens to be sent to the sponsor's position. + */ + function depositTo( + address sponsor, + FixedPoint.Unsigned memory collateralAmount + ) public onlyPreExpiration noPendingWithdrawal(sponsor) fees nonReentrant { + require(collateralAmount.isGreaterThan(0)); + PositionData storage positionData = _getPositionData(sponsor); + + // Increase the position and global collateral balance by collateral amount. + _incrementCollateralBalances(positionData, collateralAmount); + + emit Deposit(sponsor, collateralAmount.rawValue); + + // Move collateral currency from sender to contract. + collateralCurrency.safeTransferFrom(msg.sender, address(this), collateralAmount.rawValue); + } + + /** + * @notice Transfers `collateralAmount` of `collateralCurrency` into the caller's position. + * @dev Increases the collateralization level of a position after creation. This contract must be approved to spend + * at least `collateralAmount` of `collateralCurrency`. + * @param collateralAmount total amount of collateral tokens to be sent to the sponsor's position. + */ + function deposit(FixedPoint.Unsigned memory collateralAmount) public { + // This is just a thin wrapper over depositTo that specified the sender as the sponsor. + depositTo(msg.sender, collateralAmount); + } + + /** + * @notice Transfers `collateralAmount` of `collateralCurrency` from the sponsor's position to the sponsor. + * @dev Reverts if the withdrawal puts this position's collateralization ratio below the global collateralization + * ratio. In that case, use `requestWithdrawal`. Might not withdraw the full requested amount to account for precision loss. + * @param collateralAmount is the amount of collateral to withdraw. + * @return amountWithdrawn The actual amount of collateral withdrawn. + */ + function withdraw( + FixedPoint.Unsigned memory collateralAmount + ) + public + onlyPreExpiration + noPendingWithdrawal(msg.sender) + fees + nonReentrant + returns (FixedPoint.Unsigned memory amountWithdrawn) + { + require(collateralAmount.isGreaterThan(0)); + PositionData storage positionData = _getPositionData(msg.sender); + + // Decrement the sponsor's collateral and global collateral amounts. Check the GCR between decrement to ensure + // position remains above the GCR within the withdrawal. If this is not the case the caller must submit a request. + amountWithdrawn = _decrementCollateralBalancesCheckGCR(positionData, collateralAmount); + + emit Withdrawal(msg.sender, amountWithdrawn.rawValue); + + // Move collateral currency from contract to sender. + // Note: that we move the amount of collateral that is decreased from rawCollateral (inclusive of fees) + // instead of the user requested amount. This eliminates precision loss that could occur + // where the user withdraws more collateral than rawCollateral is decremented by. + collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue); + } + + /** + * @notice Starts a withdrawal request that, if passed, allows the sponsor to withdraw` from their position. + * @dev The request will be pending for `withdrawalLiveness`, during which the position can be liquidated. + * @param collateralAmount the amount of collateral requested to withdraw + */ + function requestWithdrawal( + FixedPoint.Unsigned memory collateralAmount + ) public onlyPreExpiration noPendingWithdrawal(msg.sender) nonReentrant { + PositionData storage positionData = _getPositionData(msg.sender); + require( + collateralAmount.isGreaterThan(0) && + collateralAmount.isLessThanOrEqual(_getFeeAdjustedCollateral(positionData.rawCollateral)) + ); + + // Make sure the proposed expiration of this request is not post-expiry. + uint256 requestPassTime = getCurrentTime().add(withdrawalLiveness); + require(requestPassTime < expirationTimestamp); + + // Update the position object for the user. + positionData.withdrawalRequestPassTimestamp = requestPassTime; + positionData.withdrawalRequestAmount = collateralAmount; + + emit RequestWithdrawal(msg.sender, collateralAmount.rawValue); + } + + /** + * @notice After a passed withdrawal request (i.e., by a call to `requestWithdrawal` and waiting + * `withdrawalLiveness`), withdraws `positionData.withdrawalRequestAmount` of collateral currency. + * @dev Might not withdraw the full requested amount in order to account for precision loss or if the full requested + * amount exceeds the collateral in the position (due to paying fees). + * @return amountWithdrawn The actual amount of collateral withdrawn. + */ + function withdrawPassedRequest() + external + onlyPreExpiration + fees + nonReentrant + returns (FixedPoint.Unsigned memory amountWithdrawn) + { + PositionData storage positionData = _getPositionData(msg.sender); + require( + positionData.withdrawalRequestPassTimestamp != 0 && + positionData.withdrawalRequestPassTimestamp <= getCurrentTime() + ); + + // If withdrawal request amount is > position collateral, then withdraw the full collateral amount. + // This situation is possible due to fees charged since the withdrawal was originally requested. + FixedPoint.Unsigned memory amountToWithdraw = positionData.withdrawalRequestAmount; + if (positionData.withdrawalRequestAmount.isGreaterThan(_getFeeAdjustedCollateral(positionData.rawCollateral))) { + amountToWithdraw = _getFeeAdjustedCollateral(positionData.rawCollateral); + } + + // Decrement the sponsor's collateral and global collateral amounts. + amountWithdrawn = _decrementCollateralBalances(positionData, amountToWithdraw); + + // Reset withdrawal request by setting withdrawal amount and withdrawal timestamp to 0. + _resetWithdrawalRequest(positionData); + + // Transfer approved withdrawal amount from the contract to the caller. + collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue); + + emit RequestWithdrawalExecuted(msg.sender, amountWithdrawn.rawValue); + } + + /** + * @notice Cancels a pending withdrawal request. + */ + function cancelWithdrawal() external nonReentrant { + PositionData storage positionData = _getPositionData(msg.sender); + require(positionData.withdrawalRequestPassTimestamp != 0); + + emit RequestWithdrawalCanceled(msg.sender, positionData.withdrawalRequestAmount.rawValue); + + // Reset withdrawal request by setting withdrawal amount and withdrawal timestamp to 0. + _resetWithdrawalRequest(positionData); + } + + /** + * @notice Creates tokens by creating a new position or by augmenting an existing position. Pulls `collateralAmount` into the sponsor's position and mints `numTokens` of `tokenCurrency`. + * @dev Reverts if minting these tokens would put the position's collateralization ratio below the + * global collateralization ratio. This contract must be approved to spend at least `collateralAmount` of + * `collateralCurrency`. + * @dev This contract must have the Minter role for the `tokenCurrency`. + * @param collateralAmount is the number of collateral tokens to collateralize the position with + * @param numTokens is the number of tokens to mint from the position. + */ + function create( + FixedPoint.Unsigned memory collateralAmount, + FixedPoint.Unsigned memory numTokens + ) public onlyPreExpiration fees nonReentrant { + PositionData storage positionData = positions[msg.sender]; + + // Either the new create ratio or the resultant position CR must be above the current GCR. + require( + (_checkCollateralization( + _getFeeAdjustedCollateral(positionData.rawCollateral).add(collateralAmount), + positionData.tokensOutstanding.add(numTokens) + ) || _checkCollateralization(collateralAmount, numTokens)), + "Insufficient collateral" + ); + + require(positionData.withdrawalRequestPassTimestamp == 0, "Pending withdrawal"); + if (positionData.tokensOutstanding.isEqual(0)) { + require(numTokens.isGreaterThanOrEqual(minSponsorTokens), "Below minimum sponsor position"); + emit NewSponsor(msg.sender); + } + + // Increase the position and global collateral balance by collateral amount. + _incrementCollateralBalances(positionData, collateralAmount); + + // Add the number of tokens created to the position's outstanding tokens. + positionData.tokensOutstanding = positionData.tokensOutstanding.add(numTokens); + + totalTokensOutstanding = totalTokensOutstanding.add(numTokens); + + emit PositionCreated(msg.sender, collateralAmount.rawValue, numTokens.rawValue); + + // Transfer tokens into the contract from caller and mint corresponding synthetic tokens to the caller's address. + collateralCurrency.safeTransferFrom(msg.sender, address(this), collateralAmount.rawValue); + require(tokenCurrency.mint(msg.sender, numTokens.rawValue)); + } + + /** + * @notice Burns `numTokens` of `tokenCurrency` to decrease sponsors position size, without sending back `collateralCurrency`. + * This is done by a sponsor to increase position CR. Resulting size is bounded by minSponsorTokens. + * @dev Can only be called by token sponsor. This contract must be approved to spend `numTokens` of `tokenCurrency`. + * @dev This contract must have the Burner role for the `tokenCurrency`. + * @param numTokens is the number of tokens to be burnt from the sponsor's debt position. + */ + function repay( + FixedPoint.Unsigned memory numTokens + ) public onlyPreExpiration noPendingWithdrawal(msg.sender) fees nonReentrant { + PositionData storage positionData = _getPositionData(msg.sender); + require(numTokens.isLessThanOrEqual(positionData.tokensOutstanding)); + + // Decrease the sponsors position tokens size. Ensure it is above the min sponsor size. + FixedPoint.Unsigned memory newTokenCount = positionData.tokensOutstanding.sub(numTokens); + require(newTokenCount.isGreaterThanOrEqual(minSponsorTokens)); + positionData.tokensOutstanding = newTokenCount; + + // Update the totalTokensOutstanding after redemption. + totalTokensOutstanding = totalTokensOutstanding.sub(numTokens); + + emit Repay(msg.sender, numTokens.rawValue, newTokenCount.rawValue); + + // Transfer the tokens back from the sponsor and burn them. + tokenCurrency.safeTransferFrom(msg.sender, address(this), numTokens.rawValue); + tokenCurrency.burn(numTokens.rawValue); + } + + /** + * @notice Burns `numTokens` of `tokenCurrency` and sends back the proportional amount of `collateralCurrency`. + * @dev Can only be called by a token sponsor. Might not redeem the full proportional amount of collateral + * in order to account for precision loss. This contract must be approved to spend at least `numTokens` of + * `tokenCurrency`. + * @dev This contract must have the Burner role for the `tokenCurrency`. + * @param numTokens is the number of tokens to be burnt for a commensurate amount of collateral. + * @return amountWithdrawn The actual amount of collateral withdrawn. + */ + function redeem( + FixedPoint.Unsigned memory numTokens + ) public noPendingWithdrawal(msg.sender) fees nonReentrant returns (FixedPoint.Unsigned memory amountWithdrawn) { + PositionData storage positionData = _getPositionData(msg.sender); + require(!numTokens.isGreaterThan(positionData.tokensOutstanding)); + + FixedPoint.Unsigned memory fractionRedeemed = numTokens.div(positionData.tokensOutstanding); + FixedPoint.Unsigned memory collateralRedeemed = fractionRedeemed.mul( + _getFeeAdjustedCollateral(positionData.rawCollateral) + ); + + // If redemption returns all tokens the sponsor has then we can delete their position. Else, downsize. + if (positionData.tokensOutstanding.isEqual(numTokens)) { + amountWithdrawn = _deleteSponsorPosition(msg.sender); + } else { + // Decrement the sponsor's collateral and global collateral amounts. + amountWithdrawn = _decrementCollateralBalances(positionData, collateralRedeemed); + + // Decrease the sponsors position tokens size. Ensure it is above the min sponsor size. + FixedPoint.Unsigned memory newTokenCount = positionData.tokensOutstanding.sub(numTokens); + require(newTokenCount.isGreaterThanOrEqual(minSponsorTokens), "Below minimum sponsor position"); + positionData.tokensOutstanding = newTokenCount; + + // Update the totalTokensOutstanding after redemption. + totalTokensOutstanding = totalTokensOutstanding.sub(numTokens); + } + + emit Redeem(msg.sender, amountWithdrawn.rawValue, numTokens.rawValue); + + // Transfer collateral from contract to caller and burn callers synthetic tokens. + collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue); + tokenCurrency.safeTransferFrom(msg.sender, address(this), numTokens.rawValue); + tokenCurrency.burn(numTokens.rawValue); + } + + /** + * @notice After a contract has passed expiry all token holders can redeem their tokens for underlying at the + * prevailing price defined by the DVM from the `expire` function. + * @dev This burns all tokens from the caller of `tokenCurrency` and sends back the proportional amount of + * `collateralCurrency`. Might not redeem the full proportional amount of collateral in order to account for + * precision loss. This contract must be approved to spend `tokenCurrency` at least up to the caller's full balance. + * @dev This contract must have the Burner role for the `tokenCurrency`. + * @return amountWithdrawn The actual amount of collateral withdrawn. + */ + function settleExpired() + external + onlyPostExpiration + fees + nonReentrant + returns (FixedPoint.Unsigned memory amountWithdrawn) + { + // If the contract state is open and onlyPostExpiration passed then `expire()` has not yet been called. + require(contractState != ContractState.Open, "Unexpired position"); + + // Get the current settlement price and store it. If it is not resolved will revert. + if (contractState != ContractState.ExpiredPriceReceived) { + expiryPrice = _getOraclePriceExpiration(expirationTimestamp); + contractState = ContractState.ExpiredPriceReceived; + } + + // Get caller's tokens balance and calculate amount of underlying entitled to them. + FixedPoint.Unsigned memory tokensToRedeem = FixedPoint.Unsigned(tokenCurrency.balanceOf(msg.sender)); + FixedPoint.Unsigned memory totalRedeemableCollateral = tokensToRedeem.mul(expiryPrice); + + // If the caller is a sponsor with outstanding collateral they are also entitled to their excess collateral after their debt. + PositionData storage positionData = positions[msg.sender]; + if (_getFeeAdjustedCollateral(positionData.rawCollateral).isGreaterThan(0)) { + // Calculate the underlying entitled to a token sponsor. This is collateral - debt in underlying. + FixedPoint.Unsigned memory tokenDebtValueInCollateral = positionData.tokensOutstanding.mul(expiryPrice); + FixedPoint.Unsigned memory positionCollateral = _getFeeAdjustedCollateral(positionData.rawCollateral); + + // If the debt is greater than the remaining collateral, they cannot redeem anything. + FixedPoint.Unsigned memory positionRedeemableCollateral = tokenDebtValueInCollateral.isLessThan( + positionCollateral + ) + ? positionCollateral.sub(tokenDebtValueInCollateral) + : FixedPoint.Unsigned(0); + + // Add the number of redeemable tokens for the sponsor to their total redeemable collateral. + totalRedeemableCollateral = totalRedeemableCollateral.add(positionRedeemableCollateral); + + // Reset the position state as all the value has been removed after settlement. + delete positions[msg.sender]; + emit EndedSponsorPosition(msg.sender); + } + + // Take the min of the remaining collateral and the collateral "owed". If the contract is undercapitalized, + // the caller will get as much collateral as the contract can pay out. + FixedPoint.Unsigned memory payout = FixedPoint.min( + _getFeeAdjustedCollateral(rawTotalPositionCollateral), + totalRedeemableCollateral + ); + + // Decrement total contract collateral and outstanding debt. + amountWithdrawn = _removeCollateral(rawTotalPositionCollateral, payout); + totalTokensOutstanding = totalTokensOutstanding.sub(tokensToRedeem); + + emit SettleExpiredPosition(msg.sender, amountWithdrawn.rawValue, tokensToRedeem.rawValue); + + // Transfer tokens & collateral and burn the redeemed tokens. + collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue); + tokenCurrency.safeTransferFrom(msg.sender, address(this), tokensToRedeem.rawValue); + tokenCurrency.burn(tokensToRedeem.rawValue); + } + + /**************************************** + * GLOBAL STATE FUNCTIONS * + ****************************************/ + + /** + * @notice Locks contract state in expired and requests oracle price. + * @dev this function can only be called once the contract is expired and can't be re-called. + */ + function expire() external onlyPostExpiration onlyOpenState fees nonReentrant { + contractState = ContractState.ExpiredPriceRequested; + + // Final fees do not need to be paid when sending a request to the optimistic oracle. + _requestOraclePriceExpiration(expirationTimestamp); + + emit ContractExpired(msg.sender); + } + + /** + * @notice Premature contract settlement under emergency circumstances. + * @dev Only the governor can call this function as they are permissioned within the `FinancialContractAdmin`. + * Upon emergency shutdown, the contract settlement time is set to the shutdown time. This enables withdrawal + * to occur via the standard `settleExpired` function. Contract state is set to `ExpiredPriceRequested` + * which prevents re-entry into this function or the `expire` function. No fees are paid when calling + * `emergencyShutdown` as the governor who would call the function would also receive the fees. + */ + function emergencyShutdown() external override onlyPreExpiration onlyOpenState nonReentrant { + require(msg.sender == _getFinancialContractsAdminAddress()); + + contractState = ContractState.ExpiredPriceRequested; + // Expiratory time now becomes the current time (emergency shutdown time). + // Price requested at this time stamp. `settleExpired` can now withdraw at this timestamp. + uint256 oldExpirationTimestamp = expirationTimestamp; + expirationTimestamp = getCurrentTime(); + _requestOraclePriceExpiration(expirationTimestamp); + + emit EmergencyShutdown(msg.sender, oldExpirationTimestamp, expirationTimestamp); + } + + /** + * @notice Theoretically supposed to pay fees and move money between margin accounts to make sure they + * reflect the NAV of the contract. However, this functionality doesn't apply to this contract. + * @dev This is supposed to be implemented by any contract that inherits `AdministrateeInterface` and callable + * only by the Governor contract. This method is therefore minimally implemented in this contract and does nothing. + */ + function remargin() external override onlyPreExpiration nonReentrant { + return; + } + + /** + * @notice Accessor method for a sponsor's collateral. + * @dev This is necessary because the struct returned by the positions() method shows + * rawCollateral, which isn't a user-readable value. + * @dev This method accounts for pending regular fees that have not yet been withdrawn from this contract, for + * example if the `lastPaymentTime != currentTime`. + * @param sponsor address whose collateral amount is retrieved. + * @return collateralAmount amount of collateral within a sponsors position. + */ + function getCollateral(address sponsor) external view nonReentrantView returns (FixedPoint.Unsigned memory) { + // Note: do a direct access to avoid the validity check. + return _getPendingRegularFeeAdjustedCollateral(_getFeeAdjustedCollateral(positions[sponsor].rawCollateral)); + } + + /** + * @notice Accessor method for the total collateral stored within the PricelessPositionManager. + * @return totalCollateral amount of all collateral within the Expiring Multi Party Contract. + * @dev This method accounts for pending regular fees that have not yet been withdrawn from this contract, for + * example if the `lastPaymentTime != currentTime`. + */ + function totalPositionCollateral() external view nonReentrantView returns (FixedPoint.Unsigned memory) { + return _getPendingRegularFeeAdjustedCollateral(_getFeeAdjustedCollateral(rawTotalPositionCollateral)); + } + + /** + * @notice Accessor method to compute a transformed price using the finanicalProductLibrary specified at contract + * deployment. If no library was provided then no modification to the price is done. + * @param price input price to be transformed. + * @param requestTime timestamp the oraclePrice was requested at. + * @return transformedPrice price with the transformation function applied to it. + * @dev This method should never revert. + */ + + function transformPrice( + FixedPoint.Unsigned memory price, + uint256 requestTime + ) public view nonReentrantView returns (FixedPoint.Unsigned memory) { + return _transformPrice(price, requestTime); + } + + /** + * @notice Accessor method to compute a transformed price identifier using the finanicalProductLibrary specified + * at contract deployment. If no library was provided then no modification to the identifier is done. + * @param requestTime timestamp the identifier is to be used at. + * @return transformedPrice price with the transformation function applied to it. + * @dev This method should never revert. + */ + function transformPriceIdentifier(uint256 requestTime) public view nonReentrantView returns (bytes32) { + return _transformPriceIdentifier(requestTime); + } + + /**************************************** + * INTERNAL FUNCTIONS * + ****************************************/ + + // Reduces a sponsor's position and global counters by the specified parameters. Handles deleting the entire + // position if the entire position is being removed. Does not make any external transfers. + function _reduceSponsorPosition( + address sponsor, + FixedPoint.Unsigned memory tokensToRemove, + FixedPoint.Unsigned memory collateralToRemove, + FixedPoint.Unsigned memory withdrawalAmountToRemove + ) internal { + PositionData storage positionData = _getPositionData(sponsor); + + // If the entire position is being removed, delete it instead. + if ( + tokensToRemove.isEqual(positionData.tokensOutstanding) && + _getFeeAdjustedCollateral(positionData.rawCollateral).isEqual(collateralToRemove) + ) { + _deleteSponsorPosition(sponsor); + return; + } + + // Decrement the sponsor's collateral and global collateral amounts. + _decrementCollateralBalances(positionData, collateralToRemove); + + // Ensure that the sponsor will meet the min position size after the reduction. + FixedPoint.Unsigned memory newTokenCount = positionData.tokensOutstanding.sub(tokensToRemove); + require(newTokenCount.isGreaterThanOrEqual(minSponsorTokens), "Below minimum sponsor position"); + positionData.tokensOutstanding = newTokenCount; + + // Decrement the position's withdrawal amount. + positionData.withdrawalRequestAmount = positionData.withdrawalRequestAmount.sub(withdrawalAmountToRemove); + + // Decrement the total outstanding tokens in the overall contract. + totalTokensOutstanding = totalTokensOutstanding.sub(tokensToRemove); + } + + // Deletes a sponsor's position and updates global counters. Does not make any external transfers. + function _deleteSponsorPosition(address sponsor) internal returns (FixedPoint.Unsigned memory) { + PositionData storage positionToLiquidate = _getPositionData(sponsor); + + FixedPoint.Unsigned memory startingGlobalCollateral = _getFeeAdjustedCollateral(rawTotalPositionCollateral); + + // Remove the collateral and outstanding from the overall total position. + FixedPoint.Unsigned memory remainingRawCollateral = positionToLiquidate.rawCollateral; + rawTotalPositionCollateral = rawTotalPositionCollateral.sub(remainingRawCollateral); + totalTokensOutstanding = totalTokensOutstanding.sub(positionToLiquidate.tokensOutstanding); + + // Reset the sponsors position to have zero outstanding and collateral. + delete positions[sponsor]; + + emit EndedSponsorPosition(sponsor); + + // Return fee-adjusted amount of collateral deleted from position. + return startingGlobalCollateral.sub(_getFeeAdjustedCollateral(rawTotalPositionCollateral)); + } + + function _pfc() internal view virtual override returns (FixedPoint.Unsigned memory) { + return _getFeeAdjustedCollateral(rawTotalPositionCollateral); + } + + function _getPositionData( + address sponsor + ) internal view onlyCollateralizedPosition(sponsor) returns (PositionData storage) { + return positions[sponsor]; + } + + function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) { + return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); + } + + function _getOracle() internal view returns (OracleInterface) { + return OracleInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); + } + + function _getOptimisticOracle() internal view returns (OptimisticOracleInterface) { + return OptimisticOracleInterface(finder.getImplementationAddress(OracleInterfaces.OptimisticOracle)); + } + + function _getFinancialContractsAdminAddress() internal view returns (address) { + return finder.getImplementationAddress(OracleInterfaces.FinancialContractsAdmin); + } + + // Requests a price for transformed `priceIdentifier` at `requestedTime` from the Oracle. + function _requestOraclePriceExpiration(uint256 requestedTime) internal { + OptimisticOracleInterface optimisticOracle = _getOptimisticOracle(); + + // Increase token allowance to enable the optimistic oracle reward transfer. + FixedPoint.Unsigned memory reward = _computeFinalFees(); + collateralCurrency.safeIncreaseAllowance(address(optimisticOracle), reward.rawValue); + optimisticOracle.requestPrice( + _transformPriceIdentifier(requestedTime), + requestedTime, + _getAncillaryData(), + collateralCurrency, + reward.rawValue // Reward is equal to the final fee + ); + + // Apply haircut to all sponsors by decrementing the cumlativeFeeMultiplier by the amount lost from the final fee. + _adjustCumulativeFeeMultiplier(reward, _pfc()); + } + + // Fetches a resolved Oracle price from the Oracle. Reverts if the Oracle hasn't resolved for this request. + function _getOraclePriceExpiration(uint256 requestedTime) internal returns (FixedPoint.Unsigned memory) { + // Create an instance of the oracle and get the price. If the price is not resolved revert. + OptimisticOracleInterface optimisticOracle = _getOptimisticOracle(); + require( + optimisticOracle.hasPrice( + address(this), + _transformPriceIdentifier(requestedTime), + requestedTime, + _getAncillaryData() + ) + ); + int256 optimisticOraclePrice = optimisticOracle.settleAndGetPrice( + _transformPriceIdentifier(requestedTime), + requestedTime, + _getAncillaryData() + ); + + // For now we don't want to deal with negative prices in positions. + if (optimisticOraclePrice < 0) { + optimisticOraclePrice = 0; + } + return _transformPrice(FixedPoint.Unsigned(uint256(optimisticOraclePrice)), requestedTime); + } + + // Requests a price for transformed `priceIdentifier` at `requestedTime` from the Oracle. + function _requestOraclePriceLiquidation(uint256 requestedTime) internal { + OracleInterface oracle = _getOracle(); + oracle.requestPrice(_transformPriceIdentifier(requestedTime), requestedTime); + } + + // Fetches a resolved Oracle price from the Oracle. Reverts if the Oracle hasn't resolved for this request. + function _getOraclePriceLiquidation(uint256 requestedTime) internal view returns (FixedPoint.Unsigned memory) { + // Create an instance of the oracle and get the price. If the price is not resolved revert. + OracleInterface oracle = _getOracle(); + require(oracle.hasPrice(_transformPriceIdentifier(requestedTime), requestedTime), "Unresolved oracle price"); + int256 oraclePrice = oracle.getPrice(_transformPriceIdentifier(requestedTime), requestedTime); + + // For now we don't want to deal with negative prices in positions. + if (oraclePrice < 0) { + oraclePrice = 0; + } + return _transformPrice(FixedPoint.Unsigned(uint256(oraclePrice)), requestedTime); + } + + // Reset withdrawal request by setting the withdrawal request and withdrawal timestamp to 0. + function _resetWithdrawalRequest(PositionData storage positionData) internal { + positionData.withdrawalRequestAmount = FixedPoint.fromUnscaledUint(0); + positionData.withdrawalRequestPassTimestamp = 0; + } + + // Ensure individual and global consistency when increasing collateral balances. Returns the change to the position. + function _incrementCollateralBalances( + PositionData storage positionData, + FixedPoint.Unsigned memory collateralAmount + ) internal returns (FixedPoint.Unsigned memory) { + _addCollateral(positionData.rawCollateral, collateralAmount); + return _addCollateral(rawTotalPositionCollateral, collateralAmount); + } + + // Ensure individual and global consistency when decrementing collateral balances. Returns the change to the + // position. We elect to return the amount that the global collateral is decreased by, rather than the individual + // position's collateral, because we need to maintain the invariant that the global collateral is always + // <= the collateral owned by the contract to avoid reverts on withdrawals. The amount returned = amount withdrawn. + function _decrementCollateralBalances( + PositionData storage positionData, + FixedPoint.Unsigned memory collateralAmount + ) internal returns (FixedPoint.Unsigned memory) { + _removeCollateral(positionData.rawCollateral, collateralAmount); + return _removeCollateral(rawTotalPositionCollateral, collateralAmount); + } + + // Ensure individual and global consistency when decrementing collateral balances. Returns the change to the position. + // This function is similar to the _decrementCollateralBalances function except this function checks position GCR + // between the decrements. This ensures that collateral removal will not leave the position undercollateralized. + function _decrementCollateralBalancesCheckGCR( + PositionData storage positionData, + FixedPoint.Unsigned memory collateralAmount + ) internal returns (FixedPoint.Unsigned memory) { + _removeCollateral(positionData.rawCollateral, collateralAmount); + require(_checkPositionCollateralization(positionData), "CR below GCR"); + return _removeCollateral(rawTotalPositionCollateral, collateralAmount); + } + + // These internal functions are supposed to act identically to modifiers, but re-used modifiers + // unnecessarily increase contract bytecode size. + // source: https://blog.polymath.network/solidity-tips-and-tricks-to-save-gas-and-reduce-bytecode-size-c44580b218e6 + function _onlyOpenState() internal view { + require(contractState == ContractState.Open, "Contract state is not OPEN"); + } + + function _onlyPreExpiration() internal view { + require(getCurrentTime() < expirationTimestamp, "Only callable pre-expiry"); + } + + function _onlyPostExpiration() internal view { + require(getCurrentTime() >= expirationTimestamp, "Only callable post-expiry"); + } + + function _onlyCollateralizedPosition(address sponsor) internal view { + require( + _getFeeAdjustedCollateral(positions[sponsor].rawCollateral).isGreaterThan(0), + "Position has no collateral" + ); + } + + // Note: This checks whether an already existing position has a pending withdrawal. This cannot be used on the + // `create` method because it is possible that `create` is called on a new position (i.e. one without any collateral + // or tokens outstanding) which would fail the `onlyCollateralizedPosition` modifier on `_getPositionData`. + function _positionHasNoPendingWithdrawal(address sponsor) internal view { + require(_getPositionData(sponsor).withdrawalRequestPassTimestamp == 0, "Pending withdrawal"); + } + + /**************************************** + * PRIVATE FUNCTIONS * + ****************************************/ + + function _checkPositionCollateralization(PositionData storage positionData) private view returns (bool) { + return + _checkCollateralization( + _getFeeAdjustedCollateral(positionData.rawCollateral), + positionData.tokensOutstanding + ); + } + + // Checks whether the provided `collateral` and `numTokens` have a collateralization ratio above the global + // collateralization ratio. + function _checkCollateralization( + FixedPoint.Unsigned memory collateral, + FixedPoint.Unsigned memory numTokens + ) private view returns (bool) { + FixedPoint.Unsigned memory global = _getCollateralizationRatio( + _getFeeAdjustedCollateral(rawTotalPositionCollateral), + totalTokensOutstanding + ); + FixedPoint.Unsigned memory thisChange = _getCollateralizationRatio(collateral, numTokens); + return !global.isGreaterThan(thisChange); + } + + function _getCollateralizationRatio( + FixedPoint.Unsigned memory collateral, + FixedPoint.Unsigned memory numTokens + ) private pure returns (FixedPoint.Unsigned memory ratio) { + if (!numTokens.isGreaterThan(0)) { + return FixedPoint.fromUnscaledUint(0); + } else { + return collateral.div(numTokens); + } + } + + // IERC20Standard.decimals() will revert if the collateral contract has not implemented the decimals() method, + // which is possible since the method is only an OPTIONAL method in the ERC20 standard: + // https://eips.ethereum.org/EIPS/eip-20#methods. + function _getSyntheticDecimals(address _collateralAddress) public view returns (uint8 decimals) { + try IERC20Standard(_collateralAddress).decimals() returns (uint8 _decimals) { + return _decimals; + } catch { + return 18; + } + } + + function _transformPrice( + FixedPoint.Unsigned memory price, + uint256 requestTime + ) internal view returns (FixedPoint.Unsigned memory) { + if (!address(financialProductLibrary).isContract()) return price; + try financialProductLibrary.transformPrice(price, requestTime) returns ( + FixedPoint.Unsigned memory transformedPrice + ) { + return transformedPrice; + } catch { + return price; + } + } + + function _transformPriceIdentifier(uint256 requestTime) internal view returns (bytes32) { + if (!address(financialProductLibrary).isContract()) return priceIdentifier; + try financialProductLibrary.transformPriceIdentifier(priceIdentifier, requestTime) returns ( + bytes32 transformedIdentifier + ) { + return transformedIdentifier; + } catch { + return priceIdentifier; + } + } + + function _getAncillaryData() internal view returns (bytes memory) { + // Note: when ancillary data is passed to the optimistic oracle, it should be tagged with the token address + // whose funding rate it's trying to get. + return abi.encodePacked(address(tokenCurrency)); + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/long-short-pair/LongShortPair.sol b/contracts/external/uma/core/contracts/financial-templates/long-short-pair/LongShortPair.sol new file mode 100644 index 000000000..e55437767 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/long-short-pair/LongShortPair.sol @@ -0,0 +1,431 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/utils/math/Math.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +import "../common/financial-product-libraries/long-short-pair-libraries/LongShortPairFinancialProductLibrary.sol"; + +import "../../common/implementation/AncillaryData.sol"; +import "../../common/implementation/Testable.sol"; +import "../../common/implementation/Lockable.sol"; +import "../../common/implementation/FixedPoint.sol"; + +import "../../common/interfaces/ExpandedIERC20.sol"; +import "../../common/interfaces/AddressWhitelistInterface.sol"; + +import "../../data-verification-mechanism/interfaces/OracleInterface.sol"; +import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; +import "../../data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol"; +import "../../data-verification-mechanism/implementation/Constants.sol"; + +import "../../optimistic-oracle-v2/interfaces/OptimisticOracleV2Interface.sol"; + +/** + * @title Long Short Pair. + * @notice Uses a combination of long and short tokens to tokenize the bounded price exposure to a given identifier. + */ +contract LongShortPair is Testable, Lockable { + using FixedPoint for FixedPoint.Unsigned; + using SafeERC20 for IERC20; + + /************************************* + * LONG SHORT PAIR DATA STRUCTURES * + *************************************/ + + // Define the contract's constructor parameters as a struct to enable more variables to be specified. + struct ConstructorParams { + string pairName; // Name of the long short pair contract. + uint64 expirationTimestamp; // Unix timestamp of when the contract will expire. + uint256 collateralPerPair; // How many units of collateral are required to mint one pair of synthetic tokens. + bytes32 priceIdentifier; // Price identifier, registered in the DVM for the long short pair. + bool enableEarlyExpiration; // Enables the LSP contract to be settled early. + ExpandedIERC20 longToken; // Token used as long in the LSP. Mint and burn rights needed by this contract. + ExpandedIERC20 shortToken; // Token used as short in the LSP. Mint and burn rights needed by this contract. + IERC20 collateralToken; // Collateral token used to back LSP synthetics. + LongShortPairFinancialProductLibrary financialProductLibrary; // Contract providing settlement payout logic. + bytes customAncillaryData; // Custom ancillary data to be passed along with the price request to the OO. + uint256 proposerReward; // Optimistic oracle reward amount, pulled from the caller of the expire function. + uint256 optimisticOracleLivenessTime; // OO liveness time for price requests. + uint256 optimisticOracleProposerBond; // OO proposer bond for price requests. + FinderInterface finder; // DVM finder to find other UMA ecosystem contracts. + address timerAddress; // Timer used to synchronize contract time in testing. Set to 0x000... in production. + } + + bool public receivedSettlementPrice; + + bool public enableEarlyExpiration; // If set, the LSP contract can request to be settled early by calling the OO. + uint64 public expirationTimestamp; + uint64 public earlyExpirationTimestamp; // Set in the case the contract is expired early. + string public pairName; + uint256 public collateralPerPair; // Amount of collateral a pair of tokens is always redeemable for. + + // Number between 0 and 1e18 to allocate collateral between long & short tokens at redemption. 0 entitles each short + // to collateralPerPair and each long to 0. 1e18 makes each long worth collateralPerPair and short 0. + uint256 public expiryPercentLong; + bytes32 public priceIdentifier; + + // Price returned from the Optimistic oracle at settlement time. + int256 public expiryPrice; + + // External contract interfaces. + IERC20 public collateralToken; + ExpandedIERC20 public longToken; + ExpandedIERC20 public shortToken; + FinderInterface public finder; + LongShortPairFinancialProductLibrary public financialProductLibrary; + + // Optimistic oracle customization parameters. + bytes public customAncillaryData; + uint256 public proposerReward; + uint256 public optimisticOracleLivenessTime; + uint256 public optimisticOracleProposerBond; + + /**************************************** + * EVENTS * + ****************************************/ + + event TokensCreated(address indexed sponsor, uint256 indexed collateralUsed, uint256 indexed tokensMinted); + event TokensRedeemed(address indexed sponsor, uint256 indexed collateralReturned, uint256 indexed tokensRedeemed); + event ContractExpired(address indexed caller); + event EarlyExpirationRequested(address indexed caller, uint64 earlyExpirationTimeStamp); + event PositionSettled(address indexed sponsor, uint256 collateralReturned, uint256 longTokens, uint256 shortTokens); + + /**************************************** + * MODIFIERS * + ****************************************/ + + modifier preExpiration() { + require(getCurrentTime() < expirationTimestamp, "Only callable pre-expiry"); + _; + } + + modifier postExpiration() { + require(getCurrentTime() >= expirationTimestamp, "Only callable post-expiry"); + _; + } + + modifier notEarlyExpired() { + require(!isContractEarlyExpired(), "Contract already early expired"); + _; + } + + /** + * @notice Construct the LongShortPair + * @param params Constructor params used to initialize the LSP. Key-valued object with the following structure: + * - `pairName`: Name of the long short pair contract. + * - `expirationTimestamp`: Unix timestamp of when the contract will expire. + * - `collateralPerPair`: How many units of collateral are required to mint one pair of synthetic tokens. + * - `priceIdentifier`: Price identifier, registered in the DVM for the long short pair. + * - `longToken`: Token used as long in the LSP. Mint and burn rights needed by this contract. + * - `shortToken`: Token used as short in the LSP. Mint and burn rights needed by this contract. + * - `collateralToken`: Collateral token used to back LSP synthetics. + * - `financialProductLibrary`: Contract providing settlement payout logic. + * - `customAncillaryData`: Custom ancillary data to be passed along with the price request to the OO. + * - `proposerReward`: Preloaded reward to incentivize settlement price proposals. + * - `optimisticOracleLivenessTime`: OO liveness time for price requests. + * - `optimisticOracleProposerBond`: OO proposer bond for price requests. + * - `finder`: DVM finder to find other UMA ecosystem contracts. + * - `timerAddress`: Timer used to synchronize contract time in testing. Set to 0x000... in production. + */ + constructor(ConstructorParams memory params) Testable(params.timerAddress) { + finder = params.finder; + require(bytes(params.pairName).length > 0, "Pair name cant be empty"); + require(params.expirationTimestamp > getCurrentTime(), "Expiration timestamp in past"); + require(params.collateralPerPair > 0, "Collateral per pair cannot be 0"); + require(_getIdentifierWhitelist().isIdentifierSupported(params.priceIdentifier), "Identifier not registered"); + require(address(_getOptimisticOracle()) != address(0), "Invalid finder"); + require(address(params.financialProductLibrary) != address(0), "Invalid FinancialProductLibrary"); + require(_getCollateralWhitelist().isOnWhitelist(address(params.collateralToken)), "Collateral not whitelisted"); + require(params.optimisticOracleLivenessTime > 0, "OO liveness cannot be 0"); + require(params.optimisticOracleLivenessTime < 5200 weeks, "OO liveness too large"); + + pairName = params.pairName; + expirationTimestamp = params.expirationTimestamp; + collateralPerPair = params.collateralPerPair; + priceIdentifier = params.priceIdentifier; + enableEarlyExpiration = params.enableEarlyExpiration; + + longToken = params.longToken; + shortToken = params.shortToken; + collateralToken = params.collateralToken; + + financialProductLibrary = params.financialProductLibrary; + OptimisticOracleV2Interface optimisticOracle = _getOptimisticOracle(); + + // Ancillary data + additional stamped information should be less than ancillary data limit. Consider early + // expiration ancillary data, if enableEarlyExpiration is set. + customAncillaryData = params.customAncillaryData; + require( + optimisticOracle + .stampAncillaryData( + (enableEarlyExpiration ? getEarlyExpirationAncillaryData() : customAncillaryData), + address(this) + ) + .length <= optimisticOracle.ancillaryBytesLimit(), + "Ancillary Data too long" + ); + + proposerReward = params.proposerReward; + optimisticOracleLivenessTime = params.optimisticOracleLivenessTime; + optimisticOracleProposerBond = params.optimisticOracleProposerBond; + } + + /**************************************** + * POSITION FUNCTIONS * + ****************************************/ + + /** + * @notice Creates a pair of long and short tokens equal in number to tokensToCreate. Pulls the required collateral + * amount into this contract, defined by the collateralPerPair value. + * @dev The caller must approve this contract to transfer `tokensToCreate * collateralPerPair` amount of collateral. + * @param tokensToCreate number of long and short synthetic tokens to create. + * @return collateralUsed total collateral used to mint the synthetics. + */ + function create(uint256 tokensToCreate) public preExpiration nonReentrant returns (uint256 collateralUsed) { + // Note the use of mulCeil to prevent small collateralPerPair causing rounding of collateralUsed to 0 enabling + // callers to mint dust LSP tokens without paying any collateral. + collateralUsed = FixedPoint.Unsigned(tokensToCreate).mulCeil(FixedPoint.Unsigned(collateralPerPair)).rawValue; + + collateralToken.safeTransferFrom(msg.sender, address(this), collateralUsed); + + require(longToken.mint(msg.sender, tokensToCreate)); + require(shortToken.mint(msg.sender, tokensToCreate)); + + emit TokensCreated(msg.sender, collateralUsed, tokensToCreate); + } + + /** + * @notice Redeems a pair of long and short tokens equal in number to tokensToRedeem. Returns the commensurate + * amount of collateral to the caller for the pair of tokens, defined by the collateralPerPair value. + * @dev This contract must have the `Burner` role for the `longToken` and `shortToken` in order to call `burnFrom`. + * @dev The caller does not need to approve this contract to transfer any amount of `tokensToRedeem` since long + * and short tokens are burned, rather than transferred, from the caller. + * @dev This method can be called either pre or post expiration. + * @param tokensToRedeem number of long and short synthetic tokens to redeem. + * @return collateralReturned total collateral returned in exchange for the pair of synthetics. + */ + function redeem(uint256 tokensToRedeem) public nonReentrant returns (uint256 collateralReturned) { + require(longToken.burnFrom(msg.sender, tokensToRedeem)); + require(shortToken.burnFrom(msg.sender, tokensToRedeem)); + + collateralReturned = FixedPoint.Unsigned(tokensToRedeem).mul(FixedPoint.Unsigned(collateralPerPair)).rawValue; + + collateralToken.safeTransfer(msg.sender, collateralReturned); + + emit TokensRedeemed(msg.sender, collateralReturned, tokensToRedeem); + } + + /** + * @notice Settle long and/or short tokens in for collateral at a rate informed by the contract settlement. + * @dev Uses financialProductLibrary to compute the redemption rate between long and short tokens. + * @dev This contract must have the `Burner` role for the `longToken` and `shortToken` in order to call `burnFrom`. + * @dev The caller does not need to approve this contract to transfer any amount of `tokensToRedeem` since long + * and short tokens are burned, rather than transferred, from the caller. + * @dev This function can be called before or after expiration to facilitate early expiration. If a price has + * not yet been resolved for either normal or early expiration yet then it will revert. + * @param longTokensToRedeem number of long tokens to settle. + * @param shortTokensToRedeem number of short tokens to settle. + * @return collateralReturned total collateral returned in exchange for the pair of synthetics. + */ + function settle( + uint256 longTokensToRedeem, + uint256 shortTokensToRedeem + ) public nonReentrant returns (uint256 collateralReturned) { + // Either early expiration is enabled and it's before the expiration time or it's after the expiration time. + require( + (enableEarlyExpiration && getCurrentTime() < expirationTimestamp) || + getCurrentTime() >= expirationTimestamp, + "Cannot settle" + ); + + // Get the settlement price and store it. Also sets expiryPercentLong to inform settlement. Reverts if either: + // a) the price request has not resolved (either a normal expiration call or early expiration call) or b) If the + // the contract was attempted to be settled early but the price returned is the ignore oracle price. + // Note that we use the bool receivedSettlementPrice over checking for price != 0 as 0 is a valid price. + if (!receivedSettlementPrice) getExpirationPrice(); + + require(longToken.burnFrom(msg.sender, longTokensToRedeem)); + require(shortToken.burnFrom(msg.sender, shortTokensToRedeem)); + + // expiryPercentLong is a number between 0 and 1e18. 0 means all collateral goes to short tokens and 1e18 means + // all collateral goes to the long token. Total collateral returned is the sum of payouts. + uint256 longCollateralRedeemed = FixedPoint + .Unsigned(longTokensToRedeem) + .mul(FixedPoint.Unsigned(collateralPerPair)) + .mul(FixedPoint.Unsigned(expiryPercentLong)) + .rawValue; + uint256 shortCollateralRedeemed = FixedPoint + .Unsigned(shortTokensToRedeem) + .mul(FixedPoint.Unsigned(collateralPerPair)) + .mul(FixedPoint.fromUnscaledUint(1).sub(FixedPoint.Unsigned(expiryPercentLong))) + .rawValue; + + collateralReturned = longCollateralRedeemed + shortCollateralRedeemed; + collateralToken.safeTransfer(msg.sender, collateralReturned); + + emit PositionSettled(msg.sender, collateralReturned, longTokensToRedeem, shortTokensToRedeem); + } + + /**************************************** + * GLOBAL STATE FUNCTIONS * + ****************************************/ + + /** + * @notice Enables the LSP to request early expiration. This initiates a price request to the optimistic oracle at + * the provided timestamp with a modified version of the ancillary data that includes the key "earlyExpiration:1" + * which signals to the OO that this is an early expiration request, rather than standard settlement. + * @dev The caller must approve this contract to transfer `proposerReward` amount of collateral. + * @dev Will revert if: a) the contract is already early expired, b) it is after the expiration timestamp, c) + * early expiration is disabled for this contract, d) the proposed expiration timestamp is in the future. + * e) an early expiration attempt has already been made (in pending state). + * @param _earlyExpirationTimestamp timestamp at which the early expiration is proposed. + */ + function requestEarlyExpiration( + uint64 _earlyExpirationTimestamp + ) public nonReentrant notEarlyExpired preExpiration { + require(enableEarlyExpiration, "Early expiration disabled"); + require(_earlyExpirationTimestamp <= getCurrentTime(), "Only propose expire in the past"); + require(_earlyExpirationTimestamp > 0, "Early expiration can't be 0"); + + earlyExpirationTimestamp = _earlyExpirationTimestamp; + + _requestOraclePrice(earlyExpirationTimestamp, getEarlyExpirationAncillaryData()); + + emit EarlyExpirationRequested(msg.sender, _earlyExpirationTimestamp); + } + + /** + * @notice Expire the LSP contract. Makes a request to the optimistic oracle to inform the settlement price. + * @dev The caller must approve this contract to transfer `proposerReward` amount of collateral. + * @dev Will revert if: a) the contract is already early expired, b) it is before the expiration timestamp or c) + * an expire call has already been made. + */ + function expire() public nonReentrant notEarlyExpired postExpiration { + _requestOraclePrice(expirationTimestamp, customAncillaryData); + + emit ContractExpired(msg.sender); + } + + /*********************************** + * GLOBAL VIEW FUNCTIONS * + ***********************************/ + + /** + * @notice Returns the number of long and short tokens a sponsor wallet holds. + * @param sponsor address of the sponsor to query. + * @return longTokens the number of long tokens held by the sponsor. + * @return shortTokens the number of short tokens held by the sponsor. + */ + function getPositionTokens( + address sponsor + ) public view nonReentrantView returns (uint256 longTokens, uint256 shortTokens) { + return (longToken.balanceOf(sponsor), shortToken.balanceOf(sponsor)); + } + + /** + * @notice Generates a modified ancillary data that indicates the contract is being expired early. + */ + function getEarlyExpirationAncillaryData() public view returns (bytes memory) { + return AncillaryData.appendKeyValueUint(customAncillaryData, "earlyExpiration", 1); + } + + /** + * @notice Defines a special number that, if returned during an attempted early expiration, will cause the contract + * to do nothing and not expire. This enables the OO (and DVM voters in the case of a dispute) to choose to keep + * the contract running, thereby denying the early settlement request. + */ + function ignoreEarlyExpirationPrice() public pure returns (int256) { + return type(int256).min; + } + + /** + * @notice If the earlyExpirationTimestamp is != 0 then a previous early expiration OO request might still be in the + * pending state. Check if the OO contains the ignore early price. If it does not contain this then the contract + * was early expired correctly. Note that _getOraclePrice call will revert if the price request is still pending, + * thereby reverting all upstream calls pre-settlement of the early expiration price request. + */ + function isContractEarlyExpired() public returns (bool) { + return (earlyExpirationTimestamp != 0 && + _getOraclePrice(earlyExpirationTimestamp, getEarlyExpirationAncillaryData()) != + ignoreEarlyExpirationPrice()); + } + + /**************************************** + * INTERNAL FUNCTIONS * + ****************************************/ + + // Return the oracle price for a given request timestamp and ancillary data combo. + function _getOraclePrice(uint64 requestTimestamp, bytes memory requestAncillaryData) internal returns (int256) { + return _getOptimisticOracle().settleAndGetPrice(priceIdentifier, requestTimestamp, requestAncillaryData); + } + + // Request a price in the optimistic oracle for a given request timestamp and ancillary data combo. Set the bonds + // accordingly to the deployer's parameters. Will revert if re-requesting for a previously requested combo. + function _requestOraclePrice(uint64 requestTimestamp, bytes memory requestAncillaryData) internal { + OptimisticOracleV2Interface optimisticOracle = _getOptimisticOracle(); + + // If the proposer reward was set then pull it from the caller of the function. + if (proposerReward > 0) { + collateralToken.safeTransferFrom(msg.sender, address(this), proposerReward); + collateralToken.safeApprove(address(optimisticOracle), proposerReward); + } + optimisticOracle.requestPrice( + priceIdentifier, + uint256(requestTimestamp), + requestAncillaryData, + collateralToken, + proposerReward + ); + + // Set the Optimistic oracle liveness for the price request. + optimisticOracle.setCustomLiveness( + priceIdentifier, + uint256(requestTimestamp), + requestAncillaryData, + optimisticOracleLivenessTime + ); + + // Set the Optimistic oracle proposer bond for the price request. + optimisticOracle.setBond( + priceIdentifier, + uint256(requestTimestamp), + requestAncillaryData, + optimisticOracleProposerBond + ); + } + + // Fetch the optimistic oracle expiration price. If the oracle has the price for the provided expiration timestamp + // and customData combo then return this. Else, try fetch the price on the early expiration ancillary data. If + // there is no price for either, revert. If the early expiration price is the ignore price will also revert. + function getExpirationPrice() internal { + if (_getOptimisticOracle().hasPrice(address(this), priceIdentifier, expirationTimestamp, customAncillaryData)) + expiryPrice = _getOraclePrice(expirationTimestamp, customAncillaryData); + else { + expiryPrice = _getOraclePrice(earlyExpirationTimestamp, getEarlyExpirationAncillaryData()); + require(expiryPrice != ignoreEarlyExpirationPrice(), "Oracle prevents early expiration"); + } + + // Finally, compute the value of expiryPercentLong based on the expiryPrice. Cap the return value at 1e18 as + // this should, by definition, between 0 and 1e18. + expiryPercentLong = Math.min( + financialProductLibrary.percentageLongCollateralAtExpiry(expiryPrice), + FixedPoint.fromUnscaledUint(1).rawValue + ); + + receivedSettlementPrice = true; + } + + function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) { + return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); + } + + function _getCollateralWhitelist() internal view returns (AddressWhitelistInterface) { + return AddressWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist)); + } + + function _getOptimisticOracle() internal view returns (OptimisticOracleV2Interface) { + return OptimisticOracleV2Interface(finder.getImplementationAddress(OracleInterfaces.OptimisticOracleV2)); + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/long-short-pair/LongShortPairCreator.sol b/contracts/external/uma/core/contracts/financial-templates/long-short-pair/LongShortPairCreator.sol new file mode 100644 index 000000000..3323decac --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/long-short-pair/LongShortPairCreator.sol @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/interfaces/ExpandedIERC20.sol"; +import "../../common/interfaces/IERC20Standard.sol"; +import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; +import "../../common/implementation/Testable.sol"; +import "../../common/implementation/Lockable.sol"; +import "../common/TokenFactory.sol"; +import "./LongShortPair.sol"; +import "../common/financial-product-libraries/long-short-pair-libraries/LongShortPairFinancialProductLibrary.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +/** + * @title Long Short Pair Contract Creator. + * @notice Factory contract to create new instances of long short pair contracts. + * Responsible for constraining the parameters used to construct a new LSP. These constraints can evolve over time and + * are initially constrained to conservative values in this first iteration. + */ +contract LongShortPairCreator is Testable, Lockable { + using FixedPoint for FixedPoint.Unsigned; + using SafeERC20 for IERC20Standard; + + struct CreatorParams { + string pairName; + uint64 expirationTimestamp; + uint256 collateralPerPair; + bytes32 priceIdentifier; + bool enableEarlyExpiration; + string longSynthName; + string longSynthSymbol; + string shortSynthName; + string shortSynthSymbol; + IERC20Standard collateralToken; + LongShortPairFinancialProductLibrary financialProductLibrary; + bytes customAncillaryData; + uint256 proposerReward; + uint256 optimisticOracleLivenessTime; + uint256 optimisticOracleProposerBond; + } + + // Address of TokenFactory used to create a new synthetic token. + TokenFactory public tokenFactory; + + FinderInterface public finder; + + event CreatedLongShortPair( + address indexed longShortPair, + address indexed deployerAddress, + address longToken, + address shortToken + ); + + /** + * @notice Constructs the LongShortPairCreator contract. + * @param _finder UMA protocol Finder used to discover other protocol contracts. + * @param _tokenFactory ERC20 token factory used to deploy synthetic token instances. + * @param _timer Contract that stores the current time in a testing environment. + */ + constructor(FinderInterface _finder, TokenFactory _tokenFactory, address _timer) Testable(_timer) nonReentrant() { + tokenFactory = _tokenFactory; + finder = _finder; + } + + /** + * @notice Creates a longShortPair contract and associated long and short tokens. + * @param params Constructor params used to initialize the LSP. Key-valued object with the following structure: + * - `pairName`: Name of the long short pair contract. + * - `expirationTimestamp`: Unix timestamp of when the contract will expire. + * - `collateralPerPair`: How many units of collateral are required to mint one pair of synthetic tokens. + * - `priceIdentifier`: Registered in the DVM for the synthetic. + * - `enableEarlyExpiration`: Enables the LSP contract to be settled early. + * - `longSynthName`: Name of the long synthetic tokens to be created. + * - `longSynthSymbol`: Symbol of the long synthetic tokens to be created. + * - `shortSynthName`: Name of the short synthetic tokens to be created. + * - `shortSynthSymbol`: Symbol of the short synthetic tokens to be created. + * - `collateralToken`: ERC20 token used as collateral in the LSP. + * - `financialProductLibrary`: Contract providing settlement payout logic. + * - `customAncillaryData`: Custom ancillary data to be passed along with the price request. If not needed, this + * should be left as a 0-length bytes array. + * - `proposerReward`: Optimistic oracle reward amount, pulled from the caller of the expire function. + * - `optimisticOracleLivenessTime`: Optimistic oracle liveness time for price requests. + * - `optimisticOracleProposerBond`: Optimistic oracle proposer bond for price requests. + * @return lspAddress the deployed address of the new long short pair contract. + * @notice Created LSP is not registered within the registry as the LSP uses the Optimistic Oracle for settlement. + * @notice The LSP constructor does a number of validations on input params. These are not repeated here. + */ + function createLongShortPair(CreatorParams memory params) public nonReentrant returns (address) { + // Create a new synthetic token using the params. + require(bytes(params.longSynthName).length != 0, "Missing long synthetic name"); + require(bytes(params.shortSynthName).length != 0, "Missing short synthetic name"); + require(bytes(params.longSynthSymbol).length != 0, "Missing long synthetic symbol"); + require(bytes(params.shortSynthSymbol).length != 0, "Missing short synthetic symbol"); + + // If the collateral token does not have a `decimals()` method, then a default precision of 18 will be + // applied to the newly created synthetic token. + uint8 collateralDecimals = _getSyntheticDecimals(params.collateralToken); + ExpandedIERC20 longToken = tokenFactory.createToken( + params.longSynthName, + params.longSynthSymbol, + collateralDecimals + ); + ExpandedIERC20 shortToken = tokenFactory.createToken( + params.shortSynthName, + params.shortSynthSymbol, + collateralDecimals + ); + + // Deploy the LSP contract. + LongShortPair lsp = new LongShortPair(_convertParams(params, longToken, shortToken)); + + address lspAddress = address(lsp); + + // Give permissions to new lsp contract and then hand over ownership. + longToken.addMinter(lspAddress); + longToken.addBurner(lspAddress); + longToken.resetOwner(lspAddress); + + shortToken.addMinter(lspAddress); + shortToken.addBurner(lspAddress); + shortToken.resetOwner(lspAddress); + + emit CreatedLongShortPair(lspAddress, msg.sender, address(longToken), address(shortToken)); + + return lspAddress; + } + + // Converts createLongShortPair creator params to LongShortPair constructor params. + function _convertParams( + CreatorParams memory creatorParams, + ExpandedIERC20 longToken, + ExpandedIERC20 shortToken + ) private view returns (LongShortPair.ConstructorParams memory constructorParams) { + // Input from function call. + constructorParams.pairName = creatorParams.pairName; + constructorParams.expirationTimestamp = creatorParams.expirationTimestamp; + constructorParams.collateralPerPair = creatorParams.collateralPerPair; + constructorParams.priceIdentifier = creatorParams.priceIdentifier; + constructorParams.enableEarlyExpiration = creatorParams.enableEarlyExpiration; + constructorParams.collateralToken = creatorParams.collateralToken; + constructorParams.financialProductLibrary = creatorParams.financialProductLibrary; + constructorParams.customAncillaryData = creatorParams.customAncillaryData; + constructorParams.proposerReward = creatorParams.proposerReward; + constructorParams.optimisticOracleLivenessTime = creatorParams.optimisticOracleLivenessTime; + constructorParams.optimisticOracleProposerBond = creatorParams.optimisticOracleProposerBond; + + // Constructed long & short synthetic tokens. + constructorParams.longToken = longToken; + constructorParams.shortToken = shortToken; + + // Finder and timer. Should be the same as that used in this factory contract. + constructorParams.finder = finder; + constructorParams.timerAddress = timerAddress; + } + + // IERC20Standard.decimals() will revert if the collateral contract has not implemented the decimals() method, + // which is possible since the method is only an OPTIONAL method in the ERC20 standard: + // https://eips.ethereum.org/EIPS/eip-20#methods. + function _getSyntheticDecimals(IERC20Standard _collateralToken) private view returns (uint8 decimals) { + try _collateralToken.decimals() returns (uint8 _decimals) { + return _decimals; + } catch { + return 18; + } + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/optimistic-distributor/OptimisticDistributor.sol b/contracts/external/uma/core/contracts/financial-templates/optimistic-distributor/OptimisticDistributor.sol new file mode 100644 index 000000000..04b123535 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/optimistic-distributor/OptimisticDistributor.sol @@ -0,0 +1,438 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/AncillaryData.sol"; +import "../../common/implementation/Lockable.sol"; +import "../../common/implementation/MultiCaller.sol"; +import "../../common/implementation/Testable.sol"; +import "../../common/interfaces/AddressWhitelistInterface.sol"; +import "../../merkle-distributor/implementation/MerkleDistributor.sol"; +import "../../data-verification-mechanism/implementation/Constants.sol"; +import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; +import "../../data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol"; +import "../../optimistic-oracle-v2/interfaces/OptimisticOracleV2Interface.sol"; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +/** + * @title OptimisticDistributor contract. + * @notice Allows sponsors to distribute rewards through MerkleDistributor contract secured by UMA Optimistic Oracle. + */ +contract OptimisticDistributor is Lockable, MultiCaller, Testable { + using SafeERC20 for IERC20; + + /******************************************** + * OPTIMISTIC DISTRIBUTOR DATA STRUCTURES * + ********************************************/ + + // Represents reward posted by a sponsor. + struct Reward { + bool distributionExecuted; + address sponsor; + IERC20 rewardToken; + uint256 maximumRewardAmount; + uint256 earliestProposalTimestamp; + uint256 optimisticOracleProposerBond; + uint256 optimisticOracleLivenessTime; + uint256 previousProposalTimestamp; + bytes32 priceIdentifier; + bytes customAncillaryData; + } + + // Represents proposed rewards distribution. + struct Proposal { + uint256 rewardIndex; + uint256 timestamp; + bytes32 merkleRoot; + string ipfsHash; + } + + /******************************************** + * STATE VARIABLES AND CONSTANTS * + ********************************************/ + + // Reserve for bytes appended to ancillary data (e.g. OracleSpoke) when resolving price from non-mainnet chains. + // This also covers appending rewardIndex by this contract. + uint256 public constant ANCILLARY_BYTES_RESERVE = 512; + + // Restrict Optimistic Oracle liveness to between 10 minutes and 100 years. + uint256 public constant MINIMUM_LIVENESS = 10 minutes; + uint256 public constant MAXIMUM_LIVENESS = 5200 weeks; + + // Ancillary data length limit can be synced and stored in the contract. + uint256 public ancillaryBytesLimit; + + // Rewards are stored in dynamic array. + Reward[] public rewards; + + // Immutable variables used to validate input parameters when funding new rewards. + uint256 public immutable maximumFundingPeriod; + uint256 public immutable maximumProposerBond; + + // Proposals are mapped to hash of their identifier, timestamp and ancillaryData. + mapping(bytes32 => Proposal) public proposals; + + // Immutable variables provided at deployment. + FinderInterface public immutable finder; + IERC20 public immutable bondToken; + + // Merkle Distributor is automatically deployed on constructor and owned by this contract. + MerkleDistributor public immutable merkleDistributor; + + // Interface parameters that can be synced and stored in the contract. + OptimisticOracleV2Interface public optimisticOracle; + + /******************************************** + * EVENTS * + ********************************************/ + + event RewardCreated( + address indexed sponsor, + IERC20 rewardToken, + uint256 indexed rewardIndex, + uint256 maximumRewardAmount, + uint256 earliestProposalTimestamp, + uint256 optimisticOracleProposerBond, + uint256 optimisticOracleLivenessTime, + bytes32 indexed priceIdentifier, + bytes customAncillaryData + ); + event RewardIncreased(uint256 indexed rewardIndex, uint256 newMaximumRewardAmount); + event ProposalCreated( + address indexed sponsor, + IERC20 rewardToken, + uint256 indexed rewardIndex, + uint256 proposalTimestamp, + uint256 maximumRewardAmount, + bytes32 indexed proposalId, + bytes32 merkleRoot, + string ipfsHash + ); + event RewardDistributed( + address indexed sponsor, + IERC20 rewardToken, + uint256 indexed rewardIndex, + uint256 maximumRewardAmount, + bytes32 indexed proposalId, + bytes32 merkleRoot, + string ipfsHash + ); + event ProposalRejected(uint256 indexed rewardIndex, bytes32 indexed proposalId); + + /** + * @notice Constructor. + * @param _finder Finder to look up UMA contract addresses. + * @param _bondToken ERC20 token that the bond is paid in. + * @param _timer Contract that stores the current time in a testing environment. + * @param _maximumFundingPeriod Maximum period for reward funding (proposals allowed only afterwards). + * @param _maximumProposerBond Maximum allowed Optimistic Oracle proposer bond amount. + */ + constructor( + FinderInterface _finder, + IERC20 _bondToken, + address _timer, + uint256 _maximumFundingPeriod, + uint256 _maximumProposerBond + ) Testable(_timer) { + finder = _finder; + require(_getCollateralWhitelist().isOnWhitelist(address(_bondToken)), "Bond token not supported"); + bondToken = _bondToken; + syncUmaEcosystemParams(); + maximumFundingPeriod = _maximumFundingPeriod; + maximumProposerBond = _maximumProposerBond; + merkleDistributor = new MerkleDistributor(); + } + + /******************************************** + * FUNDING FUNCTIONS * + ********************************************/ + + /** + * @notice Allows any caller to create a Reward struct and deposit tokens that are linked to these rewards. + * @dev The caller must approve this contract to transfer `maximumRewardAmount` amount of `rewardToken`. + * @param rewardToken ERC20 token that the rewards will be paid in. + * @param maximumRewardAmount Maximum reward amount that the sponsor is posting for distribution. + * @param earliestProposalTimestamp Starting timestamp when proposals for distribution can be made. + * @param priceIdentifier Identifier that should be passed to the Optimistic Oracle on proposed distribution. + * @param customAncillaryData Custom ancillary data that should be sent to the Optimistic Oracle on proposed + * distribution. + * @param optimisticOracleProposerBond Amount of bondToken that should be posted in addition to final fee + * to the Optimistic Oracle on proposed distribution. + * @param optimisticOracleLivenessTime Liveness period in seconds during which proposed distribution can be + * disputed through Optimistic Oracle. + */ + function createReward( + uint256 maximumRewardAmount, + uint256 earliestProposalTimestamp, + uint256 optimisticOracleProposerBond, + uint256 optimisticOracleLivenessTime, + bytes32 priceIdentifier, + IERC20 rewardToken, + bytes calldata customAncillaryData + ) external nonReentrant { + require(earliestProposalTimestamp <= getCurrentTime() + maximumFundingPeriod, "Too long till proposal opening"); + require(optimisticOracleProposerBond <= maximumProposerBond, "OO proposer bond too high"); + require(_getIdentifierWhitelist().isIdentifierSupported(priceIdentifier), "Identifier not registered"); + require(_ancillaryDataWithinLimits(customAncillaryData), "Ancillary data too long"); + require(optimisticOracleLivenessTime >= MINIMUM_LIVENESS, "OO liveness too small"); + require(optimisticOracleLivenessTime < MAXIMUM_LIVENESS, "OO liveness too large"); + + // Store funded reward and log created reward. + Reward memory reward = Reward({ + distributionExecuted: false, + sponsor: msg.sender, + rewardToken: rewardToken, + maximumRewardAmount: maximumRewardAmount, + earliestProposalTimestamp: earliestProposalTimestamp, + optimisticOracleProposerBond: optimisticOracleProposerBond, + optimisticOracleLivenessTime: optimisticOracleLivenessTime, + previousProposalTimestamp: 0, + priceIdentifier: priceIdentifier, + customAncillaryData: customAncillaryData + }); + uint256 rewardIndex = rewards.length; + rewards.push() = reward; + emit RewardCreated( + reward.sponsor, + reward.rewardToken, + rewardIndex, + reward.maximumRewardAmount, + reward.earliestProposalTimestamp, + reward.optimisticOracleProposerBond, + reward.optimisticOracleLivenessTime, + reward.priceIdentifier, + reward.customAncillaryData + ); + + // Pull maximum rewards from the sponsor. + rewardToken.safeTransferFrom(msg.sender, address(this), maximumRewardAmount); + } + + /** + * @notice Allows anyone to deposit additional rewards for distribution before `earliestProposalTimestamp`. + * @dev The caller must approve this contract to transfer `additionalRewardAmount` amount of `rewardToken`. + * @param rewardIndex Index for identifying existing Reward struct that should receive additional funding. + * @param additionalRewardAmount Additional reward amount that the sponsor is posting for distribution. + */ + function increaseReward(uint256 rewardIndex, uint256 additionalRewardAmount) external nonReentrant { + require(rewardIndex < rewards.length, "Invalid rewardIndex"); + require(getCurrentTime() < rewards[rewardIndex].earliestProposalTimestamp, "Funding period ended"); + + // Update maximumRewardAmount and log new amount. + rewards[rewardIndex].maximumRewardAmount += additionalRewardAmount; + emit RewardIncreased(rewardIndex, rewards[rewardIndex].maximumRewardAmount); + + // Pull additional rewards from the sponsor. + rewards[rewardIndex].rewardToken.safeTransferFrom(msg.sender, address(this), additionalRewardAmount); + } + + /******************************************** + * DISTRIBUTION FUNCTIONS * + ********************************************/ + + /** + * @notice Allows any caller to propose distribution for funded reward starting from `earliestProposalTimestamp`. + * Only one undisputed proposal at a time is allowed. + * @dev The caller must approve this contract to transfer `optimisticOracleProposerBond` + final fee amount + * of `bondToken`. + * @param rewardIndex Index for identifying existing Reward struct that should be proposed for distribution. + * @param merkleRoot Merkle root describing allocation of proposed rewards distribution. + * @param ipfsHash Hash of IPFS object, conveniently stored for clients to verify proposed distribution. + */ + function proposeDistribution( + uint256 rewardIndex, + bytes32 merkleRoot, + string calldata ipfsHash + ) external nonReentrant { + require(rewardIndex < rewards.length, "Invalid rewardIndex"); + + uint256 timestamp = getCurrentTime(); + Reward memory reward = rewards[rewardIndex]; + require(timestamp >= reward.earliestProposalTimestamp, "Cannot propose in funding period"); + require(!reward.distributionExecuted, "Reward already distributed"); + require(_noBlockingProposal(rewardIndex, reward), "New proposals blocked"); + + // Store current timestamp at reward struct so that any subsequent proposals are blocked till dispute. + rewards[rewardIndex].previousProposalTimestamp = timestamp; + + // Append rewardIndex to ancillary data. + bytes memory ancillaryData = _appendRewardIndex(rewardIndex, reward.customAncillaryData); + + // Generate hash for proposalId. + bytes32 proposalId = _getProposalId(reward.priceIdentifier, timestamp, ancillaryData); + + // Request price from Optimistic Oracle. + optimisticOracle.requestPrice(reward.priceIdentifier, timestamp, ancillaryData, bondToken, 0); + + // Set proposal liveness and bond and calculate total bond amount. + optimisticOracle.setCustomLiveness( + reward.priceIdentifier, + timestamp, + ancillaryData, + reward.optimisticOracleLivenessTime + ); + uint256 totalBond = optimisticOracle.setBond( + reward.priceIdentifier, + timestamp, + ancillaryData, + reward.optimisticOracleProposerBond + ); + + // Pull proposal bond and final fee from the proposer, and approve it for Optimistic Oracle. + bondToken.safeTransferFrom(msg.sender, address(this), totalBond); + bondToken.safeApprove(address(optimisticOracle), totalBond); + + // Propose canonical value representing "True"; i.e. the proposed distribution is valid. + optimisticOracle.proposePriceFor( + msg.sender, + address(this), + reward.priceIdentifier, + timestamp, + ancillaryData, + int256(1e18) + ); + + // Store and log proposed distribution. + proposals[proposalId] = Proposal({ + rewardIndex: rewardIndex, + timestamp: timestamp, + merkleRoot: merkleRoot, + ipfsHash: ipfsHash + }); + emit ProposalCreated( + reward.sponsor, + reward.rewardToken, + rewardIndex, + timestamp, + reward.maximumRewardAmount, + proposalId, + merkleRoot, + ipfsHash + ); + } + + /** + * @notice Allows any caller to execute distribution that has been validated by the Optimistic Oracle. + * @param proposalId Hash for identifying existing rewards distribution proposal. + * @dev Calling this for unresolved proposals will revert. + */ + function executeDistribution(bytes32 proposalId) external nonReentrant { + // All valid proposals should have non-zero proposal timestamp. + Proposal memory proposal = proposals[proposalId]; + require(proposal.timestamp != 0, "Invalid proposalId"); + + // Only one validated proposal per reward can be executed for distribution. + Reward memory reward = rewards[proposal.rewardIndex]; + require(!reward.distributionExecuted, "Reward already distributed"); + + // Append reward index to ancillary data. + bytes memory ancillaryData = _appendRewardIndex(proposal.rewardIndex, reward.customAncillaryData); + + // Get resolved price. Reverts if the request is not settled or settleable. + int256 resolvedPrice = optimisticOracle.settleAndGetPrice( + reward.priceIdentifier, + proposal.timestamp, + ancillaryData + ); + + // Transfer rewards to MerkleDistributor for accepted proposal and flag distributionExecuted. + // This does not revert on rejected proposals so that disputer could receive back its bond and winning + // in the same transaction when settleAndGetPrice is called above. + if (resolvedPrice == 1e18) { + rewards[proposal.rewardIndex].distributionExecuted = true; + + reward.rewardToken.safeApprove(address(merkleDistributor), reward.maximumRewardAmount); + merkleDistributor.setWindow( + reward.maximumRewardAmount, + address(reward.rewardToken), + proposal.merkleRoot, + proposal.ipfsHash + ); + emit RewardDistributed( + reward.sponsor, + reward.rewardToken, + proposal.rewardIndex, + reward.maximumRewardAmount, + proposalId, + proposal.merkleRoot, + proposal.ipfsHash + ); + } + // ProposalRejected can be emitted multiple times whenever someone tries to execute the same rejected proposal. + else emit ProposalRejected(proposal.rewardIndex, proposalId); + } + + /******************************************** + * MAINTENANCE FUNCTIONS * + ********************************************/ + + /** + * @notice Updates the address stored in this contract for the OptimisticOracle to the latest version set + * in the Finder. + * @dev There is no risk of leaving this function public for anyone to call as in all cases we want the address of + * OptimisticOracle in this contract to map to the latest version in the Finder. + */ + function syncUmaEcosystemParams() public nonReentrant { + optimisticOracle = _getOptimisticOracle(); + ancillaryBytesLimit = optimisticOracle.ancillaryBytesLimit(); + } + + /******************************************** + * INTERNAL FUNCTIONS * + ********************************************/ + + function _getOptimisticOracle() internal view returns (OptimisticOracleV2Interface) { + return OptimisticOracleV2Interface(finder.getImplementationAddress(OracleInterfaces.OptimisticOracleV2)); + } + + function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) { + return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); + } + + function _getCollateralWhitelist() internal view returns (AddressWhitelistInterface) { + return AddressWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist)); + } + + function _appendRewardIndex( + uint256 rewardIndex, + bytes memory customAncillaryData + ) internal pure returns (bytes memory) { + return AncillaryData.appendKeyValueUint(customAncillaryData, "rewardIndex", rewardIndex); + } + + function _ancillaryDataWithinLimits(bytes memory customAncillaryData) internal view returns (bool) { + // Since rewardIndex has variable length as string, it is not appended here and is assumed + // to be included in ANCILLARY_BYTES_RESERVE. + return + optimisticOracle.stampAncillaryData(customAncillaryData, address(this)).length + ANCILLARY_BYTES_RESERVE <= + ancillaryBytesLimit; + } + + function _getProposalId( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) internal pure returns (bytes32) { + return keccak256(abi.encode(identifier, timestamp, ancillaryData)); + } + + // Returns true if there are no blocking proposals (eiter there were no prior proposals or they were disputed). + function _noBlockingProposal(uint256 rewardIndex, Reward memory reward) internal view returns (bool) { + // Valid proposal cannot have zero timestamp. + if (reward.previousProposalTimestamp == 0) return true; + + bytes memory ancillaryData = _appendRewardIndex(rewardIndex, reward.customAncillaryData); + OptimisticOracleV2Interface.Request memory blockingRequest = optimisticOracle.getRequest( + address(this), + reward.priceIdentifier, + reward.previousProposalTimestamp, + ancillaryData + ); + + // Previous proposal is blocking till disputed that can be detected by non-zero disputer address. + // In case Optimistic Oracle was upgraded since the previous proposal it needs to be unblocked for new proposal. + // This can be detected by uninitialized bonding currency for the previous proposal. + return blockingRequest.disputer != address(0) || address(blockingRequest.currency) == address(0); + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarder.sol b/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarder.sol new file mode 100644 index 000000000..f0ca56da1 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarder.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC721/ERC721.sol"; + +import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; +import "./OptimisticRewarderBase.sol"; +import "./OptimisticRewarderToken.sol"; + +/** + * @notice The common optimistic rewarder contract. It is both the contract that pays out the rewards and the ERC721 + * token itself. + */ +contract OptimisticRewarder is OptimisticRewarderBase, OptimisticRewarderToken { + /** + * @notice Constructor. + * @param _name name for the ERC721 token. + * @param _symbol symbol for the ERC721 token. + * @param _baseUri prefix to each ERC721 tokenId's name. + * @param _liveness liveness period between submission and verification of a reward. + * @param _bondToken ERC20 token that the bond is paid in. + * @param _bond size of the bond. + * @param _identifier identifier that should be passed to the optimistic oracle on dispute. + * @param _customAncillaryData custom ancillary data that should be sent to the optimistic oracle on dispute. + * @param _finder finder to look up UMA contract addresses. + */ + constructor( + string memory _name, + string memory _symbol, + string memory _baseUri, + uint256 _liveness, + IERC20 _bondToken, + uint256 _bond, + bytes32 _identifier, + bytes memory _customAncillaryData, + FinderInterface _finder + ) + OptimisticRewarderBase(_liveness, _bondToken, _bond, _identifier, _customAncillaryData, _finder) + OptimisticRewarderToken(_name, _symbol, _baseUri) + {} + + /** + * @notice Used to mint the next ERC721 tokenId. + * @param recipient the recipient of the newly minted token. + */ + function mintNextToken( + address recipient + ) public virtual override(OptimisticRewarderBase, OptimisticRewarderToken) returns (uint256) { + return OptimisticRewarderToken.mintNextToken(recipient); + } + + /** + * @notice Used to check the owner of the token. + * @dev this override is a formality required by solidity. It forwards the call to the internal ERC721 + * immplentation. + * @param tokenId the tokenId to check the owner of. + */ + function ownerOf(uint256 tokenId) public view virtual override(OptimisticRewarderBase, ERC721) returns (address) { + return ERC721.ownerOf(tokenId); + } +} + +/** + * @notice The optimistic rewarder that does not contain the ERC721 token. It allows the user to pass in an external + * ERC721 token. + * @dev this setup allows for graceful migrations to new rewarder contracts. It also allows external ERC721 tokens, + * like uniswap v3 positions to be rewarded. + */ +contract OptimisticRewarderNoToken is OptimisticRewarderBase { + OptimisticRewarderToken public token; + + /** + * @notice Constructor. + * @param _token external ERC721 token for the rewarder to base redemptions on. Note: this token doesn't + * necessarily need to implement the mintNextToken method. See mintNextToken below for details. + * @param _liveness liveness period between submission and verification of a reward. + * @param _bondToken ERC20 token that the bond is paid in. + * @param _bond size of the bond. + * @param _identifier identifier that should be passed to the optimistic oracle on dispute. + * @param _customAncillaryData custom ancillary data that should be sent to the optimistic oracle on dispute. + * @param _finder finder to look up UMA contract addresses. + */ + constructor( + OptimisticRewarderToken _token, + uint256 _liveness, + IERC20 _bondToken, + uint256 _bond, + bytes32 _identifier, + bytes memory _customAncillaryData, + FinderInterface _finder + ) OptimisticRewarderBase(_liveness, _bondToken, _bond, _identifier, _customAncillaryData, _finder) { + token = _token; + } + + /** + * @notice Used to mint the next ERC721 tokenId. + * @dev even if token contract does not support the `mintNextToken` function, this contract can still function + * correctly assuming there is some other way to mint the ERC721 tokens. An issue in this method will only + * affect the mint token in the base contract. Other methods will work fine. + * @param recipient the recipient of the newly minted token. + */ + function mintNextToken(address recipient) public virtual override returns (uint256) { + return token.mintNextToken(recipient); + } + + /** + * @notice Used to check the owner of the token. + * @dev this override forwards the call to the external token contract. + * @param tokenId the tokenId to check the owner of. + */ + function ownerOf(uint256 tokenId) public view virtual override returns (address) { + return token.ownerOf(tokenId); + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderBase.sol b/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderBase.sol new file mode 100644 index 000000000..4fd0ffd93 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderBase.sol @@ -0,0 +1,400 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; +import "../../common/implementation/Lockable.sol"; +import "../../common/implementation/MultiCaller.sol"; +import "../../data-verification-mechanism/interfaces/StoreInterface.sol"; +import "../../data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol"; +import "../../data-verification-mechanism/implementation/Constants.sol"; +import "../../optimistic-oracle-v2/interfaces/SkinnyOptimisticOracleInterface.sol"; +import "../../common/implementation/AncillaryData.sol"; +import "../../common/interfaces/AddressWhitelistInterface.sol"; + +/** + * @notice The base rewarder contract. This manages depositing rewards and paying them out to token holders using values + * backed by the OptimisticOracle's dispute process. + */ +abstract contract OptimisticRewarderBase is Lockable, MultiCaller { + using SafeERC20 for IERC20; + + struct RedemptionAmount { + uint256 amount; + IERC20 token; + } + + struct Redemption { + uint256 finalFee; + uint256 expiryTime; + } + + // Constants. + FinderInterface public finder; + bytes public customAncillaryData; + IERC20 public bondToken; + bytes32 public identifier; + + // Note: setters are intentionally absent for these parameters. If a deployer intends to modify these parameters, + // this contract suite offers a simple migration path where a new Rewarder is created and the existing ERC721 token + // can be passed in and used as the reward token there as well. This would be minimally painful for users. + uint256 public liveness; + uint256 public bond; + + // Parameters that can be synced and stored in the contract. + uint256 public finalFee; + StoreInterface public store; + SkinnyOptimisticOracleInterface public optimisticOracle; + + // Mapping to track redemptions. + mapping(bytes32 => Redemption) public redemptions; + + // Mapping to track the past total cumulative redemptions for tokenIds. + mapping(uint256 => mapping(IERC20 => uint256)) public redeemedAmounts; + + /**************************************** + * EVENTS * + ****************************************/ + + // This allows other contracts to publish reward updates. + event UpdateToken(uint256 indexed tokenId, address indexed caller, bytes data); + + event Deposited(address indexed depositor, IERC20 indexed token, uint256 amount); + + // Lifecycle events for redemptions. + event Requested( + uint256 indexed tokenId, + bytes32 indexed redemptionId, + RedemptionAmount[] cumulativeRedemptions, + uint256 expiryTime + ); + event Canceled(uint256 indexed tokenId, bytes32 indexed redemptionId, uint256 expiryTime); + event Disputed(uint256 indexed tokenId, bytes32 indexed redemptionId, uint256 expiryTime); + event Redeemed(uint256 indexed tokenId, bytes32 indexed redemptionId, uint256 expiryTime); + + /** + * @notice Constructor. + * @param _liveness liveness period between submission and verification of a reward. + * @param _bondToken ERC20 token that the bond is paid in. + * @param _bond size of the bond. + * @param _identifier identifier that should be passed to the optimistic oracle on dispute. + * @param _customAncillaryData custom ancillary data that should be sent to the optimistic oracle on dispute. + * @param _finder finder to look up UMA contract addresses. + */ + constructor( + uint256 _liveness, + IERC20 _bondToken, + uint256 _bond, + bytes32 _identifier, + bytes memory _customAncillaryData, + FinderInterface _finder + ) { + require(_liveness > 0, "liveness can't be 0"); + liveness = _liveness; + finder = _finder; + require(_getCollateralWhitelist().isOnWhitelist(address(_bondToken)), "bond token not supported"); + bondToken = _bondToken; + bond = _bond; + require(_getIdentifierWhitelist().isIdentifierSupported(_identifier), "identifier not supported"); + identifier = _identifier; + SkinnyOptimisticOracleInterface skinnyOptimisticOracle = _getOptimisticOracle(); + require( + skinnyOptimisticOracle + .stampAncillaryData( + AncillaryData.appendKeyValueBytes32(_customAncillaryData, "redemptionId", bytes32(0)), + address(this) + ) + .length <= skinnyOptimisticOracle.ancillaryBytesLimit(), + "ancillary data too long" + ); + customAncillaryData = _customAncillaryData; + _sync(); + } + + /**************************************** + * GLOBAL PUBLIC FUNCTIONS * + ****************************************/ + + /** + * @notice Allows anyone to deposit reward tokens into the contract. Presumably, this would be the deployer or + * protocol that wishes to reward the users interacting with the system. + * @dev Once tokens are deposited, they cannot be withdrawn without claiming a reward. If a deployer wants an + * "escape hatch", they can create a special tokenId for this purpose. + * @param token ERC20 token that is being deposited. + * @param amount amount of rewards to deposit. + */ + function depositRewards(IERC20 token, uint256 amount) public nonReentrant { + token.safeTransferFrom(msg.sender, address(this), amount); + emit Deposited(msg.sender, token, amount); + } + + /** + * @notice Allows the caller to mint a token to the receiver and include a reward-relevant update event with it. + * This is intended to be used when the user first interacts with a reward-granting protocol. + * @dev if the user prefers to only mint a new token, they should call the mintNextToken function. + * @param receiver user that will receive the newly minted token. + * @param data arbitrary caller-generated data that will be associated with this update. + * @return tokenId of the newly minted token. + */ + function mint(address receiver, bytes memory data) public nonReentrant returns (uint256 tokenId) { + tokenId = mintNextToken(receiver); + emit UpdateToken(tokenId, msg.sender, data); + } + + /** + * @notice Applies a reward-relevant update to an existing token. + * @param tokenId the existing tokenId that the update should be applied to. + * @param data arbitrary caller-generated data that will be associated with this update. + */ + function updateToken(uint256 tokenId, bytes memory data) public nonReentrant { + emit UpdateToken(tokenId, msg.sender, data); + } + + /** + * @notice Requests a redemption for any tokenId. This can be called by anyone. + * @dev If called by someone who doesn't own the token, they are effectively gifting their bond to the owner. + * @param tokenId the tokenId the redemption is for. + * @param cumulativeRedemptions the cumulative token addresses and amounts that this tokenId is eligible for + * at the current timestamp. cumulative redemptions that are too low should be considered to be valid. + * @return totalBond sum of finalFee and bond paid by the caller of this function. + */ + function requestRedemption( + uint256 tokenId, + RedemptionAmount[] memory cumulativeRedemptions + ) public nonReentrant returns (uint256 totalBond) { + bytes32 redemptionId = getRedemptionId(tokenId, cumulativeRedemptions); + require(redemptions[redemptionId].expiryTime == 0, "Redemption already exists"); + require(ownerOf(tokenId) != address(0), "tokenId is invalid"); + // Note: it's important to put _some_ limit on the length of data passed in here. Otherwise, it is possible to + // create values that are so long that this transaction would fit within the block gas limit, but the dispute + // transaction would not. + require(cumulativeRedemptions.length <= 100, "too many token transfers"); + + uint256 expiryTime = getCurrentTime() + liveness; + + totalBond = finalFee + bond; + bondToken.safeTransferFrom(msg.sender, address(this), totalBond); + + redemptions[redemptionId] = Redemption({ finalFee: finalFee, expiryTime: expiryTime }); + + emit Requested(tokenId, redemptionId, cumulativeRedemptions, expiryTime); + } + + /** + * @notice Disputes a redemption request. + * @dev will cancel a request if the final fee changes or something causes the optimistic oracle proposal to fail. + * @param tokenId the tokenId the redemption is for. + * @param cumulativeRedemptions the cumulative redemptions that were provided in the original request. + */ + function dispute(uint256 tokenId, RedemptionAmount[] memory cumulativeRedemptions) public nonReentrant { + bytes32 redemptionId = getRedemptionId(tokenId, cumulativeRedemptions); + + // This automatically checks that redemptions[redemptionId] != 0. + // Check that it has not passed liveness. + Redemption storage redemption = redemptions[redemptionId]; + uint256 currentTime = getCurrentTime(); + require(currentTime < redemption.expiryTime, "redemption expired or nonexistent"); + + // Final fees don't match to those in the current store, which means the bond the initial caller provided was + // incorrect. Cancel the request to allow the requester to resubmit with the correct params. + // Note: we pull the store directly from the finder to avoid any issues with an outdated store causing the + // final fee to appear to be correct, but actually be outdated due to the OptimisticOracle pulling from a + // newer store deployment. + if (redemption.finalFee != _getStore().computeFinalFee(address(bondToken)).rawValue) { + _cancelRedemption(tokenId, redemptionId); + return; + } else { + uint256 totalBond = bond + redemption.finalFee; + bondToken.safeIncreaseAllowance(address(optimisticOracle), totalBond * 2); + bytes memory ancillaryData = AncillaryData.appendKeyValueBytes32( + customAncillaryData, + "redemptionId", + redemptionId + ); + uint32 requestTimestamp = uint32(redemption.expiryTime - liveness); + address proposer = ownerOf(tokenId); + + try + optimisticOracle.requestAndProposePriceFor( + identifier, + requestTimestamp, + ancillaryData, + bondToken, + 0, // Reward = 0 + bond, // Bond (on top of the final fee) for the proposer and disputer. + liveness, + proposer, + int256(1e18) // Canonical value representing "True"; i.e. the proposed redemption is valid. + ) + returns (uint256) {} catch { + // There are various cases that can cause an OptimisticOracle proposal to fail. These are unlikely, but + // this is intended as a worst-case fallback to avoid undisputable requests. + // A few examples: + // 1. The token ceases to be approved. + // 2. The identifier ceases to be approved. + // 3. The request has been submitted before (same identifier, timestap, ancillary data, and requester). + // This should be impossible for this contract. + // 4. The money bond + final fee is larger than approved or in the contract's balance. This should also + // be impossible in this contract. + _cancelRedemption(tokenId, redemptionId); + bondToken.safeApprove(address(optimisticOracle), 0); // Reset allowance. + return; + } + + SkinnyOptimisticOracleInterface.Request memory request = SkinnyOptimisticOracleInterface.Request({ + proposer: proposer, + disputer: address(0), + currency: bondToken, + settled: false, + proposedPrice: int256(1e18), + resolvedPrice: 0, + expirationTime: currentTime + liveness, + reward: 0, + finalFee: redemption.finalFee, + bond: bond, + customLiveness: liveness + }); + + // Note: don't pull funds until here to avoid any transfers that aren't needed. + bondToken.safeTransferFrom(msg.sender, address(this), totalBond); + + // Dispute the request that we just sent. + optimisticOracle.disputePriceFor( + identifier, + requestTimestamp, + ancillaryData, + request, + msg.sender, + address(this) + ); + + emit Disputed(tokenId, redemptionId, redemption.expiryTime); + } + + delete redemptions[redemptionId]; + } + + /** + * @notice Redeem a redemption request that has passed liveness. + * @dev returns the bond that was paid with the initial proposal. + * @param tokenId the tokenId the redemption is for. + * @param cumulativeRedemptions the cumulative redemptions that were provided in the original request. + */ + function redeem(uint256 tokenId, RedemptionAmount[] memory cumulativeRedemptions) public nonReentrant { + bytes32 redemptionId = getRedemptionId(tokenId, cumulativeRedemptions); + + // Can only be redeemed by owner. + require(msg.sender == ownerOf(tokenId), "must be called by token owner"); + + // Check that the redemption is initialized and that it passed liveness. + require( + redemptions[redemptionId].expiryTime != 0 && getCurrentTime() >= redemptions[redemptionId].expiryTime, + "unexpired or nonexistent" + ); + + for (uint256 i = 0; i < cumulativeRedemptions.length; i++) { + IERC20 token = cumulativeRedemptions[i].token; + uint256 currentRedemptionTotal = redeemedAmounts[tokenId][token]; + uint256 proposedRedemptionTotal = cumulativeRedemptions[i].amount; + + // Only pay if the cumulative amount specified in the request is larger than the amount paid out already. + // Note: disallow payments of the bond token even if it's in the approved request is passed to ensure + // rewards don't interfere with bond bookkeeping. This is checked here rather than at the initiation of the + // request to avoid the cost of looping over the array twice in the lifecycle. + if (proposedRedemptionTotal > currentRedemptionTotal && token != bondToken) { + uint256 amountToPay = proposedRedemptionTotal - currentRedemptionTotal; + redeemedAmounts[tokenId][token] = proposedRedemptionTotal; + token.safeTransfer(msg.sender, amountToPay); + } + } + + // Return the bond to the owner. + bondToken.safeTransfer(msg.sender, bond + redemptions[redemptionId].finalFee); + + emit Redeemed(tokenId, redemptionId, redemptions[redemptionId].expiryTime); + + delete redemptions[redemptionId]; + } + + /** + * @notice Syncs external addresses and parameters into the contract. + * @dev These are stored rather than read on each run to avoid expensive external calls in the happy-path. + */ + function sync() public nonReentrant { + _sync(); + } + + /** + * @notice gets the current time. Can be overridden for testing. + * @return current block timestamp. + */ + function getCurrentTime() public view virtual returns (uint256) { + return block.timestamp; + } + + /** + * @notice Abstract function that is called to mint the next ERC721 tokenId. + * @param recipient the recipient of the newly minted token. + * @return index of the next minted token. + */ + function mintNextToken(address recipient) public virtual returns (uint256); + + /** + * @notice Abstract function that is called to check the owner of the token. + * @dev this matches the ERC721 ownerOf interface. + * @param tokenId the tokenId to check the owner of. + * @return owner of a particular tokenId. + */ + function ownerOf(uint256 tokenId) public view virtual returns (address); + + /** + * @notice Generates a redemption id for the tokenId and the claim amounts. + * @param tokenId the tokenId that the claim is for. + * @param cumulativeRedemptions the cumulative redemptions that were provided in the request. + * @return redemption id. This is a hash of the tokenId and the cumulative redemptions. + */ + function getRedemptionId( + uint256 tokenId, + RedemptionAmount[] memory cumulativeRedemptions + ) public pure returns (bytes32) { + return keccak256(abi.encode(tokenId, cumulativeRedemptions)); + } + + /**************************************** + * INTERNAL FUNCTIONS * + ****************************************/ + + function _sync() internal { + store = _getStore(); + finalFee = store.computeFinalFee(address(bondToken)).rawValue; + optimisticOracle = _getOptimisticOracle(); + } + + function _getStore() internal view returns (StoreInterface) { + return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store)); + } + + function _getOptimisticOracle() internal view returns (SkinnyOptimisticOracleInterface) { + return + SkinnyOptimisticOracleInterface(finder.getImplementationAddress(OracleInterfaces.SkinnyOptimisticOracle)); + } + + function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) { + return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); + } + + function _getCollateralWhitelist() internal view returns (AddressWhitelistInterface) { + return AddressWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist)); + } + + function _cancelRedemption(uint256 tokenId, bytes32 redemptionId) internal { + // On cancellation, perform a sync to ensure the contract has the most up-to-date addresses and params. + _sync(); + Redemption storage redemption = redemptions[redemptionId]; + bondToken.safeTransfer(ownerOf(tokenId), redemption.finalFee + bond); + emit Canceled(tokenId, redemptionId, redemption.expiryTime); + delete redemptions[redemptionId]; + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderCreator.sol b/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderCreator.sol new file mode 100644 index 000000000..9d6151ef4 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderCreator.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +import "../../common/implementation/Lockable.sol"; + +import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; +import "./OptimisticRewarder.sol"; + +/** + * @notice The creator contract for optimistic rewarders. Using this contract is totally optional. It only aids in + * creating a simpler deployment experience with a guarantee of repeatable verification and easier tracking through + * events. + */ +contract OptimisticRewarderCreator is Lockable { + FinderInterface public finder; + + event CreatedOptimisticRewarder(address indexed optimisticRewarder, bool includesToken); + + constructor(FinderInterface _finder) { + finder = _finder; + } + + /** + * @notice Deploys an optimistic rewarder. + * @param _name name for the ERC721 token. + * @param _symbol symbol for the ERC721 token. + * @param _baseUri prefix to each ERC721 tokenId's name. + * @param _liveness liveness period between submission and verification of a reward. + * @param _bondToken ERC20 token that the bond is paid in. + * @param _bond size of the bond. + * @param _identifier identifier that should be passed to the optimistic oracle on dispute. + * @param _customAncillaryData custom ancillary data that should be sent to the optimistic oracle on dispute. + */ + function createOptimisticRewarder( + string memory _name, + string memory _symbol, + string memory _baseUri, + uint256 _liveness, + IERC20 _bondToken, + uint256 _bond, + bytes32 _identifier, + bytes memory _customAncillaryData + ) public nonReentrant returns (address) { + OptimisticRewarder optimisticRewarder = new OptimisticRewarder( + _name, + _symbol, + _baseUri, + _liveness, + _bondToken, + _bond, + _identifier, + _customAncillaryData, + finder + ); + emit CreatedOptimisticRewarder(address(optimisticRewarder), true); + return address(optimisticRewarder); + } + + /** + * @notice Deploys an optimistic rewarder with an external ERC721 token. + * @param _token external ERC721 token for the rewarder to base redemptions on. + * @param _liveness liveness period between submission and verification of a reward. + * @param _bondToken ERC20 token that the bond is paid in. + * @param _bond size of the bond. + * @param _identifier identifier that should be passed to the optimistic oracle on dispute. + * @param _customAncillaryData custom ancillary data that should be sent to the optimistic oracle on dispute. + */ + function createOptimisticRewarderNoToken( + OptimisticRewarderToken _token, + uint256 _liveness, + IERC20 _bondToken, + uint256 _bond, + bytes32 _identifier, + bytes memory _customAncillaryData + ) public nonReentrant returns (address) { + OptimisticRewarderNoToken optimisticRewarder = new OptimisticRewarderNoToken( + _token, + _liveness, + _bondToken, + _bond, + _identifier, + _customAncillaryData, + finder + ); + emit CreatedOptimisticRewarder(address(optimisticRewarder), false); + return address(optimisticRewarder); + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderToken.sol b/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderToken.sol new file mode 100644 index 000000000..15baa27f7 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderToken.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC721/ERC721.sol"; + +contract OptimisticRewarderToken is ERC721 { + string public baseUri; + uint256 public nextTokenId; + + /** + * @notice Constructor. + * @param _name name for the ERC721 token. + * @param _symbol symbol for the ERC721 token. + * @param _baseUri prefix to each ERC721 tokenId's name. + */ + constructor(string memory _name, string memory _symbol, string memory _baseUri) ERC721(_name, _symbol) { + nextTokenId = 0; + baseUri = _baseUri; + } + + /** + * @notice Used to mint the next ERC721 tokenId. + * @param recipient the recipient of the newly minted token. + */ + function mintNextToken(address recipient) public virtual returns (uint256 tokenId) { + tokenId = nextTokenId++; + _safeMint(recipient, tokenId); + } + + function _baseURI() internal view override returns (string memory) { + return baseUri; + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticStaker.sol b/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticStaker.sol new file mode 100644 index 000000000..27f23a7a2 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticStaker.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +import "../../common/implementation/Lockable.sol"; +import "../../common/implementation/MultiCaller.sol"; +import "./OptimisticRewarderBase.sol"; + +/** + * @notice An example use case of the OptimisticRewarder in use by a contract that allows users to stake an ERC20 to + * earn rewards. + */ +contract OptimisticStaker is Lockable, MultiCaller { + using SafeERC20 for IERC20; + + // Optimistic rewarder contract used to pay out user rewards. + OptimisticRewarderBase public optimisticRewarder; + + // Staked ERC20 token. + IERC20 public stakedToken; + + // Balances by tokenId. + mapping(uint256 => uint256) public balances; + + event Deposit(uint256 indexed tokenId, uint256 amount); + event Withdraw(uint256 indexed tokenId, uint256 amount); + + /** + * @notice Constructor. + * @param _optimisticRewarder Optimistic rewarder contract used to pay out user rewards. + * @param _stakedToken staked ERC20 token. + */ + constructor(OptimisticRewarderBase _optimisticRewarder, IERC20 _stakedToken) { + optimisticRewarder = _optimisticRewarder; + stakedToken = _stakedToken; + } + + modifier onlyTokenOwner(uint256 tokenId) { + require(optimisticRewarder.ownerOf(tokenId) == msg.sender, "caller != token owner"); + _; + } + + /** + * @notice Deposit the staked token into the contract and mint a fresh token to manage the position. + * @param amount the amount of the ERC20 to deposit. + * @return tokenId the token id for the freshly minted token. + */ + function depositNew(uint256 amount) public nonReentrant returns (uint256 tokenId) { + tokenId = optimisticRewarder.mint(msg.sender, msg.data); + _depositFor(tokenId, amount); + } + + /** + * @notice Deposit the staked token into the contract. + * @param tokenId the tokenId that will own this liquidity. User must be the owner of this tokenId. + * @param amount the amount of the ERC20 to deposit. + */ + function deposit(uint256 tokenId, uint256 amount) public nonReentrant onlyTokenOwner(tokenId) { + _depositFor(tokenId, amount); + } + + /** + * @notice Deposit staked tokens on behalf of a tokenId that the user may not control. + * @param tokenId the tokenId that will own this liquidity. + * @param amount the amount of the ERC20 to deposit. + */ + function depositFor(uint256 tokenId, uint256 amount) public nonReentrant { + _depositFor(tokenId, amount); + } + + /** + * @notice Withdraw the staked tokens. + * @param tokenId the tokenId that owns this liquidity. User must own this tokenId. + * @param amount the amount of the ERC20 to withdraw. + */ + function withdraw(uint256 tokenId, uint256 amount) public nonReentrant onlyTokenOwner(tokenId) { + balances[tokenId] -= amount; + stakedToken.safeTransfer(msg.sender, amount); + optimisticRewarder.updateToken(tokenId, msg.data); + emit Withdraw(tokenId, amount); + } + + function _depositFor(uint256 tokenId, uint256 amount) internal { + balances[tokenId] += amount; + stakedToken.safeTransferFrom(msg.sender, address(this), amount); + optimisticRewarder.updateToken(tokenId, msg.data); + emit Deposit(tokenId, amount); + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/test/OptimisticRewarderTest.sol b/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/test/OptimisticRewarderTest.sol new file mode 100644 index 000000000..f6678b084 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/test/OptimisticRewarderTest.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../OptimisticRewarder.sol"; +import "../../../common/implementation/Testable.sol"; + +// Test contract to add controllable timing to the OptimisticRewarder. +contract OptimisticRewarderTest is OptimisticRewarder, Testable { + constructor( + string memory _name, + string memory _symbol, + string memory _baseUri, + uint256 _liveness, + IERC20 _bondToken, + uint256 _bond, + bytes32 _identifier, + bytes memory _customAncillaryData, + FinderInterface _finder, + address _timerAddress + ) + Testable(_timerAddress) + OptimisticRewarder( + _name, + _symbol, + _baseUri, + _liveness, + _bondToken, + _bond, + _identifier, + _customAncillaryData, + _finder + ) + {} + + function getCurrentTime() public view override(OptimisticRewarderBase, Testable) returns (uint256) { + return Testable.getCurrentTime(); + } +} + +// Test contract to add controllable timing to the OptimisticRewarderNoToken. +contract OptimisticRewarderNoTokenTest is OptimisticRewarderNoToken, Testable { + constructor( + OptimisticRewarderToken _token, + uint256 _liveness, + IERC20 _bondToken, + uint256 _bond, + bytes32 _identifier, + bytes memory _customAncillaryData, + FinderInterface _finder, + address _timerAddress + ) + Testable(_timerAddress) + OptimisticRewarderNoToken(_token, _liveness, _bondToken, _bond, _identifier, _customAncillaryData, _finder) + {} + + function getCurrentTime() public view override(OptimisticRewarderBase, Testable) returns (uint256) { + return Testable.getCurrentTime(); + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/ConfigStore.sol b/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/ConfigStore.sol new file mode 100644 index 000000000..c61360db8 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/ConfigStore.sol @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; +import "@openzeppelin/contracts-v4/access/Ownable.sol"; + +import "./ConfigStoreInterface.sol"; +import "../../common/implementation/Testable.sol"; +import "../../common/implementation/Lockable.sol"; +import "../../common/implementation/FixedPoint.sol"; + +/** + * @notice ConfigStore stores configuration settings for a perpetual contract and provides an interface for it + * to query settings such as reward rates, proposal bond sizes, etc. The configuration settings can be upgraded + * by a privileged account and the upgraded changes are timelocked. + */ +contract ConfigStore is ConfigStoreInterface, Testable, Lockable, Ownable { + using SafeMath for uint256; + using FixedPoint for FixedPoint.Unsigned; + + /**************************************** + * STORE DATA STRUCTURES * + ****************************************/ + + // Make currentConfig private to force user to call getCurrentConfig, which returns the pendingConfig + // if its liveness has expired. + ConfigStoreInterface.ConfigSettings private currentConfig; + + // Beginning on `pendingPassedTimestamp`, the `pendingConfig` can be published as the current config. + ConfigStoreInterface.ConfigSettings public pendingConfig; + uint256 public pendingPassedTimestamp; + + /**************************************** + * EVENTS * + ****************************************/ + + event ProposedNewConfigSettings( + address indexed proposer, + uint256 rewardRatePerSecond, + uint256 proposerBondPercentage, + uint256 timelockLiveness, + int256 maxFundingRate, + int256 minFundingRate, + uint256 proposalTimePastLimit, + uint256 proposalPassedTimestamp + ); + event ChangedConfigSettings( + uint256 rewardRatePerSecond, + uint256 proposerBondPercentage, + uint256 timelockLiveness, + int256 maxFundingRate, + int256 minFundingRate, + uint256 proposalTimePastLimit + ); + + /**************************************** + * MODIFIERS * + ****************************************/ + + // Update config settings if possible. + modifier updateConfig() { + _updateConfig(); + _; + } + + /** + * @notice Construct the Config Store. An initial configuration is provided and set on construction. + * @param _initialConfig Configuration settings to initialize `currentConfig` with. + * @param _timerAddress Address of testable Timer contract. + */ + constructor(ConfigSettings memory _initialConfig, address _timerAddress) Testable(_timerAddress) { + _validateConfig(_initialConfig); + currentConfig = _initialConfig; + } + + /** + * @notice Returns current config or pending config if pending liveness has expired. + * @return ConfigSettings config settings that calling financial contract should view as "live". + */ + function updateAndGetCurrentConfig() + external + override + updateConfig + nonReentrant + returns (ConfigStoreInterface.ConfigSettings memory) + { + return currentConfig; + } + + /** + * @notice Propose new configuration settings. New settings go into effect after a liveness period passes. + * @param newConfig Configuration settings to publish after `currentConfig.timelockLiveness` passes from block.timestamp. + * @dev Callable only by owner. Calling this while there is already a pending proposal will overwrite the pending proposal. + */ + function proposeNewConfig(ConfigSettings memory newConfig) external onlyOwner nonReentrant updateConfig { + _validateConfig(newConfig); + + // Warning: This overwrites a pending proposal! + pendingConfig = newConfig; + + // Use current config's liveness period to timelock this proposal. + pendingPassedTimestamp = getCurrentTime().add(currentConfig.timelockLiveness); + + emit ProposedNewConfigSettings( + msg.sender, + newConfig.rewardRatePerSecond.rawValue, + newConfig.proposerBondPercentage.rawValue, + newConfig.timelockLiveness, + newConfig.maxFundingRate.rawValue, + newConfig.minFundingRate.rawValue, + newConfig.proposalTimePastLimit, + pendingPassedTimestamp + ); + } + + /** + * @notice Publish any pending configuration settings if there is a pending proposal that has passed liveness. + */ + function publishPendingConfig() external nonReentrant updateConfig {} + + /**************************************** + * INTERNAL FUNCTIONS * + ****************************************/ + + // Check if pending proposal can overwrite the current config. + function _updateConfig() internal { + // If liveness has passed, publish proposed configuration settings. + if (_pendingProposalPassed()) { + currentConfig = pendingConfig; + + _deletePendingConfig(); + + emit ChangedConfigSettings( + currentConfig.rewardRatePerSecond.rawValue, + currentConfig.proposerBondPercentage.rawValue, + currentConfig.timelockLiveness, + currentConfig.maxFundingRate.rawValue, + currentConfig.minFundingRate.rawValue, + currentConfig.proposalTimePastLimit + ); + } + } + + function _deletePendingConfig() internal { + delete pendingConfig; + pendingPassedTimestamp = 0; + } + + function _pendingProposalPassed() internal view returns (bool) { + return (pendingPassedTimestamp != 0 && pendingPassedTimestamp <= getCurrentTime()); + } + + // Use this method to constrain values with which you can set ConfigSettings. + function _validateConfig(ConfigStoreInterface.ConfigSettings memory config) internal pure { + // We don't set limits on proposal timestamps because there are already natural limits: + // - Future: price requests to the OptimisticOracle must be in the past---we can't add further constraints. + // - Past: proposal times must always be after the last update time, and a reasonable past limit would be 30 + // mins, meaning that no proposal timestamp can be more than 30 minutes behind the current time. + + // Make sure timelockLiveness is not too long, otherwise contract might not be able to fix itself + // before a vulnerability drains its collateral. + require(config.timelockLiveness <= 7 days && config.timelockLiveness >= 1 days, "Invalid timelockLiveness"); + + // The reward rate should be modified as needed to incentivize honest proposers appropriately. + // Additionally, the rate should be less than 100% a year => 100% / 360 days / 24 hours / 60 mins / 60 secs + // = 0.0000033 + FixedPoint.Unsigned memory maxRewardRatePerSecond = FixedPoint.fromUnscaledUint(33).div(1e7); + require(config.rewardRatePerSecond.isLessThan(maxRewardRatePerSecond), "Invalid rewardRatePerSecond"); + + // We don't set a limit on the proposer bond because it is a defense against dishonest proposers. If a proposer + // were to successfully propose a very high or low funding rate, then their PfC would be very high. The proposer + // could theoretically keep their "evil" funding rate alive indefinitely by continuously disputing honest + // proposers, so we would want to be able to set the proposal bond (equal to the dispute bond) higher than their + // PfC for each proposal liveness window. The downside of not limiting this is that the config store owner + // can set it arbitrarily high and preclude a new funding rate from ever coming in. We suggest setting the + // proposal bond based on the configuration's funding rate range like in this discussion: + // https://github.com/UMAprotocol/protocol/issues/2039#issuecomment-719734383 + + // We also don't set a limit on the funding rate max/min because we might need to allow very high magnitude + // funding rates in extraordinarily volatile market situations. Note, that even though we do not bound + // the max/min, we still recommend that the deployer of this contract set the funding rate max/min values + // to bound the PfC of a dishonest proposer. A reasonable range might be the equivalent of [+200%/year, -200%/year]. + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/ConfigStoreInterface.sol b/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/ConfigStoreInterface.sol new file mode 100644 index 000000000..5b65186c3 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/ConfigStoreInterface.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/FixedPoint.sol"; + +interface ConfigStoreInterface { + // All of the configuration settings available for querying by a perpetual. + struct ConfigSettings { + // Liveness period (in seconds) for an update to currentConfig to become official. + uint256 timelockLiveness; + // Reward rate paid to successful proposers. Percentage of 1 E.g., .1 is 10%. + FixedPoint.Unsigned rewardRatePerSecond; + // Bond % (of given contract's PfC) that must be staked by proposers. Percentage of 1, e.g. 0.0005 is 0.05%. + FixedPoint.Unsigned proposerBondPercentage; + // Maximum funding rate % per second that can be proposed. + FixedPoint.Signed maxFundingRate; + // Minimum funding rate % per second that can be proposed. + FixedPoint.Signed minFundingRate; + // Funding rate proposal timestamp cannot be more than this amount of seconds in the past from the latest + // update time. + uint256 proposalTimePastLimit; + } + + function updateAndGetCurrentConfig() external returns (ConfigSettings memory); +} diff --git a/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/Perpetual.sol b/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/Perpetual.sol new file mode 100644 index 000000000..669e41f83 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/Perpetual.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "./PerpetualLiquidatable.sol"; + +/** + * @title Perpetual Multiparty Contract. + * @notice Convenient wrapper for Liquidatable. + */ +contract Perpetual is PerpetualLiquidatable { + /** + * @notice Constructs the Perpetual contract. + * @param params struct to define input parameters for construction of Liquidatable. Some params + * are fed directly into the PositionManager's constructor within the inheritance tree. + */ + constructor( + ConstructorParams memory params + ) PerpetualLiquidatable(params) // Note: since there is no logic here, there is no need to add a re-entrancy guard. + { + + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualCreator.sol b/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualCreator.sol new file mode 100644 index 000000000..b6ace10f1 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualCreator.sol @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../data-verification-mechanism/implementation/ContractCreator.sol"; +import "../../common/interfaces/ExpandedIERC20.sol"; +import "../../common/interfaces/IERC20Standard.sol"; +import "../../common/implementation/Testable.sol"; +import "../../common/implementation/AddressWhitelist.sol"; +import "../../common/implementation/Lockable.sol"; +import "../common/TokenFactory.sol"; +import "../common/SyntheticToken.sol"; +import "./PerpetualLib.sol"; +import "./ConfigStore.sol"; + +/** + * @title Perpetual Contract creator. + * @notice Factory contract to create and register new instances of perpetual contracts. + * Responsible for constraining the parameters used to construct a new perpetual. This creator contains a number of constraints + * that are applied to newly created contract. These constraints can evolve over time and are + * initially constrained to conservative values in this first iteration. Technically there is nothing in the + * Perpetual contract requiring these constraints. However, because `createPerpetual()` is intended + * to be the only way to create valid financial contracts that are registered with the DVM (via _registerContract), + we can enforce deployment configurations here. + */ +contract PerpetualCreator is ContractCreator, Testable, Lockable { + using FixedPoint for FixedPoint.Unsigned; + + /**************************************** + * PERP CREATOR DATA STRUCTURES * + ****************************************/ + + // Immutable params for perpetual contract. + struct Params { + address collateralAddress; + bytes32 priceFeedIdentifier; + bytes32 fundingRateIdentifier; + string syntheticName; + string syntheticSymbol; + FixedPoint.Unsigned collateralRequirement; + FixedPoint.Unsigned disputeBondPercentage; + FixedPoint.Unsigned sponsorDisputeRewardPercentage; + FixedPoint.Unsigned disputerDisputeRewardPercentage; + FixedPoint.Unsigned minSponsorTokens; + FixedPoint.Unsigned tokenScaling; + uint256 withdrawalLiveness; + uint256 liquidationLiveness; + } + // Address of TokenFactory used to create a new synthetic token. + address public tokenFactoryAddress; + + event CreatedPerpetual(address indexed perpetualAddress, address indexed deployerAddress); + event CreatedConfigStore(address indexed configStoreAddress, address indexed ownerAddress); + + /** + * @notice Constructs the Perpetual contract. + * @param _finderAddress UMA protocol Finder used to discover other protocol contracts. + * @param _tokenFactoryAddress ERC20 token factory used to deploy synthetic token instances. + * @param _timerAddress Contract that stores the current time in a testing environment. + */ + constructor( + address _finderAddress, + address _tokenFactoryAddress, + address _timerAddress + ) ContractCreator(_finderAddress) Testable(_timerAddress) nonReentrant() { + tokenFactoryAddress = _tokenFactoryAddress; + } + + /** + * @notice Creates an instance of perpetual and registers it within the registry. + * @param params is a `ConstructorParams` object from Perpetual. + * @return address of the deployed contract. + */ + function createPerpetual( + Params memory params, + ConfigStore.ConfigSettings memory configSettings + ) public nonReentrant returns (address) { + require(bytes(params.syntheticName).length != 0, "Missing synthetic name"); + require(bytes(params.syntheticSymbol).length != 0, "Missing synthetic symbol"); + + // Create new config settings store for this contract and reset ownership to the deployer. + ConfigStore configStore = new ConfigStore(configSettings, timerAddress); + configStore.transferOwnership(msg.sender); + emit CreatedConfigStore(address(configStore), configStore.owner()); + + // Create a new synthetic token using the params. + TokenFactory tf = TokenFactory(tokenFactoryAddress); + + // If the collateral token does not have a `decimals()` method, + // then a default precision of 18 will be applied to the newly created synthetic token. + uint8 syntheticDecimals = _getSyntheticDecimals(params.collateralAddress); + ExpandedIERC20 tokenCurrency = tf.createToken(params.syntheticName, params.syntheticSymbol, syntheticDecimals); + address derivative = PerpetualLib.deploy(_convertParams(params, tokenCurrency, address(configStore))); + + // Give permissions to new derivative contract and then hand over ownership. + tokenCurrency.addMinter(derivative); + tokenCurrency.addBurner(derivative); + tokenCurrency.resetOwner(derivative); + + _registerContract(new address[](0), derivative); + + emit CreatedPerpetual(derivative, msg.sender); + + return derivative; + } + + /**************************************** + * PRIVATE FUNCTIONS * + ****************************************/ + + // Converts createPerpetual params to Perpetual constructor params. + function _convertParams( + Params memory params, + ExpandedIERC20 newTokenCurrency, + address configStore + ) private view returns (Perpetual.ConstructorParams memory constructorParams) { + // Known from creator deployment. + constructorParams.finderAddress = finderAddress; + constructorParams.timerAddress = timerAddress; + + // Enforce configuration constraints. + require(params.withdrawalLiveness != 0, "Withdrawal liveness cannot be 0"); + require(params.liquidationLiveness != 0, "Liquidation liveness cannot be 0"); + _requireWhitelistedCollateral(params.collateralAddress); + + // We don't want perpetual deployers to be able to intentionally or unintentionally set + // liveness periods that could induce arithmetic overflow, but we also don't want + // to be opinionated about what livenesses are "correct", so we will somewhat + // arbitrarily set the liveness upper bound to 100 years (5200 weeks). In practice, liveness + // periods even greater than a few days would make the perpetual unusable for most users. + require(params.withdrawalLiveness < 5200 weeks, "Withdrawal liveness too large"); + require(params.liquidationLiveness < 5200 weeks, "Liquidation liveness too large"); + + // To avoid precision loss or overflows, prevent the token scaling from being too large or too small. + FixedPoint.Unsigned memory minScaling = FixedPoint.Unsigned(1e8); // 1e-10 + FixedPoint.Unsigned memory maxScaling = FixedPoint.Unsigned(1e28); // 1e10 + require( + params.tokenScaling.isGreaterThan(minScaling) && params.tokenScaling.isLessThan(maxScaling), + "Invalid tokenScaling" + ); + + // Input from function call. + constructorParams.configStoreAddress = configStore; + constructorParams.tokenAddress = address(newTokenCurrency); + constructorParams.collateralAddress = params.collateralAddress; + constructorParams.priceFeedIdentifier = params.priceFeedIdentifier; + constructorParams.fundingRateIdentifier = params.fundingRateIdentifier; + constructorParams.collateralRequirement = params.collateralRequirement; + constructorParams.disputeBondPercentage = params.disputeBondPercentage; + constructorParams.sponsorDisputeRewardPercentage = params.sponsorDisputeRewardPercentage; + constructorParams.disputerDisputeRewardPercentage = params.disputerDisputeRewardPercentage; + constructorParams.minSponsorTokens = params.minSponsorTokens; + constructorParams.withdrawalLiveness = params.withdrawalLiveness; + constructorParams.liquidationLiveness = params.liquidationLiveness; + constructorParams.tokenScaling = params.tokenScaling; + } + + // IERC20Standard.decimals() will revert if the collateral contract has not implemented the decimals() method, + // which is possible since the method is only an OPTIONAL method in the ERC20 standard: + // https://eips.ethereum.org/EIPS/eip-20#methods. + function _getSyntheticDecimals(address _collateralAddress) public view returns (uint8 decimals) { + try IERC20Standard(_collateralAddress).decimals() returns (uint8 _decimals) { + return _decimals; + } catch { + return 18; + } + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualLib.sol b/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualLib.sol new file mode 100644 index 000000000..496c83aa4 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualLib.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "./Perpetual.sol"; + +/** + * @title Provides convenient Perpetual Multi Party contract utilities. + * @dev Using this library to deploy Perpetuals allows calling contracts to avoid importing the full bytecode. + */ +library PerpetualLib { + /** + * @notice Returns address of new Perpetual deployed with given `params` configuration. + * @dev Caller will need to register new Perpetual with the Registry to begin requesting prices. Caller is also + * responsible for enforcing constraints on `params`. + * @param params is a `ConstructorParams` object from Perpetual. + * @return address of the deployed Perpetual contract + */ + function deploy(Perpetual.ConstructorParams memory params) public returns (address) { + Perpetual derivative = new Perpetual(params); + return address(derivative); + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualLiquidatable.sol b/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualLiquidatable.sol new file mode 100644 index 000000000..08419e95f --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualLiquidatable.sol @@ -0,0 +1,595 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +import "./PerpetualPositionManager.sol"; + +import "../../common/implementation/FixedPoint.sol"; + +/** + * @title PerpetualLiquidatable + * @notice Adds logic to a position-managing contract that enables callers to liquidate an undercollateralized position. + * @dev The liquidation has a liveness period before expiring successfully, during which someone can "dispute" the + * liquidation, which sends a price request to the relevant Oracle to settle the final collateralization ratio based on + * a DVM price. The contract enforces dispute rewards in order to incentivize disputers to correctly dispute false + * liquidations and compensate position sponsors who had their position incorrectly liquidated. Importantly, a + * prospective disputer must deposit a dispute bond that they can lose in the case of an unsuccessful dispute. + * NOTE: this contract does _not_ work with ERC777 collateral currencies or any others that call into the receiver on + * transfer(). Using an ERC777 token would allow a user to maliciously grief other participants (while also losing + * money themselves). + */ +contract PerpetualLiquidatable is PerpetualPositionManager { + using FixedPoint for FixedPoint.Unsigned; + using SafeMath for uint256; + using SafeERC20 for IERC20; + using SafeERC20 for ExpandedIERC20; + + /**************************************** + * LIQUIDATION DATA STRUCTURES * + ****************************************/ + + // Because of the check in withdrawable(), the order of these enum values should not change. + enum Status { + Uninitialized, + NotDisputed, + Disputed, + DisputeSucceeded, + DisputeFailed + } + + struct LiquidationData { + // Following variables set upon creation of liquidation: + address sponsor; // Address of the liquidated position's sponsor + address liquidator; // Address who created this liquidation + Status state; // Liquidated (and expired or not), Pending a Dispute, or Dispute has resolved + uint256 liquidationTime; // Time when liquidation is initiated, needed to get price from Oracle + // Following variables determined by the position that is being liquidated: + FixedPoint.Unsigned tokensOutstanding; // Synthetic tokens required to be burned by liquidator to initiate dispute + FixedPoint.Unsigned lockedCollateral; // Collateral locked by contract and released upon expiry or post-dispute + // Amount of collateral being liquidated, which could be different from + // lockedCollateral if there were pending withdrawals at the time of liquidation + FixedPoint.Unsigned liquidatedCollateral; + // Unit value (starts at 1) that is used to track the fees per unit of collateral over the course of the liquidation. + FixedPoint.Unsigned rawUnitCollateral; + // Following variable set upon initiation of a dispute: + address disputer; // Person who is disputing a liquidation + // Following variable set upon a resolution of a dispute: + FixedPoint.Unsigned settlementPrice; // Final price as determined by an Oracle following a dispute + FixedPoint.Unsigned finalFee; + } + + // Define the contract's constructor parameters as a struct to enable more variables to be specified. + // This is required to enable more params, over and above Solidity's limits. + struct ConstructorParams { + // Params for PerpetualPositionManager only. + uint256 withdrawalLiveness; + address configStoreAddress; + address collateralAddress; + address tokenAddress; + address finderAddress; + address timerAddress; + bytes32 priceFeedIdentifier; + bytes32 fundingRateIdentifier; + FixedPoint.Unsigned minSponsorTokens; + FixedPoint.Unsigned tokenScaling; + // Params specifically for PerpetualLiquidatable. + uint256 liquidationLiveness; + FixedPoint.Unsigned collateralRequirement; + FixedPoint.Unsigned disputeBondPercentage; + FixedPoint.Unsigned sponsorDisputeRewardPercentage; + FixedPoint.Unsigned disputerDisputeRewardPercentage; + } + + // This struct is used in the `withdrawLiquidation` method that disperses liquidation and dispute rewards. + // `payToX` stores the total collateral to withdraw from the contract to pay X. This value might differ + // from `paidToX` due to precision loss between accounting for the `rawCollateral` versus the + // fee-adjusted collateral. These variables are stored within a struct to avoid the stack too deep error. + struct RewardsData { + FixedPoint.Unsigned payToSponsor; + FixedPoint.Unsigned payToLiquidator; + FixedPoint.Unsigned payToDisputer; + FixedPoint.Unsigned paidToSponsor; + FixedPoint.Unsigned paidToLiquidator; + FixedPoint.Unsigned paidToDisputer; + } + + // Liquidations are unique by ID per sponsor + mapping(address => LiquidationData[]) public liquidations; + + // Total collateral in liquidation. + FixedPoint.Unsigned public rawLiquidationCollateral; + + // Immutable contract parameters: + // Amount of time for pending liquidation before expiry. + // !!Note: The lower the liquidation liveness value, the more risk incurred by sponsors. + // Extremely low liveness values increase the chance that opportunistic invalid liquidations + // expire without dispute, thereby decreasing the usability for sponsors and increasing the risk + // for the contract as a whole. An insolvent contract is extremely risky for any sponsor or synthetic + // token holder for the contract. + uint256 public liquidationLiveness; + // Required collateral:TRV ratio for a position to be considered sufficiently collateralized. + FixedPoint.Unsigned public collateralRequirement; + // Percent of a Liquidation/Position's lockedCollateral to be deposited by a potential disputer + // Represented as a multiplier, for example 1.5e18 = "150%" and 0.05e18 = "5%" + FixedPoint.Unsigned public disputeBondPercentage; + // Percent of oraclePrice paid to sponsor in the Disputed state (i.e. following a successful dispute) + // Represented as a multiplier, see above. + FixedPoint.Unsigned public sponsorDisputeRewardPercentage; + // Percent of oraclePrice paid to disputer in the Disputed state (i.e. following a successful dispute) + // Represented as a multiplier, see above. + FixedPoint.Unsigned public disputerDisputeRewardPercentage; + + /**************************************** + * EVENTS * + ****************************************/ + + event LiquidationCreated( + address indexed sponsor, + address indexed liquidator, + uint256 indexed liquidationId, + uint256 tokensOutstanding, + uint256 lockedCollateral, + uint256 liquidatedCollateral, + uint256 liquidationTime + ); + event LiquidationDisputed( + address indexed sponsor, + address indexed liquidator, + address indexed disputer, + uint256 liquidationId, + uint256 disputeBondAmount + ); + event DisputeSettled( + address indexed caller, + address indexed sponsor, + address indexed liquidator, + address disputer, + uint256 liquidationId, + bool disputeSucceeded + ); + event LiquidationWithdrawn( + address indexed caller, + uint256 paidToLiquidator, + uint256 paidToDisputer, + uint256 paidToSponsor, + Status indexed liquidationStatus, + uint256 settlementPrice + ); + + /**************************************** + * MODIFIERS * + ****************************************/ + + modifier disputable(uint256 liquidationId, address sponsor) { + _disputable(liquidationId, sponsor); + _; + } + + modifier withdrawable(uint256 liquidationId, address sponsor) { + _withdrawable(liquidationId, sponsor); + _; + } + + /** + * @notice Constructs the liquidatable contract. + * @param params struct to define input parameters for construction of Liquidatable. Some params + * are fed directly into the PositionManager's constructor within the inheritance tree. + */ + constructor( + ConstructorParams memory params + ) + PerpetualPositionManager( + params.withdrawalLiveness, + params.collateralAddress, + params.tokenAddress, + params.finderAddress, + params.priceFeedIdentifier, + params.fundingRateIdentifier, + params.minSponsorTokens, + params.configStoreAddress, + params.tokenScaling, + params.timerAddress + ) + { + require(params.collateralRequirement.isGreaterThan(1)); + require(params.sponsorDisputeRewardPercentage.add(params.disputerDisputeRewardPercentage).isLessThan(1)); + + // Set liquidatable specific variables. + liquidationLiveness = params.liquidationLiveness; + collateralRequirement = params.collateralRequirement; + disputeBondPercentage = params.disputeBondPercentage; + sponsorDisputeRewardPercentage = params.sponsorDisputeRewardPercentage; + disputerDisputeRewardPercentage = params.disputerDisputeRewardPercentage; + } + + /**************************************** + * LIQUIDATION FUNCTIONS * + ****************************************/ + + /** + * @notice Liquidates the sponsor's position if the caller has enough + * synthetic tokens to retire the position's outstanding tokens. Liquidations above + * a minimum size also reset an ongoing "slow withdrawal"'s liveness. + * @dev This method generates an ID that will uniquely identify liquidation for the sponsor. This contract must be + * approved to spend at least `tokensLiquidated` of `tokenCurrency` and at least `finalFeeBond` of `collateralCurrency`. + * @dev This contract must have the Burner role for the `tokenCurrency`. + * @param sponsor address of the sponsor to liquidate. + * @param minCollateralPerToken abort the liquidation if the position's collateral per token is below this value. + * @param maxCollateralPerToken abort the liquidation if the position's collateral per token exceeds this value. + * @param maxTokensToLiquidate max number of tokens to liquidate. + * @param deadline abort the liquidation if the transaction is mined after this timestamp. + * @return liquidationId ID of the newly created liquidation. + * @return tokensLiquidated amount of synthetic tokens removed and liquidated from the `sponsor`'s position. + * @return finalFeeBond amount of collateral to be posted by liquidator and returned if not disputed successfully. + */ + function createLiquidation( + address sponsor, + FixedPoint.Unsigned calldata minCollateralPerToken, + FixedPoint.Unsigned calldata maxCollateralPerToken, + FixedPoint.Unsigned calldata maxTokensToLiquidate, + uint256 deadline + ) + external + notEmergencyShutdown + fees + nonReentrant + returns ( + uint256 liquidationId, + FixedPoint.Unsigned memory tokensLiquidated, + FixedPoint.Unsigned memory finalFeeBond + ) + { + // Check that this transaction was mined pre-deadline. + require(getCurrentTime() <= deadline); + + // Retrieve Position data for sponsor + PositionData storage positionToLiquidate = _getPositionData(sponsor); + + tokensLiquidated = FixedPoint.min(maxTokensToLiquidate, positionToLiquidate.tokensOutstanding); + require(tokensLiquidated.isGreaterThan(0)); + + // Starting values for the Position being liquidated. If withdrawal request amount is > position's collateral, + // then set this to 0, otherwise set it to (startCollateral - withdrawal request amount). + FixedPoint.Unsigned memory startCollateral = _getFeeAdjustedCollateral(positionToLiquidate.rawCollateral); + FixedPoint.Unsigned memory startCollateralNetOfWithdrawal = FixedPoint.fromUnscaledUint(0); + if (positionToLiquidate.withdrawalRequestAmount.isLessThanOrEqual(startCollateral)) { + startCollateralNetOfWithdrawal = startCollateral.sub(positionToLiquidate.withdrawalRequestAmount); + } + + // Scoping to get rid of a stack too deep error. + { + FixedPoint.Unsigned memory startTokens = positionToLiquidate.tokensOutstanding; + + // The Position's collateralization ratio must be between [minCollateralPerToken, maxCollateralPerToken]. + require(maxCollateralPerToken.mul(startTokens).isGreaterThanOrEqual(startCollateralNetOfWithdrawal)); + // minCollateralPerToken >= startCollateralNetOfWithdrawal / startTokens. + require(minCollateralPerToken.mul(startTokens).isLessThanOrEqual(startCollateralNetOfWithdrawal)); + } + + // Compute final fee at time of liquidation. + finalFeeBond = _computeFinalFees(); + + // These will be populated within the scope below. + FixedPoint.Unsigned memory lockedCollateral; + FixedPoint.Unsigned memory liquidatedCollateral; + + // Scoping to get rid of a stack too deep error. The amount of tokens to remove from the position + // are not funding-rate adjusted because the multiplier only affects their redemption value, not their + // notional. + { + FixedPoint.Unsigned memory ratio = tokensLiquidated.div(positionToLiquidate.tokensOutstanding); + + // The actual amount of collateral that gets moved to the liquidation. + lockedCollateral = startCollateral.mul(ratio); + + // For purposes of disputes, it's actually this liquidatedCollateral value that's used. This value is net of + // withdrawal requests. + liquidatedCollateral = startCollateralNetOfWithdrawal.mul(ratio); + + // Part of the withdrawal request is also removed. Ideally: + // liquidatedCollateral + withdrawalAmountToRemove = lockedCollateral. + FixedPoint.Unsigned memory withdrawalAmountToRemove = positionToLiquidate.withdrawalRequestAmount.mul( + ratio + ); + _reduceSponsorPosition(sponsor, tokensLiquidated, lockedCollateral, withdrawalAmountToRemove); + } + + // Add to the global liquidation collateral count. + _addCollateral(rawLiquidationCollateral, lockedCollateral.add(finalFeeBond)); + + // Construct liquidation object. + // Note: All dispute-related values are zeroed out until a dispute occurs. liquidationId is the index of the new + // LiquidationData that is pushed into the array, which is equal to the current length of the array pre-push. + liquidationId = liquidations[sponsor].length; + liquidations[sponsor].push( + LiquidationData({ + sponsor: sponsor, + liquidator: msg.sender, + state: Status.NotDisputed, + liquidationTime: getCurrentTime(), + tokensOutstanding: _getFundingRateAppliedTokenDebt(tokensLiquidated), + lockedCollateral: lockedCollateral, + liquidatedCollateral: liquidatedCollateral, + rawUnitCollateral: _convertToRawCollateral(FixedPoint.fromUnscaledUint(1)), + disputer: address(0), + settlementPrice: FixedPoint.fromUnscaledUint(0), + finalFee: finalFeeBond + }) + ); + + // If this liquidation is a subsequent liquidation on the position, and the liquidation size is larger than + // some "griefing threshold", then re-set the liveness. This enables a liquidation against a withdraw request to be + // "dragged out" if the position is very large and liquidators need time to gather funds. The griefing threshold + // is enforced so that liquidations for trivially small # of tokens cannot drag out an honest sponsor's slow withdrawal. + + // We arbitrarily set the "griefing threshold" to `minSponsorTokens` because it is the only parameter + // denominated in token currency units and we can avoid adding another parameter. + FixedPoint.Unsigned memory griefingThreshold = minSponsorTokens; + if ( + positionToLiquidate.withdrawalRequestPassTimestamp > 0 && // The position is undergoing a slow withdrawal. + positionToLiquidate.withdrawalRequestPassTimestamp > getCurrentTime() && // The slow withdrawal has not yet expired. + tokensLiquidated.isGreaterThanOrEqual(griefingThreshold) // The liquidated token count is above a "griefing threshold". + ) { + positionToLiquidate.withdrawalRequestPassTimestamp = getCurrentTime().add(withdrawalLiveness); + } + + emit LiquidationCreated( + sponsor, + msg.sender, + liquidationId, + _getFundingRateAppliedTokenDebt(tokensLiquidated).rawValue, + lockedCollateral.rawValue, + liquidatedCollateral.rawValue, + getCurrentTime() + ); + + // Destroy tokens + tokenCurrency.safeTransferFrom(msg.sender, address(this), tokensLiquidated.rawValue); + tokenCurrency.burn(tokensLiquidated.rawValue); + + // Pull final fee from liquidator. + collateralCurrency.safeTransferFrom(msg.sender, address(this), finalFeeBond.rawValue); + } + + /** + * @notice Disputes a liquidation, if the caller has enough collateral to post a dispute bond and pay a fixed final + * fee charged on each price request. + * @dev Can only dispute a liquidation before the liquidation expires and if there are no other pending disputes. + * This contract must be approved to spend at least the dispute bond amount of `collateralCurrency`. This dispute + * bond amount is calculated from `disputeBondPercentage` times the collateral in the liquidation. + * @param liquidationId of the disputed liquidation. + * @param sponsor the address of the sponsor whose liquidation is being disputed. + * @return totalPaid amount of collateral charged to disputer (i.e. final fee bond + dispute bond). + */ + function dispute( + uint256 liquidationId, + address sponsor + ) external disputable(liquidationId, sponsor) fees nonReentrant returns (FixedPoint.Unsigned memory totalPaid) { + LiquidationData storage disputedLiquidation = _getLiquidationData(sponsor, liquidationId); + + // Multiply by the unit collateral so the dispute bond is a percentage of the locked collateral after fees. + FixedPoint.Unsigned memory disputeBondAmount = disputedLiquidation + .lockedCollateral + .mul(disputeBondPercentage) + .mul(_getFeeAdjustedCollateral(disputedLiquidation.rawUnitCollateral)); + _addCollateral(rawLiquidationCollateral, disputeBondAmount); + + // Request a price from DVM. Liquidation is pending dispute until DVM returns a price. + disputedLiquidation.state = Status.Disputed; + disputedLiquidation.disputer = msg.sender; + + // Enqueue a request with the DVM. + _requestOraclePrice(disputedLiquidation.liquidationTime); + + emit LiquidationDisputed( + sponsor, + disputedLiquidation.liquidator, + msg.sender, + liquidationId, + disputeBondAmount.rawValue + ); + totalPaid = disputeBondAmount.add(disputedLiquidation.finalFee); + + // Pay the final fee for requesting price from the DVM. + _payFinalFees(msg.sender, disputedLiquidation.finalFee); + + // Transfer the dispute bond amount from the caller to this contract. + collateralCurrency.safeTransferFrom(msg.sender, address(this), disputeBondAmount.rawValue); + } + + /** + * @notice After a dispute has settled or after a non-disputed liquidation has expired, + * anyone can call this method to disperse payments to the sponsor, liquidator, and disputer. + * @dev If the dispute SUCCEEDED: the sponsor, liquidator, and disputer are eligible for payment. + * If the dispute FAILED: only the liquidator receives payment. This method deletes the liquidation data. + * This method will revert if rewards have already been dispersed. + * @param liquidationId uniquely identifies the sponsor's liquidation. + * @param sponsor address of the sponsor associated with the liquidation. + * @return data about rewards paid out. + */ + function withdrawLiquidation( + uint256 liquidationId, + address sponsor + ) public withdrawable(liquidationId, sponsor) fees nonReentrant returns (RewardsData memory) { + LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId); + + // Settles the liquidation if necessary. This call will revert if the price has not resolved yet. + _settle(liquidationId, sponsor); + + // Calculate rewards as a function of the TRV. + // Note1: all payouts are scaled by the unit collateral value so all payouts are charged the fees pro rata. + // Note2: the tokenRedemptionValue uses the tokensOutstanding which was calculated using the funding rate at + // liquidation time from _getFundingRateAppliedTokenDebt. Therefore the TRV considers the full debt value at that time. + FixedPoint.Unsigned memory feeAttenuation = _getFeeAdjustedCollateral(liquidation.rawUnitCollateral); + FixedPoint.Unsigned memory settlementPrice = liquidation.settlementPrice; + FixedPoint.Unsigned memory tokenRedemptionValue = liquidation.tokensOutstanding.mul(settlementPrice).mul( + feeAttenuation + ); + FixedPoint.Unsigned memory collateral = liquidation.lockedCollateral.mul(feeAttenuation); + FixedPoint.Unsigned memory disputerDisputeReward = disputerDisputeRewardPercentage.mul(tokenRedemptionValue); + FixedPoint.Unsigned memory sponsorDisputeReward = sponsorDisputeRewardPercentage.mul(tokenRedemptionValue); + FixedPoint.Unsigned memory disputeBondAmount = collateral.mul(disputeBondPercentage); + FixedPoint.Unsigned memory finalFee = liquidation.finalFee.mul(feeAttenuation); + + // There are three main outcome states: either the dispute succeeded, failed or was not updated. + // Based on the state, different parties of a liquidation receive different amounts. + // After assigning rewards based on the liquidation status, decrease the total collateral held in this contract + // by the amount to pay each party. The actual amounts withdrawn might differ if _removeCollateral causes + // precision loss. + RewardsData memory rewards; + if (liquidation.state == Status.DisputeSucceeded) { + // If the dispute is successful then all three users should receive rewards: + + // Pay DISPUTER: disputer reward + dispute bond + returned final fee + rewards.payToDisputer = disputerDisputeReward.add(disputeBondAmount).add(finalFee); + + // Pay SPONSOR: remaining collateral (collateral - TRV) + sponsor reward + rewards.payToSponsor = sponsorDisputeReward.add(collateral.sub(tokenRedemptionValue)); + + // Pay LIQUIDATOR: TRV - dispute reward - sponsor reward + // If TRV > Collateral, then subtract rewards from collateral + // NOTE: This should never be below zero since we prevent (sponsorDisputePercentage+disputerDisputePercentage) >= 0 in + // the constructor when these params are set. + rewards.payToLiquidator = tokenRedemptionValue.sub(sponsorDisputeReward).sub(disputerDisputeReward); + + // Transfer rewards and debit collateral + rewards.paidToLiquidator = _removeCollateral(rawLiquidationCollateral, rewards.payToLiquidator); + rewards.paidToSponsor = _removeCollateral(rawLiquidationCollateral, rewards.payToSponsor); + rewards.paidToDisputer = _removeCollateral(rawLiquidationCollateral, rewards.payToDisputer); + + collateralCurrency.safeTransfer(liquidation.disputer, rewards.paidToDisputer.rawValue); + collateralCurrency.safeTransfer(liquidation.liquidator, rewards.paidToLiquidator.rawValue); + collateralCurrency.safeTransfer(liquidation.sponsor, rewards.paidToSponsor.rawValue); + + // In the case of a failed dispute only the liquidator can withdraw. + } else if (liquidation.state == Status.DisputeFailed) { + // Pay LIQUIDATOR: collateral + dispute bond + returned final fee + rewards.payToLiquidator = collateral.add(disputeBondAmount).add(finalFee); + + // Transfer rewards and debit collateral + rewards.paidToLiquidator = _removeCollateral(rawLiquidationCollateral, rewards.payToLiquidator); + + collateralCurrency.safeTransfer(liquidation.liquidator, rewards.paidToLiquidator.rawValue); + + // If the state is pre-dispute but time has passed liveness then there was no dispute. We represent this + // state as a dispute failed and the liquidator can withdraw. + } else if (liquidation.state == Status.NotDisputed) { + // Pay LIQUIDATOR: collateral + returned final fee + rewards.payToLiquidator = collateral.add(finalFee); + + // Transfer rewards and debit collateral + rewards.paidToLiquidator = _removeCollateral(rawLiquidationCollateral, rewards.payToLiquidator); + + collateralCurrency.safeTransfer(liquidation.liquidator, rewards.paidToLiquidator.rawValue); + } + + emit LiquidationWithdrawn( + msg.sender, + rewards.paidToLiquidator.rawValue, + rewards.paidToDisputer.rawValue, + rewards.paidToSponsor.rawValue, + liquidation.state, + settlementPrice.rawValue + ); + + // Free up space after collateral is withdrawn by removing the liquidation object from the array. + delete liquidations[sponsor][liquidationId]; + + return rewards; + } + + /** + * @notice Gets all liquidation information for a given sponsor address. + * @param sponsor address of the position sponsor. + * @return liquidationData array of all liquidation information for the given sponsor address. + */ + function getLiquidations( + address sponsor + ) external view nonReentrantView returns (LiquidationData[] memory liquidationData) { + return liquidations[sponsor]; + } + + /**************************************** + * INTERNAL FUNCTIONS * + ****************************************/ + + // This settles a liquidation if it is in the Disputed state. If not, it will immediately return. + // If the liquidation is in the Disputed state, but a price is not available, this will revert. + function _settle(uint256 liquidationId, address sponsor) internal { + LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId); + + // Settlement only happens when state == Disputed and will only happen once per liquidation. + // If this liquidation is not ready to be settled, this method should return immediately. + if (liquidation.state != Status.Disputed) { + return; + } + + // Get the returned price from the oracle. If this has not yet resolved will revert. + liquidation.settlementPrice = _getOraclePrice(liquidation.liquidationTime); + + // Find the value of the tokens in the underlying collateral. + FixedPoint.Unsigned memory tokenRedemptionValue = liquidation.tokensOutstanding.mul( + liquidation.settlementPrice + ); + + // The required collateral is the value of the tokens in underlying * required collateral ratio. + FixedPoint.Unsigned memory requiredCollateral = tokenRedemptionValue.mul(collateralRequirement); + + // If the position has more than the required collateral it is solvent and the dispute is valid (liquidation is invalid) + // Note that this check uses the liquidatedCollateral not the lockedCollateral as this considers withdrawals. + bool disputeSucceeded = liquidation.liquidatedCollateral.isGreaterThanOrEqual(requiredCollateral); + liquidation.state = disputeSucceeded ? Status.DisputeSucceeded : Status.DisputeFailed; + + emit DisputeSettled( + msg.sender, + sponsor, + liquidation.liquidator, + liquidation.disputer, + liquidationId, + disputeSucceeded + ); + } + + function _pfc() internal view override returns (FixedPoint.Unsigned memory) { + return super._pfc().add(_getFeeAdjustedCollateral(rawLiquidationCollateral)); + } + + function _getLiquidationData( + address sponsor, + uint256 liquidationId + ) internal view returns (LiquidationData storage liquidation) { + LiquidationData[] storage liquidationArray = liquidations[sponsor]; + + // Revert if the caller is attempting to access an invalid liquidation + // (one that has never been created or one has never been initialized). + require( + liquidationId < liquidationArray.length && liquidationArray[liquidationId].state != Status.Uninitialized + ); + return liquidationArray[liquidationId]; + } + + function _getLiquidationExpiry(LiquidationData storage liquidation) internal view returns (uint256) { + return liquidation.liquidationTime.add(liquidationLiveness); + } + + // These internal functions are supposed to act identically to modifiers, but re-used modifiers + // unnecessarily increase contract bytecode size. + // source: https://blog.polymath.network/solidity-tips-and-tricks-to-save-gas-and-reduce-bytecode-size-c44580b218e6 + function _disputable(uint256 liquidationId, address sponsor) internal view { + LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId); + require((getCurrentTime() < _getLiquidationExpiry(liquidation)) && (liquidation.state == Status.NotDisputed)); + } + + function _withdrawable(uint256 liquidationId, address sponsor) internal view { + LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId); + Status state = liquidation.state; + + // Must be disputed or the liquidation has passed expiry. + require( + (state > Status.NotDisputed) || + ((_getLiquidationExpiry(liquidation) <= getCurrentTime()) && (state == Status.NotDisputed)) + ); + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualPositionManager.sol b/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualPositionManager.sol new file mode 100644 index 000000000..185315a67 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualPositionManager.sol @@ -0,0 +1,756 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +import "../../common/implementation/FixedPoint.sol"; +import "../../common/interfaces/ExpandedIERC20.sol"; + +import "../../data-verification-mechanism/interfaces/OracleInterface.sol"; +import "../../data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol"; +import "../../data-verification-mechanism/implementation/Constants.sol"; + +import "../common/FundingRateApplier.sol"; + +/** + * @title Financial contract with priceless position management. + * @notice Handles positions for multiple sponsors in an optimistic (i.e., priceless) way without relying + * on a price feed. On construction, deploys a new ERC20, managed by this contract, that is the synthetic token. + */ + +contract PerpetualPositionManager is FundingRateApplier { + using SafeMath for uint256; + using FixedPoint for FixedPoint.Unsigned; + using SafeERC20 for IERC20; + using SafeERC20 for ExpandedIERC20; + + /**************************************** + * PRICELESS POSITION DATA STRUCTURES * + ****************************************/ + + // Represents a single sponsor's position. All collateral is held by this contract. + // This struct acts as bookkeeping for how much of that collateral is allocated to each sponsor. + struct PositionData { + FixedPoint.Unsigned tokensOutstanding; + // Tracks pending withdrawal requests. A withdrawal request is pending if `withdrawalRequestPassTimestamp != 0`. + uint256 withdrawalRequestPassTimestamp; + FixedPoint.Unsigned withdrawalRequestAmount; + // Raw collateral value. This value should never be accessed directly -- always use _getFeeAdjustedCollateral(). + // To add or remove collateral, use _addCollateral() and _removeCollateral(). + FixedPoint.Unsigned rawCollateral; + } + + // Maps sponsor addresses to their positions. Each sponsor can have only one position. + mapping(address => PositionData) public positions; + + // Keep track of the total collateral and tokens across all positions to enable calculating the + // global collateralization ratio without iterating over all positions. + FixedPoint.Unsigned public totalTokensOutstanding; + + // Similar to the rawCollateral in PositionData, this value should not be used directly. + // _getFeeAdjustedCollateral(), _addCollateral() and _removeCollateral() must be used to access and adjust. + FixedPoint.Unsigned public rawTotalPositionCollateral; + + // Synthetic token created by this contract. + ExpandedIERC20 public tokenCurrency; + + // Unique identifier for DVM price feed ticker. + bytes32 public priceIdentifier; + + // Time that has to elapse for a withdrawal request to be considered passed, if no liquidations occur. + // !!Note: The lower the withdrawal liveness value, the more risk incurred by the contract. + // Extremely low liveness values increase the chance that opportunistic invalid withdrawal requests + // expire without liquidation, thereby increasing the insolvency risk for the contract as a whole. An insolvent + // contract is extremely risky for any sponsor or synthetic token holder for the contract. + uint256 public withdrawalLiveness; + + // Minimum number of tokens in a sponsor's position. + FixedPoint.Unsigned public minSponsorTokens; + + // Expiry price pulled from the DVM in the case of an emergency shutdown. + FixedPoint.Unsigned public emergencyShutdownPrice; + + /**************************************** + * EVENTS * + ****************************************/ + + event Deposit(address indexed sponsor, uint256 indexed collateralAmount); + event Withdrawal(address indexed sponsor, uint256 indexed collateralAmount); + event RequestWithdrawal(address indexed sponsor, uint256 indexed collateralAmount); + event RequestWithdrawalExecuted(address indexed sponsor, uint256 indexed collateralAmount); + event RequestWithdrawalCanceled(address indexed sponsor, uint256 indexed collateralAmount); + event PositionCreated(address indexed sponsor, uint256 indexed collateralAmount, uint256 indexed tokenAmount); + event NewSponsor(address indexed sponsor); + event EndedSponsorPosition(address indexed sponsor); + event Redeem(address indexed sponsor, uint256 indexed collateralAmount, uint256 indexed tokenAmount); + event Repay(address indexed sponsor, uint256 indexed numTokensRepaid, uint256 indexed newTokenCount); + event EmergencyShutdown(address indexed caller, uint256 shutdownTimestamp); + event SettleEmergencyShutdown( + address indexed caller, + uint256 indexed collateralReturned, + uint256 indexed tokensBurned + ); + + /**************************************** + * MODIFIERS * + ****************************************/ + + modifier onlyCollateralizedPosition(address sponsor) { + _onlyCollateralizedPosition(sponsor); + _; + } + + modifier noPendingWithdrawal(address sponsor) { + _positionHasNoPendingWithdrawal(sponsor); + _; + } + + /** + * @notice Construct the PerpetualPositionManager. + * @dev Deployer of this contract should consider carefully which parties have ability to mint and burn + * the synthetic tokens referenced by `_tokenAddress`. This contract's security assumes that no external accounts + * can mint new tokens, which could be used to steal all of this contract's locked collateral. + * We recommend to only use synthetic token contracts whose sole Owner role (the role capable of adding & removing roles) + * is assigned to this contract, whose sole Minter role is assigned to this contract, and whose + * total supply is 0 prior to construction of this contract. + * @param _withdrawalLiveness liveness delay, in seconds, for pending withdrawals. + * @param _collateralAddress ERC20 token used as collateral for all positions. + * @param _tokenAddress ERC20 token used as synthetic token. + * @param _finderAddress UMA protocol Finder used to discover other protocol contracts. + * @param _priceIdentifier registered in the DVM for the synthetic. + * @param _fundingRateIdentifier Unique identifier for DVM price feed ticker for child financial contract. + * @param _minSponsorTokens minimum number of tokens that must exist at any time in a position. + * @param _tokenScaling initial scaling to apply to the token value (i.e. scales the tracking index). + * @param _timerAddress Contract that stores the current time in a testing environment. Set to 0x0 for production. + */ + constructor( + uint256 _withdrawalLiveness, + address _collateralAddress, + address _tokenAddress, + address _finderAddress, + bytes32 _priceIdentifier, + bytes32 _fundingRateIdentifier, + FixedPoint.Unsigned memory _minSponsorTokens, + address _configStoreAddress, + FixedPoint.Unsigned memory _tokenScaling, + address _timerAddress + ) + FundingRateApplier( + _fundingRateIdentifier, + _collateralAddress, + _finderAddress, + _configStoreAddress, + _tokenScaling, + _timerAddress + ) + { + require(_getIdentifierWhitelist().isIdentifierSupported(_priceIdentifier)); + + withdrawalLiveness = _withdrawalLiveness; + tokenCurrency = ExpandedIERC20(_tokenAddress); + minSponsorTokens = _minSponsorTokens; + priceIdentifier = _priceIdentifier; + } + + /**************************************** + * POSITION FUNCTIONS * + ****************************************/ + + /** + * @notice Transfers `collateralAmount` of `collateralCurrency` into the specified sponsor's position. + * @dev Increases the collateralization level of a position after creation. This contract must be approved to spend + * at least `collateralAmount` of `collateralCurrency`. + * @param sponsor the sponsor to credit the deposit to. + * @param collateralAmount total amount of collateral tokens to be sent to the sponsor's position. + */ + function depositTo( + address sponsor, + FixedPoint.Unsigned memory collateralAmount + ) public notEmergencyShutdown noPendingWithdrawal(sponsor) fees nonReentrant { + require(collateralAmount.isGreaterThan(0)); + PositionData storage positionData = _getPositionData(sponsor); + + // Increase the position and global collateral balance by collateral amount. + _incrementCollateralBalances(positionData, collateralAmount); + + emit Deposit(sponsor, collateralAmount.rawValue); + + // Move collateral currency from sender to contract. + collateralCurrency.safeTransferFrom(msg.sender, address(this), collateralAmount.rawValue); + } + + /** + * @notice Transfers `collateralAmount` of `collateralCurrency` into the caller's position. + * @dev Increases the collateralization level of a position after creation. This contract must be approved to spend + * at least `collateralAmount` of `collateralCurrency`. + * @param collateralAmount total amount of collateral tokens to be sent to the sponsor's position. + */ + function deposit(FixedPoint.Unsigned memory collateralAmount) public { + // This is just a thin wrapper over depositTo that specified the sender as the sponsor. + depositTo(msg.sender, collateralAmount); + } + + /** + * @notice Transfers `collateralAmount` of `collateralCurrency` from the sponsor's position to the sponsor. + * @dev Reverts if the withdrawal puts this position's collateralization ratio below the global collateralization + * ratio. In that case, use `requestWithdrawal`. Might not withdraw the full requested amount to account for precision loss. + * @param collateralAmount is the amount of collateral to withdraw. + * @return amountWithdrawn The actual amount of collateral withdrawn. + */ + function withdraw( + FixedPoint.Unsigned memory collateralAmount + ) + public + notEmergencyShutdown + noPendingWithdrawal(msg.sender) + fees + nonReentrant + returns (FixedPoint.Unsigned memory amountWithdrawn) + { + require(collateralAmount.isGreaterThan(0)); + PositionData storage positionData = _getPositionData(msg.sender); + + // Decrement the sponsor's collateral and global collateral amounts. Check the GCR between decrement to ensure + // position remains above the GCR within the withdrawal. If this is not the case the caller must submit a request. + amountWithdrawn = _decrementCollateralBalancesCheckGCR(positionData, collateralAmount); + + emit Withdrawal(msg.sender, amountWithdrawn.rawValue); + + // Move collateral currency from contract to sender. + // Note: that we move the amount of collateral that is decreased from rawCollateral (inclusive of fees) + // instead of the user requested amount. This eliminates precision loss that could occur + // where the user withdraws more collateral than rawCollateral is decremented by. + collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue); + } + + /** + * @notice Starts a withdrawal request that, if passed, allows the sponsor to withdraw from their position. + * @dev The request will be pending for `withdrawalLiveness`, during which the position can be liquidated. + * @param collateralAmount the amount of collateral requested to withdraw + */ + function requestWithdrawal( + FixedPoint.Unsigned memory collateralAmount + ) public notEmergencyShutdown noPendingWithdrawal(msg.sender) nonReentrant { + PositionData storage positionData = _getPositionData(msg.sender); + require( + collateralAmount.isGreaterThan(0) && + collateralAmount.isLessThanOrEqual(_getFeeAdjustedCollateral(positionData.rawCollateral)) + ); + + // Update the position object for the user. + positionData.withdrawalRequestPassTimestamp = getCurrentTime().add(withdrawalLiveness); + positionData.withdrawalRequestAmount = collateralAmount; + + emit RequestWithdrawal(msg.sender, collateralAmount.rawValue); + } + + /** + * @notice After a passed withdrawal request (i.e., by a call to `requestWithdrawal` and waiting + * `withdrawalLiveness`), withdraws `positionData.withdrawalRequestAmount` of collateral currency. + * @dev Might not withdraw the full requested amount in order to account for precision loss or if the full requested + * amount exceeds the collateral in the position (due to paying fees). + * @return amountWithdrawn The actual amount of collateral withdrawn. + */ + function withdrawPassedRequest() + external + notEmergencyShutdown + fees + nonReentrant + returns (FixedPoint.Unsigned memory amountWithdrawn) + { + PositionData storage positionData = _getPositionData(msg.sender); + require( + positionData.withdrawalRequestPassTimestamp != 0 && + positionData.withdrawalRequestPassTimestamp <= getCurrentTime() + ); + + // If withdrawal request amount is > position collateral, then withdraw the full collateral amount. + // This situation is possible due to fees charged since the withdrawal was originally requested. + FixedPoint.Unsigned memory amountToWithdraw = positionData.withdrawalRequestAmount; + if (positionData.withdrawalRequestAmount.isGreaterThan(_getFeeAdjustedCollateral(positionData.rawCollateral))) { + amountToWithdraw = _getFeeAdjustedCollateral(positionData.rawCollateral); + } + + // Decrement the sponsor's collateral and global collateral amounts. + amountWithdrawn = _decrementCollateralBalances(positionData, amountToWithdraw); + + // Reset withdrawal request by setting withdrawal amount and withdrawal timestamp to 0. + _resetWithdrawalRequest(positionData); + + // Transfer approved withdrawal amount from the contract to the caller. + collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue); + + emit RequestWithdrawalExecuted(msg.sender, amountWithdrawn.rawValue); + } + + /** + * @notice Cancels a pending withdrawal request. + */ + function cancelWithdrawal() external notEmergencyShutdown nonReentrant { + PositionData storage positionData = _getPositionData(msg.sender); + // No pending withdrawal require message removed to save bytecode. + require(positionData.withdrawalRequestPassTimestamp != 0); + + emit RequestWithdrawalCanceled(msg.sender, positionData.withdrawalRequestAmount.rawValue); + + // Reset withdrawal request by setting withdrawal amount and withdrawal timestamp to 0. + _resetWithdrawalRequest(positionData); + } + + /** + * @notice Creates tokens by creating a new position or by augmenting an existing position. Pulls `collateralAmount + * ` into the sponsor's position and mints `numTokens` of `tokenCurrency`. + * @dev This contract must have the Minter role for the `tokenCurrency`. + * @dev Reverts if minting these tokens would put the position's collateralization ratio below the + * global collateralization ratio. This contract must be approved to spend at least `collateralAmount` of + * `collateralCurrency`. + * @param collateralAmount is the number of collateral tokens to collateralize the position with + * @param numTokens is the number of tokens to mint from the position. + */ + function create( + FixedPoint.Unsigned memory collateralAmount, + FixedPoint.Unsigned memory numTokens + ) public notEmergencyShutdown fees nonReentrant { + PositionData storage positionData = positions[msg.sender]; + + // Either the new create ratio or the resultant position CR must be above the current GCR. + require( + (_checkCollateralization( + _getFeeAdjustedCollateral(positionData.rawCollateral).add(collateralAmount), + positionData.tokensOutstanding.add(numTokens) + ) || _checkCollateralization(collateralAmount, numTokens)) + ); + + require(positionData.withdrawalRequestPassTimestamp == 0); + if (positionData.tokensOutstanding.isEqual(0)) { + require(numTokens.isGreaterThanOrEqual(minSponsorTokens)); + emit NewSponsor(msg.sender); + } + + // Increase the position and global collateral balance by collateral amount. + _incrementCollateralBalances(positionData, collateralAmount); + + // Add the number of tokens created to the position's outstanding tokens. + positionData.tokensOutstanding = positionData.tokensOutstanding.add(numTokens); + + totalTokensOutstanding = totalTokensOutstanding.add(numTokens); + + emit PositionCreated(msg.sender, collateralAmount.rawValue, numTokens.rawValue); + + // Transfer tokens into the contract from caller and mint corresponding synthetic tokens to the caller's address. + collateralCurrency.safeTransferFrom(msg.sender, address(this), collateralAmount.rawValue); + + // Note: revert reason removed to save bytecode. + require(tokenCurrency.mint(msg.sender, numTokens.rawValue)); + } + + /** + * @notice Burns `numTokens` of `tokenCurrency` and sends back the proportional amount of `collateralCurrency`. + * @dev Can only be called by a token sponsor. Might not redeem the full proportional amount of collateral + * in order to account for precision loss. This contract must be approved to spend at least `numTokens` of + * `tokenCurrency`. + * @dev This contract must have the Burner role for the `tokenCurrency`. + * @param numTokens is the number of tokens to be burnt for a commensurate amount of collateral. + * @return amountWithdrawn The actual amount of collateral withdrawn. + */ + function redeem( + FixedPoint.Unsigned memory numTokens + ) + public + notEmergencyShutdown + noPendingWithdrawal(msg.sender) + fees + nonReentrant + returns (FixedPoint.Unsigned memory amountWithdrawn) + { + PositionData storage positionData = _getPositionData(msg.sender); + require(numTokens.isLessThanOrEqual(positionData.tokensOutstanding)); + + FixedPoint.Unsigned memory fractionRedeemed = numTokens.div(positionData.tokensOutstanding); + FixedPoint.Unsigned memory collateralRedeemed = fractionRedeemed.mul( + _getFeeAdjustedCollateral(positionData.rawCollateral) + ); + + // If redemption returns all tokens the sponsor has then we can delete their position. Else, downsize. + if (positionData.tokensOutstanding.isEqual(numTokens)) { + amountWithdrawn = _deleteSponsorPosition(msg.sender); + } else { + // Decrement the sponsor's collateral and global collateral amounts. + amountWithdrawn = _decrementCollateralBalances(positionData, collateralRedeemed); + + // Decrease the sponsors position tokens size. Ensure it is above the min sponsor size. + FixedPoint.Unsigned memory newTokenCount = positionData.tokensOutstanding.sub(numTokens); + require(newTokenCount.isGreaterThanOrEqual(minSponsorTokens)); + positionData.tokensOutstanding = newTokenCount; + + // Update the totalTokensOutstanding after redemption. + totalTokensOutstanding = totalTokensOutstanding.sub(numTokens); + } + + emit Redeem(msg.sender, amountWithdrawn.rawValue, numTokens.rawValue); + + // Transfer collateral from contract to caller and burn callers synthetic tokens. + collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue); + tokenCurrency.safeTransferFrom(msg.sender, address(this), numTokens.rawValue); + tokenCurrency.burn(numTokens.rawValue); + } + + /** + * @notice Burns `numTokens` of `tokenCurrency` to decrease sponsors position size, without sending back `collateralCurrency`. + * This is done by a sponsor to increase position CR. Resulting size is bounded by minSponsorTokens. + * @dev Can only be called by token sponsor. This contract must be approved to spend `numTokens` of `tokenCurrency`. + * @dev This contract must have the Burner role for the `tokenCurrency`. + * @param numTokens is the number of tokens to be burnt from the sponsor's debt position. + */ + function repay( + FixedPoint.Unsigned memory numTokens + ) public notEmergencyShutdown noPendingWithdrawal(msg.sender) fees nonReentrant { + PositionData storage positionData = _getPositionData(msg.sender); + require(numTokens.isLessThanOrEqual(positionData.tokensOutstanding)); + + // Decrease the sponsors position tokens size. Ensure it is above the min sponsor size. + FixedPoint.Unsigned memory newTokenCount = positionData.tokensOutstanding.sub(numTokens); + require(newTokenCount.isGreaterThanOrEqual(minSponsorTokens)); + positionData.tokensOutstanding = newTokenCount; + + // Update the totalTokensOutstanding after redemption. + totalTokensOutstanding = totalTokensOutstanding.sub(numTokens); + + emit Repay(msg.sender, numTokens.rawValue, newTokenCount.rawValue); + + // Transfer the tokens back from the sponsor and burn them. + tokenCurrency.safeTransferFrom(msg.sender, address(this), numTokens.rawValue); + tokenCurrency.burn(numTokens.rawValue); + } + + /** + * @notice If the contract is emergency shutdown then all token holders and sponsors can redeem their tokens or + * remaining collateral for underlying at the prevailing price defined by a DVM vote. + * @dev This burns all tokens from the caller of `tokenCurrency` and sends back the resolved settlement value of + * `collateralCurrency`. Might not redeem the full proportional amount of collateral in order to account for + * precision loss. This contract must be approved to spend `tokenCurrency` at least up to the caller's full balance. + * @dev This contract must have the Burner role for the `tokenCurrency`. + * @dev Note that this function does not call the updateFundingRate modifier to update the funding rate as this + * function is only called after an emergency shutdown & there should be no funding rate updates after the shutdown. + * @return amountWithdrawn The actual amount of collateral withdrawn. + */ + function settleEmergencyShutdown() + external + isEmergencyShutdown + fees + nonReentrant + returns (FixedPoint.Unsigned memory amountWithdrawn) + { + // Set the emergency shutdown price as resolved from the DVM. If DVM has not resolved will revert. + if (emergencyShutdownPrice.isEqual(FixedPoint.fromUnscaledUint(0))) { + emergencyShutdownPrice = _getOracleEmergencyShutdownPrice(); + } + + // Get caller's tokens balance and calculate amount of underlying entitled to them. + FixedPoint.Unsigned memory tokensToRedeem = FixedPoint.Unsigned(tokenCurrency.balanceOf(msg.sender)); + FixedPoint.Unsigned memory totalRedeemableCollateral = _getFundingRateAppliedTokenDebt(tokensToRedeem).mul( + emergencyShutdownPrice + ); + + // If the caller is a sponsor with outstanding collateral they are also entitled to their excess collateral after their debt. + PositionData storage positionData = positions[msg.sender]; + if (_getFeeAdjustedCollateral(positionData.rawCollateral).isGreaterThan(0)) { + // Calculate the underlying entitled to a token sponsor. This is collateral - debt in underlying with + // the funding rate applied to the outstanding token debt. + + FixedPoint.Unsigned memory tokenDebtValueInCollateral = _getFundingRateAppliedTokenDebt( + positionData.tokensOutstanding + ).mul(emergencyShutdownPrice); + FixedPoint.Unsigned memory positionCollateral = _getFeeAdjustedCollateral(positionData.rawCollateral); + + // If the debt is greater than the remaining collateral, they cannot redeem anything. + FixedPoint.Unsigned memory positionRedeemableCollateral = tokenDebtValueInCollateral.isLessThan( + positionCollateral + ) + ? positionCollateral.sub(tokenDebtValueInCollateral) + : FixedPoint.Unsigned(0); + + // Add the number of redeemable tokens for the sponsor to their total redeemable collateral. + totalRedeemableCollateral = totalRedeemableCollateral.add(positionRedeemableCollateral); + + // Reset the position state as all the value has been removed after settlement. + delete positions[msg.sender]; + emit EndedSponsorPosition(msg.sender); + } + + // Take the min of the remaining collateral and the collateral "owed". If the contract is undercapitalized, + // the caller will get as much collateral as the contract can pay out. + FixedPoint.Unsigned memory payout = FixedPoint.min( + _getFeeAdjustedCollateral(rawTotalPositionCollateral), + totalRedeemableCollateral + ); + + // Decrement total contract collateral and outstanding debt. + amountWithdrawn = _removeCollateral(rawTotalPositionCollateral, payout); + totalTokensOutstanding = totalTokensOutstanding.sub(tokensToRedeem); + + emit SettleEmergencyShutdown(msg.sender, amountWithdrawn.rawValue, tokensToRedeem.rawValue); + + // Transfer tokens & collateral and burn the redeemed tokens. + collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue); + tokenCurrency.safeTransferFrom(msg.sender, address(this), tokensToRedeem.rawValue); + tokenCurrency.burn(tokensToRedeem.rawValue); + } + + /**************************************** + * GLOBAL STATE FUNCTIONS * + ****************************************/ + + /** + * @notice Premature contract settlement under emergency circumstances. + * @dev Only the governor can call this function as they are permissioned within the `FinancialContractAdmin`. + * Upon emergency shutdown, the contract settlement time is set to the shutdown time. This enables withdrawal + * to occur via the `settleEmergencyShutdown` function. + */ + function emergencyShutdown() external override notEmergencyShutdown fees nonReentrant { + // Note: revert reason removed to save bytecode. + require(msg.sender == _getFinancialContractsAdminAddress()); + + emergencyShutdownTimestamp = getCurrentTime(); + _requestOraclePrice(emergencyShutdownTimestamp); + + emit EmergencyShutdown(msg.sender, emergencyShutdownTimestamp); + } + + /** + * @notice Theoretically supposed to pay fees and move money between margin accounts to make sure they + * reflect the NAV of the contract. However, this functionality doesn't apply to this contract. + * @dev This is supposed to be implemented by any contract that inherits `AdministrateeInterface` and callable + * only by the Governor contract. This method is therefore minimally implemented in this contract and does nothing. + */ + function remargin() external pure override { + return; + } + + /** + * @notice Accessor method for a sponsor's collateral. + * @dev This is necessary because the struct returned by the positions() method shows + * rawCollateral, which isn't a user-readable value. + * @dev This method accounts for pending regular fees that have not yet been withdrawn from this contract, for + * example if the `lastPaymentTime != currentTime`. + * @param sponsor address whose collateral amount is retrieved. + * @return collateralAmount amount of collateral within a sponsors position. + */ + function getCollateral( + address sponsor + ) external view nonReentrantView returns (FixedPoint.Unsigned memory collateralAmount) { + // Note: do a direct access to avoid the validity check. + return _getPendingRegularFeeAdjustedCollateral(_getFeeAdjustedCollateral(positions[sponsor].rawCollateral)); + } + + /** + * @notice Accessor method for the total collateral stored within the PerpetualPositionManager. + * @return totalCollateral amount of all collateral within the position manager. + */ + function totalPositionCollateral() + external + view + nonReentrantView + returns (FixedPoint.Unsigned memory totalCollateral) + { + return _getPendingRegularFeeAdjustedCollateral(_getFeeAdjustedCollateral(rawTotalPositionCollateral)); + } + + function getFundingRateAppliedTokenDebt( + FixedPoint.Unsigned memory rawTokenDebt + ) external view nonReentrantView returns (FixedPoint.Unsigned memory totalCollateral) { + return _getFundingRateAppliedTokenDebt(rawTokenDebt); + } + + /**************************************** + * INTERNAL FUNCTIONS * + ****************************************/ + + // Reduces a sponsor's position and global counters by the specified parameters. Handles deleting the entire + // position if the entire position is being removed. Does not make any external transfers. + function _reduceSponsorPosition( + address sponsor, + FixedPoint.Unsigned memory tokensToRemove, + FixedPoint.Unsigned memory collateralToRemove, + FixedPoint.Unsigned memory withdrawalAmountToRemove + ) internal { + PositionData storage positionData = _getPositionData(sponsor); + + // If the entire position is being removed, delete it instead. + if ( + tokensToRemove.isEqual(positionData.tokensOutstanding) && + _getFeeAdjustedCollateral(positionData.rawCollateral).isEqual(collateralToRemove) + ) { + _deleteSponsorPosition(sponsor); + return; + } + + // Decrement the sponsor's collateral and global collateral amounts. + _decrementCollateralBalances(positionData, collateralToRemove); + + // Ensure that the sponsor will meet the min position size after the reduction. + positionData.tokensOutstanding = positionData.tokensOutstanding.sub(tokensToRemove); + require(positionData.tokensOutstanding.isGreaterThanOrEqual(minSponsorTokens)); + + // Decrement the position's withdrawal amount. + positionData.withdrawalRequestAmount = positionData.withdrawalRequestAmount.sub(withdrawalAmountToRemove); + + // Decrement the total outstanding tokens in the overall contract. + totalTokensOutstanding = totalTokensOutstanding.sub(tokensToRemove); + } + + // Deletes a sponsor's position and updates global counters. Does not make any external transfers. + function _deleteSponsorPosition(address sponsor) internal returns (FixedPoint.Unsigned memory) { + PositionData storage positionToLiquidate = _getPositionData(sponsor); + + FixedPoint.Unsigned memory startingGlobalCollateral = _getFeeAdjustedCollateral(rawTotalPositionCollateral); + + // Remove the collateral and outstanding from the overall total position. + rawTotalPositionCollateral = rawTotalPositionCollateral.sub(positionToLiquidate.rawCollateral); + totalTokensOutstanding = totalTokensOutstanding.sub(positionToLiquidate.tokensOutstanding); + + // Reset the sponsors position to have zero outstanding and collateral. + delete positions[sponsor]; + + emit EndedSponsorPosition(sponsor); + + // Return fee-adjusted amount of collateral deleted from position. + return startingGlobalCollateral.sub(_getFeeAdjustedCollateral(rawTotalPositionCollateral)); + } + + function _pfc() internal view virtual override returns (FixedPoint.Unsigned memory) { + return _getFeeAdjustedCollateral(rawTotalPositionCollateral); + } + + function _getPositionData( + address sponsor + ) internal view onlyCollateralizedPosition(sponsor) returns (PositionData storage) { + return positions[sponsor]; + } + + function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) { + return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); + } + + function _getOracle() internal view returns (OracleInterface) { + return OracleInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); + } + + function _getFinancialContractsAdminAddress() internal view returns (address) { + return finder.getImplementationAddress(OracleInterfaces.FinancialContractsAdmin); + } + + // Requests a price for `priceIdentifier` at `requestedTime` from the Oracle. + function _requestOraclePrice(uint256 requestedTime) internal { + _getOracle().requestPrice(priceIdentifier, requestedTime); + } + + // Fetches a resolved Oracle price from the Oracle. Reverts if the Oracle hasn't resolved for this request. + function _getOraclePrice(uint256 requestedTime) internal view returns (FixedPoint.Unsigned memory price) { + // Create an instance of the oracle and get the price. If the price is not resolved revert. + int256 oraclePrice = _getOracle().getPrice(priceIdentifier, requestedTime); + + // For now we don't want to deal with negative prices in positions. + if (oraclePrice < 0) { + oraclePrice = 0; + } + return FixedPoint.Unsigned(uint256(oraclePrice)); + } + + // Fetches a resolved Oracle price from the Oracle. Reverts if the Oracle hasn't resolved for this request. + function _getOracleEmergencyShutdownPrice() internal view returns (FixedPoint.Unsigned memory) { + return _getOraclePrice(emergencyShutdownTimestamp); + } + + // Reset withdrawal request by setting the withdrawal request and withdrawal timestamp to 0. + function _resetWithdrawalRequest(PositionData storage positionData) internal { + positionData.withdrawalRequestAmount = FixedPoint.fromUnscaledUint(0); + positionData.withdrawalRequestPassTimestamp = 0; + } + + // Ensure individual and global consistency when increasing collateral balances. Returns the change to the position. + function _incrementCollateralBalances( + PositionData storage positionData, + FixedPoint.Unsigned memory collateralAmount + ) internal returns (FixedPoint.Unsigned memory) { + _addCollateral(positionData.rawCollateral, collateralAmount); + return _addCollateral(rawTotalPositionCollateral, collateralAmount); + } + + // Ensure individual and global consistency when decrementing collateral balances. Returns the change to the + // position. We elect to return the amount that the global collateral is decreased by, rather than the individual + // position's collateral, because we need to maintain the invariant that the global collateral is always + // <= the collateral owned by the contract to avoid reverts on withdrawals. The amount returned = amount withdrawn. + function _decrementCollateralBalances( + PositionData storage positionData, + FixedPoint.Unsigned memory collateralAmount + ) internal returns (FixedPoint.Unsigned memory) { + _removeCollateral(positionData.rawCollateral, collateralAmount); + return _removeCollateral(rawTotalPositionCollateral, collateralAmount); + } + + // Ensure individual and global consistency when decrementing collateral balances. Returns the change to the position. + // This function is similar to the _decrementCollateralBalances function except this function checks position GCR + // between the decrements. This ensures that collateral removal will not leave the position undercollateralized. + function _decrementCollateralBalancesCheckGCR( + PositionData storage positionData, + FixedPoint.Unsigned memory collateralAmount + ) internal returns (FixedPoint.Unsigned memory) { + _removeCollateral(positionData.rawCollateral, collateralAmount); + require(_checkPositionCollateralization(positionData)); + return _removeCollateral(rawTotalPositionCollateral, collateralAmount); + } + + // These internal functions are supposed to act identically to modifiers, but re-used modifiers + // unnecessarily increase contract bytecode size. + // source: https://blog.polymath.network/solidity-tips-and-tricks-to-save-gas-and-reduce-bytecode-size-c44580b218e6 + function _onlyCollateralizedPosition(address sponsor) internal view { + require(_getFeeAdjustedCollateral(positions[sponsor].rawCollateral).isGreaterThan(0)); + } + + // Note: This checks whether an already existing position has a pending withdrawal. This cannot be used on the + // `create` method because it is possible that `create` is called on a new position (i.e. one without any collateral + // or tokens outstanding) which would fail the `onlyCollateralizedPosition` modifier on `_getPositionData`. + function _positionHasNoPendingWithdrawal(address sponsor) internal view { + require(_getPositionData(sponsor).withdrawalRequestPassTimestamp == 0); + } + + /**************************************** + * PRIVATE FUNCTIONS * + ****************************************/ + + function _checkPositionCollateralization(PositionData storage positionData) private view returns (bool) { + return + _checkCollateralization( + _getFeeAdjustedCollateral(positionData.rawCollateral), + positionData.tokensOutstanding + ); + } + + // Checks whether the provided `collateral` and `numTokens` have a collateralization ratio above the global + // collateralization ratio. + function _checkCollateralization( + FixedPoint.Unsigned memory collateral, + FixedPoint.Unsigned memory numTokens + ) private view returns (bool) { + FixedPoint.Unsigned memory global = _getCollateralizationRatio( + _getFeeAdjustedCollateral(rawTotalPositionCollateral), + totalTokensOutstanding + ); + FixedPoint.Unsigned memory thisChange = _getCollateralizationRatio(collateral, numTokens); + return !global.isGreaterThan(thisChange); + } + + function _getCollateralizationRatio( + FixedPoint.Unsigned memory collateral, + FixedPoint.Unsigned memory numTokens + ) private pure returns (FixedPoint.Unsigned memory ratio) { + return numTokens.isLessThanOrEqual(0) ? FixedPoint.fromUnscaledUint(0) : collateral.div(numTokens); + } + + function _getTokenAddress() internal view override returns (address) { + return address(tokenCurrency); + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/test/ExpiringMultiPartyMock.sol b/contracts/external/uma/core/contracts/financial-templates/test/ExpiringMultiPartyMock.sol new file mode 100644 index 000000000..f3c313ae5 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/test/ExpiringMultiPartyMock.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/Testable.sol"; +import "../../common/implementation/FixedPoint.sol"; +import "../common/financial-product-libraries/expiring-multiparty-libraries/FinancialProductLibrary.sol"; + +contract ExpiringMultiPartyMock is Testable { + using FixedPoint for FixedPoint.Unsigned; + + FinancialProductLibrary public financialProductLibrary; + uint256 public expirationTimestamp; + FixedPoint.Unsigned public collateralRequirement; + bytes32 public priceIdentifier; + + constructor( + address _financialProductLibraryAddress, + uint256 _expirationTimestamp, + FixedPoint.Unsigned memory _collateralRequirement, + bytes32 _priceIdentifier, + address _timerAddress + ) Testable(_timerAddress) { + expirationTimestamp = _expirationTimestamp; + collateralRequirement = _collateralRequirement; + financialProductLibrary = FinancialProductLibrary(_financialProductLibraryAddress); + priceIdentifier = _priceIdentifier; + } + + function transformPrice( + FixedPoint.Unsigned memory price, + uint256 requestTime + ) public view returns (FixedPoint.Unsigned memory) { + if (address(financialProductLibrary) == address(0)) return price; + try financialProductLibrary.transformPrice(price, requestTime) returns ( + FixedPoint.Unsigned memory transformedPrice + ) { + return transformedPrice; + } catch { + return price; + } + } + + function transformCollateralRequirement( + FixedPoint.Unsigned memory price + ) public view returns (FixedPoint.Unsigned memory) { + if (address(financialProductLibrary) == address(0)) return collateralRequirement; + try financialProductLibrary.transformCollateralRequirement(price, collateralRequirement) returns ( + FixedPoint.Unsigned memory transformedCollateralRequirement + ) { + return transformedCollateralRequirement; + } catch { + return collateralRequirement; + } + } + + function transformPriceIdentifier(uint256 requestTime) public view returns (bytes32) { + if (address(financialProductLibrary) == address(0)) return priceIdentifier; + try financialProductLibrary.transformPriceIdentifier(priceIdentifier, requestTime) returns ( + bytes32 transformedIdentifier + ) { + return transformedIdentifier; + } catch { + return priceIdentifier; + } + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/test/FinancialProductLibraryTest.sol b/contracts/external/uma/core/contracts/financial-templates/test/FinancialProductLibraryTest.sol new file mode 100644 index 000000000..15231967c --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/test/FinancialProductLibraryTest.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; +import "../common/financial-product-libraries/expiring-multiparty-libraries/FinancialProductLibrary.sol"; + +// Implements a simple FinancialProductLibrary to test price and collateral requirement transoformations. +contract FinancialProductLibraryTest is FinancialProductLibrary { + using FixedPoint for FixedPoint.Unsigned; + + FixedPoint.Unsigned public priceTransformationScalar; + FixedPoint.Unsigned public collateralRequirementTransformationScalar; + bytes32 public transformedPriceIdentifier; + bool public shouldRevert; + + constructor( + FixedPoint.Unsigned memory _priceTransformationScalar, + FixedPoint.Unsigned memory _collateralRequirementTransformationScalar, + bytes32 _transformedPriceIdentifier + ) { + priceTransformationScalar = _priceTransformationScalar; + collateralRequirementTransformationScalar = _collateralRequirementTransformationScalar; + transformedPriceIdentifier = _transformedPriceIdentifier; + } + + // Set the mocked methods to revert to test failed library computation. + function setShouldRevert(bool _shouldRevert) public { + shouldRevert = _shouldRevert; + } + + // Create a simple price transformation function that scales the input price by the scalar for testing. + function transformPrice( + FixedPoint.Unsigned memory oraclePrice, + uint256 + ) public view override returns (FixedPoint.Unsigned memory) { + require(!shouldRevert, "set to always reverts"); + return oraclePrice.mul(priceTransformationScalar); + } + + // Create a simple collateral requirement transformation that doubles the input collateralRequirement. + function transformCollateralRequirement( + FixedPoint.Unsigned memory, + FixedPoint.Unsigned memory collateralRequirement + ) public view override returns (FixedPoint.Unsigned memory) { + require(!shouldRevert, "set to always reverts"); + return collateralRequirement.mul(collateralRequirementTransformationScalar); + } + + // Create a simple transformPriceIdentifier function that returns the transformed price identifier. + function transformPriceIdentifier(bytes32, uint256) public view override returns (bytes32) { + require(!shouldRevert, "set to always reverts"); + return transformedPriceIdentifier; + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/test/FundingRateApplierTest.sol b/contracts/external/uma/core/contracts/financial-templates/test/FundingRateApplierTest.sol new file mode 100644 index 000000000..9053990a6 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/test/FundingRateApplierTest.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../common/FundingRateApplier.sol"; +import "../../common/implementation/FixedPoint.sol"; + +// Implements FundingRateApplier internal methods to enable unit testing. +contract FundingRateApplierTest is FundingRateApplier { + constructor( + bytes32 _fundingRateIdentifier, + address _collateralAddress, + address _finderAddress, + address _configStoreAddress, + FixedPoint.Unsigned memory _tokenScaling, + address _timerAddress + ) + FundingRateApplier( + _fundingRateIdentifier, + _collateralAddress, + _finderAddress, + _configStoreAddress, + _tokenScaling, + _timerAddress + ) + {} + + function calculateEffectiveFundingRate( + uint256 paymentPeriodSeconds, + FixedPoint.Signed memory fundingRatePerSecond, + FixedPoint.Unsigned memory currentCumulativeFundingRateMultiplier + ) public pure returns (FixedPoint.Unsigned memory) { + return + _calculateEffectiveFundingRate( + paymentPeriodSeconds, + fundingRatePerSecond, + currentCumulativeFundingRateMultiplier + ); + } + + // Required overrides. + function _pfc() internal view virtual override returns (FixedPoint.Unsigned memory currentPfc) { + return FixedPoint.Unsigned(collateralCurrency.balanceOf(address(this))); + } + + function emergencyShutdown() external override {} + + function remargin() external override {} + + function _getTokenAddress() internal view override returns (address) { + return address(collateralCurrency); + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/test/LongShortPairFinancialProjectLibraryTest.sol b/contracts/external/uma/core/contracts/financial-templates/test/LongShortPairFinancialProjectLibraryTest.sol new file mode 100644 index 000000000..91c11f8fd --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/test/LongShortPairFinancialProjectLibraryTest.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; +import "../common/financial-product-libraries/long-short-pair-libraries/LongShortPairFinancialProductLibrary.sol"; + +// Implements a simple FinancialProductLibrary to test price and collateral requirement transoformations. +contract LongShortPairFinancialProjectLibraryTest is LongShortPairFinancialProductLibrary { + using FixedPoint for FixedPoint.Unsigned; + + uint256 public valueToReturn; + + function setValueToReturn(uint256 value) public { + valueToReturn = value; + } + + function percentageLongCollateralAtExpiry(int256 /*expiryPrice*/) public view override returns (uint256) { + return valueToReturn; + } +} diff --git a/contracts/external/uma/core/contracts/financial-templates/test/LongShortPairMock.sol b/contracts/external/uma/core/contracts/financial-templates/test/LongShortPairMock.sol new file mode 100644 index 000000000..4bd5015d2 --- /dev/null +++ b/contracts/external/uma/core/contracts/financial-templates/test/LongShortPairMock.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +contract LongShortPairMock { + uint256 public expirationTimestamp; + uint256 public collateralPerPair; + + constructor(uint256 _expirationTimestamp, uint256 _collateralPerPair) { + expirationTimestamp = _expirationTimestamp; + collateralPerPair = _collateralPerPair; + } +} diff --git a/contracts/external/uma/core/contracts/merkle-distributor/implementation/MerkleDistributor.sol b/contracts/external/uma/core/contracts/merkle-distributor/implementation/MerkleDistributor.sol new file mode 100644 index 000000000..5e7528281 --- /dev/null +++ b/contracts/external/uma/core/contracts/merkle-distributor/implementation/MerkleDistributor.sol @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/utils/cryptography/MerkleProof.sol"; +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "./MerkleDistributorInterface.sol"; + +/** + * Inspired by: + * - https://github.com/pie-dao/vested-token-migration-app + * - https://github.com/Uniswap/merkle-distributor + * - https://github.com/balancer-labs/erc20-redeemable + * + * @title MerkleDistributor contract. + * @notice Allows an owner to distribute any reward ERC20 to claimants according to Merkle roots. The owner can specify + * multiple Merkle roots distributions with customized reward currencies. + * @dev The Merkle trees are not validated in any way, so the system assumes the contract owner behaves honestly. + */ +contract MerkleDistributor is MerkleDistributorInterface, Ownable { + using SafeERC20 for IERC20; + + // Windows are mapped to arbitrary indices. + mapping(uint256 => Window) public merkleWindows; + + // Index of next created Merkle root. + uint256 public nextCreatedIndex; + + // Track which accounts have claimed for each window index. + // Note: uses a packed array of bools for gas optimization on tracking certain claims. Copied from Uniswap's contract. + mapping(uint256 => mapping(uint256 => uint256)) private claimedBitMap; + + /**************************************** + * EVENTS + ****************************************/ + event Claimed( + address indexed caller, + uint256 windowIndex, + address indexed account, + uint256 accountIndex, + uint256 amount, + address indexed rewardToken + ); + event CreatedWindow( + uint256 indexed windowIndex, + uint256 rewardsDeposited, + address indexed rewardToken, + address owner + ); + event WithdrawRewards(address indexed owner, uint256 amount, address indexed currency); + event DeleteWindow(uint256 indexed windowIndex, address owner); + + /**************************** + * ADMIN FUNCTIONS + ****************************/ + + /** + * @notice Set merkle root for the next available window index and seed allocations. + * @notice Callable only by owner of this contract. Caller must have approved this contract to transfer + * `rewardsToDeposit` amount of `rewardToken` or this call will fail. Importantly, we assume that the + * owner of this contract correctly chooses an amount `rewardsToDeposit` that is sufficient to cover all + * claims within the `merkleRoot`. + * @param rewardsToDeposit amount of rewards to deposit to seed this allocation. + * @param rewardToken ERC20 reward token. + * @param merkleRoot merkle root describing allocation. + * @param ipfsHash hash of IPFS object, conveniently stored for clients + */ + function setWindow( + uint256 rewardsToDeposit, + address rewardToken, + bytes32 merkleRoot, + string calldata ipfsHash + ) external onlyOwner { + uint256 indexToSet = nextCreatedIndex; + nextCreatedIndex = indexToSet + 1; + + _setWindow(indexToSet, rewardsToDeposit, rewardToken, merkleRoot, ipfsHash); + } + + /** + * @notice Delete merkle root at window index. + * @dev Callable only by owner. Likely to be followed by a withdrawRewards call to clear contract state. + * @param windowIndex merkle root index to delete. + */ + function deleteWindow(uint256 windowIndex) external onlyOwner { + delete merkleWindows[windowIndex]; + emit DeleteWindow(windowIndex, msg.sender); + } + + /** + * @notice Emergency method that transfers rewards out of the contract if the contract was configured improperly. + * @dev Callable only by owner. + * @param rewardCurrency rewards to withdraw from contract. + * @param amount amount of rewards to withdraw. + */ + function withdrawRewards(IERC20 rewardCurrency, uint256 amount) external onlyOwner { + rewardCurrency.safeTransfer(msg.sender, amount); + emit WithdrawRewards(msg.sender, amount, address(rewardCurrency)); + } + + /**************************** + * NON-ADMIN FUNCTIONS + ****************************/ + + /** + * @notice Batch claims to reduce gas versus individual submitting all claims. Method will fail + * if any individual claims within the batch would fail. + * @dev Optimistically tries to batch together consecutive claims for the same account and same + * reward token to reduce gas. Therefore, the most gas-cost-optimal way to use this method + * is to pass in an array of claims sorted by account and reward currency. It also reverts + * when any of individual `_claim`'s `amount` exceeds `remainingAmount` for its window. + * @param claims array of claims to claim. + */ + function claimMulti(Claim[] memory claims) public virtual override { + uint256 batchedAmount; + uint256 claimCount = claims.length; + for (uint256 i = 0; i < claimCount; i++) { + Claim memory _claim = claims[i]; + _verifyAndMarkClaimed(_claim); + batchedAmount += _claim.amount; + + // If the next claim is NOT the same account or the same token (or this claim is the last one), + // then disburse the `batchedAmount` to the current claim's account for the current claim's reward token. + uint256 nextI = i + 1; + IERC20 currentRewardToken = merkleWindows[_claim.windowIndex].rewardToken; + if ( + nextI == claimCount || + // This claim is last claim. + claims[nextI].account != _claim.account || + // Next claim account is different than current one. + merkleWindows[claims[nextI].windowIndex].rewardToken != currentRewardToken + // Next claim reward token is different than current one. + ) { + currentRewardToken.safeTransfer(_claim.account, batchedAmount); + batchedAmount = 0; + } + } + } + + /** + * @notice Claim amount of reward tokens for account, as described by Claim input object. + * @dev If the `_claim`'s `amount`, `accountIndex`, and `account` do not exactly match the + * values stored in the merkle root for the `_claim`'s `windowIndex` this method + * will revert. It also reverts when `_claim`'s `amount` exceeds `remainingAmount` for the window. + * @param _claim claim object describing amount, accountIndex, account, window index, and merkle proof. + */ + function claim(Claim memory _claim) public virtual override { + _verifyAndMarkClaimed(_claim); + merkleWindows[_claim.windowIndex].rewardToken.safeTransfer(_claim.account, _claim.amount); + } + + /** + * @notice Returns True if the claim for `accountIndex` has already been completed for the Merkle root at + * `windowIndex`. + * @dev This method will only work as intended if all `accountIndex`'s are unique for a given `windowIndex`. + * The onus is on the Owner of this contract to submit only valid Merkle roots. + * @param windowIndex merkle root to check. + * @param accountIndex account index to check within window index. + * @return True if claim has been executed already, False otherwise. + */ + function isClaimed(uint256 windowIndex, uint256 accountIndex) public view returns (bool) { + uint256 claimedWordIndex = accountIndex / 256; + uint256 claimedBitIndex = accountIndex % 256; + uint256 claimedWord = claimedBitMap[windowIndex][claimedWordIndex]; + uint256 mask = (1 << claimedBitIndex); + return claimedWord & mask == mask; + } + + /** + * @notice Returns rewardToken set by admin for windowIndex. + * @param windowIndex merkle root to check. + * @return address Reward token address + */ + function getRewardTokenForWindow(uint256 windowIndex) public view override returns (address) { + return address(merkleWindows[windowIndex].rewardToken); + } + + /** + * @notice Returns True if leaf described by {account, amount, accountIndex} is stored in Merkle root at given + * window index. + * @param _claim claim object describing amount, accountIndex, account, window index, and merkle proof. + * @return valid True if leaf exists. + */ + function verifyClaim(Claim memory _claim) public view returns (bool valid) { + bytes32 leaf = keccak256(abi.encodePacked(_claim.account, _claim.amount, _claim.accountIndex)); + return MerkleProof.verify(_claim.merkleProof, merkleWindows[_claim.windowIndex].merkleRoot, leaf); + } + + /**************************** + * PRIVATE FUNCTIONS + ****************************/ + + // Mark claim as completed for `accountIndex` for Merkle root at `windowIndex`. + function _setClaimed(uint256 windowIndex, uint256 accountIndex) private { + uint256 claimedWordIndex = accountIndex / 256; + uint256 claimedBitIndex = accountIndex % 256; + claimedBitMap[windowIndex][claimedWordIndex] = + claimedBitMap[windowIndex][claimedWordIndex] | + (1 << claimedBitIndex); + } + + // Store new Merkle root at `windowindex`. Pull `rewardsDeposited` from caller to seed distribution for this root. + function _setWindow( + uint256 windowIndex, + uint256 rewardsDeposited, + address rewardToken, + bytes32 merkleRoot, + string memory ipfsHash + ) private { + Window storage window = merkleWindows[windowIndex]; + window.merkleRoot = merkleRoot; + window.remainingAmount = rewardsDeposited; + window.rewardToken = IERC20(rewardToken); + window.ipfsHash = ipfsHash; + + emit CreatedWindow(windowIndex, rewardsDeposited, rewardToken, msg.sender); + + window.rewardToken.safeTransferFrom(msg.sender, address(this), rewardsDeposited); + } + + // Verify claim is valid and mark it as completed in this contract. + function _verifyAndMarkClaimed(Claim memory _claim) internal { + // Check claimed proof against merkle window at given index. + require(verifyClaim(_claim), "Incorrect merkle proof"); + // Check the account has not yet claimed for this window. + require(!isClaimed(_claim.windowIndex, _claim.accountIndex), "Account has already claimed for this window"); + + // Proof is correct and claim has not occurred yet, mark claimed complete. + _setClaimed(_claim.windowIndex, _claim.accountIndex); + merkleWindows[_claim.windowIndex].remainingAmount -= _claim.amount; + emit Claimed( + msg.sender, + _claim.windowIndex, + _claim.account, + _claim.accountIndex, + _claim.amount, + address(merkleWindows[_claim.windowIndex].rewardToken) + ); + } +} diff --git a/contracts/external/uma/core/contracts/merkle-distributor/implementation/MerkleDistributorInterface.sol b/contracts/external/uma/core/contracts/merkle-distributor/implementation/MerkleDistributorInterface.sol new file mode 100644 index 000000000..022c40cf9 --- /dev/null +++ b/contracts/external/uma/core/contracts/merkle-distributor/implementation/MerkleDistributorInterface.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; + +/** + * @notice Concise list of functions in MerkleDistributor implementation that would be called by + * a consuming external contract (such as the Across Protocol's AcceleratingDistributor). + */ +interface MerkleDistributorInterface { + // A Window maps a Merkle root to a reward token address. + struct Window { + // Merkle root describing the distribution. + bytes32 merkleRoot; + // Remaining amount of deposited rewards that have not yet been claimed. + uint256 remainingAmount; + // Currency in which reward is processed. + IERC20 rewardToken; + // IPFS hash of the merkle tree. Can be used to independently fetch recipient proofs and tree. Note that the canonical + // data type for storing an IPFS hash is a multihash which is the concatenation of + // . We opted to store this in a string type to make it easier + // for users to query the ipfs data without needing to reconstruct the multihash. to view the IPFS data simply + // go to https://cloudflare-ipfs.com/ipfs/. + string ipfsHash; + } + + // Represents an account's claim for `amount` within the Merkle root located at the `windowIndex`. + struct Claim { + uint256 windowIndex; + uint256 amount; + uint256 accountIndex; // Used only for bitmap. Assumed to be unique for each claim. + address account; + bytes32[] merkleProof; + } + + function claim(Claim memory _claim) external; + + function claimMulti(Claim[] memory claims) external; + + function getRewardTokenForWindow(uint256 windowIndex) external view returns (address); +} diff --git a/contracts/external/uma/core/contracts/optimistic-governor/test/TestAvatar.sol b/contracts/external/uma/core/contracts/optimistic-governor/test/TestAvatar.sol new file mode 100644 index 000000000..5174ea2a6 --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-governor/test/TestAvatar.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.0; + +contract TestAvatar { + address public module; + + receive() external payable {} + + function setModule(address _module) external { + module = _module; + } + + function exec(address payable to, uint256 value, bytes calldata data) external { + bool success; + bytes memory response; + (success, response) = to.call{ value: value }(data); + if (!success) { + assembly { + revert(add(response, 0x20), mload(response)) + } + } + } + + function execTransactionFromModule( + address payable to, + uint256 value, + bytes calldata data, + uint8 operation + ) external returns (bool success) { + require(msg.sender == module, "Not authorized"); + if (operation == 1) (success, ) = to.delegatecall(data); + else (success, ) = to.call{ value: value }(data); + } +} diff --git a/contracts/external/uma/core/contracts/optimistic-governor/test/TestModuleProxyFactory.sol b/contracts/external/uma/core/contracts/optimistic-governor/test/TestModuleProxyFactory.sol new file mode 100644 index 000000000..8fc457231 --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-governor/test/TestModuleProxyFactory.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.6; + +import "@gnosis.pm/zodiac/contracts/factory/ModuleProxyFactory.sol"; + +contract TestModuleProxyFactory is ModuleProxyFactory {} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v2/implementation/OptimisticOracleV2.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v2/implementation/OptimisticOracleV2.sol new file mode 100644 index 000000000..0bc59c746 --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v2/implementation/OptimisticOracleV2.sol @@ -0,0 +1,711 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; +import "@openzeppelin/contracts-v4/utils/Address.sol"; + +import "../../data-verification-mechanism/interfaces/StoreInterface.sol"; +import "../../data-verification-mechanism/interfaces/OracleAncillaryInterface.sol"; +import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; +import "../../data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol"; +import "../../data-verification-mechanism/implementation/Constants.sol"; + +import "../interfaces/OptimisticOracleV2Interface.sol"; + +import "../../common/implementation/Testable.sol"; +import "../../common/implementation/Lockable.sol"; +import "../../common/implementation/FixedPoint.sol"; +import "../../common/implementation/AncillaryData.sol"; +import "../../common/implementation/AddressWhitelist.sol"; + +/** + * @title Optimistic Requester. + * @notice Optional interface that requesters can implement to receive callbacks. + * @dev this contract does _not_ work with ERC777 collateral currencies or any others that call into the receiver on + * transfer(). Using an ERC777 token would allow a user to maliciously grief other participants (while also losing + * money themselves). + */ +interface OptimisticRequester { + /** + * @notice Callback for proposals. + * @param identifier price identifier being requested. + * @param timestamp timestamp of the price being requested. + * @param ancillaryData ancillary data of the price being requested. + */ + function priceProposed(bytes32 identifier, uint256 timestamp, bytes memory ancillaryData) external; + + /** + * @notice Callback for disputes. + * @param identifier price identifier being requested. + * @param timestamp timestamp of the price being requested. + * @param ancillaryData ancillary data of the price being requested. + * @param refund refund received in the case that refundOnDispute was enabled. + */ + function priceDisputed(bytes32 identifier, uint256 timestamp, bytes memory ancillaryData, uint256 refund) external; + + /** + * @notice Callback for settlement. + * @param identifier price identifier being requested. + * @param timestamp timestamp of the price being requested. + * @param ancillaryData ancillary data of the price being requested. + * @param price price that was resolved by the escalation process. + */ + function priceSettled(bytes32 identifier, uint256 timestamp, bytes memory ancillaryData, int256 price) external; +} + +/** + * @title Optimistic Oracle. + * @notice Pre-DVM escalation contract that allows faster settlement. + */ +contract OptimisticOracleV2 is OptimisticOracleV2Interface, Testable, Lockable { + using SafeMath for uint256; + using SafeERC20 for IERC20; + using Address for address; + + // Finder to provide addresses for DVM contracts. + FinderInterface public override finder; + + // Default liveness value for all price requests. + uint256 public override defaultLiveness; + + // This is effectively the extra ancillary data to add ",ooRequester:0000000000000000000000000000000000000000". + uint256 private constant MAX_ADDED_ANCILLARY_DATA = 53; + uint256 public constant OO_ANCILLARY_DATA_LIMIT = ancillaryBytesLimit - MAX_ADDED_ANCILLARY_DATA; + int256 public constant TOO_EARLY_RESPONSE = type(int256).min; + + /** + * @notice Constructor. + * @param _liveness default liveness applied to each price request. + * @param _finderAddress finder to use to get addresses of DVM contracts. + * @param _timerAddress address of the timer contract. Should be 0x0 in prod. + */ + constructor(uint256 _liveness, address _finderAddress, address _timerAddress) Testable(_timerAddress) { + finder = FinderInterface(_finderAddress); + _validateLiveness(_liveness); + defaultLiveness = _liveness; + } + + /** + * @notice Requests a new price. + * @param identifier price identifier being requested. + * @param timestamp timestamp of the price being requested. + * @param ancillaryData ancillary data representing additional args being passed with the price request. + * @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM. + * @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0, + * which could make sense if the contract requests and proposes the value in the same call or + * provides its own reward system. + * @return totalBond default bond (final fee) + final fee that the proposer and disputer will be required to pay. + * This can be changed with a subsequent call to setBond(). + */ + function requestPrice( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + IERC20 currency, + uint256 reward + ) external override nonReentrant returns (uint256 totalBond) { + require(_getState(msg.sender, identifier, timestamp, ancillaryData) == State.Invalid, "requestPrice: Invalid"); + require(_getIdentifierWhitelist().isIdentifierSupported(identifier), "Unsupported identifier"); + require(_getCollateralWhitelist().isOnWhitelist(address(currency)), "Unsupported currency"); + require(timestamp <= getCurrentTime(), "Timestamp in future"); + + // This ensures that the ancillary data is <= the OO limit, which is lower than the DVM limit because the + // OO adds some data before sending to the DVM. + require(ancillaryData.length <= OO_ANCILLARY_DATA_LIMIT, "Ancillary Data too long"); + + uint256 finalFee = _getStore().computeFinalFee(address(currency)).rawValue; + requests[_getId(msg.sender, identifier, timestamp, ancillaryData)] = Request({ + proposer: address(0), + disputer: address(0), + currency: currency, + settled: false, + requestSettings: RequestSettings({ + eventBased: false, + refundOnDispute: false, + callbackOnPriceProposed: false, + callbackOnPriceDisputed: false, + callbackOnPriceSettled: false, + bond: finalFee, + customLiveness: 0 + }), + proposedPrice: 0, + resolvedPrice: 0, + expirationTime: 0, + reward: reward, + finalFee: finalFee + }); + + if (reward > 0) { + currency.safeTransferFrom(msg.sender, address(this), reward); + } + + emit RequestPrice(msg.sender, identifier, timestamp, ancillaryData, address(currency), reward, finalFee); + + // This function returns the initial proposal bond for this request, which can be customized by calling + // setBond() with the same identifier and timestamp. + return finalFee.mul(2); + } + + /** + * @notice Set the proposal bond associated with a price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param bond custom bond amount to set. + * @return totalBond new bond + final fee that the proposer and disputer will be required to pay. This can be + * changed again with a subsequent call to setBond(). + */ + function setBond( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + uint256 bond + ) external override nonReentrant returns (uint256 totalBond) { + require(_getState(msg.sender, identifier, timestamp, ancillaryData) == State.Requested, "setBond: Requested"); + Request storage request = _getRequest(msg.sender, identifier, timestamp, ancillaryData); + request.requestSettings.bond = bond; + + // Total bond is the final fee + the newly set bond. + return bond.add(request.finalFee); + } + + /** + * @notice Sets the request to refund the reward if the proposal is disputed. This can help to "hedge" the caller + * in the event of a dispute-caused delay. Note: in the event of a dispute, the winner still receives the other's + * bond, so there is still profit to be made even if the reward is refunded. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + */ + function setRefundOnDispute( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) external override nonReentrant { + require( + _getState(msg.sender, identifier, timestamp, ancillaryData) == State.Requested, + "setRefundOnDispute: Requested" + ); + _getRequest(msg.sender, identifier, timestamp, ancillaryData).requestSettings.refundOnDispute = true; + } + + /** + * @notice Sets a custom liveness value for the request. Liveness is the amount of time a proposal must wait before + * being auto-resolved. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param customLiveness new custom liveness. + */ + function setCustomLiveness( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + uint256 customLiveness + ) external override nonReentrant { + require( + _getState(msg.sender, identifier, timestamp, ancillaryData) == State.Requested, + "setCustomLiveness: Requested" + ); + _validateLiveness(customLiveness); + _getRequest(msg.sender, identifier, timestamp, ancillaryData).requestSettings.customLiveness = customLiveness; + } + + /** + * @notice Sets the request to be an "event-based" request. + * @dev Calling this method has a few impacts on the request: + * + * 1. The timestamp at which the request is evaluated is the time of the proposal, not the timestamp associated + * with the request. + * + * 2. The proposer cannot propose the "too early" value (TOO_EARLY_RESPONSE). This is to ensure that a proposer who + * prematurely proposes a response loses their bond. + * + * 3. RefundoOnDispute is automatically set, meaning disputes trigger the reward to be automatically refunded to + * the requesting contract. + * + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + */ + function setEventBased( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) external override nonReentrant { + require( + _getState(msg.sender, identifier, timestamp, ancillaryData) == State.Requested, + "setEventBased: Requested" + ); + Request storage request = _getRequest(msg.sender, identifier, timestamp, ancillaryData); + request.requestSettings.eventBased = true; + request.requestSettings.refundOnDispute = true; + } + + /** + * @notice Sets which callbacks should be enabled for the request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param callbackOnPriceProposed whether to enable the callback onPriceProposed. + * @param callbackOnPriceDisputed whether to enable the callback onPriceDisputed. + * @param callbackOnPriceSettled whether to enable the callback onPriceSettled. + */ + function setCallbacks( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + bool callbackOnPriceProposed, + bool callbackOnPriceDisputed, + bool callbackOnPriceSettled + ) external override nonReentrant { + require( + _getState(msg.sender, identifier, timestamp, ancillaryData) == State.Requested, + "setCallbacks: Requested" + ); + Request storage request = _getRequest(msg.sender, identifier, timestamp, ancillaryData); + request.requestSettings.callbackOnPriceProposed = callbackOnPriceProposed; + request.requestSettings.callbackOnPriceDisputed = callbackOnPriceDisputed; + request.requestSettings.callbackOnPriceSettled = callbackOnPriceSettled; + } + + /** + * @notice Proposes a price value on another address' behalf. Note: this address will receive any rewards that come + * from this proposal. However, any bonds are pulled from the caller. + * @param proposer address to set as the proposer. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param proposedPrice price being proposed. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the proposer once settled if the proposal is correct. + */ + function proposePriceFor( + address proposer, + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + int256 proposedPrice + ) public override nonReentrant returns (uint256 totalBond) { + require(proposer != address(0), "proposer address must be non 0"); + require( + _getState(requester, identifier, timestamp, ancillaryData) == State.Requested, + "proposePriceFor: Requested" + ); + Request storage request = _getRequest(requester, identifier, timestamp, ancillaryData); + if (request.requestSettings.eventBased) + require(proposedPrice != TOO_EARLY_RESPONSE, "Cannot propose 'too early'"); + request.proposer = proposer; + request.proposedPrice = proposedPrice; + + // If a custom liveness has been set, use it instead of the default. + request.expirationTime = getCurrentTime().add( + request.requestSettings.customLiveness != 0 ? request.requestSettings.customLiveness : defaultLiveness + ); + + totalBond = request.requestSettings.bond.add(request.finalFee); + if (totalBond > 0) request.currency.safeTransferFrom(msg.sender, address(this), totalBond); + + emit ProposePrice( + requester, + proposer, + identifier, + timestamp, + ancillaryData, + proposedPrice, + request.expirationTime, + address(request.currency) + ); + + // End the re-entrancy guard early to allow the caller to potentially take OO-related actions inside this callback. + _startReentrantGuardDisabled(); + // Callback. + if (address(requester).isContract() && request.requestSettings.callbackOnPriceProposed) + OptimisticRequester(requester).priceProposed(identifier, timestamp, ancillaryData); + _endReentrantGuardDisabled(); + } + + /** + * @notice Proposes a price value for an existing price request. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param proposedPrice price being proposed. + * @return totalBond the amount that's pulled from the proposer's wallet as a bond. The bond will be returned to + * the proposer once settled if the proposal is correct. + */ + function proposePrice( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + int256 proposedPrice + ) external override returns (uint256 totalBond) { + // Note: re-entrancy guard is done in the inner call. + return proposePriceFor(msg.sender, requester, identifier, timestamp, ancillaryData, proposedPrice); + } + + /** + * @notice Disputes a price request with an active proposal on another address' behalf. Note: this address will + * receive any rewards that come from this dispute. However, any bonds are pulled from the caller. + * @param disputer address to set as the disputer. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the disputer once settled if the dispute was valid (the proposal was incorrect). + */ + function disputePriceFor( + address disputer, + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) public override nonReentrant returns (uint256 totalBond) { + require(disputer != address(0), "disputer address must be non 0"); + require( + _getState(requester, identifier, timestamp, ancillaryData) == State.Proposed, + "disputePriceFor: Proposed" + ); + Request storage request = _getRequest(requester, identifier, timestamp, ancillaryData); + request.disputer = disputer; + + uint256 finalFee = request.finalFee; + uint256 bond = request.requestSettings.bond; + totalBond = bond.add(finalFee); + if (totalBond > 0) { + request.currency.safeTransferFrom(msg.sender, address(this), totalBond); + } + + StoreInterface store = _getStore(); + + // Along with the final fee, "burn" part of the loser's bond to ensure that a larger bond always makes it + // proportionally more expensive to delay the resolution even if the proposer and disputer are the same + // party. + + // The total fee is the burned bond and the final fee added together. + uint256 totalFee = finalFee.add(_computeBurnedBond(request)); + if (totalFee > 0) { + request.currency.safeIncreaseAllowance(address(store), totalFee); + _getStore().payOracleFeesErc20(address(request.currency), FixedPoint.Unsigned(totalFee)); + } + + _getOracle().requestPrice( + identifier, + _getTimestampForDvmRequest(request, timestamp), + _stampAncillaryData(ancillaryData, requester) + ); + + // Compute refund. + uint256 refund = 0; + if (request.reward > 0 && request.requestSettings.refundOnDispute) { + refund = request.reward; + request.reward = 0; + request.currency.safeTransfer(requester, refund); + } + + emit DisputePrice( + requester, + request.proposer, + disputer, + identifier, + timestamp, + ancillaryData, + request.proposedPrice + ); + + // End the re-entrancy guard early to allow the caller to potentially re-request inside this callback. + _startReentrantGuardDisabled(); + // Callback. + if (address(requester).isContract() && request.requestSettings.callbackOnPriceDisputed) + OptimisticRequester(requester).priceDisputed(identifier, timestamp, ancillaryData, refund); + _endReentrantGuardDisabled(); + } + + /** + * @notice Disputes a price value for an existing price request with an active proposal. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return totalBond the amount that's pulled from the disputer's wallet as a bond. The bond will be returned to + * the disputer once settled if the dispute was valid (the proposal was incorrect). + */ + function disputePrice( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) external override returns (uint256 totalBond) { + // Note: re-entrancy guard is done in the inner call. + return disputePriceFor(msg.sender, requester, identifier, timestamp, ancillaryData); + } + + /** + * @notice Retrieves a price that was previously requested by a caller. Reverts if the request is not settled + * or settleable. Note: this method is not view so that this call may actually settle the price request if it + * hasn't been settled. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return resolved price. + */ + function settleAndGetPrice( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) external override nonReentrant returns (int256) { + if (_getState(msg.sender, identifier, timestamp, ancillaryData) != State.Settled) { + _settle(msg.sender, identifier, timestamp, ancillaryData); + } + + return _getRequest(msg.sender, identifier, timestamp, ancillaryData).resolvedPrice; + } + + /** + * @notice Attempts to settle an outstanding price request. Will revert if it isn't settleable. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return payout the amount that the "winner" (proposer or disputer) receives on settlement. This amount includes + * the returned bonds as well as additional rewards. + */ + function settle( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) external override nonReentrant returns (uint256 payout) { + return _settle(requester, identifier, timestamp, ancillaryData); + } + + /** + * @notice Gets the current data structure containing all information about a price request. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return the Request data structure. + */ + function getRequest( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) public view override nonReentrantView returns (Request memory) { + return _getRequest(requester, identifier, timestamp, ancillaryData); + } + + /** + * @notice Computes the current state of a price request. See the State enum for more details. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return the State. + */ + function getState( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) public view override nonReentrantView returns (State) { + return _getState(requester, identifier, timestamp, ancillaryData); + } + + /** + * @notice Checks if a given request has resolved, expired or been settled (i.e the optimistic oracle has a price). + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return boolean indicating true if price exists and false if not. + */ + function hasPrice( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) public view override nonReentrantView returns (bool) { + State state = _getState(requester, identifier, timestamp, ancillaryData); + return state == State.Settled || state == State.Resolved || state == State.Expired; + } + + /** + * @notice Generates stamped ancillary data in the format that it would be used in the case of a price dispute. + * @param ancillaryData ancillary data of the price being requested. + * @param requester sender of the initial price request. + * @return the stamped ancillary bytes. + */ + function stampAncillaryData( + bytes memory ancillaryData, + address requester + ) public pure override returns (bytes memory) { + return _stampAncillaryData(ancillaryData, requester); + } + + function _getId( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) private pure returns (bytes32) { + return keccak256(abi.encodePacked(requester, identifier, timestamp, ancillaryData)); + } + + function _settle( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) private returns (uint256 payout) { + State state = _getState(requester, identifier, timestamp, ancillaryData); + + // Set it to settled so this function can never be entered again. + Request storage request = _getRequest(requester, identifier, timestamp, ancillaryData); + request.settled = true; + + if (state == State.Expired) { + // In the expiry case, just pay back the proposer's bond and final fee along with the reward. + request.resolvedPrice = request.proposedPrice; + payout = request.requestSettings.bond.add(request.finalFee).add(request.reward); + request.currency.safeTransfer(request.proposer, payout); + } else if (state == State.Resolved) { + // In the Resolved case, pay either the disputer or the proposer the entire payout (+ bond and reward). + request.resolvedPrice = _getOracle().getPrice( + identifier, + _getTimestampForDvmRequest(request, timestamp), + _stampAncillaryData(ancillaryData, requester) + ); + bool disputeSuccess = request.resolvedPrice != request.proposedPrice; + uint256 bond = request.requestSettings.bond; + + // Unburned portion of the loser's bond = 1 - burned bond. + uint256 unburnedBond = bond.sub(_computeBurnedBond(request)); + + // Winner gets: + // - Their bond back. + // - The unburned portion of the loser's bond. + // - Their final fee back. + // - The request reward (if not already refunded -- if refunded, it will be set to 0). + payout = bond.add(unburnedBond).add(request.finalFee).add(request.reward); + request.currency.safeTransfer(disputeSuccess ? request.disputer : request.proposer, payout); + } else revert("_settle: not settleable"); + + emit Settle( + requester, + request.proposer, + request.disputer, + identifier, + timestamp, + ancillaryData, + request.resolvedPrice, + payout + ); + + // Temporarily disable the re-entrancy guard early to allow the caller to take an OO-related action inside this callback. + _startReentrantGuardDisabled(); + // Callback. + if (address(requester).isContract() && request.requestSettings.callbackOnPriceSettled) + OptimisticRequester(requester).priceSettled(identifier, timestamp, ancillaryData, request.resolvedPrice); + _endReentrantGuardDisabled(); + } + + function _getRequest( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) private view returns (Request storage) { + return requests[_getId(requester, identifier, timestamp, ancillaryData)]; + } + + function _computeBurnedBond(Request storage request) private view returns (uint256) { + // burnedBond = floor(bond / 2) + return request.requestSettings.bond.div(2); + } + + function _validateLiveness(uint256 _liveness) private pure { + require(_liveness < 5200 weeks, "Liveness too large"); + require(_liveness > 0, "Liveness cannot be 0"); + } + + function _getState( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) internal view returns (State) { + Request storage request = _getRequest(requester, identifier, timestamp, ancillaryData); + + if (address(request.currency) == address(0)) return State.Invalid; + + if (request.proposer == address(0)) return State.Requested; + + if (request.settled) return State.Settled; + + if (request.disputer == address(0)) + return request.expirationTime <= getCurrentTime() ? State.Expired : State.Proposed; + + return + _getOracle().hasPrice( + identifier, + _getTimestampForDvmRequest(request, timestamp), + _stampAncillaryData(ancillaryData, requester) + ) + ? State.Resolved + : State.Disputed; + } + + function _getOracle() internal view returns (OracleAncillaryInterface) { + return OracleAncillaryInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); + } + + function _getCollateralWhitelist() internal view returns (AddressWhitelist) { + return AddressWhitelist(finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist)); + } + + function _getStore() internal view returns (StoreInterface) { + return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store)); + } + + function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) { + return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); + } + + function _getTimestampForDvmRequest( + Request storage request, + uint256 requestTimestamp + ) internal view returns (uint256) { + if (request.requestSettings.eventBased) { + uint256 liveness = request.requestSettings.customLiveness != 0 + ? request.requestSettings.customLiveness + : defaultLiveness; + return request.expirationTime.sub(liveness); + } else { + return requestTimestamp; + } + } + + /** + * @dev We don't handle specifically the case where `ancillaryData` is not already readily translateable in utf8. + * For those cases, we assume that the client will be able to strip out the utf8-translateable part of the + * ancillary data that this contract stamps. + */ + function _stampAncillaryData(bytes memory ancillaryData, address requester) internal pure returns (bytes memory) { + // Since this contract will be the one to formally submit DVM price requests, its useful for voters to know who + // the original requester was. + return AncillaryData.appendKeyValueAddress(ancillaryData, "ooRequester", requester); + } + + function getCurrentTime() public view override(Testable, OptimisticOracleV2Interface) returns (uint256) { + return Testable.getCurrentTime(); + } +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v2/implementation/SkinnyOptimisticOracleV2.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v2/implementation/SkinnyOptimisticOracleV2.sol new file mode 100644 index 000000000..e2eb701ad --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v2/implementation/SkinnyOptimisticOracleV2.sol @@ -0,0 +1,686 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; +import "@openzeppelin/contracts-v4/utils/Address.sol"; + +import "../../data-verification-mechanism/interfaces/StoreInterface.sol"; +import "../../data-verification-mechanism/interfaces/OracleAncillaryInterface.sol"; +import "../interfaces/OptimisticOracleInterface.sol"; +import "../interfaces/SkinnyOptimisticOracleV2Interface.sol"; +import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; +import "../../data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol"; +import "../../data-verification-mechanism/implementation/Constants.sol"; + +import "../../common/implementation/Testable.sol"; +import "../../common/implementation/Lockable.sol"; +import "../../common/implementation/FixedPoint.sol"; +import "../../common/implementation/AncillaryData.sol"; +import "../../common/implementation/AddressWhitelist.sol"; + +/** + * @title Optimistic Requester. + * @notice Optional interface that requesters can implement to receive callbacks. + * @dev This contract does _not_ work with ERC777 collateral currencies or any others that call into the receiver on + * transfer(). Using an ERC777 token would allow a user to maliciously grief other participants (while also losing + * money themselves). + */ +interface OptimisticRequesterV2 { + /** + * @notice Callback for proposals. + * @param identifier price identifier being requested. + * @param timestamp timestamp of the price being requested. + * @param ancillaryData ancillary data of the price being requested. + * @param request request params after proposal. + */ + function priceProposed( + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + SkinnyOptimisticOracleV2Interface.Request memory request + ) external; + + /** + * @notice Callback for disputes. + * @param identifier price identifier being requested. + * @param timestamp timestamp of the price being requested. + * @param ancillaryData ancillary data of the price being requested. + * @param request request params after dispute. + */ + function priceDisputed( + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + SkinnyOptimisticOracleV2Interface.Request memory request + ) external; + + /** + * @notice Callback for settlement. + * @param identifier price identifier being requested. + * @param timestamp timestamp of the price being requested. + * @param ancillaryData ancillary data of the price being requested. + * @param request request params after settlement. + */ + function priceSettled( + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + SkinnyOptimisticOracleV2Interface.Request memory request + ) external; +} + +/** + * @title Optimistic Oracle with a different interface and fewer features that emphasizes gas cost reductions. + * @notice Pre-DVM escalation contract that allows faster settlement. + */ +contract SkinnyOptimisticOracleV2 is SkinnyOptimisticOracleV2Interface, Testable, Lockable { + using SafeMath for uint256; + using SafeERC20 for IERC20; + using Address for address; + + event RequestPrice( + address indexed requester, + bytes32 indexed identifier, + uint32 timestamp, + bytes ancillaryData, + Request request + ); + event ProposePrice( + address indexed requester, + bytes32 indexed identifier, + uint32 timestamp, + bytes ancillaryData, + Request request + ); + event DisputePrice( + address indexed requester, + bytes32 indexed identifier, + uint32 timestamp, + bytes ancillaryData, + Request request + ); + event Settle( + address indexed requester, + bytes32 indexed identifier, + uint32 timestamp, + bytes ancillaryData, + Request request + ); + + // Maps hash of unique request params {identifier, timestamp, ancillary data} to customizable variables such as + // reward and bond amounts. + mapping(bytes32 => bytes32) public requests; + + // Finder to provide addresses for DVM contracts. + FinderInterface public finder; + + // Default liveness value for all price requests. + uint256 public defaultLiveness; + + /** + * @notice Constructor. + * @param _liveness default liveness applied to each price request. + * @param _finderAddress finder to use to get addresses of DVM contracts. + * @param _timerAddress address of the timer contract. Should be 0x0 in prod. + */ + constructor(uint256 _liveness, address _finderAddress, address _timerAddress) Testable(_timerAddress) { + finder = FinderInterface(_finderAddress); + _validateLiveness(_liveness); + defaultLiveness = _liveness; + } + + /** + * @notice Requests a new price. + * @param identifier price identifier being requested. + * @param timestamp timestamp of the price being requested. + * @param ancillaryData ancillary data representing additional args being passed with the price request. + * @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM. + * @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0, + * which could make sense if the contract requests and proposes the value in the same call or + * provides its own reward system. + * @param requestSettings settings for the request. + * @return totalBond default bond + final fee that the proposer and disputer will be required to pay. + */ + function requestPrice( + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + IERC20 currency, + uint256 reward, + RequestSettings memory requestSettings + ) external override nonReentrant returns (uint256 totalBond) { + bytes32 requestId = _getId(msg.sender, identifier, timestamp, ancillaryData); + require(requests[requestId] == bytes32(0), "Request already initialized"); + require(_getIdentifierWhitelist().isIdentifierSupported(identifier), "Unsupported identifier"); + require(_getCollateralWhitelist().isOnWhitelist(address(currency)), "Unsupported currency"); + require(timestamp <= getCurrentTime(), "Timestamp in future"); + require( + _stampAncillaryData(ancillaryData, msg.sender).length <= ancillaryBytesLimit, + "Ancillary Data too long" + ); + uint256 finalFee = _getStore().computeFinalFee(address(currency)).rawValue; + + // Associate new request with ID + Request memory request; + request.currency = currency; + request.reward = reward; + request.finalFee = finalFee; + request.requestSettings = requestSettings; + request.requestSettings.bond = requestSettings.bond != 0 ? requestSettings.bond : finalFee; + _storeRequestHash(requestId, request); + + if (reward > 0) currency.safeTransferFrom(msg.sender, address(this), reward); + + emit RequestPrice(msg.sender, identifier, timestamp, ancillaryData, request); + + return request.requestSettings.bond.add(finalFee); + } + + /** + * @notice Proposes a price value on another address' behalf. Note: this address will receive any rewards that come + * from this proposal. However, any bonds are pulled from the caller. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters whose hash must match the request that the caller wants to + * propose a price for. + * @param proposer address to set as the proposer. + * @param proposedPrice price being proposed. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the proposer once settled if the proposal is correct. + */ + function proposePriceFor( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request, + address proposer, + int256 proposedPrice + ) public override nonReentrant returns (uint256 totalBond) { + require(proposer != address(0), "Proposer address must be non 0"); + require( + _getState(requester, identifier, timestamp, ancillaryData, request) == + OptimisticOracleInterface.State.Requested, + "Must be requested" + ); + bytes32 requestId = _getId(requester, identifier, timestamp, ancillaryData); + _validateRequestHash(requestId, request); + + // Associate newly proposed request params with ID + Request memory proposedRequest = Request({ + proposer: proposer, // Modified + disputer: request.disputer, + currency: request.currency, + settled: request.settled, + proposedPrice: proposedPrice, // Modified + resolvedPrice: request.resolvedPrice, + expirationTime: getCurrentTime().add( + request.requestSettings.customLiveness != 0 ? request.requestSettings.customLiveness : defaultLiveness + ), // Modified + reward: request.reward, + finalFee: request.finalFee, + requestSettings: request.requestSettings + }); + _storeRequestHash(requestId, proposedRequest); + + totalBond = request.requestSettings.bond.add(request.finalFee); + if (totalBond > 0) request.currency.safeTransferFrom(msg.sender, address(this), totalBond); + + emit ProposePrice(requester, identifier, timestamp, ancillaryData, proposedRequest); + + // Callback. + if (address(requester).isContract() && request.requestSettings.callbackOnPriceProposed) + OptimisticRequesterV2(requester).priceProposed(identifier, timestamp, ancillaryData, proposedRequest); + } + + /** + * @notice Proposes a price value where caller is the proposer. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters whose hash must match the request that the caller wants to + * propose a price for. + * @param proposedPrice price being proposed. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the proposer once settled if the proposal is correct. + */ + function proposePrice( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request, + int256 proposedPrice + ) external override returns (uint256 totalBond) { + // Note: re-entrancy guard is done in the inner call. + return proposePriceFor(requester, identifier, timestamp, ancillaryData, request, msg.sender, proposedPrice); + } + + /** + * @notice Combines logic of requestPrice and proposePrice while taking advantage of gas savings from not having to + * overwrite Request params that a normal requestPrice() => proposePrice() flow would entail. Note: The proposer + * will receive any rewards that come from this proposal. However, any bonds are pulled from the caller. + * @dev The caller is the requester, but the proposer can be customized. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM. + * @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0, + * which could make sense if the contract requests and proposes the value in the same call or + * provides its own reward system. + * @param requestSettings settings for the request. + * @param proposer address to set as the proposer. + * @param proposedPrice price being proposed. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the proposer once settled if the proposal is correct. + */ + function requestAndProposePriceFor( + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + IERC20 currency, + uint256 reward, + RequestSettings memory requestSettings, + address proposer, + int256 proposedPrice + ) external override nonReentrant returns (uint256 totalBond) { + bytes32 requestId = _getId(msg.sender, identifier, timestamp, ancillaryData); + require(requests[requestId] == bytes32(0), "Request already initialized"); + require(proposer != address(0), "proposer address must be non 0"); + require(_getIdentifierWhitelist().isIdentifierSupported(identifier), "Unsupported identifier"); + require(_getCollateralWhitelist().isOnWhitelist(address(currency)), "Unsupported currency"); + require(timestamp <= getCurrentTime(), "Timestamp in future"); + require( + _stampAncillaryData(ancillaryData, msg.sender).length <= ancillaryBytesLimit, + "Ancillary Data too long" + ); + uint256 finalFee = _getStore().computeFinalFee(address(currency)).rawValue; + + // Associate new request with ID + Request memory request; + request.currency = currency; + request.reward = reward; + request.finalFee = finalFee; + request.requestSettings = requestSettings; + request.requestSettings.bond = requestSettings.bond != 0 ? requestSettings.bond : finalFee; + request.proposer = proposer; + request.proposedPrice = proposedPrice; + request.expirationTime = getCurrentTime().add( + requestSettings.customLiveness != 0 ? requestSettings.customLiveness : defaultLiveness + ); + _storeRequestHash(requestId, request); + + // Pull reward from requester, who is the caller. + if (reward > 0) currency.safeTransferFrom(msg.sender, address(this), reward); + // Pull proposal bond from caller. + totalBond = request.requestSettings.bond.add(request.finalFee); + if (totalBond > 0) currency.safeTransferFrom(msg.sender, address(this), totalBond); + + emit RequestPrice(msg.sender, identifier, timestamp, ancillaryData, request); + emit ProposePrice(msg.sender, identifier, timestamp, ancillaryData, request); + + // Callback. + if (address(msg.sender).isContract() && requestSettings.callbackOnPriceProposed) + OptimisticRequesterV2(msg.sender).priceProposed(identifier, timestamp, ancillaryData, request); + } + + /** + * @notice Disputes a price request with an active proposal on another address' behalf. Note: this address will + * receive any rewards that come from this dispute. However, any bonds are pulled from the caller. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters whose hash must match the request that the caller wants to + * dispute. + * @param disputer address to set as the disputer. + * @param requester sender of the initial price request. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the disputer once settled if the dispute was valid (the proposal was incorrect). + */ + function disputePriceFor( + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request, + address disputer, + address requester + ) public override nonReentrant returns (uint256 totalBond) { + require(disputer != address(0), "disputer address must be non 0"); + require( + _getState(requester, identifier, timestamp, ancillaryData, request) == + OptimisticOracleInterface.State.Proposed, + "Must be proposed" + ); + bytes32 requestId = _getId(requester, identifier, timestamp, ancillaryData); + _validateRequestHash(requestId, request); + + // Associate newly disputed request params with ID + Request memory disputedRequest = Request({ + proposer: request.proposer, + disputer: disputer, // Modified + currency: request.currency, + settled: request.settled, + proposedPrice: request.proposedPrice, + resolvedPrice: request.resolvedPrice, + expirationTime: request.expirationTime, + reward: request.reward, + finalFee: request.finalFee, + requestSettings: request.requestSettings + }); + _storeRequestHash(requestId, disputedRequest); + + totalBond = request.requestSettings.bond.add(request.finalFee); + if (totalBond > 0) request.currency.safeTransferFrom(msg.sender, address(this), totalBond); + + StoreInterface store = _getStore(); + + // Avoids stack too deep compilation error. + { + // Along with the final fee, "burn" part of the loser's bond to ensure that a larger bond always makes it + // proportionally more expensive to delay the resolution even if the proposer and disputer are the same + // party. + uint256 burnedBond = _computeBurnedBond(disputedRequest); + + // The total fee is the burned bond and the final fee added together. + uint256 totalFee = request.finalFee.add(burnedBond); + + if (totalFee > 0) { + request.currency.safeIncreaseAllowance(address(store), totalFee); + _getStore().payOracleFeesErc20(address(request.currency), FixedPoint.Unsigned(totalFee)); + } + } + + _getOracle().requestPrice(identifier, timestamp, _stampAncillaryData(ancillaryData, requester)); + + emit DisputePrice(requester, identifier, timestamp, ancillaryData, disputedRequest); + + // Callback. + if (address(requester).isContract() && request.requestSettings.callbackOnPriceDisputed) + OptimisticRequesterV2(requester).priceDisputed(identifier, timestamp, ancillaryData, disputedRequest); + } + + /** + * @notice Disputes a price request with an active proposal where caller is the disputer. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters whose hash must match the request that the caller wants to + * dispute. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the disputer once settled if the dispute was valid (the proposal was incorrect). + */ + function disputePrice( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request + ) external override returns (uint256 totalBond) { + // Note: re-entrancy guard is done in the inner call. + return disputePriceFor(identifier, timestamp, ancillaryData, request, msg.sender, requester); + } + + /** + * @notice Attempts to settle an outstanding price request. Will revert if it isn't settleable. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters whose hash must match the request that the caller wants to + * settle. + * @return payout the amount that the "winner" (proposer or disputer) receives on settlement. This amount includes + * the returned bonds as well as additional rewards. + * @return resolvedPrice the price that the request settled to. + */ + function settle( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request + ) external override nonReentrant returns (uint256 payout, int256 resolvedPrice) { + return _settle(requester, identifier, timestamp, ancillaryData, request); + } + + /** + * @notice Computes the current state of a price request. See the State enum for more details. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters. + * @return the State. + */ + function getState( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request + ) external override nonReentrant returns (OptimisticOracleInterface.State) { + return _getState(requester, identifier, timestamp, ancillaryData, request); + } + + /** + * @notice Checks if a given request has resolved, expired or been settled (i.e the optimistic oracle has a price). + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters. The hash of these parameters must match with the request hash that is + * associated with the price request unique ID {requester, identifier, timestamp, ancillaryData}, or this method + * will revert. + * @return boolean indicating true if price exists and false if not. + */ + function hasPrice( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request + ) public override nonReentrant returns (bool) { + bytes32 requestId = _getId(requester, identifier, timestamp, ancillaryData); + _validateRequestHash(requestId, request); + OptimisticOracleInterface.State state = _getState(requester, identifier, timestamp, ancillaryData, request); + return + state == OptimisticOracleInterface.State.Settled || + state == OptimisticOracleInterface.State.Resolved || + state == OptimisticOracleInterface.State.Expired; + } + + /** + * @notice Generates stamped ancillary data in the format that it would be used in the case of a price dispute. + * @param ancillaryData ancillary data of the price being requested. + * @param requester sender of the initial price request. + * @return the stamped ancillary bytes. + */ + function stampAncillaryData( + bytes memory ancillaryData, + address requester + ) public pure override returns (bytes memory) { + return _stampAncillaryData(ancillaryData, requester); + } + + /**************************************** + * PRIVATE AND INTERNAL FUNCTIONS * + ****************************************/ + // Returns hash of unique request identifiers. This contract maps request ID hashes to hashes of the request's + // parameters. + function _getId( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData + ) private pure returns (bytes32) { + return keccak256(abi.encode(requester, identifier, timestamp, ancillaryData)); + } + + // Returns hash of request parameters. These are mapped to the unique request ID to track a request's lifecycle. + function _getRequestHash(Request memory request) private pure returns (bytes32) { + return keccak256(abi.encode(request)); + } + + // Resolves a price request that has expired or been disputed and a price is available from the DVM. This will + // revert if the unique request ID does not match the hashed request parameters. This also marks the request + // as settled, therefore this method can only be triggered once per eligible request. + function _settle( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request + ) private returns (uint256 payout, int256 resolvedPrice) { + bytes32 requestId = _getId(requester, identifier, timestamp, ancillaryData); + _validateRequestHash(requestId, request); + + // Associate settled request params with ID. + Request memory settledRequest = Request({ + proposer: request.proposer, + disputer: request.disputer, + currency: request.currency, + settled: true, // Modified + proposedPrice: request.proposedPrice, + resolvedPrice: request.resolvedPrice, + expirationTime: request.expirationTime, + reward: request.reward, + finalFee: request.finalFee, + requestSettings: request.requestSettings + }); + + OptimisticOracleInterface.State state = _getState(requester, identifier, timestamp, ancillaryData, request); + if (state == OptimisticOracleInterface.State.Expired) { + // In the expiry case, just pay back the proposer's bond and final fee along with the reward. + resolvedPrice = request.proposedPrice; + settledRequest.resolvedPrice = resolvedPrice; + payout = request.requestSettings.bond.add(request.finalFee).add(request.reward); + request.currency.safeTransfer(request.proposer, payout); + } else if (state == OptimisticOracleInterface.State.Resolved) { + // In the Resolved case, pay either the disputer or the proposer the entire payout (+ bond and reward). + resolvedPrice = _getOracle().getPrice(identifier, timestamp, _stampAncillaryData(ancillaryData, requester)); + settledRequest.resolvedPrice = resolvedPrice; + bool disputeSuccess = settledRequest.resolvedPrice != request.proposedPrice; + + // Winner gets: + // - Their bond back. + // - The unburned portion of the loser's bond: proposal bond (not including final fee) - burned bond. + // - Their final fee back. + // - The request reward (if not already refunded -- if refunded, it will be set to 0). + payout = request + .requestSettings + .bond + .add(request.requestSettings.bond.sub(_computeBurnedBond(settledRequest))) + .add(request.finalFee) + .add(request.reward); + request.currency.safeTransfer(disputeSuccess ? request.disputer : request.proposer, payout); + } else { + revert("Already settled or not settleable"); + } + + _storeRequestHash(requestId, settledRequest); + emit Settle(requester, identifier, timestamp, ancillaryData, settledRequest); + + // Callback. + if (address(requester).isContract() && request.requestSettings.callbackOnPriceSettled) + OptimisticRequesterV2(requester).priceSettled(identifier, timestamp, ancillaryData, settledRequest); + } + + function _computeBurnedBond(Request memory request) private pure returns (uint256) { + // burnedBond = floor(bond / 2) + return request.requestSettings.bond.div(2); + } + + function _validateLiveness(uint256 liveness) private pure { + require(liveness < 5200 weeks, "Liveness too large"); + require(liveness > 0, "Liveness cannot be 0"); + } + + function _validateRequestHash(bytes32 requestId, Request memory request) private view { + require( + requests[requestId] == _getRequestHash(request), + "Hashed request params do not match existing request hash" + ); + } + + function _storeRequestHash(bytes32 requestId, Request memory request) internal { + requests[requestId] = _getRequestHash(request); + } + + function _getState( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request + ) internal view returns (OptimisticOracleInterface.State) { + // Note: This function does not check whether all of the _request parameter values are correct. For example, + // the request.reward could be any value and it would not impact this function's return value. Therefore, it + // is the caller's responsibility to check that _request matches with the expected ID corresponding to + // {requester, identifier, timestamp, ancillaryData} via _validateRequestHash(). + if (address(request.currency) == address(0)) return OptimisticOracleInterface.State.Invalid; + + if (request.proposer == address(0)) return OptimisticOracleInterface.State.Requested; + + if (request.settled) return OptimisticOracleInterface.State.Settled; + + if (request.disputer == address(0)) + return + request.expirationTime <= getCurrentTime() + ? OptimisticOracleInterface.State.Expired + : OptimisticOracleInterface.State.Proposed; + + return + _getOracle().hasPrice(identifier, timestamp, _stampAncillaryData(ancillaryData, requester)) + ? OptimisticOracleInterface.State.Resolved + : OptimisticOracleInterface.State.Disputed; + } + + function _getOracle() internal view returns (OracleAncillaryInterface) { + return OracleAncillaryInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); + } + + function _getCollateralWhitelist() internal view returns (AddressWhitelist) { + return AddressWhitelist(finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist)); + } + + function _getStore() internal view returns (StoreInterface) { + return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store)); + } + + function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) { + return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); + } + + /** + * @dev We don't handle specifically the case where `ancillaryData` is not already readily translateable in utf8. + * For those cases, we assume that the client will be able to strip out the utf8-translateable part of the + * ancillary data that this contract stamps. + */ + function _stampAncillaryData(bytes memory ancillaryData, address requester) internal pure returns (bytes memory) { + // Since this contract will be the one to formally submit DVM price requests, its useful for voters to know who + // the original requester was. + return AncillaryData.appendKeyValueAddress(ancillaryData, "ooRequester", requester); + } +} + +/** + * @notice This is the SkinnyOptimisticOracle contract that should be deployed on live networks. It is exactly the same + * as the regular SkinnyOptimisticOracle contract, but it overrides getCurrentTime to make the call a simply return + * block.timestamp with no branching or storage queries. + */ +contract SkinnyOptimisticOracleV2Prod is SkinnyOptimisticOracleV2 { + constructor( + uint256 _liveness, + address _finderAddress, + address _timerAddress + ) SkinnyOptimisticOracleV2(_liveness, _finderAddress, _timerAddress) {} + + function getCurrentTime() public view virtual override returns (uint256) { + return block.timestamp; + } +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/OptimisticOracleInterface.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/OptimisticOracleInterface.sol new file mode 100644 index 000000000..ef2a67e4b --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/OptimisticOracleInterface.sol @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; + +/** + * @title Financial contract facing Oracle interface. + * @dev Interface used by financial contracts to interact with the Oracle. Voters will use a different interface. + */ +abstract contract OptimisticOracleInterface { + event RequestPrice( + address indexed requester, + bytes32 identifier, + uint256 timestamp, + bytes ancillaryData, + address currency, + uint256 reward, + uint256 finalFee + ); + event ProposePrice( + address indexed requester, + address indexed proposer, + bytes32 identifier, + uint256 timestamp, + bytes ancillaryData, + int256 proposedPrice, + uint256 expirationTimestamp, + address currency + ); + event DisputePrice( + address indexed requester, + address indexed proposer, + address indexed disputer, + bytes32 identifier, + uint256 timestamp, + bytes ancillaryData, + int256 proposedPrice + ); + event Settle( + address indexed requester, + address indexed proposer, + address indexed disputer, + bytes32 identifier, + uint256 timestamp, + bytes ancillaryData, + int256 price, + uint256 payout + ); + + // Struct representing the state of a price request. + enum State { + Invalid, // Never requested. + Requested, // Requested, no other actions taken. + Proposed, // Proposed, but not expired or disputed yet. + Expired, // Proposed, not disputed, past liveness. + Disputed, // Disputed, but no DVM price returned yet. + Resolved, // Disputed and DVM price is available. + Settled // Final price has been set in the contract (can get here from Expired or Resolved). + } + + // Struct representing a price request. + struct Request { + address proposer; // Address of the proposer. + address disputer; // Address of the disputer. + IERC20 currency; // ERC20 token used to pay rewards and fees. + bool settled; // True if the request is settled. + bool refundOnDispute; // True if the requester should be refunded their reward on dispute. + int256 proposedPrice; // Price that the proposer submitted. + int256 resolvedPrice; // Price resolved once the request is settled. + uint256 expirationTime; // Time at which the request auto-settles without a dispute. + uint256 reward; // Amount of the currency to pay to the proposer on settlement. + uint256 finalFee; // Final fee to pay to the Store upon request to the DVM. + uint256 bond; // Bond that the proposer and disputer must pay on top of the final fee. + uint256 customLiveness; // Custom liveness value set by the requester. + } + + // This value must be <= the Voting contract's `ancillaryBytesLimit` value otherwise it is possible + // that a price can be requested to this contract successfully, but cannot be disputed because the DVM refuses + // to accept a price request made with ancillary data length over a certain size. + uint256 public constant ancillaryBytesLimit = 8192; + + function defaultLiveness() external view virtual returns (uint256); + + function finder() external view virtual returns (FinderInterface); + + function getCurrentTime() external view virtual returns (uint256); + + // Note: this is required so that typechain generates a return value with named fields. + mapping(bytes32 => Request) public requests; + + /** + * @notice Requests a new price. + * @param identifier price identifier being requested. + * @param timestamp timestamp of the price being requested. + * @param ancillaryData ancillary data representing additional args being passed with the price request. + * @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM. + * @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0, + * which could make sense if the contract requests and proposes the value in the same call or + * provides its own reward system. + * @return totalBond default bond (final fee) + final fee that the proposer and disputer will be required to pay. + * This can be changed with a subsequent call to setBond(). + */ + function requestPrice( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + IERC20 currency, + uint256 reward + ) external virtual returns (uint256 totalBond); + + /** + * @notice Set the proposal bond associated with a price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param bond custom bond amount to set. + * @return totalBond new bond + final fee that the proposer and disputer will be required to pay. This can be + * changed again with a subsequent call to setBond(). + */ + function setBond( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + uint256 bond + ) external virtual returns (uint256 totalBond); + + /** + * @notice Sets the request to refund the reward if the proposal is disputed. This can help to "hedge" the caller + * in the event of a dispute-caused delay. Note: in the event of a dispute, the winner still receives the other's + * bond, so there is still profit to be made even if the reward is refunded. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + */ + function setRefundOnDispute(bytes32 identifier, uint256 timestamp, bytes memory ancillaryData) external virtual; + + /** + * @notice Sets a custom liveness value for the request. Liveness is the amount of time a proposal must wait before + * being auto-resolved. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param customLiveness new custom liveness. + */ + function setCustomLiveness( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + uint256 customLiveness + ) external virtual; + + /** + * @notice Proposes a price value on another address' behalf. Note: this address will receive any rewards that come + * from this proposal. However, any bonds are pulled from the caller. + * @param proposer address to set as the proposer. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param proposedPrice price being proposed. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the proposer once settled if the proposal is correct. + */ + function proposePriceFor( + address proposer, + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + int256 proposedPrice + ) public virtual returns (uint256 totalBond); + + /** + * @notice Proposes a price value for an existing price request. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param proposedPrice price being proposed. + * @return totalBond the amount that's pulled from the proposer's wallet as a bond. The bond will be returned to + * the proposer once settled if the proposal is correct. + */ + function proposePrice( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + int256 proposedPrice + ) external virtual returns (uint256 totalBond); + + /** + * @notice Disputes a price request with an active proposal on another address' behalf. Note: this address will + * receive any rewards that come from this dispute. However, any bonds are pulled from the caller. + * @param disputer address to set as the disputer. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the disputer once settled if the dispute was value (the proposal was incorrect). + */ + function disputePriceFor( + address disputer, + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) public virtual returns (uint256 totalBond); + + /** + * @notice Disputes a price value for an existing price request with an active proposal. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return totalBond the amount that's pulled from the disputer's wallet as a bond. The bond will be returned to + * the disputer once settled if the dispute was valid (the proposal was incorrect). + */ + function disputePrice( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) external virtual returns (uint256 totalBond); + + /** + * @notice Retrieves a price that was previously requested by a caller. Reverts if the request is not settled + * or settleable. Note: this method is not view so that this call may actually settle the price request if it + * hasn't been settled. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return resolved price. + */ + function settleAndGetPrice( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) external virtual returns (int256); + + /** + * @notice Attempts to settle an outstanding price request. Will revert if it isn't settleable. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return payout the amount that the "winner" (proposer or disputer) receives on settlement. This amount includes + * the returned bonds as well as additional rewards. + */ + function settle( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) external virtual returns (uint256 payout); + + /** + * @notice Gets the current data structure containing all information about a price request. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return the Request data structure. + */ + function getRequest( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) public view virtual returns (Request memory); + + /** + * @notice Returns the state of a price request. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return the State enum value. + */ + function getState( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) public view virtual returns (State); + + /** + * @notice Checks if a given request has resolved or been settled (i.e the optimistic oracle has a price). + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return true if price has resolved or settled, false otherwise. + */ + function hasPrice( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) public view virtual returns (bool); + + function stampAncillaryData( + bytes memory ancillaryData, + address requester + ) public view virtual returns (bytes memory); +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/OptimisticOracleV2Interface.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/OptimisticOracleV2Interface.sol new file mode 100644 index 000000000..6e1cff600 --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/OptimisticOracleV2Interface.sol @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; + +/** + * @title Financial contract facing Oracle interface. + * @dev Interface used by financial contracts to interact with the Oracle. Voters will use a different interface. + */ +abstract contract OptimisticOracleV2Interface { + event RequestPrice( + address indexed requester, + bytes32 identifier, + uint256 timestamp, + bytes ancillaryData, + address currency, + uint256 reward, + uint256 finalFee + ); + event ProposePrice( + address indexed requester, + address indexed proposer, + bytes32 identifier, + uint256 timestamp, + bytes ancillaryData, + int256 proposedPrice, + uint256 expirationTimestamp, + address currency + ); + event DisputePrice( + address indexed requester, + address indexed proposer, + address indexed disputer, + bytes32 identifier, + uint256 timestamp, + bytes ancillaryData, + int256 proposedPrice + ); + event Settle( + address indexed requester, + address indexed proposer, + address indexed disputer, + bytes32 identifier, + uint256 timestamp, + bytes ancillaryData, + int256 price, + uint256 payout + ); + // Struct representing the state of a price request. + enum State { + Invalid, // Never requested. + Requested, // Requested, no other actions taken. + Proposed, // Proposed, but not expired or disputed yet. + Expired, // Proposed, not disputed, past liveness. + Disputed, // Disputed, but no DVM price returned yet. + Resolved, // Disputed and DVM price is available. + Settled // Final price has been set in the contract (can get here from Expired or Resolved). + } + + struct RequestSettings { + bool eventBased; // True if the request is set to be event-based. + bool refundOnDispute; // True if the requester should be refunded their reward on dispute. + bool callbackOnPriceProposed; // True if callbackOnPriceProposed callback is required. + bool callbackOnPriceDisputed; // True if callbackOnPriceDisputed callback is required. + bool callbackOnPriceSettled; // True if callbackOnPriceSettled callback is required. + uint256 bond; // Bond that the proposer and disputer must pay on top of the final fee. + uint256 customLiveness; // Custom liveness value set by the requester. + } + + // Struct representing a price request. + struct Request { + address proposer; // Address of the proposer. + address disputer; // Address of the disputer. + IERC20 currency; // ERC20 token used to pay rewards and fees. + bool settled; // True if the request is settled. + RequestSettings requestSettings; // Custom settings associated with a request. + int256 proposedPrice; // Price that the proposer submitted. + int256 resolvedPrice; // Price resolved once the request is settled. + uint256 expirationTime; // Time at which the request auto-settles without a dispute. + uint256 reward; // Amount of the currency to pay to the proposer on settlement. + uint256 finalFee; // Final fee to pay to the Store upon request to the DVM. + } + + // This value must be <= the Voting contract's `ancillaryBytesLimit` value otherwise it is possible + // that a price can be requested to this contract successfully, but cannot be disputed because the DVM refuses + // to accept a price request made with ancillary data length over a certain size. + uint256 public constant ancillaryBytesLimit = 8192; + + function defaultLiveness() external view virtual returns (uint256); + + function finder() external view virtual returns (FinderInterface); + + function getCurrentTime() external view virtual returns (uint256); + + // Note: this is required so that typechain generates a return value with named fields. + mapping(bytes32 => Request) public requests; + + /** + * @notice Requests a new price. + * @param identifier price identifier being requested. + * @param timestamp timestamp of the price being requested. + * @param ancillaryData ancillary data representing additional args being passed with the price request. + * @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM. + * @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0, + * which could make sense if the contract requests and proposes the value in the same call or + * provides its own reward system. + * @return totalBond default bond (final fee) + final fee that the proposer and disputer will be required to pay. + * This can be changed with a subsequent call to setBond(). + */ + function requestPrice( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + IERC20 currency, + uint256 reward + ) external virtual returns (uint256 totalBond); + + /** + * @notice Set the proposal bond associated with a price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param bond custom bond amount to set. + * @return totalBond new bond + final fee that the proposer and disputer will be required to pay. This can be + * changed again with a subsequent call to setBond(). + */ + function setBond( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + uint256 bond + ) external virtual returns (uint256 totalBond); + + /** + * @notice Sets the request to refund the reward if the proposal is disputed. This can help to "hedge" the caller + * in the event of a dispute-caused delay. Note: in the event of a dispute, the winner still receives the other's + * bond, so there is still profit to be made even if the reward is refunded. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + */ + function setRefundOnDispute(bytes32 identifier, uint256 timestamp, bytes memory ancillaryData) external virtual; + + /** + * @notice Sets a custom liveness value for the request. Liveness is the amount of time a proposal must wait before + * being auto-resolved. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param customLiveness new custom liveness. + */ + function setCustomLiveness( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + uint256 customLiveness + ) external virtual; + + /** + * @notice Sets the request to be an "event-based" request. + * @dev Calling this method has a few impacts on the request: + * + * 1. The timestamp at which the request is evaluated is the time of the proposal, not the timestamp associated + * with the request. + * + * 2. The proposer cannot propose the "too early" value (TOO_EARLY_RESPONSE). This is to ensure that a proposer who + * prematurely proposes a response loses their bond. + * + * 3. RefundoOnDispute is automatically set, meaning disputes trigger the reward to be automatically refunded to + * the requesting contract. + * + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + */ + function setEventBased(bytes32 identifier, uint256 timestamp, bytes memory ancillaryData) external virtual; + + /** + * @notice Sets which callbacks should be enabled for the request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param callbackOnPriceProposed whether to enable the callback onPriceProposed. + * @param callbackOnPriceDisputed whether to enable the callback onPriceDisputed. + * @param callbackOnPriceSettled whether to enable the callback onPriceSettled. + */ + function setCallbacks( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + bool callbackOnPriceProposed, + bool callbackOnPriceDisputed, + bool callbackOnPriceSettled + ) external virtual; + + /** + * @notice Proposes a price value on another address' behalf. Note: this address will receive any rewards that come + * from this proposal. However, any bonds are pulled from the caller. + * @param proposer address to set as the proposer. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param proposedPrice price being proposed. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the proposer once settled if the proposal is correct. + */ + function proposePriceFor( + address proposer, + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + int256 proposedPrice + ) public virtual returns (uint256 totalBond); + + /** + * @notice Proposes a price value for an existing price request. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param proposedPrice price being proposed. + * @return totalBond the amount that's pulled from the proposer's wallet as a bond. The bond will be returned to + * the proposer once settled if the proposal is correct. + */ + function proposePrice( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + int256 proposedPrice + ) external virtual returns (uint256 totalBond); + + /** + * @notice Disputes a price request with an active proposal on another address' behalf. Note: this address will + * receive any rewards that come from this dispute. However, any bonds are pulled from the caller. + * @param disputer address to set as the disputer. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the disputer once settled if the dispute was value (the proposal was incorrect). + */ + function disputePriceFor( + address disputer, + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) public virtual returns (uint256 totalBond); + + /** + * @notice Disputes a price value for an existing price request with an active proposal. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return totalBond the amount that's pulled from the disputer's wallet as a bond. The bond will be returned to + * the disputer once settled if the dispute was valid (the proposal was incorrect). + */ + function disputePrice( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) external virtual returns (uint256 totalBond); + + /** + * @notice Retrieves a price that was previously requested by a caller. Reverts if the request is not settled + * or settleable. Note: this method is not view so that this call may actually settle the price request if it + * hasn't been settled. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return resolved price. + */ + function settleAndGetPrice( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) external virtual returns (int256); + + /** + * @notice Attempts to settle an outstanding price request. Will revert if it isn't settleable. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return payout the amount that the "winner" (proposer or disputer) receives on settlement. This amount includes + * the returned bonds as well as additional rewards. + */ + function settle( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) external virtual returns (uint256 payout); + + /** + * @notice Gets the current data structure containing all information about a price request. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return the Request data structure. + */ + function getRequest( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) public view virtual returns (Request memory); + + /** + * @notice Returns the state of a price request. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return the State enum value. + */ + function getState( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) public view virtual returns (State); + + /** + * @notice Checks if a given request has resolved or been settled (i.e the optimistic oracle has a price). + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return true if price has resolved or settled, false otherwise. + */ + function hasPrice( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) public view virtual returns (bool); + + function stampAncillaryData( + bytes memory ancillaryData, + address requester + ) public view virtual returns (bytes memory); +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/SkinnyOptimisticOracleInterface.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/SkinnyOptimisticOracleInterface.sol new file mode 100644 index 000000000..ccea90ff7 --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/SkinnyOptimisticOracleInterface.sol @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "../interfaces/OptimisticOracleInterface.sol"; + +/** + * @title Interface for the gas-cost-reduced version of the OptimisticOracle. + * @notice Differences from normal OptimisticOracle: + * - refundOnDispute: flag is removed, by default there are no refunds on disputes. + * - customizing request parameters: In the OptimisticOracle, parameters like `bond` and `customLiveness` can be reset + * after a request is already made via `requestPrice`. In the SkinnyOptimisticOracle, these parameters can only be + * set in `requestPrice`, which has an expanded input set. + * - settleAndGetPrice: Replaced by `settle`, which can only be called once per settleable request. The resolved price + * can be fetched via the `Settle` event or the return value of `settle`. + * - general changes to interface: Functions that interact with existing requests all require the parameters of the + * request to modify to be passed as input. These parameters must match with the existing request parameters or the + * function will revert. This change reflects the internal refactor to store hashed request parameters instead of the + * full request struct. + * @dev Interface used by financial contracts to interact with the Oracle. Voters will use a different interface. + */ +abstract contract SkinnyOptimisticOracleInterface { + // Struct representing a price request. Note that this differs from the OptimisticOracleInterface's Request struct + // in that refundOnDispute is removed. + struct Request { + address proposer; // Address of the proposer. + address disputer; // Address of the disputer. + IERC20 currency; // ERC20 token used to pay rewards and fees. + bool settled; // True if the request is settled. + int256 proposedPrice; // Price that the proposer submitted. + int256 resolvedPrice; // Price resolved once the request is settled. + uint256 expirationTime; // Time at which the request auto-settles without a dispute. + uint256 reward; // Amount of the currency to pay to the proposer on settlement. + uint256 finalFee; // Final fee to pay to the Store upon request to the DVM. + uint256 bond; // Bond that the proposer and disputer must pay on top of the final fee. + uint256 customLiveness; // Custom liveness value set by the requester. + } + + // This value must be <= the Voting contract's `ancillaryBytesLimit` value otherwise it is possible + // that a price can be requested to this contract successfully, but cannot be disputed because the DVM refuses + // to accept a price request made with ancillary data length over a certain size. + uint256 public constant ancillaryBytesLimit = 8192; + + /** + * @notice Requests a new price. + * @param identifier price identifier being requested. + * @param timestamp timestamp of the price being requested. + * @param ancillaryData ancillary data representing additional args being passed with the price request. + * @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM. + * @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0, + * which could make sense if the contract requests and proposes the value in the same call or + * provides its own reward system. + * @param bond custom proposal bond to set for request. If set to 0, defaults to the final fee. + * @param customLiveness custom proposal liveness to set for request. + * @return totalBond default bond + final fee that the proposer and disputer will be required to pay. + */ + function requestPrice( + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + IERC20 currency, + uint256 reward, + uint256 bond, + uint256 customLiveness + ) external virtual returns (uint256 totalBond); + + /** + * @notice Proposes a price value on another address' behalf. Note: this address will receive any rewards that come + * from this proposal. However, any bonds are pulled from the caller. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters whose hash must match the request that the caller wants to + * propose a price for. + * @param proposer address to set as the proposer. + * @param proposedPrice price being proposed. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the proposer once settled if the proposal is correct. + */ + function proposePriceFor( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request, + address proposer, + int256 proposedPrice + ) public virtual returns (uint256 totalBond); + + /** + * @notice Proposes a price value where caller is the proposer. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters whose hash must match the request that the caller wants to + * propose a price for. + * @param proposedPrice price being proposed. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the proposer once settled if the proposal is correct. + */ + function proposePrice( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request, + int256 proposedPrice + ) external virtual returns (uint256 totalBond); + + /** + * @notice Combines logic of requestPrice and proposePrice while taking advantage of gas savings from not having to + * overwrite Request params that a normal requestPrice() => proposePrice() flow would entail. Note: The proposer + * will receive any rewards that come from this proposal. However, any bonds are pulled from the caller. + * @dev The caller is the requester, but the proposer can be customized. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM. + * @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0, + * which could make sense if the contract requests and proposes the value in the same call or + * provides its own reward system. + * @param bond custom proposal bond to set for request. If set to 0, defaults to the final fee. + * @param customLiveness custom proposal liveness to set for request. + * @param proposer address to set as the proposer. + * @param proposedPrice price being proposed. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the proposer once settled if the proposal is correct. + */ + function requestAndProposePriceFor( + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + IERC20 currency, + uint256 reward, + uint256 bond, + uint256 customLiveness, + address proposer, + int256 proposedPrice + ) external virtual returns (uint256 totalBond); + + /** + * @notice Disputes a price request with an active proposal on another address' behalf. Note: this address will + * receive any rewards that come from this dispute. However, any bonds are pulled from the caller. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters whose hash must match the request that the caller wants to + * dispute. + * @param disputer address to set as the disputer. + * @param requester sender of the initial price request. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the disputer once settled if the dispute was valid (the proposal was incorrect). + */ + function disputePriceFor( + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request, + address disputer, + address requester + ) public virtual returns (uint256 totalBond); + + /** + * @notice Disputes a price request with an active proposal where caller is the disputer. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters whose hash must match the request that the caller wants to + * dispute. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the disputer once settled if the dispute was valid (the proposal was incorrect). + */ + function disputePrice( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request + ) external virtual returns (uint256 totalBond); + + /** + * @notice Attempts to settle an outstanding price request. Will revert if it isn't settleable. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters whose hash must match the request that the caller wants to + * settle. + * @return payout the amount that the "winner" (proposer or disputer) receives on settlement. This amount includes + * the returned bonds as well as additional rewards. + * @return resolvedPrice the price that the request settled to. + */ + function settle( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request + ) external virtual returns (uint256 payout, int256 resolvedPrice); + + /** + * @notice Computes the current state of a price request. See the State enum for more details. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters. + * @return the State. + */ + function getState( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request + ) external virtual returns (OptimisticOracleInterface.State); + + /** + * @notice Checks if a given request has resolved, expired or been settled (i.e the optimistic oracle has a price). + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters. The hash of these parameters must match with the request hash that is + * associated with the price request unique ID {requester, identifier, timestamp, ancillaryData}, or this method + * will revert. + * @return boolean indicating true if price exists and false if not. + */ + function hasPrice( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request + ) public virtual returns (bool); + + /** + * @notice Generates stamped ancillary data in the format that it would be used in the case of a price dispute. + * @param ancillaryData ancillary data of the price being requested. + * @param requester sender of the initial price request. + * @return the stamped ancillary bytes. + */ + function stampAncillaryData( + bytes memory ancillaryData, + address requester + ) public pure virtual returns (bytes memory); +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/SkinnyOptimisticOracleV2Interface.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/SkinnyOptimisticOracleV2Interface.sol new file mode 100644 index 000000000..481958dc7 --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/SkinnyOptimisticOracleV2Interface.sol @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "../interfaces/OptimisticOracleInterface.sol"; + +/** + * @title Interface for the gas-cost-reduced version of the OptimisticOracle. + * @notice Differences from normal OptimisticOracle: + * - refundOnDispute: flag is removed, by default there are no refunds on disputes. + * - customizing request parameters: In the OptimisticOracle, parameters like `bond` and `customLiveness` can be reset + * after a request is already made via `requestPrice`. In the SkinnyOptimisticOracle, these parameters can only be + * set in `requestPrice`, which has an expanded input set. + * - settleAndGetPrice: Replaced by `settle`, which can only be called once per settleable request. The resolved price + * can be fetched via the `Settle` event or the return value of `settle`. + * - general changes to interface: Functions that interact with existing requests all require the parameters of the + * request to modify to be passed as input. These parameters must match with the existing request parameters or the + * function will revert. This change reflects the internal refactor to store hashed request parameters instead of the + * full request struct. + * @dev Interface used by financial contracts to interact with the Oracle. Voters will use a different interface. + */ +abstract contract SkinnyOptimisticOracleV2Interface { + struct RequestSettings { + bool callbackOnPriceProposed; // True if callbackOnPriceProposed callback is required. + bool callbackOnPriceDisputed; // True if callbackOnPriceDisputed callback is required. + bool callbackOnPriceSettled; // True if callbackOnPriceSettled callback is required. + uint256 bond; // Bond that the proposer and disputer must pay on top of the final fee. + uint256 customLiveness; // Custom liveness value set by the requester. + } + + // Struct representing a price request. Note that this differs from the OptimisticOracleInterface's Request struct + // in that refundOnDispute is removed. + struct Request { + address proposer; // Address of the proposer. + address disputer; // Address of the disputer. + IERC20 currency; // ERC20 token used to pay rewards and fees. + bool settled; // True if the request is settled. + RequestSettings requestSettings; // Custom settings associated with a request. + int256 proposedPrice; // Price that the proposer submitted. + int256 resolvedPrice; // Price resolved once the request is settled. + uint256 expirationTime; // Time at which the request auto-settles without a dispute. + uint256 reward; // Amount of the currency to pay to the proposer on settlement. + uint256 finalFee; // Final fee to pay to the Store upon request to the DVM. + } + + // This value must be <= the Voting contract's `ancillaryBytesLimit` value otherwise it is possible + // that a price can be requested to this contract successfully, but cannot be disputed because the DVM refuses + // to accept a price request made with ancillary data length over a certain size. + uint256 public constant ancillaryBytesLimit = 8192; + + /** + * @notice Requests a new price. + * @param identifier price identifier being requested. + * @param timestamp timestamp of the price being requested. + * @param ancillaryData ancillary data representing additional args being passed with the price request. + * @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM. + * @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0, + * which could make sense if the contract requests and proposes the value in the same call or + * provides its own reward system. + * @param requestSettings settings for the request. + * @return totalBond default bond + final fee that the proposer and disputer will be required to pay. + */ + function requestPrice( + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + IERC20 currency, + uint256 reward, + RequestSettings memory requestSettings + ) external virtual returns (uint256 totalBond); + + /** + * @notice Proposes a price value on another address' behalf. Note: this address will receive any rewards that come + * from this proposal. However, any bonds are pulled from the caller. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters whose hash must match the request that the caller wants to + * propose a price for. + * @param proposer address to set as the proposer. + * @param proposedPrice price being proposed. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the proposer once settled if the proposal is correct. + */ + function proposePriceFor( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request, + address proposer, + int256 proposedPrice + ) public virtual returns (uint256 totalBond); + + /** + * @notice Proposes a price value where caller is the proposer. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters whose hash must match the request that the caller wants to + * propose a price for. + * @param proposedPrice price being proposed. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the proposer once settled if the proposal is correct. + */ + function proposePrice( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request, + int256 proposedPrice + ) external virtual returns (uint256 totalBond); + + /** + * @notice Combines logic of requestPrice and proposePrice while taking advantage of gas savings from not having to + * overwrite Request params that a normal requestPrice() => proposePrice() flow would entail. Note: The proposer + * will receive any rewards that come from this proposal. However, any bonds are pulled from the caller. + * @dev The caller is the requester, but the proposer can be customized. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM. + * @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0, + * which could make sense if the contract requests and proposes the value in the same call or + * provides its own reward system. + * @param requestSettings settings for the request. + * @param proposer address to set as the proposer. + * @param proposedPrice price being proposed. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the proposer once settled if the proposal is correct. + */ + function requestAndProposePriceFor( + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + IERC20 currency, + uint256 reward, + RequestSettings memory requestSettings, + address proposer, + int256 proposedPrice + ) external virtual returns (uint256 totalBond); + + /** + * @notice Disputes a price request with an active proposal on another address' behalf. Note: this address will + * receive any rewards that come from this dispute. However, any bonds are pulled from the caller. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters whose hash must match the request that the caller wants to + * dispute. + * @param disputer address to set as the disputer. + * @param requester sender of the initial price request. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the disputer once settled if the dispute was valid (the proposal was incorrect). + */ + function disputePriceFor( + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request, + address disputer, + address requester + ) public virtual returns (uint256 totalBond); + + /** + * @notice Disputes a price request with an active proposal where caller is the disputer. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters whose hash must match the request that the caller wants to + * dispute. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the disputer once settled if the dispute was valid (the proposal was incorrect). + */ + function disputePrice( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request + ) external virtual returns (uint256 totalBond); + + /** + * @notice Attempts to settle an outstanding price request. Will revert if it isn't settleable. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters whose hash must match the request that the caller wants to + * settle. + * @return payout the amount that the "winner" (proposer or disputer) receives on settlement. This amount includes + * the returned bonds as well as additional rewards. + * @return resolvedPrice the price that the request settled to. + */ + function settle( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request + ) external virtual returns (uint256 payout, int256 resolvedPrice); + + /** + * @notice Computes the current state of a price request. See the State enum for more details. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters. + * @return the State. + */ + function getState( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request + ) external virtual returns (OptimisticOracleInterface.State); + + /** + * @notice Checks if a given request has resolved, expired or been settled (i.e the optimistic oracle has a price). + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters. The hash of these parameters must match with the request hash that is + * associated with the price request unique ID {requester, identifier, timestamp, ancillaryData}, or this method + * will revert. + * @return boolean indicating true if price exists and false if not. + */ + function hasPrice( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request + ) public virtual returns (bool); + + /** + * @notice Generates stamped ancillary data in the format that it would be used in the case of a price dispute. + * @param ancillaryData ancillary data of the price being requested. + * @param requester sender of the initial price request. + * @return the stamped ancillary bytes. + */ + function stampAncillaryData( + bytes memory ancillaryData, + address requester + ) public pure virtual returns (bytes memory); +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v2/previous-versions/OptimisticOracle.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v2/previous-versions/OptimisticOracle.sol new file mode 100644 index 000000000..5d0178d21 --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v2/previous-versions/OptimisticOracle.sol @@ -0,0 +1,629 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +// WARNING: This contract has been deprecated! It is left in the UMA Protocol repo for backwards compatibility reasons. +// You should refer to the latest implementation of the Optimistic Oracle which is named OptimisticOracleV2 and can +// be found in the UMA Finder under the same name. + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; +import "@openzeppelin/contracts-v4/utils/Address.sol"; + +import "../../data-verification-mechanism/interfaces/StoreInterface.sol"; +import "../../data-verification-mechanism/interfaces/OracleAncillaryInterface.sol"; +import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; +import "../../data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol"; +import "../interfaces/OptimisticOracleInterface.sol"; +import "../../data-verification-mechanism/implementation/Constants.sol"; + +import "../../common/implementation/Testable.sol"; +import "../../common/implementation/Lockable.sol"; +import "../../common/implementation/FixedPoint.sol"; +import "../../common/implementation/AncillaryData.sol"; +import "../../common/implementation/AddressWhitelist.sol"; + +/** + * @title Optimistic Requester. + * @notice Optional interface that requesters can implement to receive callbacks. + * @dev this contract does _not_ work with ERC777 collateral currencies or any others that call into the receiver on + * transfer(). Using an ERC777 token would allow a user to maliciously grief other participants (while also losing + * money themselves). + */ +interface OptimisticRequester { + /** + * @notice Callback for proposals. + * @param identifier price identifier being requested. + * @param timestamp timestamp of the price being requested. + * @param ancillaryData ancillary data of the price being requested. + */ + function priceProposed(bytes32 identifier, uint256 timestamp, bytes memory ancillaryData) external; + + /** + * @notice Callback for disputes. + * @param identifier price identifier being requested. + * @param timestamp timestamp of the price being requested. + * @param ancillaryData ancillary data of the price being requested. + * @param refund refund received in the case that refundOnDispute was enabled. + */ + function priceDisputed(bytes32 identifier, uint256 timestamp, bytes memory ancillaryData, uint256 refund) external; + + /** + * @notice Callback for settlement. + * @param identifier price identifier being requested. + * @param timestamp timestamp of the price being requested. + * @param ancillaryData ancillary data of the price being requested. + * @param price price that was resolved by the escalation process. + */ + function priceSettled(bytes32 identifier, uint256 timestamp, bytes memory ancillaryData, int256 price) external; +} + +/** + * @title Optimistic Oracle. + * @notice Pre-DVM escalation contract that allows faster settlement. + */ +contract OptimisticOracle is OptimisticOracleInterface, Testable, Lockable { + using SafeMath for uint256; + using SafeERC20 for IERC20; + using Address for address; + + // Finder to provide addresses for DVM contracts. + FinderInterface public override finder; + + // Default liveness value for all price requests. + uint256 public override defaultLiveness; + + /** + * @notice Constructor. + * @param _liveness default liveness applied to each price request. + * @param _finderAddress finder to use to get addresses of DVM contracts. + * @param _timerAddress address of the timer contract. Should be 0x0 in prod. + */ + constructor(uint256 _liveness, address _finderAddress, address _timerAddress) Testable(_timerAddress) { + finder = FinderInterface(_finderAddress); + _validateLiveness(_liveness); + defaultLiveness = _liveness; + } + + /** + * @notice Requests a new price. + * @param identifier price identifier being requested. + * @param timestamp timestamp of the price being requested. + * @param ancillaryData ancillary data representing additional args being passed with the price request. + * @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM. + * @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0, + * which could make sense if the contract requests and proposes the value in the same call or + * provides its own reward system. + * @return totalBond default bond (final fee) + final fee that the proposer and disputer will be required to pay. + * This can be changed with a subsequent call to setBond(). + */ + function requestPrice( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + IERC20 currency, + uint256 reward + ) external override nonReentrant returns (uint256 totalBond) { + require(_getState(msg.sender, identifier, timestamp, ancillaryData) == State.Invalid, "requestPrice: Invalid"); + require(_getIdentifierWhitelist().isIdentifierSupported(identifier), "Unsupported identifier"); + require(_getCollateralWhitelist().isOnWhitelist(address(currency)), "Unsupported currency"); + require(timestamp <= getCurrentTime(), "Timestamp in future"); + require( + _stampAncillaryData(ancillaryData, msg.sender).length <= ancillaryBytesLimit, + "Ancillary Data too long" + ); + uint256 finalFee = _getStore().computeFinalFee(address(currency)).rawValue; + requests[_getId(msg.sender, identifier, timestamp, ancillaryData)] = Request({ + proposer: address(0), + disputer: address(0), + currency: currency, + settled: false, + refundOnDispute: false, + proposedPrice: 0, + resolvedPrice: 0, + expirationTime: 0, + reward: reward, + finalFee: finalFee, + bond: finalFee, + customLiveness: 0 + }); + + if (reward > 0) { + currency.safeTransferFrom(msg.sender, address(this), reward); + } + + emit RequestPrice(msg.sender, identifier, timestamp, ancillaryData, address(currency), reward, finalFee); + + // This function returns the initial proposal bond for this request, which can be customized by calling + // setBond() with the same identifier and timestamp. + return finalFee.mul(2); + } + + /** + * @notice Set the proposal bond associated with a price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param bond custom bond amount to set. + * @return totalBond new bond + final fee that the proposer and disputer will be required to pay. This can be + * changed again with a subsequent call to setBond(). + */ + function setBond( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + uint256 bond + ) external override nonReentrant returns (uint256 totalBond) { + require(_getState(msg.sender, identifier, timestamp, ancillaryData) == State.Requested, "setBond: Requested"); + Request storage request = _getRequest(msg.sender, identifier, timestamp, ancillaryData); + request.bond = bond; + + // Total bond is the final fee + the newly set bond. + return bond.add(request.finalFee); + } + + /** + * @notice Sets the request to refund the reward if the proposal is disputed. This can help to "hedge" the caller + * in the event of a dispute-caused delay. Note: in the event of a dispute, the winner still receives the other's + * bond, so there is still profit to be made even if the reward is refunded. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + */ + function setRefundOnDispute( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) external override nonReentrant { + require( + _getState(msg.sender, identifier, timestamp, ancillaryData) == State.Requested, + "setRefundOnDispute: Requested" + ); + _getRequest(msg.sender, identifier, timestamp, ancillaryData).refundOnDispute = true; + } + + /** + * @notice Sets a custom liveness value for the request. Liveness is the amount of time a proposal must wait before + * being auto-resolved. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param customLiveness new custom liveness. + */ + function setCustomLiveness( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + uint256 customLiveness + ) external override nonReentrant { + require( + _getState(msg.sender, identifier, timestamp, ancillaryData) == State.Requested, + "setCustomLiveness: Requested" + ); + _validateLiveness(customLiveness); + _getRequest(msg.sender, identifier, timestamp, ancillaryData).customLiveness = customLiveness; + } + + /** + * @notice Proposes a price value on another address' behalf. Note: this address will receive any rewards that come + * from this proposal. However, any bonds are pulled from the caller. + * @param proposer address to set as the proposer. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param proposedPrice price being proposed. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the proposer once settled if the proposal is correct. + */ + function proposePriceFor( + address proposer, + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + int256 proposedPrice + ) public override nonReentrant returns (uint256 totalBond) { + require(proposer != address(0), "proposer address must be non 0"); + require( + _getState(requester, identifier, timestamp, ancillaryData) == State.Requested, + "proposePriceFor: Requested" + ); + Request storage request = _getRequest(requester, identifier, timestamp, ancillaryData); + request.proposer = proposer; + request.proposedPrice = proposedPrice; + + // If a custom liveness has been set, use it instead of the default. + request.expirationTime = getCurrentTime().add( + request.customLiveness != 0 ? request.customLiveness : defaultLiveness + ); + + totalBond = request.bond.add(request.finalFee); + if (totalBond > 0) { + request.currency.safeTransferFrom(msg.sender, address(this), totalBond); + } + + emit ProposePrice( + requester, + proposer, + identifier, + timestamp, + ancillaryData, + proposedPrice, + request.expirationTime, + address(request.currency) + ); + + // Callback. + if (address(requester).isContract()) + try OptimisticRequester(requester).priceProposed(identifier, timestamp, ancillaryData) {} catch {} + } + + /** + * @notice Proposes a price value for an existing price request. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param proposedPrice price being proposed. + * @return totalBond the amount that's pulled from the proposer's wallet as a bond. The bond will be returned to + * the proposer once settled if the proposal is correct. + */ + function proposePrice( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + int256 proposedPrice + ) external override returns (uint256 totalBond) { + // Note: re-entrancy guard is done in the inner call. + return proposePriceFor(msg.sender, requester, identifier, timestamp, ancillaryData, proposedPrice); + } + + /** + * @notice Disputes a price request with an active proposal on another address' behalf. Note: this address will + * receive any rewards that come from this dispute. However, any bonds are pulled from the caller. + * @param disputer address to set as the disputer. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the disputer once settled if the dispute was valid (the proposal was incorrect). + */ + function disputePriceFor( + address disputer, + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) public override nonReentrant returns (uint256 totalBond) { + require(disputer != address(0), "disputer address must be non 0"); + require( + _getState(requester, identifier, timestamp, ancillaryData) == State.Proposed, + "disputePriceFor: Proposed" + ); + Request storage request = _getRequest(requester, identifier, timestamp, ancillaryData); + request.disputer = disputer; + + uint256 finalFee = request.finalFee; + uint256 bond = request.bond; + totalBond = bond.add(finalFee); + if (totalBond > 0) { + request.currency.safeTransferFrom(msg.sender, address(this), totalBond); + } + + StoreInterface store = _getStore(); + + // Avoids stack too deep compilation error. + { + // Along with the final fee, "burn" part of the loser's bond to ensure that a larger bond always makes it + // proportionally more expensive to delay the resolution even if the proposer and disputer are the same + // party. + uint256 burnedBond = _computeBurnedBond(request); + + // The total fee is the burned bond and the final fee added together. + uint256 totalFee = finalFee.add(burnedBond); + + if (totalFee > 0) { + request.currency.safeIncreaseAllowance(address(store), totalFee); + _getStore().payOracleFeesErc20(address(request.currency), FixedPoint.Unsigned(totalFee)); + } + } + + _getOracle().requestPrice(identifier, timestamp, _stampAncillaryData(ancillaryData, requester)); + + // Compute refund. + uint256 refund = 0; + if (request.reward > 0 && request.refundOnDispute) { + refund = request.reward; + request.reward = 0; + request.currency.safeTransfer(requester, refund); + } + + emit DisputePrice( + requester, + request.proposer, + disputer, + identifier, + timestamp, + ancillaryData, + request.proposedPrice + ); + + // Callback. + if (address(requester).isContract()) + try OptimisticRequester(requester).priceDisputed(identifier, timestamp, ancillaryData, refund) {} catch {} + } + + /** + * @notice Disputes a price value for an existing price request with an active proposal. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return totalBond the amount that's pulled from the disputer's wallet as a bond. The bond will be returned to + * the disputer once settled if the dispute was valid (the proposal was incorrect). + */ + function disputePrice( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) external override returns (uint256 totalBond) { + // Note: re-entrancy guard is done in the inner call. + return disputePriceFor(msg.sender, requester, identifier, timestamp, ancillaryData); + } + + /** + * @notice Retrieves a price that was previously requested by a caller. Reverts if the request is not settled + * or settleable. Note: this method is not view so that this call may actually settle the price request if it + * hasn't been settled. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return resolved price. + */ + function settleAndGetPrice( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) external override nonReentrant returns (int256) { + if (_getState(msg.sender, identifier, timestamp, ancillaryData) != State.Settled) { + _settle(msg.sender, identifier, timestamp, ancillaryData); + } + + return _getRequest(msg.sender, identifier, timestamp, ancillaryData).resolvedPrice; + } + + /** + * @notice Attempts to settle an outstanding price request. Will revert if it isn't settleable. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return payout the amount that the "winner" (proposer or disputer) receives on settlement. This amount includes + * the returned bonds as well as additional rewards. + */ + function settle( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) external override nonReentrant returns (uint256 payout) { + return _settle(requester, identifier, timestamp, ancillaryData); + } + + /** + * @notice Gets the current data structure containing all information about a price request. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return the Request data structure. + */ + function getRequest( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) public view override nonReentrantView returns (Request memory) { + return _getRequest(requester, identifier, timestamp, ancillaryData); + } + + /** + * @notice Computes the current state of a price request. See the State enum for more details. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return the State. + */ + function getState( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) public view override nonReentrantView returns (State) { + return _getState(requester, identifier, timestamp, ancillaryData); + } + + /** + * @notice Checks if a given request has resolved, expired or been settled (i.e the optimistic oracle has a price). + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @return boolean indicating true if price exists and false if not. + */ + function hasPrice( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) public view override nonReentrantView returns (bool) { + State state = _getState(requester, identifier, timestamp, ancillaryData); + return state == State.Settled || state == State.Resolved || state == State.Expired; + } + + /** + * @notice Generates stamped ancillary data in the format that it would be used in the case of a price dispute. + * @param ancillaryData ancillary data of the price being requested. + * @param requester sender of the initial price request. + * @return the stamped ancillary bytes. + */ + function stampAncillaryData( + bytes memory ancillaryData, + address requester + ) public pure override returns (bytes memory) { + return _stampAncillaryData(ancillaryData, requester); + } + + function _getId( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) private pure returns (bytes32) { + return keccak256(abi.encodePacked(requester, identifier, timestamp, ancillaryData)); + } + + function _settle( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) private returns (uint256 payout) { + State state = _getState(requester, identifier, timestamp, ancillaryData); + + // Set it to settled so this function can never be entered again. + Request storage request = _getRequest(requester, identifier, timestamp, ancillaryData); + request.settled = true; + + if (state == State.Expired) { + // In the expiry case, just pay back the proposer's bond and final fee along with the reward. + request.resolvedPrice = request.proposedPrice; + payout = request.bond.add(request.finalFee).add(request.reward); + request.currency.safeTransfer(request.proposer, payout); + } else if (state == State.Resolved) { + // In the Resolved case, pay either the disputer or the proposer the entire payout (+ bond and reward). + request.resolvedPrice = _getOracle().getPrice( + identifier, + timestamp, + _stampAncillaryData(ancillaryData, requester) + ); + bool disputeSuccess = request.resolvedPrice != request.proposedPrice; + uint256 bond = request.bond; + + // Unburned portion of the loser's bond = 1 - burned bond. + uint256 unburnedBond = bond.sub(_computeBurnedBond(request)); + + // Winner gets: + // - Their bond back. + // - The unburned portion of the loser's bond. + // - Their final fee back. + // - The request reward (if not already refunded -- if refunded, it will be set to 0). + payout = bond.add(unburnedBond).add(request.finalFee).add(request.reward); + request.currency.safeTransfer(disputeSuccess ? request.disputer : request.proposer, payout); + } else { + revert("_settle: not settleable"); + } + + emit Settle( + requester, + request.proposer, + request.disputer, + identifier, + timestamp, + ancillaryData, + request.resolvedPrice, + payout + ); + + // Callback. + if (address(requester).isContract()) + try + OptimisticRequester(requester).priceSettled(identifier, timestamp, ancillaryData, request.resolvedPrice) + {} catch {} + } + + function _getRequest( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) private view returns (Request storage) { + return requests[_getId(requester, identifier, timestamp, ancillaryData)]; + } + + function _computeBurnedBond(Request storage request) private view returns (uint256) { + // burnedBond = floor(bond / 2) + return request.bond.div(2); + } + + function _validateLiveness(uint256 _liveness) private pure { + require(_liveness < 5200 weeks, "Liveness too large"); + require(_liveness > 0, "Liveness cannot be 0"); + } + + function _getState( + address requester, + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData + ) internal view returns (State) { + Request storage request = _getRequest(requester, identifier, timestamp, ancillaryData); + + if (address(request.currency) == address(0)) { + return State.Invalid; + } + + if (request.proposer == address(0)) { + return State.Requested; + } + + if (request.settled) { + return State.Settled; + } + + if (request.disputer == address(0)) { + return request.expirationTime <= getCurrentTime() ? State.Expired : State.Proposed; + } + + return + _getOracle().hasPrice(identifier, timestamp, _stampAncillaryData(ancillaryData, requester)) + ? State.Resolved + : State.Disputed; + } + + function _getOracle() internal view returns (OracleAncillaryInterface) { + return OracleAncillaryInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); + } + + function _getCollateralWhitelist() internal view returns (AddressWhitelist) { + return AddressWhitelist(finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist)); + } + + function _getStore() internal view returns (StoreInterface) { + return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store)); + } + + function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) { + return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); + } + + /** + * @dev We don't handle specifically the case where `ancillaryData` is not already readily translateable in utf8. + * For those cases, we assume that the client will be able to strip out the utf8-translateable part of the + * ancillary data that this contract stamps. + */ + function _stampAncillaryData(bytes memory ancillaryData, address requester) internal pure returns (bytes memory) { + // Since this contract will be the one to formally submit DVM price requests, its useful for voters to know who + // the original requester was. + return AncillaryData.appendKeyValueAddress(ancillaryData, "ooRequester", requester); + } + + function getCurrentTime() public view override(Testable, OptimisticOracleInterface) returns (uint256) { + return Testable.getCurrentTime(); + } +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v2/previous-versions/SkinnyOptimisticOracle.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v2/previous-versions/SkinnyOptimisticOracle.sol new file mode 100644 index 000000000..79e953ab0 --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v2/previous-versions/SkinnyOptimisticOracle.sol @@ -0,0 +1,695 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; +import "@openzeppelin/contracts-v4/utils/Address.sol"; + +import "../interfaces/OptimisticOracleInterface.sol"; +import "../interfaces/SkinnyOptimisticOracleInterface.sol"; + +import "../../data-verification-mechanism/interfaces/StoreInterface.sol"; +import "../../data-verification-mechanism/interfaces/OracleAncillaryInterface.sol"; +import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; +import "../../data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol"; +import "../../data-verification-mechanism/implementation/Constants.sol"; + +import "../../common/implementation/Testable.sol"; +import "../../common/implementation/Lockable.sol"; +import "../../common/implementation/FixedPoint.sol"; +import "../../common/implementation/AncillaryData.sol"; +import "../../common/implementation/AddressWhitelist.sol"; + +/** + * @title Optimistic Requester. + * @notice Optional interface that requesters can implement to receive callbacks. + * @dev This contract does _not_ work with ERC777 collateral currencies or any others that call into the receiver on + * transfer(). Using an ERC777 token would allow a user to maliciously grief other participants (while also losing + * money themselves). + */ +interface OptimisticRequester { + /** + * @notice Callback for proposals. + * @param identifier price identifier being requested. + * @param timestamp timestamp of the price being requested. + * @param ancillaryData ancillary data of the price being requested. + * @param request request params after proposal. + */ + function priceProposed( + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + SkinnyOptimisticOracleInterface.Request memory request + ) external; + + /** + * @notice Callback for disputes. + * @param identifier price identifier being requested. + * @param timestamp timestamp of the price being requested. + * @param ancillaryData ancillary data of the price being requested. + * @param request request params after dispute. + */ + function priceDisputed( + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + SkinnyOptimisticOracleInterface.Request memory request + ) external; + + /** + * @notice Callback for settlement. + * @param identifier price identifier being requested. + * @param timestamp timestamp of the price being requested. + * @param ancillaryData ancillary data of the price being requested. + * @param request request params after settlement. + */ + function priceSettled( + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + SkinnyOptimisticOracleInterface.Request memory request + ) external; +} + +/** + * @title Optimistic Oracle with a different interface and fewer features that emphasizes gas cost reductions. + * @notice Pre-DVM escalation contract that allows faster settlement. + */ +contract SkinnyOptimisticOracle is SkinnyOptimisticOracleInterface, Testable, Lockable { + using SafeMath for uint256; + using SafeERC20 for IERC20; + using Address for address; + + event RequestPrice( + address indexed requester, + bytes32 indexed identifier, + uint32 timestamp, + bytes ancillaryData, + Request request + ); + event ProposePrice( + address indexed requester, + bytes32 indexed identifier, + uint32 timestamp, + bytes ancillaryData, + Request request + ); + event DisputePrice( + address indexed requester, + bytes32 indexed identifier, + uint32 timestamp, + bytes ancillaryData, + Request request + ); + event Settle( + address indexed requester, + bytes32 indexed identifier, + uint32 timestamp, + bytes ancillaryData, + Request request + ); + + // Maps hash of unique request params {identifier, timestamp, ancillary data} to customizable variables such as + // reward and bond amounts. + mapping(bytes32 => bytes32) public requests; + + // Finder to provide addresses for DVM contracts. + FinderInterface public finder; + + // Default liveness value for all price requests. + uint256 public defaultLiveness; + + /** + * @notice Constructor. + * @param _liveness default liveness applied to each price request. + * @param _finderAddress finder to use to get addresses of DVM contracts. + * @param _timerAddress address of the timer contract. Should be 0x0 in prod. + */ + constructor(uint256 _liveness, address _finderAddress, address _timerAddress) Testable(_timerAddress) { + finder = FinderInterface(_finderAddress); + _validateLiveness(_liveness); + defaultLiveness = _liveness; + } + + /** + * @notice Requests a new price. + * @param identifier price identifier being requested. + * @param timestamp timestamp of the price being requested. + * @param ancillaryData ancillary data representing additional args being passed with the price request. + * @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM. + * @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0, + * which could make sense if the contract requests and proposes the value in the same call or + * provides its own reward system. + * @param bond custom proposal bond to set for request. If set to 0, defaults to the final fee. + * @param customLiveness custom proposal liveness to set for request. + * @return totalBond default bond + final fee that the proposer and disputer will be required to pay. + */ + function requestPrice( + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + IERC20 currency, + uint256 reward, + uint256 bond, + uint256 customLiveness + ) external override nonReentrant returns (uint256 totalBond) { + bytes32 requestId = _getId(msg.sender, identifier, timestamp, ancillaryData); + require(requests[requestId] == bytes32(0), "Request already initialized"); + require(_getIdentifierWhitelist().isIdentifierSupported(identifier), "Unsupported identifier"); + require(_getCollateralWhitelist().isOnWhitelist(address(currency)), "Unsupported currency"); + require(timestamp <= getCurrentTime(), "Timestamp in future"); + require( + _stampAncillaryData(ancillaryData, msg.sender).length <= ancillaryBytesLimit, + "Ancillary Data too long" + ); + uint256 finalFee = _getStore().computeFinalFee(address(currency)).rawValue; + + // Associate new request with ID + Request memory request; + request.currency = currency; + request.reward = reward; + request.finalFee = finalFee; + request.bond = bond != 0 ? bond : finalFee; + request.customLiveness = customLiveness; + _storeRequestHash(requestId, request); + + if (reward > 0) currency.safeTransferFrom(msg.sender, address(this), reward); + + emit RequestPrice(msg.sender, identifier, timestamp, ancillaryData, request); + + return request.bond.add(finalFee); + } + + /** + * @notice Proposes a price value on another address' behalf. Note: this address will receive any rewards that come + * from this proposal. However, any bonds are pulled from the caller. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters whose hash must match the request that the caller wants to + * propose a price for. + * @param proposer address to set as the proposer. + * @param proposedPrice price being proposed. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the proposer once settled if the proposal is correct. + */ + function proposePriceFor( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request, + address proposer, + int256 proposedPrice + ) public override nonReentrant returns (uint256 totalBond) { + require(proposer != address(0), "Proposer address must be non 0"); + require( + _getState(requester, identifier, timestamp, ancillaryData, request) == + OptimisticOracleInterface.State.Requested, + "Must be requested" + ); + bytes32 requestId = _getId(requester, identifier, timestamp, ancillaryData); + _validateRequestHash(requestId, request); + + // Associate newly proposed request params with ID + Request memory proposedRequest = Request({ + proposer: proposer, // Modified + disputer: request.disputer, + currency: request.currency, + settled: request.settled, + proposedPrice: proposedPrice, // Modified + resolvedPrice: request.resolvedPrice, + expirationTime: getCurrentTime().add( + request.customLiveness != 0 ? request.customLiveness : defaultLiveness + ), // Modified + reward: request.reward, + finalFee: request.finalFee, + bond: request.bond, + customLiveness: request.customLiveness + }); + _storeRequestHash(requestId, proposedRequest); + + totalBond = request.bond.add(request.finalFee); + if (totalBond > 0) request.currency.safeTransferFrom(msg.sender, address(this), totalBond); + + emit ProposePrice(requester, identifier, timestamp, ancillaryData, proposedRequest); + + // Callback. + if (address(requester).isContract()) + try + OptimisticRequester(requester).priceProposed(identifier, timestamp, ancillaryData, proposedRequest) + {} catch {} + } + + /** + * @notice Proposes a price value where caller is the proposer. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters whose hash must match the request that the caller wants to + * propose a price for. + * @param proposedPrice price being proposed. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the proposer once settled if the proposal is correct. + */ + function proposePrice( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request, + int256 proposedPrice + ) external override returns (uint256 totalBond) { + // Note: re-entrancy guard is done in the inner call. + return proposePriceFor(requester, identifier, timestamp, ancillaryData, request, msg.sender, proposedPrice); + } + + /** + * @notice Combines logic of requestPrice and proposePrice while taking advantage of gas savings from not having to + * overwrite Request params that a normal requestPrice() => proposePrice() flow would entail. Note: The proposer + * will receive any rewards that come from this proposal. However, any bonds are pulled from the caller. + * @dev The caller is the requester, but the proposer can be customized. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM. + * @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0, + * which could make sense if the contract requests and proposes the value in the same call or + * provides its own reward system. + * @param bond custom proposal bond to set for request. If set to 0, defaults to the final fee. + * @param customLiveness custom proposal liveness to set for request. + * @param proposer address to set as the proposer. + * @param proposedPrice price being proposed. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the proposer once settled if the proposal is correct. + */ + function requestAndProposePriceFor( + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + IERC20 currency, + uint256 reward, + uint256 bond, + uint256 customLiveness, + address proposer, + int256 proposedPrice + ) external override nonReentrant returns (uint256 totalBond) { + bytes32 requestId = _getId(msg.sender, identifier, timestamp, ancillaryData); + require(requests[requestId] == bytes32(0), "Request already initialized"); + require(proposer != address(0), "proposer address must be non 0"); + require(_getIdentifierWhitelist().isIdentifierSupported(identifier), "Unsupported identifier"); + require(_getCollateralWhitelist().isOnWhitelist(address(currency)), "Unsupported currency"); + require(timestamp <= getCurrentTime(), "Timestamp in future"); + require( + _stampAncillaryData(ancillaryData, msg.sender).length <= ancillaryBytesLimit, + "Ancillary Data too long" + ); + uint256 finalFee = _getStore().computeFinalFee(address(currency)).rawValue; + + // Associate new request with ID + Request memory request; + request.currency = currency; + request.reward = reward; + request.finalFee = finalFee; + request.bond = bond != 0 ? bond : finalFee; + request.customLiveness = customLiveness; + request.proposer = proposer; + request.proposedPrice = proposedPrice; + request.expirationTime = getCurrentTime().add(customLiveness != 0 ? customLiveness : defaultLiveness); + _storeRequestHash(requestId, request); + + // Pull reward from requester, who is the caller. + if (reward > 0) currency.safeTransferFrom(msg.sender, address(this), reward); + // Pull proposal bond from caller. + totalBond = request.bond.add(request.finalFee); + if (totalBond > 0) currency.safeTransferFrom(msg.sender, address(this), totalBond); + + emit RequestPrice(msg.sender, identifier, timestamp, ancillaryData, request); + emit ProposePrice(msg.sender, identifier, timestamp, ancillaryData, request); + + // Callback. + if (address(msg.sender).isContract()) + try OptimisticRequester(msg.sender).priceProposed(identifier, timestamp, ancillaryData, request) {} catch {} + } + + /** + * @notice Disputes a price request with an active proposal on another address' behalf. Note: this address will + * receive any rewards that come from this dispute. However, any bonds are pulled from the caller. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters whose hash must match the request that the caller wants to + * dispute. + * @param disputer address to set as the disputer. + * @param requester sender of the initial price request. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the disputer once settled if the dispute was valid (the proposal was incorrect). + */ + function disputePriceFor( + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request, + address disputer, + address requester + ) public override nonReentrant returns (uint256 totalBond) { + require(disputer != address(0), "disputer address must be non 0"); + require( + _getState(requester, identifier, timestamp, ancillaryData, request) == + OptimisticOracleInterface.State.Proposed, + "Must be proposed" + ); + bytes32 requestId = _getId(requester, identifier, timestamp, ancillaryData); + _validateRequestHash(requestId, request); + + // Associate newly disputed request params with ID + Request memory disputedRequest = Request({ + proposer: request.proposer, + disputer: disputer, // Modified + currency: request.currency, + settled: request.settled, + proposedPrice: request.proposedPrice, + resolvedPrice: request.resolvedPrice, + expirationTime: request.expirationTime, + reward: request.reward, + finalFee: request.finalFee, + bond: request.bond, + customLiveness: request.customLiveness + }); + _storeRequestHash(requestId, disputedRequest); + + totalBond = request.bond.add(request.finalFee); + if (totalBond > 0) request.currency.safeTransferFrom(msg.sender, address(this), totalBond); + + StoreInterface store = _getStore(); + + // Avoids stack too deep compilation error. + { + // Along with the final fee, "burn" part of the loser's bond to ensure that a larger bond always makes it + // proportionally more expensive to delay the resolution even if the proposer and disputer are the same + // party. + uint256 burnedBond = _computeBurnedBond(disputedRequest); + + // The total fee is the burned bond and the final fee added together. + uint256 totalFee = request.finalFee.add(burnedBond); + + if (totalFee > 0) { + request.currency.safeIncreaseAllowance(address(store), totalFee); + _getStore().payOracleFeesErc20(address(request.currency), FixedPoint.Unsigned(totalFee)); + } + } + + _getOracle().requestPrice(identifier, timestamp, _stampAncillaryData(ancillaryData, requester)); + + emit DisputePrice(requester, identifier, timestamp, ancillaryData, disputedRequest); + + // Callback. + if (address(requester).isContract()) + try + OptimisticRequester(requester).priceDisputed(identifier, timestamp, ancillaryData, disputedRequest) + {} catch {} + } + + /** + * @notice Disputes a price request with an active proposal where caller is the disputer. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters whose hash must match the request that the caller wants to + * dispute. + * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to + * the disputer once settled if the dispute was valid (the proposal was incorrect). + */ + function disputePrice( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request + ) external override returns (uint256 totalBond) { + // Note: re-entrancy guard is done in the inner call. + return disputePriceFor(identifier, timestamp, ancillaryData, request, msg.sender, requester); + } + + /** + * @notice Attempts to settle an outstanding price request. Will revert if it isn't settleable. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters whose hash must match the request that the caller wants to + * settle. + * @return payout the amount that the "winner" (proposer or disputer) receives on settlement. This amount includes + * the returned bonds as well as additional rewards. + * @return resolvedPrice the price that the request settled to. + */ + function settle( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request + ) external override nonReentrant returns (uint256 payout, int256 resolvedPrice) { + return _settle(requester, identifier, timestamp, ancillaryData, request); + } + + /** + * @notice Computes the current state of a price request. See the State enum for more details. + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters. + * @return the State. + */ + function getState( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request + ) external override nonReentrant returns (OptimisticOracleInterface.State) { + return _getState(requester, identifier, timestamp, ancillaryData, request); + } + + /** + * @notice Checks if a given request has resolved, expired or been settled (i.e the optimistic oracle has a price). + * @param requester sender of the initial price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param request price request parameters. The hash of these parameters must match with the request hash that is + * associated with the price request unique ID {requester, identifier, timestamp, ancillaryData}, or this method + * will revert. + * @return boolean indicating true if price exists and false if not. + */ + function hasPrice( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request + ) public override nonReentrant returns (bool) { + bytes32 requestId = _getId(requester, identifier, timestamp, ancillaryData); + _validateRequestHash(requestId, request); + OptimisticOracleInterface.State state = _getState(requester, identifier, timestamp, ancillaryData, request); + return + state == OptimisticOracleInterface.State.Settled || + state == OptimisticOracleInterface.State.Resolved || + state == OptimisticOracleInterface.State.Expired; + } + + /** + * @notice Generates stamped ancillary data in the format that it would be used in the case of a price dispute. + * @param ancillaryData ancillary data of the price being requested. + * @param requester sender of the initial price request. + * @return the stamped ancillary bytes. + */ + function stampAncillaryData( + bytes memory ancillaryData, + address requester + ) public pure override returns (bytes memory) { + return _stampAncillaryData(ancillaryData, requester); + } + + /**************************************** + * PRIVATE AND INTERNAL FUNCTIONS * + ****************************************/ + // Returns hash of unique request identifiers. This contract maps request ID hashes to hashes of the request's + // parameters. + function _getId( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData + ) private pure returns (bytes32) { + return keccak256(abi.encode(requester, identifier, timestamp, ancillaryData)); + } + + // Returns hash of request parameters. These are mapped to the unique request ID to track a request's lifecycle. + function _getRequestHash(Request memory request) private pure returns (bytes32) { + return keccak256(abi.encode(request)); + } + + // Resolves a price request that has expired or been disputed and a price is available from the DVM. This will + // revert if the unique request ID does not match the hashed request parameters. This also marks the request + // as settled, therefore this method can only be triggered once per eligible request. + function _settle( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request + ) private returns (uint256 payout, int256 resolvedPrice) { + bytes32 requestId = _getId(requester, identifier, timestamp, ancillaryData); + _validateRequestHash(requestId, request); + + // Associate settled request params with ID. + Request memory settledRequest = Request({ + proposer: request.proposer, + disputer: request.disputer, + currency: request.currency, + settled: true, // Modified + proposedPrice: request.proposedPrice, + resolvedPrice: request.resolvedPrice, + expirationTime: request.expirationTime, + reward: request.reward, + finalFee: request.finalFee, + bond: request.bond, + customLiveness: request.customLiveness + }); + + OptimisticOracleInterface.State state = _getState(requester, identifier, timestamp, ancillaryData, request); + if (state == OptimisticOracleInterface.State.Expired) { + // In the expiry case, just pay back the proposer's bond and final fee along with the reward. + resolvedPrice = request.proposedPrice; + settledRequest.resolvedPrice = resolvedPrice; + payout = request.bond.add(request.finalFee).add(request.reward); + request.currency.safeTransfer(request.proposer, payout); + } else if (state == OptimisticOracleInterface.State.Resolved) { + // In the Resolved case, pay either the disputer or the proposer the entire payout (+ bond and reward). + resolvedPrice = _getOracle().getPrice(identifier, timestamp, _stampAncillaryData(ancillaryData, requester)); + settledRequest.resolvedPrice = resolvedPrice; + bool disputeSuccess = settledRequest.resolvedPrice != request.proposedPrice; + + // Winner gets: + // - Their bond back. + // - The unburned portion of the loser's bond: proposal bond (not including final fee) - burned bond. + // - Their final fee back. + // - The request reward (if not already refunded -- if refunded, it will be set to 0). + payout = request.bond.add(request.bond.sub(_computeBurnedBond(settledRequest))).add(request.finalFee).add( + request.reward + ); + request.currency.safeTransfer(disputeSuccess ? request.disputer : request.proposer, payout); + } else { + revert("Already settled or not settleable"); + } + + _storeRequestHash(requestId, settledRequest); + emit Settle(requester, identifier, timestamp, ancillaryData, settledRequest); + + // Callback. + if (address(requester).isContract()) + try + OptimisticRequester(requester).priceSettled(identifier, timestamp, ancillaryData, settledRequest) + {} catch {} + } + + function _computeBurnedBond(Request memory request) private pure returns (uint256) { + // burnedBond = floor(bond / 2) + return request.bond.div(2); + } + + function _validateLiveness(uint256 liveness) private pure { + require(liveness < 5200 weeks, "Liveness too large"); + require(liveness > 0, "Liveness cannot be 0"); + } + + function _validateRequestHash(bytes32 requestId, Request memory request) private view { + require( + requests[requestId] == _getRequestHash(request), + "Hashed request params do not match existing request hash" + ); + } + + function _storeRequestHash(bytes32 requestId, Request memory request) internal { + requests[requestId] = _getRequestHash(request); + } + + function _getState( + address requester, + bytes32 identifier, + uint32 timestamp, + bytes memory ancillaryData, + Request memory request + ) internal view returns (OptimisticOracleInterface.State) { + // Note: This function does not check whether all of the _request parameter values are correct. For example, + // the request.reward could be any value and it would not impact this function's return value. Therefore, it + // is the caller's responsibility to check that _request matches with the expected ID corresponding to + // {requester, identifier, timestamp, ancillaryData} via _validateRequestHash(). + if (address(request.currency) == address(0)) return OptimisticOracleInterface.State.Invalid; + + if (request.proposer == address(0)) return OptimisticOracleInterface.State.Requested; + + if (request.settled) return OptimisticOracleInterface.State.Settled; + + if (request.disputer == address(0)) + return + request.expirationTime <= getCurrentTime() + ? OptimisticOracleInterface.State.Expired + : OptimisticOracleInterface.State.Proposed; + + return + _getOracle().hasPrice(identifier, timestamp, _stampAncillaryData(ancillaryData, requester)) + ? OptimisticOracleInterface.State.Resolved + : OptimisticOracleInterface.State.Disputed; + } + + function _getOracle() internal view returns (OracleAncillaryInterface) { + return OracleAncillaryInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); + } + + function _getCollateralWhitelist() internal view returns (AddressWhitelist) { + return AddressWhitelist(finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist)); + } + + function _getStore() internal view returns (StoreInterface) { + return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store)); + } + + function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) { + return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); + } + + /** + * @dev We don't handle specifically the case where `ancillaryData` is not already readily translateable in utf8. + * For those cases, we assume that the client will be able to strip out the utf8-translateable part of the + * ancillary data that this contract stamps. + */ + function _stampAncillaryData(bytes memory ancillaryData, address requester) internal pure returns (bytes memory) { + // Since this contract will be the one to formally submit DVM price requests, its useful for voters to know who + // the original requester was. + return AncillaryData.appendKeyValueAddress(ancillaryData, "ooRequester", requester); + } +} + +/** + * @notice This is the SkinnyOptimisticOracle contract that should be deployed on live networks. It is exactly the same + * as the regular SkinnyOptimisticOracle contract, but it overrides getCurrentTime to make the call a simply return + * block.timestamp with no branching or storage queries. + */ +contract SkinnyOptimisticOracleProd is SkinnyOptimisticOracle { + constructor( + uint256 _liveness, + address _finderAddress, + address _timerAddress + ) SkinnyOptimisticOracle(_liveness, _finderAddress, _timerAddress) {} + + function getCurrentTime() public view virtual override returns (uint256) { + return block.timestamp; + } +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v2/test/OptimisticRequesterTest.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v2/test/OptimisticRequesterTest.sol new file mode 100644 index 000000000..bd52fdd20 --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v2/test/OptimisticRequesterTest.sol @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; + +import "../implementation/OptimisticOracleV2.sol"; + +// This is just a test contract to make requests to the optimistic oracle. +contract OptimisticRequesterTest is OptimisticRequester { + OptimisticOracleV2 optimisticOracle; + bool public shouldRevert = false; + + // State variables to track incoming calls. + bytes32 public identifier; + uint256 public timestamp; + bytes public ancillaryData; + uint256 public refund; + int256 public price; + + // Implement collateralCurrency so that this contract simulates a financial contract whose collateral + // token can be fetched by off-chain clients. + IERC20 public collateralCurrency; + + // Manually set an expiration timestamp to simulate expiry price requests + uint256 public expirationTimestamp; + + constructor(OptimisticOracleV2 _optimisticOracle) { + optimisticOracle = _optimisticOracle; + } + + function requestPrice( + bytes32 _identifier, + uint256 _timestamp, + bytes memory _ancillaryData, + IERC20 currency, + uint256 reward + ) external { + // Set collateral currency to last requested currency: + collateralCurrency = currency; + + currency.approve(address(optimisticOracle), reward); + optimisticOracle.requestPrice(_identifier, _timestamp, _ancillaryData, currency, reward); + } + + function settleAndGetPrice( + bytes32 _identifier, + uint256 _timestamp, + bytes memory _ancillaryData + ) external returns (int256) { + return optimisticOracle.settleAndGetPrice(_identifier, _timestamp, _ancillaryData); + } + + function setBond(bytes32 _identifier, uint256 _timestamp, bytes memory _ancillaryData, uint256 bond) external { + optimisticOracle.setBond(_identifier, _timestamp, _ancillaryData, bond); + } + + function setRefundOnDispute(bytes32 _identifier, uint256 _timestamp, bytes memory _ancillaryData) external { + optimisticOracle.setRefundOnDispute(_identifier, _timestamp, _ancillaryData); + } + + function setCustomLiveness( + bytes32 _identifier, + uint256 _timestamp, + bytes memory _ancillaryData, + uint256 customLiveness + ) external { + optimisticOracle.setCustomLiveness(_identifier, _timestamp, _ancillaryData, customLiveness); + } + + function setEventBased(bytes32 _identifier, uint256 _timestamp, bytes memory _ancillaryData) external { + optimisticOracle.setEventBased(_identifier, _timestamp, _ancillaryData); + } + + function setCallbacks( + bytes32 _identifier, + uint256 _timestamp, + bytes memory _ancillaryData, + bool _callbackOnPriceProposed, + bool _callbackOnPriceDisputed, + bool _callbackOnPriceSettled + ) external { + optimisticOracle.setCallbacks( + _identifier, + _timestamp, + _ancillaryData, + _callbackOnPriceProposed, + _callbackOnPriceDisputed, + _callbackOnPriceSettled + ); + } + + function setRevert(bool _shouldRevert) external { + shouldRevert = _shouldRevert; + } + + function setExpirationTimestamp(uint256 _expirationTimestamp) external { + expirationTimestamp = _expirationTimestamp; + } + + function clearState() external { + delete identifier; + delete timestamp; + delete refund; + delete price; + } + + function priceProposed(bytes32 _identifier, uint256 _timestamp, bytes memory _ancillaryData) external override { + require(!shouldRevert); + identifier = _identifier; + timestamp = _timestamp; + ancillaryData = _ancillaryData; + } + + function priceDisputed( + bytes32 _identifier, + uint256 _timestamp, + bytes memory _ancillaryData, + uint256 _refund + ) external override { + require(!shouldRevert); + identifier = _identifier; + timestamp = _timestamp; + ancillaryData = _ancillaryData; + refund = _refund; + } + + function priceSettled( + bytes32 _identifier, + uint256 _timestamp, + bytes memory _ancillaryData, + int256 _price + ) external override { + require(!shouldRevert); + identifier = _identifier; + timestamp = _timestamp; + ancillaryData = _ancillaryData; + price = _price; + } +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v2/test/SkinnyOptimisticRequesterTest.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v2/test/SkinnyOptimisticRequesterTest.sol new file mode 100644 index 000000000..5c5d4b761 --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v2/test/SkinnyOptimisticRequesterTest.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; + +import "../../data-verification-mechanism/interfaces/StoreInterface.sol"; +import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; +import "../previous-versions/SkinnyOptimisticOracle.sol"; +import "../../data-verification-mechanism/implementation/Constants.sol"; + +// This is just a test contract to make requests to the optimistic oracle. +contract SkinnyOptimisticRequesterTest { + using SafeMath for uint256; + + SkinnyOptimisticOracle optimisticOracle; + bool public shouldRevert = false; + + // Finder to provide addresses for DVM contracts. + FinderInterface public finder; + + // State variables to track incoming calls. + bytes32 public identifier; + uint32 public timestamp; + bytes public ancillaryData; + SkinnyOptimisticOracle.Request public request; + + // Manually set an expiration timestamp to simulate expiry price requests + uint256 public expirationTimestamp; + + constructor(SkinnyOptimisticOracle _optimisticOracle, FinderInterface _finderAddress) { + optimisticOracle = _optimisticOracle; + finder = _finderAddress; + } + + function requestAndProposePriceFor( + bytes32 _identifier, + uint32 _timestamp, + bytes memory _ancillaryData, + IERC20 currency, + uint256 reward, + uint256 bond, + uint256 customLiveness, + address proposer, + int256 proposedPrice + ) external { + uint256 finalFee = _getStore().computeFinalFee(address(currency)).rawValue; + + currency.approve(address(optimisticOracle), reward.add(bond).add(finalFee)); + optimisticOracle.requestAndProposePriceFor( + _identifier, + _timestamp, + _ancillaryData, + currency, + reward, + bond, + customLiveness, + proposer, + proposedPrice + ); + } + + function requestPrice( + bytes32 _identifier, + uint32 _timestamp, + bytes memory _ancillaryData, + IERC20 currency, + uint256 reward, + uint256 bond, + uint256 customLiveness + ) external { + currency.approve(address(optimisticOracle), reward); + optimisticOracle.requestPrice(_identifier, _timestamp, _ancillaryData, currency, reward, bond, customLiveness); + } + + function setExpirationTimestamp(uint256 _expirationTimestamp) external { + expirationTimestamp = _expirationTimestamp; + } + + function setRevert(bool _shouldRevert) external { + shouldRevert = _shouldRevert; + } + + function priceProposed( + bytes32 _identifier, + uint32 _timestamp, + bytes memory _ancillaryData, + SkinnyOptimisticOracle.Request memory _request + ) external { + require(!shouldRevert); + identifier = _identifier; + timestamp = _timestamp; + ancillaryData = _ancillaryData; + request = _request; + } + + function priceDisputed( + bytes32 _identifier, + uint32 _timestamp, + bytes memory _ancillaryData, + SkinnyOptimisticOracle.Request memory _request + ) external { + require(!shouldRevert); + identifier = _identifier; + timestamp = _timestamp; + ancillaryData = _ancillaryData; + request = _request; + } + + function priceSettled( + bytes32 _identifier, + uint32 _timestamp, + bytes memory _ancillaryData, + SkinnyOptimisticOracle.Request memory _request + ) external { + require(!shouldRevert); + identifier = _identifier; + timestamp = _timestamp; + ancillaryData = _ancillaryData; + request = _request; + } + + function _getStore() internal view returns (StoreInterface) { + return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store)); + } +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v2/test/SkinnyOptimisticV2RequesterTest.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v2/test/SkinnyOptimisticV2RequesterTest.sol new file mode 100644 index 000000000..bb7486a6b --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v2/test/SkinnyOptimisticV2RequesterTest.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; + +import "../../data-verification-mechanism/interfaces/StoreInterface.sol"; +import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; +import "../../data-verification-mechanism/implementation/Constants.sol"; +import "../implementation/SkinnyOptimisticOracleV2.sol"; + +// This is just a test contract to make requests to the optimistic oracle. +contract SkinnyOptimisticV2RequesterTest { + using SafeMath for uint256; + + SkinnyOptimisticOracleV2 optimisticOracle; + bool public shouldRevert = false; + + // Finder to provide addresses for DVM contracts. + FinderInterface public finder; + + // State variables to track incoming calls. + bytes32 public identifier; + uint32 public timestamp; + bytes public ancillaryData; + SkinnyOptimisticOracleV2.Request public request; + + // Manually set an expiration timestamp to simulate expiry price requests + uint256 public expirationTimestamp; + + constructor(SkinnyOptimisticOracleV2 _optimisticOracle, FinderInterface _finderAddress) { + optimisticOracle = _optimisticOracle; + finder = _finderAddress; + } + + function requestAndProposePriceFor( + bytes32 _identifier, + uint32 _timestamp, + bytes memory _ancillaryData, + IERC20 currency, + uint256 reward, + SkinnyOptimisticOracleV2Interface.RequestSettings memory requestSettings, + address proposer, + int256 proposedPrice + ) external { + uint256 finalFee = _getStore().computeFinalFee(address(currency)).rawValue; + + currency.approve(address(optimisticOracle), reward.add(requestSettings.bond).add(finalFee)); + optimisticOracle.requestAndProposePriceFor( + _identifier, + _timestamp, + _ancillaryData, + currency, + reward, + requestSettings, + proposer, + proposedPrice + ); + } + + function requestPrice( + bytes32 _identifier, + uint32 _timestamp, + bytes memory _ancillaryData, + IERC20 currency, + uint256 reward, + SkinnyOptimisticOracleV2Interface.RequestSettings memory requestSettings + ) external { + currency.approve(address(optimisticOracle), reward); + optimisticOracle.requestPrice(_identifier, _timestamp, _ancillaryData, currency, reward, requestSettings); + } + + function setExpirationTimestamp(uint256 _expirationTimestamp) external { + expirationTimestamp = _expirationTimestamp; + } + + function setRevert(bool _shouldRevert) external { + shouldRevert = _shouldRevert; + } + + function priceProposed( + bytes32 _identifier, + uint32 _timestamp, + bytes memory _ancillaryData, + SkinnyOptimisticOracleV2.Request memory _request + ) external { + require(!shouldRevert); + identifier = _identifier; + timestamp = _timestamp; + ancillaryData = _ancillaryData; + request = _request; + } + + function priceDisputed( + bytes32 _identifier, + uint32 _timestamp, + bytes memory _ancillaryData, + SkinnyOptimisticOracleV2.Request memory _request + ) external { + require(!shouldRevert); + identifier = _identifier; + timestamp = _timestamp; + ancillaryData = _ancillaryData; + request = _request; + } + + function priceSettled( + bytes32 _identifier, + uint32 _timestamp, + bytes memory _ancillaryData, + SkinnyOptimisticOracleV2.Request memory _request + ) external { + require(!shouldRevert); + identifier = _identifier; + timestamp = _timestamp; + ancillaryData = _ancillaryData; + request = _request; + } + + function _getStore() internal view returns (StoreInterface) { + return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store)); + } +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/ClaimData.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/ClaimData.sol new file mode 100644 index 000000000..31698d73d --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/ClaimData.sol @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import { AncillaryData as ClaimData } from "../../common/implementation/AncillaryData.sol"; diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/OptimisticOracleV3.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/OptimisticOracleV3.sol new file mode 100644 index 000000000..c3d5ff1a8 --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/OptimisticOracleV3.sol @@ -0,0 +1,488 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +import "../interfaces/OptimisticOracleV3CallbackRecipientInterface.sol"; +import "../interfaces/OptimisticOracleV3Interface.sol"; +import "../interfaces/EscalationManagerInterface.sol"; + +import "../../data-verification-mechanism/implementation/Constants.sol"; +import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; +import "../../data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol"; +import "../../data-verification-mechanism/interfaces/OracleAncillaryInterface.sol"; +import "../../data-verification-mechanism/interfaces/StoreInterface.sol"; + +import "../../common/implementation/AddressWhitelist.sol"; +import "../../common/implementation/AncillaryData.sol"; +import "../../common/implementation/Lockable.sol"; +import "../../common/implementation/MultiCaller.sol"; + +/** + * @title Optimistic Oracle V3. + * @notice The OOv3 is used to assert truths about the world which are verified using an optimistic escalation game. + * @dev Core idea: an asserter makes a statement about a truth, calling "assertTruth". If this statement is not + * challenged, it is taken as the state of the world. If challenged, it is arbitrated using the UMA DVM, or if + * configured, an escalation manager. Escalation managers enable integrations to define their own security properties and + * tradeoffs, enabling the notion of "sovereign security". + */ + +contract OptimisticOracleV3 is OptimisticOracleV3Interface, Lockable, Ownable, MultiCaller { + using SafeERC20 for IERC20; + + FinderInterface public immutable finder; // Finder used to discover other UMA ecosystem contracts. + + // Cached UMA parameters. + address public cachedOracle; + mapping(address => WhitelistedCurrency) public cachedCurrencies; + mapping(bytes32 => bool) public cachedIdentifiers; + + mapping(bytes32 => Assertion) public assertions; // All assertions made by the Optimistic Oracle V3. + + uint256 public burnedBondPercentage; // Percentage of the bond that is paid to the UMA store if the assertion is disputed. + + bytes32 public constant defaultIdentifier = "ASSERT_TRUTH"; + int256 public constant numericalTrue = 1e18; // Numerical representation of true. + IERC20 public defaultCurrency; + uint64 public defaultLiveness; + + /** + * @notice Construct the OptimisticOracleV3 contract. + * @param _finder keeps track of all contracts within the UMA system based on their interfaceName. + * @param _defaultCurrency the default currency to bond asserters in assertTruthWithDefaults. + * @param _defaultLiveness the default liveness for assertions in assertTruthWithDefaults. + */ + constructor(FinderInterface _finder, IERC20 _defaultCurrency, uint64 _defaultLiveness) { + finder = _finder; + setAdminProperties(_defaultCurrency, _defaultLiveness, 0.5e18); + } + + /** + * @notice Sets the default currency, liveness, and burned bond percentage. + * @dev Only callable by the contract owner (UMA governor). + * @param _defaultCurrency the default currency to bond asserters in assertTruthWithDefaults. + * @param _defaultLiveness the default liveness for assertions in assertTruthWithDefaults. + * @param _burnedBondPercentage the percentage of the bond that is sent as fee to UMA Store contract on disputes. + */ + function setAdminProperties( + IERC20 _defaultCurrency, + uint64 _defaultLiveness, + uint256 _burnedBondPercentage + ) public onlyOwner { + require(_burnedBondPercentage <= 1e18, "Burned bond percentage > 100"); + require(_burnedBondPercentage > 0, "Burned bond percentage is 0"); + burnedBondPercentage = _burnedBondPercentage; + defaultCurrency = _defaultCurrency; + defaultLiveness = _defaultLiveness; + syncUmaParams(defaultIdentifier, address(_defaultCurrency)); + + emit AdminPropertiesSet(_defaultCurrency, _defaultLiveness, _burnedBondPercentage); + } + + /** + * @notice Asserts a truth about the world, using the default currency and liveness. No callback recipient or + * escalation manager is enabled. The caller is expected to provide a bond of finalFee/burnedBondPercentage + * (with burnedBondPercentage set to 50%, the bond is 2x final fee) of the default currency. + * @dev The caller must approve this contract to spend at least the result of getMinimumBond(defaultCurrency). + * @param claim the truth claim being asserted. This is an assertion about the world, and is verified by disputers. + * @param asserter account that receives bonds back at settlement. This could be msg.sender or + * any other account that the caller wants to receive the bond at settlement time. + * @return assertionId unique identifier for this assertion. + */ + + function assertTruthWithDefaults(bytes calldata claim, address asserter) external returns (bytes32) { + // Note: re-entrancy guard is done in the inner call. + return + assertTruth( + claim, + asserter, // asserter + address(0), // callbackRecipient + address(0), // escalationManager + defaultLiveness, + defaultCurrency, + getMinimumBond(address(defaultCurrency)), + defaultIdentifier, + bytes32(0) + ); + } + + /** + * @notice Asserts a truth about the world, using a fully custom configuration. + * @dev The caller must approve this contract to spend at least bond amount of currency. + * @param claim the truth claim being asserted. This is an assertion about the world, and is verified by disputers. + * @param asserter account that receives bonds back at settlement. This could be msg.sender or + * any other account that the caller wants to receive the bond at settlement time. + * @param callbackRecipient if configured, this address will receive a function call assertionResolvedCallback and + * assertionDisputedCallback at resolution or dispute respectively. Enables dynamic responses to these events. The + * recipient _must_ implement these callbacks and not revert or the assertion resolution will be blocked. + * @param escalationManager if configured, this address will control escalation properties of the assertion. This + * means a) choosing to arbitrate via the UMA DVM, b) choosing to discard assertions on dispute, or choosing to + * validate disputes. Combining these, the asserter can define their own security properties for the assertion. + * escalationManager also _must_ implement the same callbacks as callbackRecipient. + * @param liveness time to wait before the assertion can be resolved. Assertion can be disputed in this time. + * @param currency bond currency pulled from the caller and held in escrow until the assertion is resolved. + * @param bond amount of currency to pull from the caller and hold in escrow until the assertion is resolved. This + * must be >= getMinimumBond(address(currency)). + * @param identifier UMA DVM identifier to use for price requests in the event of a dispute. Must be pre-approved. + * @param domainId optional domain that can be used to relate this assertion to others in the escalationManager and + * can be used by the configured escalationManager to define custom behavior for groups of assertions. This is + * typically used for "escalation games" by changing bonds or other assertion properties based on the other + * assertions that have come before. If not needed this value should be 0 to save gas. + * @return assertionId unique identifier for this assertion. + */ + function assertTruth( + bytes memory claim, + address asserter, + address callbackRecipient, + address escalationManager, + uint64 liveness, + IERC20 currency, + uint256 bond, + bytes32 identifier, + bytes32 domainId + ) public nonReentrant returns (bytes32 assertionId) { + uint64 time = uint64(getCurrentTime()); + assertionId = _getId(claim, bond, time, liveness, currency, callbackRecipient, escalationManager, identifier); + + require(asserter != address(0), "Asserter cant be 0"); + require(assertions[assertionId].asserter == address(0), "Assertion already exists"); + require(_validateAndCacheIdentifier(identifier), "Unsupported identifier"); + require(_validateAndCacheCurrency(address(currency)), "Unsupported currency"); + require(bond >= getMinimumBond(address(currency)), "Bond amount too low"); + + assertions[assertionId] = Assertion({ + escalationManagerSettings: EscalationManagerSettings({ + arbitrateViaEscalationManager: false, // Default behavior: use the DVM as an oracle. + discardOracle: false, // Default behavior: respect the Oracle result. + validateDisputers: false, // Default behavior: disputer will not be validated. + escalationManager: escalationManager, + assertingCaller: msg.sender + }), + asserter: asserter, + disputer: address(0), + callbackRecipient: callbackRecipient, + currency: currency, + domainId: domainId, + identifier: identifier, + bond: bond, + settled: false, + settlementResolution: false, + assertionTime: time, + expirationTime: time + liveness + }); + + { + EscalationManagerInterface.AssertionPolicy memory assertionPolicy = _getAssertionPolicy(assertionId); + require(!assertionPolicy.blockAssertion, "Assertion not allowed"); // Check if the assertion is permitted. + EscalationManagerSettings storage emSettings = assertions[assertionId].escalationManagerSettings; + (emSettings.arbitrateViaEscalationManager, emSettings.discardOracle, emSettings.validateDisputers) = ( + // Choose which oracle to arbitrate disputes via. If set to true then the escalation manager will + // arbitrate disputes. Else, the DVM arbitrates disputes. This lets integrations "unplug" the DVM. + assertionPolicy.arbitrateViaEscalationManager, + // Choose whether to discard the Oracle result. If true then "throw away" the assertion. To get an + // assertion to be true it must be re-asserted and not disputed. + assertionPolicy.discardOracle, + // Configures if the escalation manager should validate the disputer on assertions. This enables you + // to construct setups such as whitelisted disputers. + assertionPolicy.validateDisputers + ); + } + + currency.safeTransferFrom(msg.sender, address(this), bond); // Pull the bond from the caller. + + emit AssertionMade( + assertionId, + domainId, + claim, + asserter, + callbackRecipient, + escalationManager, + msg.sender, + time + liveness, + currency, + bond, + identifier + ); + } + + /** + * @notice Disputes an assertion. Depending on how the assertion was configured, this may either escalate to the UMA + * DVM or the configured escalation manager for arbitration. + * @dev The caller must approve this contract to spend at least bond amount of currency for the associated assertion. + * @param assertionId unique identifier for the assertion to dispute. + * @param disputer receives bonds back at settlement. + */ + function disputeAssertion(bytes32 assertionId, address disputer) external nonReentrant { + require(disputer != address(0), "Disputer can't be 0"); + Assertion storage assertion = assertions[assertionId]; + require(assertion.asserter != address(0), "Assertion does not exist"); + require(assertion.disputer == address(0), "Assertion already disputed"); + require(assertion.expirationTime > getCurrentTime(), "Assertion is expired"); + require(_isDisputeAllowed(assertionId), "Dispute not allowed"); + + assertion.disputer = disputer; + + assertion.currency.safeTransferFrom(msg.sender, address(this), assertion.bond); + + _oracleRequestPrice(assertionId, assertion.identifier, assertion.assertionTime); + + _callbackOnAssertionDispute(assertionId); + + // Send resolve callback if dispute resolution is discarded + if (assertion.escalationManagerSettings.discardOracle) _callbackOnAssertionResolve(assertionId, false); + + emit AssertionDisputed(assertionId, msg.sender, disputer); + } + + /** + * @notice Resolves an assertion. If the assertion has not been disputed, the assertion is resolved as true and the + * asserter receives the bond. If the assertion has been disputed, the assertion is resolved depending on the oracle + * result. Based on the result, the asserter or disputer receives the bond. If the assertion was disputed then an + * amount of the bond is sent to the UMA Store as an oracle fee based on the burnedBondPercentage. The remainder of + * the bond is returned to the asserter or disputer. + * @param assertionId unique identifier for the assertion to resolve. + */ + function settleAssertion(bytes32 assertionId) public nonReentrant { + Assertion storage assertion = assertions[assertionId]; + require(assertion.asserter != address(0), "Assertion does not exist"); // Revert if assertion does not exist. + require(!assertion.settled, "Assertion already settled"); // Revert if assertion already settled. + assertion.settled = true; + if (assertion.disputer == address(0)) { + // No dispute, settle with the asserter + require(assertion.expirationTime <= getCurrentTime(), "Assertion not expired"); // Revert if not expired. + assertion.settlementResolution = true; + assertion.currency.safeTransfer(assertion.asserter, assertion.bond); + _callbackOnAssertionResolve(assertionId, true); + + emit AssertionSettled(assertionId, assertion.asserter, false, true, msg.sender); + } else { + // Dispute, settle with the disputer. Reverts if price not resolved. + int256 resolvedPrice = _oracleGetPrice(assertionId, assertion.identifier, assertion.assertionTime); + + // If set to discard settlement resolution then false. Else, use oracle value to find resolution. + if (assertion.escalationManagerSettings.discardOracle) assertion.settlementResolution = false; + else assertion.settlementResolution = resolvedPrice == numericalTrue; + + address bondRecipient = resolvedPrice == numericalTrue ? assertion.asserter : assertion.disputer; + + // Calculate oracle fee and the remaining amount of bonds to send to the correct party (asserter or disputer). + uint256 oracleFee = (burnedBondPercentage * assertion.bond) / 1e18; + uint256 bondRecipientAmount = assertion.bond * 2 - oracleFee; + + // Pay out the oracle fee and remaining bonds to the correct party. Note: the oracle fee is sent to the + // Store contract, even if the escalation manager is used to arbitrate disputes. + assertion.currency.safeTransfer(address(_getStore()), oracleFee); + assertion.currency.safeTransfer(bondRecipient, bondRecipientAmount); + + if (!assertion.escalationManagerSettings.discardOracle) + _callbackOnAssertionResolve(assertionId, assertion.settlementResolution); + + emit AssertionSettled(assertionId, bondRecipient, true, assertion.settlementResolution, msg.sender); + } + } + + /** + * @notice Settles an assertion and returns the resolution. + * @param assertionId unique identifier for the assertion to resolve and return the resolution for. + * @return resolution of the assertion. + */ + function settleAndGetAssertionResult(bytes32 assertionId) external returns (bool) { + // Note: re-entrancy guard is done in the inner settleAssertion call. + if (!assertions[assertionId].settled) settleAssertion(assertionId); + return getAssertionResult(assertionId); + } + + /** + * @notice Fetches information about a specific identifier & currency from the UMA contracts and stores a local copy + * of the information within this contract. This is used to save gas when making assertions as we can avoid an + * external call to the UMA contracts to fetch this. + * @param identifier identifier to fetch information for and store locally. + * @param currency currency to fetch information for and store locally. + */ + function syncUmaParams(bytes32 identifier, address currency) public { + cachedOracle = finder.getImplementationAddress(OracleInterfaces.Oracle); + cachedIdentifiers[identifier] = _getIdentifierWhitelist().isIdentifierSupported(identifier); + cachedCurrencies[currency].isWhitelisted = _getCollateralWhitelist().isOnWhitelist(currency); + cachedCurrencies[currency].finalFee = _getStore().computeFinalFee(currency).rawValue; + } + + /** + * @notice Fetches information about a specific assertion and returns it. + * @param assertionId unique identifier for the assertion to fetch information for. + * @return assertion information about the assertion. + */ + function getAssertion(bytes32 assertionId) external view returns (Assertion memory) { + return assertions[assertionId]; + } + + /** + * @notice Fetches the resolution of a specific assertion and returns it. If the assertion has not been settled then + * this will revert. If the assertion was disputed and configured to discard the oracle resolution return false. + * @param assertionId unique identifier for the assertion to fetch the resolution for. + * @return resolution of the assertion. + */ + function getAssertionResult(bytes32 assertionId) public view returns (bool) { + Assertion memory assertion = assertions[assertionId]; + // Return early if not using answer from resolved dispute. + if (assertion.disputer != address(0) && assertion.escalationManagerSettings.discardOracle) return false; + require(assertion.settled, "Assertion not settled"); // Revert if assertion not settled. + return assertion.settlementResolution; + } + + /** + * @notice Returns the current block timestamp. + * @dev Can be overridden to control contract time. + * @return current block timestamp. + */ + function getCurrentTime() public view virtual returns (uint256) { + return block.timestamp; + } + + /** + * @notice Appends information onto an assertionId to construct ancillary data used for dispute resolution. + * @param assertionId unique identifier for the assertion to construct ancillary data for. + * @return ancillaryData stamped assertion information. + */ + function stampAssertion(bytes32 assertionId) public view returns (bytes memory) { + return _stampAssertion(assertionId); + } + + /** + * @notice Returns the minimum bond amount required to make an assertion. This is calculated as the final fee of the + * currency divided by the burnedBondPercentage. If burn percentage is 50% then the min bond is 2x the final fee. + * @param currency currency to calculate the minimum bond for. + * @return minimum bond amount. + */ + function getMinimumBond(address currency) public view returns (uint256) { + uint256 finalFee = cachedCurrencies[currency].finalFee; + return (finalFee * 1e18) / burnedBondPercentage; + } + + // Returns the unique identifier for this assertion. This identifier is used to identify the assertion. + function _getId( + bytes memory claim, + uint256 bond, + uint256 time, + uint64 liveness, + IERC20 currency, + address callbackRecipient, + address escalationManager, + bytes32 identifier + ) internal view returns (bytes32) { + return + keccak256( + abi.encode( + claim, + bond, + time, + liveness, + currency, + callbackRecipient, + escalationManager, + identifier, + msg.sender + ) + ); + } + + // Returns ancillary data for the Oracle request containing assertionId and asserter. + function _stampAssertion(bytes32 assertionId) internal view returns (bytes memory) { + return + AncillaryData.appendKeyValueAddress( + AncillaryData.appendKeyValueBytes32("", "assertionId", assertionId), + "ooAsserter", + assertions[assertionId].asserter + ); + } + + // Returns the Address Whitelist contract to validate the currency. + function _getCollateralWhitelist() internal view returns (AddressWhitelist) { + return AddressWhitelist(finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist)); + } + + // Returns the Identifier Whitelist contract to validate the identifier. + function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) { + return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); + } + + // Returns the Store contract to fetch the final fee. + function _getStore() internal view returns (StoreInterface) { + return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store)); + } + + // Returns the Oracle contract to use on dispute. This can be either UMA DVM or the escalation manager. + function _getOracle(bytes32 assertionId) internal view returns (OracleAncillaryInterface) { + if (assertions[assertionId].escalationManagerSettings.arbitrateViaEscalationManager) + return OracleAncillaryInterface(_getEscalationManager(assertionId)); + return OracleAncillaryInterface(cachedOracle); + } + + // Requests resolving dispute from the Oracle (UMA DVM or escalation manager). + function _oracleRequestPrice(bytes32 assertionId, bytes32 identifier, uint256 time) internal { + _getOracle(assertionId).requestPrice(identifier, time, _stampAssertion(assertionId)); + } + + // Returns the resolved resolution from the Oracle (UMA DVM or escalation manager). + function _oracleGetPrice(bytes32 assertionId, bytes32 identifier, uint256 time) internal view returns (int256) { + return _getOracle(assertionId).getPrice(identifier, time, _stampAssertion(assertionId)); + } + + // Returns the escalation manager address for the assertion. + function _getEscalationManager(bytes32 assertionId) internal view returns (address) { + return assertions[assertionId].escalationManagerSettings.escalationManager; + } + + // Returns the assertion policy parameters from the escalation manager. If no escalation manager is set then return + // default values. + function _getAssertionPolicy( + bytes32 assertionId + ) internal view returns (EscalationManagerInterface.AssertionPolicy memory) { + address em = _getEscalationManager(assertionId); + if (em == address(0)) return EscalationManagerInterface.AssertionPolicy(false, false, false, false); + return EscalationManagerInterface(em).getAssertionPolicy(assertionId); + } + + // Returns whether the dispute is allowed by the escalation manager. If no escalation manager is set or the + // escalation manager is not configured to validate disputers then return true. + function _isDisputeAllowed(bytes32 assertionId) internal view returns (bool) { + if (!assertions[assertionId].escalationManagerSettings.validateDisputers) return true; + address em = assertions[assertionId].escalationManagerSettings.escalationManager; + if (em == address(0)) return true; + return EscalationManagerInterface(em).isDisputeAllowed(assertionId, msg.sender); + } + + // Validates if the identifier is whitelisted by first checking the cache. If not whitelisted in the cache then + // checks it from the identifier whitelist contract and caches result. + function _validateAndCacheIdentifier(bytes32 identifier) internal returns (bool) { + if (cachedIdentifiers[identifier]) return true; + cachedIdentifiers[identifier] = _getIdentifierWhitelist().isIdentifierSupported(identifier); + return cachedIdentifiers[identifier]; + } + + // Validates if the currency is whitelisted by first checking the cache. If not whitelisted in the cache then + // checks it from the collateral whitelist contract and caches whitelist status and final fee. + function _validateAndCacheCurrency(address currency) internal returns (bool) { + if (cachedCurrencies[currency].isWhitelisted) return true; + cachedCurrencies[currency].isWhitelisted = _getCollateralWhitelist().isOnWhitelist(currency); + cachedCurrencies[currency].finalFee = _getStore().computeFinalFee(currency).rawValue; + return cachedCurrencies[currency].isWhitelisted; + } + + // Sends assertion resolved callback to the callback recipient and escalation manager (if set). + function _callbackOnAssertionResolve(bytes32 assertionId, bool assertedTruthfully) internal { + address cr = assertions[assertionId].callbackRecipient; + address em = _getEscalationManager(assertionId); + if (cr != address(0)) + OptimisticOracleV3CallbackRecipientInterface(cr).assertionResolvedCallback(assertionId, assertedTruthfully); + if (em != address(0)) EscalationManagerInterface(em).assertionResolvedCallback(assertionId, assertedTruthfully); + } + + // Sends assertion disputed callback to the callback recipient and escalation manager (if set). + function _callbackOnAssertionDispute(bytes32 assertionId) internal { + address cr = assertions[assertionId].callbackRecipient; + address em = _getEscalationManager(assertionId); + if (cr != address(0)) OptimisticOracleV3CallbackRecipientInterface(cr).assertionDisputedCallback(assertionId); + if (em != address(0)) EscalationManagerInterface(em).assertionDisputedCallback(assertionId); + } +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/BaseEscalationManager.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/BaseEscalationManager.sol new file mode 100644 index 000000000..5158f4b08 --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/BaseEscalationManager.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "../../interfaces/EscalationManagerInterface.sol"; +import "../../interfaces/OptimisticOracleV3Interface.sol"; + +/** + * @title BaseEscalationManager + * @notice Base contract for escalation managers. This contract is responsible for managing the escalation policy for + * assertions. This base implementation simply exposes the required interface and provides a default implementation + * (returning default values or doing nothing). + */ +contract BaseEscalationManager is EscalationManagerInterface { + OptimisticOracleV3Interface public immutable optimisticOracleV3; + + event PriceRequestAdded(bytes32 indexed identifier, uint256 time, bytes ancillaryData); + + /** + * @notice Reverts unless the configured Optimistic Oracle V3 is the caller. + */ + modifier onlyOptimisticOracleV3() { + require(msg.sender == address(optimisticOracleV3), "Not the Optimistic Oracle V3"); + _; + } + + /** + * @notice Constructs the escalation manager. + * @param _optimisticOracleV3 the Optimistic Oracle V3 to use. + */ + constructor(address _optimisticOracleV3) { + optimisticOracleV3 = OptimisticOracleV3Interface(_optimisticOracleV3); + } + + /** + * @notice Returns the assertion policy for the given assertionId. + * @param assertionId the assertionId to get the assertion policy for. + * @return the assertion policy for the given assertionId. + */ + function getAssertionPolicy(bytes32 assertionId) public view virtual returns (AssertionPolicy memory) { + return + AssertionPolicy({ + blockAssertion: false, + arbitrateViaEscalationManager: false, + discardOracle: false, + validateDisputers: false + }); + } + + /** + * @notice Callback function that is called by Optimistic Oracle V3 when an assertion is disputed. Used to validate + * if the dispute should be allowed based on the escalation policy. + * @param assertionId the assertionId to validate the dispute for. + * @param disputeCaller the caller of the dispute function. + * @return bool true if the dispute is allowed, false otherwise. + */ + function isDisputeAllowed(bytes32 assertionId, address disputeCaller) public view virtual returns (bool) { + return true; + } + + /** + * @notice Implements price getting logic. This method is called by Optimistic Oracle V3 settling an assertion that + * is configured to use the escalation manager as the oracle. The interface is constructed to mimic the UMA DVM. + * @param identifier price identifier being requested. + * @param time timestamp of the price being requested. + * @param ancillaryData ancillary data of the price being requested. + * @return price from the escalation manager to inform the resolution of the dispute. + */ + function getPrice( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) public view virtual returns (int256) {} + + /** + * @notice Implements price requesting logic for the escalation manager. This function is called by the Optimistic + * Oracle V3 on dispute and is constructed to mimic that of the UMA DVM interface. + * @param identifier the identifier to fetch the price for. + * @param time the time to fetch the price for. + * @param ancillaryData ancillary data of the price being requested. + */ + function requestPrice( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) public virtual onlyOptimisticOracleV3 { + emit PriceRequestAdded(identifier, time, ancillaryData); + } + + /** + * @notice Callback function that is called by Optimistic Oracle V3 when an assertion is resolved. + * @param assertionId The identifier of the assertion that was resolved. + * @param assertedTruthfully Whether the assertion was resolved as truthful or not. + */ + function assertionResolvedCallback( + bytes32 assertionId, + bool assertedTruthfully + ) public virtual onlyOptimisticOracleV3 {} + + /** + * @notice Callback function that is called by Optimistic Oracle V3 when an assertion is disputed. + * @param assertionId The identifier of the assertion that was disputed. + */ + function assertionDisputedCallback(bytes32 assertionId) public virtual onlyOptimisticOracleV3 {} +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/DisputeLimitingEscalationManager.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/DisputeLimitingEscalationManager.sol new file mode 100644 index 000000000..35ee59ac4 --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/DisputeLimitingEscalationManager.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "./BaseEscalationManager.sol"; + +// This Escalation Manager blocks all assertions till the blocking dispute is resolved by Oracle. In order to avoid +// interference among different applications this Escalation Manager allows assertions only from one requesting contract. +// This is useful to create a system where only one assertion dispute can occur at a time. +contract DisputeLimitingEscalationManager is BaseEscalationManager, Ownable { + // Address of linked requesting contract. Before this is set via setAssertingCaller all assertions will be blocked. + address public assertingCaller; + + bytes32 public disputedAssertionId; + + event AssertingCallerSet(address indexed assertingCaller); + + constructor(address _optimisticOracleV3) BaseEscalationManager(_optimisticOracleV3) {} + + // Set the address of the contract that will be allowed to use Optimistic Oracle V3. + // This can only be set once. We do not set this at constructor just to allow for some flexibility in the ordering + // of how contracts are deployed. + function setAssertingCaller(address _assertingCaller) public onlyOwner { + require(_assertingCaller != address(0), "Invalid asserting caller"); + require(assertingCaller == address(0), "Asserting caller already set"); + assertingCaller = _assertingCaller; + emit AssertingCallerSet(_assertingCaller); + } + + function getAssertionPolicy(bytes32 assertionId) public view override returns (AssertionPolicy memory) { + return + AssertionPolicy({ + blockAssertion: _checkIfShouldBlockAssertion(assertionId), + arbitrateViaEscalationManager: false, + discardOracle: false, + validateDisputers: false + }); + } + + // Callback function that is called by Optimistic Oracle V3 when an assertion is disputed. + function assertionDisputedCallback(bytes32 assertionId) public override onlyOptimisticOracleV3 { + // Only apply new assertion block if the dispute is related to the linked client contract. + if (optimisticOracleV3.getAssertion(assertionId).escalationManagerSettings.assertingCaller == assertingCaller) { + disputedAssertionId = assertionId; + } + } + + // Callback function that is called by Optimistic Oracle V3 when an assertion is resolved. + function assertionResolvedCallback(bytes32 assertionId, bool) public override onlyOptimisticOracleV3 { + // Remove assertion block if the disputed assertion was resolved. + if (assertionId == disputedAssertionId) disputedAssertionId = bytes32(0); + } + + function _checkIfShouldBlockAssertion(bytes32 assertionId) internal view returns (bool) { + OptimisticOracleV3Interface.Assertion memory assertion = optimisticOracleV3.getAssertion(assertionId); + if (assertion.escalationManagerSettings.assertingCaller != assertingCaller) return true; // Only allow assertions through linked client contract. + return disputedAssertionId != bytes32(0); // Block if there is outstanding dispute. + } +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/FullPolicyEscalationManager.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/FullPolicyEscalationManager.sol new file mode 100644 index 000000000..0a328e3ef --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/FullPolicyEscalationManager.sol @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "./BaseEscalationManager.sol"; + +/** + * @title The FullPolicyEscalationManager enables the owner to configure all policy parameters and store the arbitration + * resolutions for the Escalation Manager. Optionally, assertion blocking can be enabled using a whitelist of + * assertingCallers or assertingCallers and asserters. On the other hand, it enables the determination of whether to + * arbitrate via the escalation manager as opposed to the DVM, whether to disregard the resolution of a potential + * dispute arbitrated by the Oracle, and whether to restrict who can register disputes via whitelistedDisputeCallers. + * @dev If nothing is configured using the setters and configureEscalationManager method upon deployment, the + * FullPolicyEscalationManager will return a default policy with all values set to false. + */ +contract FullPolicyEscalationManager is BaseEscalationManager, Ownable { + // Struct to store the arbitration resolution for a given identifier, time, and ancillary data. + struct ArbitrationResolution { + bool valueSet; // True if the resolution has been set. + bool resolution; // True or false depending on the resolution. + } + + event EscalationManagerConfigured( + bool blockByAssertingCaller, + bool blockByAsserter, + bool validateDisputers, + bool arbitrateViaEscalationManager, + bool discardOracle + ); + + event ArbitrationResolutionSet(bytes32 indexed identifier, uint256 time, bytes ancillaryData, bool resolution); + + event DisputeCallerWhitelistSet(address indexed disputeCaller, bool whitelisted); + + event AssertingCallerWhitelistSet(address indexed assertingCaller, bool whitelisted); + + event AsserterWhitelistSet(address indexed asserter, bool whitelisted); + + int256 public constant numericalTrue = 1e18; // Numerical representation of true. + + bool public blockByAssertingCaller; // True if assertions are allowed only by whitelisted asserting callers. + + bool public blockByAsserter; // True if assertions are allowed only by whitelisted asserters. + + bool public arbitrateViaEscalationManager; // True if it is determined that the escalation manager should arbitrate. + + bool public discardOracle; // True if escalation manager should disregard the Oracle's resolution. + + bool public validateDisputers; // True if escalation manager should validate disputers via whitelistedDisputeCallers. + + mapping(bytes32 => ArbitrationResolution) public arbitrationResolutions; // Arbitration resolutions for a given identifier, time, and ancillary data. + + mapping(address => bool) public whitelistedDisputeCallers; // Whitelisted disputer that can file disputes. + + mapping(address => bool) public whitelistedAssertingCallers; // Whitelisted assertingCallers that can assert prices. + + mapping(address => bool) public whitelistedAsserters; // Whitelisted asserters that can assert prices. + + /** + * @notice Constructs the escalation manager. + * @param _optimisticOracleV3 the Optimistic Oracle V3 to use. + */ + constructor(address _optimisticOracleV3) BaseEscalationManager(_optimisticOracleV3) {} + + /** + * @notice Returns the Assertion Policy defined by this contract's parameters and functions. + * @param assertionId the ID of the assertion to get the policy for. + * @return the Assertion Policy defined by this contract's parameters and functions. + * @dev If no configuration is done after deployment, this function returns an all false default policy. + */ + function getAssertionPolicy(bytes32 assertionId) public view override returns (AssertionPolicy memory) { + bool blocked = _checkIfAssertionBlocked(assertionId); + return + AssertionPolicy({ + blockAssertion: blocked, // Block assertion if it is blocked. + arbitrateViaEscalationManager: arbitrateViaEscalationManager, // Arbitrate via escalation manager if configured. + discardOracle: discardOracle, // Ignore Oracle (DVM or EM) resolution if configured. + validateDisputers: validateDisputers // Validate disputers if configured. + }); + } + + /** + * @notice Gets the price for identifier and time if it has already been requested and resolved. + * @dev If the price is not available, the method reverts. + * @param identifier uniquely identifies the price requested. + * @param time unix timestamp of the price request. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + * @return int256 representing the resolved price for the given identifier and timestamp. + * @dev This function replicates the interface of the corresponding DVM function to allow the user to use his own + * dispute arbitration system when arbitrating via the escalation manager in a DVM-compatible manner. Refer to the + * UMA Voting and VotingV2 contracts for further details. + */ + function getPrice( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) public view override returns (int256) { + bytes32 requestId = getRequestId(identifier, time, ancillaryData); + require(arbitrationResolutions[requestId].valueSet, "Arbitration resolution not set"); + if (arbitrationResolutions[requestId].resolution) return numericalTrue; + return 0; + } + + /** + * @notice Returns, given an assertionId and a disputerCaller address, if the disputerCaller is authorised to + * dispute the assertion. + * @param assertionId the ID of the assertion to check the disputerCaller for. + * @param disputeCaller the address of the disputeCaller to check. + * @return true if the disputerCaller is authorised to dispute the assertion. + * @dev In order for this function to be used by the Optimistic Oracle V3, validateDisputers must be set to true. + */ + function isDisputeAllowed(bytes32 assertionId, address disputeCaller) public view override returns (bool) { + return whitelistedDisputeCallers[disputeCaller]; + } + + /** + * @notice Defines how the assertion policy for each configuration's rules is to be defined. + * @param _blockByAssertingCaller true if assertions are allowed only by whitelisted asserting callers. + * @param _blockByAsserter true if assertions are allowed only by whitelisted asserters. + * @param _validateDisputers true if the escalation manager should validate disputers via whitelistedDisputeCallers. + * @param _arbitrateViaEscalationManager true if the escalation manager should arbitrate instead of the DVM. + * @param _discardOracle true if the escalation manager should disregard the Oracle's (DVM or EM) resolution. + * @dev This setting just activates the rules that will be executed; each rule must additionally be defined using + * the other functions. + */ + function configureEscalationManager( + bool _blockByAssertingCaller, + bool _blockByAsserter, + bool _validateDisputers, + bool _arbitrateViaEscalationManager, + bool _discardOracle + ) public onlyOwner { + require(!_blockByAsserter || _blockByAssertingCaller, "Cannot block only by asserter"); + blockByAssertingCaller = _blockByAssertingCaller; + blockByAsserter = _blockByAsserter; + validateDisputers = _validateDisputers; + arbitrateViaEscalationManager = _arbitrateViaEscalationManager; + discardOracle = _discardOracle; + emit EscalationManagerConfigured( + _blockByAssertingCaller, + _blockByAsserter, + _validateDisputers, + _arbitrateViaEscalationManager, + _discardOracle + ); + } + + /** + * @notice Set the arbitration resolution for a given identifier, time, and ancillary data. + * @param identifier uniquely identifies the price requested. + * @param time unix timestamp of the price request. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + * @param arbitrationResolution true if the assertion should be resolved as true, false otherwise. + * @dev The owner should use this function whenever a dispute arises and it should be arbitrated by the Escalation + * Manager; it is up to the owner to determine how to resolve the dispute. See the requestPrice implementation in + * BaseEscalationManager, which escalates a dispute to the Escalation Manager for resolution. + */ + function setArbitrationResolution( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData, + bool arbitrationResolution + ) public onlyOwner { + bytes32 requestId = getRequestId(identifier, time, ancillaryData); + require(arbitrationResolutions[requestId].valueSet == false, "Arbitration already resolved"); + arbitrationResolutions[requestId] = ArbitrationResolution(true, arbitrationResolution); + emit ArbitrationResolutionSet(identifier, time, ancillaryData, arbitrationResolution); + } + + /** + * @notice Adds/removes a disputeCaller to the whitelist of disputers that can file disputes. + * @param disputeCaller the address of the disputeCaller to add. + * @param value true represents adding and false represents removing the disputeCaller from the whitelist. + * @dev This function is only used if validateDisputers is set to true. + */ + function setDisputeCallerInWhitelist(address disputeCaller, bool value) public onlyOwner { + whitelistedDisputeCallers[disputeCaller] = value; + emit DisputeCallerWhitelistSet(disputeCaller, value); + } + + /** + * @notice Adds/removes an asserter to the whitelist of assertingCallers that can make assertions. + * @param assertingCaller the address of the assertingCaller to add. + * @param value true represents adding and false represents removing the assertingCaller from the whitelist. + */ + function setWhitelistedAssertingCallers(address assertingCaller, bool value) public onlyOwner { + whitelistedAssertingCallers[assertingCaller] = value; + emit AssertingCallerWhitelistSet(assertingCaller, value); + } + + /** + * @notice Adds/removes an asserter to the whitelist of asserters that can make assertions. + * @param asserter the address of the asserter to add. + * @param value true represents adding and false represents removing the asserter from the whitelist. + * @dev This function must be used in conjunction with setWhitelistedAssertingCallers in order to have an effect. + */ + function setWhitelistedAsserters(address asserter, bool value) public onlyOwner { + whitelistedAsserters[asserter] = value; + emit AsserterWhitelistSet(asserter, value); + } + + /** + * @notice Calculates price request identifier for a given identifier, time, and ancillary data. + * @param identifier uniquely identifies the price requested. + * @param time unix timestamp of the price request. + * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. + * @return price request identifier. + */ + function getRequestId(bytes32 identifier, uint256 time, bytes memory ancillaryData) public pure returns (bytes32) { + return keccak256(abi.encode(identifier, time, ancillaryData)); + } + + // Checks if an assertion is blocked depending on the blockByAssertingCaller / blockByAsserter settings and the + // assertion's properties. + function _checkIfAssertionBlocked(bytes32 assertionId) internal view returns (bool) { + OptimisticOracleV3Interface.Assertion memory assertion = optimisticOracleV3.getAssertion(assertionId); + return + (blockByAssertingCaller && + !whitelistedAssertingCallers[assertion.escalationManagerSettings.assertingCaller]) || + (blockByAsserter && !whitelistedAsserters[assertion.asserter]); + } +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/OwnerDiscardOracleEscalationManager.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/OwnerDiscardOracleEscalationManager.sol new file mode 100644 index 000000000..7cfd84ae7 --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/OwnerDiscardOracleEscalationManager.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "./BaseEscalationManager.sol"; + +contract OwnerDiscardOracleEscalationManager is BaseEscalationManager, Ownable { + bool public discardOracle; + + constructor(address _optimisticOracleV3) BaseEscalationManager(_optimisticOracleV3) {} + + function setDiscardOracle(bool value) public onlyOwner { + discardOracle = value; + } + + function getAssertionPolicy(bytes32 assertionId) public view override returns (AssertionPolicy memory) { + return + AssertionPolicy({ + blockAssertion: false, + arbitrateViaEscalationManager: false, + discardOracle: discardOracle, + validateDisputers: false + }); + } +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/OwnerSelectOracleEscalationManager.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/OwnerSelectOracleEscalationManager.sol new file mode 100644 index 000000000..eb8cc2959 --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/OwnerSelectOracleEscalationManager.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "./BaseEscalationManager.sol"; + +contract OwnerSelectOracleEscalationManager is BaseEscalationManager, Ownable { + struct ArbitrationResolution { + bool valueSet; + bool resolution; + } + + mapping(bytes32 => ArbitrationResolution) arbitrationResolutions; + + bool arbitrateViaEscalationManager; + + constructor(address _optimisticOracleV3) BaseEscalationManager(_optimisticOracleV3) {} + + function setArbitrationResolution( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData, + bool arbitrationResolution + ) public onlyOwner { + bytes32 requestId = keccak256(abi.encode(identifier, time, ancillaryData)); + arbitrationResolutions[requestId] = ArbitrationResolution(true, arbitrationResolution); + } + + function setArbitrateViaSs(bool value) public onlyOwner { + arbitrateViaEscalationManager = value; + } + + function getAssertionPolicy(bytes32 assertionId) public view override returns (AssertionPolicy memory) { + return + AssertionPolicy({ + blockAssertion: false, + arbitrateViaEscalationManager: arbitrateViaEscalationManager, + discardOracle: false, + validateDisputers: false + }); + } + + function getPrice( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) public view override returns (int256) { + bytes32 requestId = keccak256(abi.encode(identifier, time, ancillaryData)); + require(arbitrationResolutions[requestId].valueSet, "Arbitration resolution not set"); + if (arbitrationResolutions[requestId].resolution) return 1e18; + return 0; + } +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/SuperbondEscalationManager.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/SuperbondEscalationManager.sol new file mode 100644 index 000000000..be79d4e5e --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/SuperbondEscalationManager.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "./BaseEscalationManager.sol"; +import "@openzeppelin/contracts-v4/access/Ownable.sol"; + +// This EscalationManager allows to arbitrate for each assertion based on its bond via the DVM or the EscalationManager. +// If the bond is greater than the superbond, it is arbitrated automatically through the EscalationManager; otherwise, +// it is arbitrated through the DVM. +contract SuperbondEscalationManager is BaseEscalationManager, Ownable { + uint256 public superbond; + address public superbondCurrency; + + constructor(address _optimisticOracleV3) BaseEscalationManager(_optimisticOracleV3) {} + + function setSuperbond(uint256 newSuperbond) public onlyOwner { + superbond = newSuperbond; + } + + function setSuperbondCurrency(address newSuperbondCurrency) public onlyOwner { + superbondCurrency = newSuperbondCurrency; + } + + function getAssertionPolicy(bytes32 assertionId) public view override returns (AssertionPolicy memory) { + OptimisticOracleV3Interface.Assertion memory assertion = optimisticOracleV3.getAssertion(assertionId); + bool isSuperbondCurrency = address(assertion.currency) == superbondCurrency; + return + AssertionPolicy({ + blockAssertion: false, + arbitrateViaEscalationManager: isSuperbondCurrency ? assertion.bond > superbond : false, + discardOracle: false, + validateDisputers: false + }); + } +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistAsserterEscalationManager.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistAsserterEscalationManager.sol new file mode 100644 index 000000000..55dfece9c --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistAsserterEscalationManager.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "./BaseEscalationManager.sol"; + +contract WhitelistAsserterEscalationManager is BaseEscalationManager, Ownable { + // Address of linked requesting contract. Before this is set via setAssertingCaller all assertions will be blocked. + // Security of returning correct policy depends on requesting contract passing msg.sender as asserter. + address public assertingCaller; + + mapping(address => bool) public whitelistedAsserters; + + event AssertingCallerSet(address indexed assertingCaller); + + constructor(address _optimisticOracleV3) BaseEscalationManager(_optimisticOracleV3) {} + + // Set the address of the contract that will be allowed to use Optimistic Oracle V3. + // This can only be set once. We do not set this at constructor just to allow for some flexibility in the ordering + // of how contracts are deployed. + function setAssertingCaller(address _assertingCaller) public onlyOwner { + require(_assertingCaller != address(0), "Invalid asserting caller"); + require(assertingCaller == address(0), "Asserting caller already set"); + assertingCaller = _assertingCaller; + emit AssertingCallerSet(_assertingCaller); + } + + function setAsserterInWhitelist(address asserter, bool value) public onlyOwner { + whitelistedAsserters[asserter] = value; + } + + function getAssertionPolicy(bytes32 assertionId) public view override returns (AssertionPolicy memory) { + OptimisticOracleV3Interface.Assertion memory assertion = optimisticOracleV3.getAssertion(assertionId); + bool blocked = _checkIfAssertionBlocked(assertion); + return + AssertionPolicy({ + blockAssertion: blocked, + arbitrateViaEscalationManager: false, + discardOracle: false, + validateDisputers: false + }); + } + + function _checkIfAssertionBlocked( + OptimisticOracleV3Interface.Assertion memory assertion + ) internal view returns (bool) { + if (assertion.escalationManagerSettings.assertingCaller != assertingCaller) return true; // Only allow assertions through linked client contract. + return !whitelistedAsserters[assertion.asserter]; // Return if asserter is not whitelisted. + } +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistCallerEscalationManager.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistCallerEscalationManager.sol new file mode 100644 index 000000000..b83af10a4 --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistCallerEscalationManager.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "./BaseEscalationManager.sol"; + +contract WhitelistCallerEscalationManager is BaseEscalationManager, Ownable { + mapping(address => bool) whitelistedAssertingCallers; + + constructor(address _optimisticOracleV3) BaseEscalationManager(_optimisticOracleV3) {} + + function setAssertingCallerInWhitelist(address assertingCaller, bool value) public onlyOwner { + whitelistedAssertingCallers[assertingCaller] = value; + } + + function getAssertionPolicy(bytes32 assertionId) public view override returns (AssertionPolicy memory) { + return + AssertionPolicy({ + blockAssertion: !whitelistedAssertingCallers[ + optimisticOracleV3.getAssertion(assertionId).escalationManagerSettings.assertingCaller + ], + arbitrateViaEscalationManager: false, + discardOracle: false, + validateDisputers: false + }); + } +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistDisputerEscalationManager.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistDisputerEscalationManager.sol new file mode 100644 index 000000000..ea1c73407 --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistDisputerEscalationManager.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "./BaseEscalationManager.sol"; + +contract WhitelistDisputerEscalationManager is BaseEscalationManager, Ownable { + mapping(address => bool) whitelistedDisputeCallers; + + constructor(address _optimisticOracleV3) BaseEscalationManager(_optimisticOracleV3) {} + + function getAssertionPolicy(bytes32 assertionId) public view override returns (AssertionPolicy memory) { + return + AssertionPolicy({ + blockAssertion: false, + arbitrateViaEscalationManager: false, + discardOracle: false, + validateDisputers: true + }); + } + + function setDisputeCallerInWhitelist(address disputeCaller, bool value) public onlyOwner { + whitelistedDisputeCallers[disputeCaller] = value; + } + + function isDisputeAllowed(bytes32 assertionId, address disputeCaller) public view override returns (bool) { + return whitelistedDisputeCallers[disputeCaller]; + } +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/DataAsserter.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/DataAsserter.sol new file mode 100644 index 000000000..853feb44c --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/DataAsserter.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../implementation/ClaimData.sol"; +import "../../interfaces/OptimisticOracleV3Interface.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +// This contract allows assertions on any form of data to be made using the UMA Optimistic Oracle V3 and stores the +// proposed value so that it may be retrieved on chain. The dataId is intended to be an arbitrary value that uniquely +// identifies a specific piece of information in the consuming contract and is replaceable. Similarly, any data +// structure can be used to replace the asserted data. +contract DataAsserter { + using SafeERC20 for IERC20; + IERC20 public immutable defaultCurrency; + OptimisticOracleV3Interface public immutable oo; + uint64 public constant assertionLiveness = 7200; + bytes32 public immutable defaultIdentifier; + + struct DataAssertion { + bytes32 dataId; // The dataId that was asserted. + bytes32 data; // This could be an arbitrary data type. + address asserter; // The address that made the assertion. + bool resolved; // Whether the assertion has been resolved. + } + + mapping(bytes32 => DataAssertion) public assertionsData; + + event DataAsserted(bytes32 indexed dataId, bytes32 data, address indexed asserter, bytes32 assertionId); + + event DataAssertionResolved(bytes32 indexed dataId, bytes32 data, address indexed asserter, bytes32 assertionId); + + constructor(address _defaultCurrency, address _optimisticOracleV3) { + defaultCurrency = IERC20(_defaultCurrency); + oo = OptimisticOracleV3Interface(_optimisticOracleV3); + defaultIdentifier = oo.defaultIdentifier(); + } + + // For a given assertionId, returns a boolean indicating whether the data is accessible and the data itself. + function getData(bytes32 assertionId) public view returns (bool, bytes32) { + if (!assertionsData[assertionId].resolved) return (false, 0); + return (true, assertionsData[assertionId].data); + } + + // Asserts data for a specific dataId on behalf of an asserter address. + // Data can be asserted many times with the same combination of arguments, resulting in unique assertionIds. This is + // because the block.timestamp is included in the claim. The consumer contract must store the returned assertionId + // identifiers to able to get the information using getData. + function assertDataFor(bytes32 dataId, bytes32 data, address asserter) public returns (bytes32 assertionId) { + asserter = asserter == address(0) ? msg.sender : asserter; + uint256 bond = oo.getMinimumBond(address(defaultCurrency)); + defaultCurrency.safeTransferFrom(msg.sender, address(this), bond); + defaultCurrency.safeApprove(address(oo), bond); + + // The claim we want to assert is the first argument of assertTruth. It must contain all of the relevant + // details so that anyone may verify the claim without having to read any further information on chain. As a + // result, the claim must include both the data id and data, as well as a set of instructions that allow anyone + // to verify the information in publicly available sources. + // See the UMIP corresponding to the defaultIdentifier used in the OptimisticOracleV3 "ASSERT_TRUTH" for more + // information on how to construct the claim. + assertionId = oo.assertTruth( + abi.encodePacked( + "Data asserted: 0x", // in the example data is type bytes32 so we add the hex prefix 0x. + ClaimData.toUtf8Bytes(data), + " for dataId: 0x", + ClaimData.toUtf8Bytes(dataId), + " and asserter: 0x", + ClaimData.toUtf8BytesAddress(asserter), + " at timestamp: ", + ClaimData.toUtf8BytesUint(block.timestamp), + " in the DataAsserter contract at 0x", + ClaimData.toUtf8BytesAddress(address(this)), + " is valid." + ), + asserter, + address(this), + address(0), // No sovereign security. + assertionLiveness, + defaultCurrency, + bond, + defaultIdentifier, + bytes32(0) // No domain. + ); + assertionsData[assertionId] = DataAssertion(dataId, data, asserter, false); + emit DataAsserted(dataId, data, asserter, assertionId); + } + + // OptimisticOracleV3 resolve callback. + function assertionResolvedCallback(bytes32 assertionId, bool assertedTruthfully) public { + require(msg.sender == address(oo)); + // If the assertion was true, then the data assertion is resolved. + if (assertedTruthfully) { + assertionsData[assertionId].resolved = true; + DataAssertion memory dataAssertion = assertionsData[assertionId]; + emit DataAssertionResolved(dataAssertion.dataId, dataAssertion.data, dataAssertion.asserter, assertionId); + // Else delete the data assertion if it was false to save gas. + } else delete assertionsData[assertionId]; + } + + // If assertion is disputed, do nothing and wait for resolution. + // This OptimisticOracleV3 callback function needs to be defined so the OOv3 doesn't revert when it tries to call it. + function assertionDisputedCallback(bytes32 assertionId) public {} +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/Insurance.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/Insurance.sol new file mode 100644 index 000000000..b4eb9e2f9 --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/Insurance.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../implementation/ClaimData.sol"; +import "../../interfaces/OptimisticOracleV3Interface.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +// This Isurance contract enables for the issuance of a single unlimited time policy per event/payout recipient There is +// no limit to the number of payout requests that can be made of the same policy; however, only the first asserted +// request will settle the insurance payment, whereas OOv3 will settle bonds for all requestors. +contract Insurance { + using SafeERC20 for IERC20; + IERC20 public immutable defaultCurrency; + OptimisticOracleV3Interface public immutable oo; + uint64 public constant assertionLiveness = 7200; + bytes32 public immutable defaultIdentifier; + + struct Policy { + uint256 insuranceAmount; + address payoutAddress; + bytes insuredEvent; + bool settled; + } + + mapping(bytes32 => bytes32) public assertedPolicies; + + mapping(bytes32 => Policy) public policies; + + event InsuranceIssued( + bytes32 indexed policyId, + bytes insuredEvent, + uint256 insuranceAmount, + address indexed payoutAddress + ); + + event InsurancePayoutRequested(bytes32 indexed policyId, bytes32 indexed assertionId); + + event InsurancePayoutSettled(bytes32 indexed policyId, bytes32 indexed assertionId); + + constructor(address _defaultCurrency, address _optimisticOracleV3) { + defaultCurrency = IERC20(_defaultCurrency); + oo = OptimisticOracleV3Interface(_optimisticOracleV3); + defaultIdentifier = oo.defaultIdentifier(); + } + + function issueInsurance( + uint256 insuranceAmount, + address payoutAddress, + bytes memory insuredEvent + ) public returns (bytes32 policyId) { + policyId = keccak256(abi.encode(insuredEvent, payoutAddress)); + require(policies[policyId].payoutAddress == address(0), "Policy already exists"); + policies[policyId] = Policy({ + insuranceAmount: insuranceAmount, + payoutAddress: payoutAddress, + insuredEvent: insuredEvent, + settled: false + }); + defaultCurrency.safeTransferFrom(msg.sender, address(this), insuranceAmount); + emit InsuranceIssued(policyId, insuredEvent, insuranceAmount, payoutAddress); + } + + function requestPayout(bytes32 policyId) public returns (bytes32 assertionId) { + require(policies[policyId].payoutAddress != address(0), "Policy does not exist"); + uint256 bond = oo.getMinimumBond(address(defaultCurrency)); + defaultCurrency.safeTransferFrom(msg.sender, address(this), bond); + defaultCurrency.safeApprove(address(oo), bond); + assertionId = oo.assertTruth( + abi.encodePacked( + "Insurance contract is claiming that insurance event ", + policies[policyId].insuredEvent, + " had occurred as of ", + ClaimData.toUtf8BytesUint(block.timestamp), + "." + ), + msg.sender, + address(this), + address(0), // No sovereign security. + assertionLiveness, + defaultCurrency, + bond, + defaultIdentifier, + bytes32(0) // No domain. + ); + assertedPolicies[assertionId] = policyId; + emit InsurancePayoutRequested(policyId, assertionId); + } + + function assertionResolvedCallback(bytes32 assertionId, bool assertedTruthfully) public { + require(msg.sender == address(oo)); + // If the assertion was true, then the policy is settled. + if (assertedTruthfully) { + _settlePayout(assertionId); + } + } + + function assertionDisputedCallback(bytes32 assertionId) public {} + + function _settlePayout(bytes32 assertionId) internal { + // If already settled, do nothing. We don't revert because this function is called by the + // OptimisticOracleV3, which may block the assertion resolution. + bytes32 policyId = assertedPolicies[assertionId]; + Policy storage policy = policies[policyId]; + if (policy.settled) return; + policy.settled = true; + defaultCurrency.safeTransfer(policy.payoutAddress, policy.insuranceAmount); + emit InsurancePayoutSettled(policyId, assertionId); + } +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/PredictionMarket.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/PredictionMarket.sol new file mode 100644 index 000000000..86e89a924 --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/PredictionMarket.sol @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; +import "../../../common/implementation/AddressWhitelist.sol"; +import "../../../common/implementation/ExpandedERC20.sol"; +import "../../../data-verification-mechanism/implementation/Constants.sol"; +import "../../../data-verification-mechanism/interfaces/FinderInterface.sol"; +import "../../implementation/ClaimData.sol"; +import "../../interfaces/OptimisticOracleV3Interface.sol"; +import "../../interfaces/OptimisticOracleV3CallbackRecipientInterface.sol"; + +// This contract allows to initialize prediction markets each having a pair of binary outcome tokens. Anyone can mint +// and burn the same amount of paired outcome tokens for the default payout currency. Trading of outcome tokens is +// outside the scope of this contract. Anyone can assert 3 possible outcomes (outcome 1, outcome 2 or split) that is +// verified through Optimistic Oracle V3. If the assertion is resolved true then holders of outcome tokens can settle +// them for the payout currency based on resolved market outcome. +contract PredictionMarket is OptimisticOracleV3CallbackRecipientInterface { + using SafeERC20 for IERC20; + + struct Market { + bool resolved; // True if the market has been resolved and payouts can be settled. + bytes32 assertedOutcomeId; // Hash of asserted outcome (outcome1, outcome2 or unresolvable). + ExpandedIERC20 outcome1Token; // ERC20 token representing the value of the first outcome. + ExpandedIERC20 outcome2Token; // ERC20 token representing the value of the second outcome. + uint256 reward; // Reward available for asserting true market outcome. + uint256 requiredBond; // Expected bond to assert market outcome (OOv3 can require higher bond). + bytes outcome1; // Short name of the first outcome. + bytes outcome2; // Short name of the second outcome. + bytes description; // Description of the market. + } + + struct AssertedMarket { + address asserter; // Address of the asserter used for reward payout. + bytes32 marketId; // Identifier for markets mapping. + } + + mapping(bytes32 => Market) public markets; // Maps marketId to Market struct. + + mapping(bytes32 => AssertedMarket) public assertedMarkets; // Maps assertionId to AssertedMarket. + + FinderInterface public immutable finder; // UMA protocol Finder used to discover other protocol contracts. + IERC20 public immutable currency; // Currency used for all prediction markets. + OptimisticOracleV3Interface public immutable oo; + uint64 public constant assertionLiveness = 7200; // 2 hours. + bytes32 public immutable defaultIdentifier; // Identifier used for all prediction markets. + bytes public constant unresolvable = "Unresolvable"; // Name of the unresolvable outcome where payouts are split. + + event MarketInitialized( + bytes32 indexed marketId, + string outcome1, + string outcome2, + string description, + address outcome1Token, + address outcome2Token, + uint256 reward, + uint256 requiredBond + ); + event MarketAsserted(bytes32 indexed marketId, string assertedOutcome, bytes32 indexed assertionId); + event MarketResolved(bytes32 indexed marketId); + event TokensCreated(bytes32 indexed marketId, address indexed account, uint256 tokensCreated); + event TokensRedeemed(bytes32 indexed marketId, address indexed account, uint256 tokensRedeemed); + event TokensSettled( + bytes32 indexed marketId, + address indexed account, + uint256 payout, + uint256 outcome1Tokens, + uint256 outcome2Tokens + ); + + constructor(address _finder, address _currency, address _optimisticOracleV3) { + finder = FinderInterface(_finder); + require(_getCollateralWhitelist().isOnWhitelist(_currency), "Unsupported currency"); + currency = IERC20(_currency); + oo = OptimisticOracleV3Interface(_optimisticOracleV3); + defaultIdentifier = oo.defaultIdentifier(); + } + + function getMarket(bytes32 marketId) public view returns (Market memory) { + return markets[marketId]; + } + + function initializeMarket( + string memory outcome1, // Short name of the first outcome. + string memory outcome2, // Short name of the second outcome. + string memory description, // Description of the market. + uint256 reward, // Reward available for asserting true market outcome. + uint256 requiredBond // Expected bond to assert market outcome (OOv3 can require higher bond). + ) public returns (bytes32 marketId) { + require(bytes(outcome1).length > 0, "Empty first outcome"); + require(bytes(outcome2).length > 0, "Empty second outcome"); + require(keccak256(bytes(outcome1)) != keccak256(bytes(outcome2)), "Outcomes are the same"); + require(bytes(description).length > 0, "Empty description"); + marketId = keccak256(abi.encode(block.number, description)); + require(markets[marketId].outcome1Token == ExpandedIERC20(address(0)), "Market already exists"); + + // Create position tokens with this contract having minter and burner roles. + ExpandedIERC20 outcome1Token = new ExpandedERC20(string(abi.encodePacked(outcome1, " Token")), "O1T", 18); + ExpandedIERC20 outcome2Token = new ExpandedERC20(string(abi.encodePacked(outcome2, " Token")), "O2T", 18); + outcome1Token.addMinter(address(this)); + outcome2Token.addMinter(address(this)); + outcome1Token.addBurner(address(this)); + outcome2Token.addBurner(address(this)); + + markets[marketId] = Market({ + resolved: false, + assertedOutcomeId: bytes32(0), + outcome1Token: outcome1Token, + outcome2Token: outcome2Token, + reward: reward, + requiredBond: requiredBond, + outcome1: bytes(outcome1), + outcome2: bytes(outcome2), + description: bytes(description) + }); + if (reward > 0) currency.safeTransferFrom(msg.sender, address(this), reward); // Pull reward. + + emit MarketInitialized( + marketId, + outcome1, + outcome2, + description, + address(outcome1Token), + address(outcome2Token), + reward, + requiredBond + ); + } + + // Assert the market with any of 3 possible outcomes: names of outcome1, outcome2 or unresolvable. + // Only one concurrent assertion per market is allowed. + function assertMarket(bytes32 marketId, string memory assertedOutcome) public returns (bytes32 assertionId) { + Market storage market = markets[marketId]; + require(market.outcome1Token != ExpandedIERC20(address(0)), "Market does not exist"); + bytes32 assertedOutcomeId = keccak256(bytes(assertedOutcome)); + require(market.assertedOutcomeId == bytes32(0), "Assertion active or resolved"); + require( + assertedOutcomeId == keccak256(market.outcome1) || + assertedOutcomeId == keccak256(market.outcome2) || + assertedOutcomeId == keccak256(unresolvable), + "Invalid asserted outcome" + ); + + market.assertedOutcomeId = assertedOutcomeId; + uint256 minimumBond = oo.getMinimumBond(address(currency)); // OOv3 might require higher bond. + uint256 bond = market.requiredBond > minimumBond ? market.requiredBond : minimumBond; + bytes memory claim = _composeClaim(assertedOutcome, market.description); + + // Pull bond and make the assertion. + currency.safeTransferFrom(msg.sender, address(this), bond); + currency.safeApprove(address(oo), bond); + assertionId = _assertTruthWithDefaults(claim, bond); + + // Store the asserter and marketId for the assertionResolvedCallback. + assertedMarkets[assertionId] = AssertedMarket({ asserter: msg.sender, marketId: marketId }); + + emit MarketAsserted(marketId, assertedOutcome, assertionId); + } + + // Callback from settled assertion. + // If the assertion was resolved true, then the asserter gets the reward and the market is marked as resolved. + // Otherwise, assertedOutcomeId is reset and the market can be asserted again. + function assertionResolvedCallback(bytes32 assertionId, bool assertedTruthfully) public { + require(msg.sender == address(oo), "Not authorized"); + Market storage market = markets[assertedMarkets[assertionId].marketId]; + + if (assertedTruthfully) { + market.resolved = true; + if (market.reward > 0) currency.safeTransfer(assertedMarkets[assertionId].asserter, market.reward); + emit MarketResolved(assertedMarkets[assertionId].marketId); + } else market.assertedOutcomeId = bytes32(0); + delete assertedMarkets[assertionId]; + } + + // Dispute callback does nothing. + function assertionDisputedCallback(bytes32 assertionId) public {} + + // Mints pair of tokens representing the value of outcome1 and outcome2. Trading of outcome tokens is outside of the + // scope of this contract. The caller must approve this contract to spend the currency tokens. + function createOutcomeTokens(bytes32 marketId, uint256 tokensToCreate) public { + Market storage market = markets[marketId]; + require(market.outcome1Token != ExpandedIERC20(address(0)), "Market does not exist"); + + currency.safeTransferFrom(msg.sender, address(this), tokensToCreate); + + market.outcome1Token.mint(msg.sender, tokensToCreate); + market.outcome2Token.mint(msg.sender, tokensToCreate); + + emit TokensCreated(marketId, msg.sender, tokensToCreate); + } + + // Burns equal amount of outcome1 and outcome2 tokens returning settlement currency tokens. + function redeemOutcomeTokens(bytes32 marketId, uint256 tokensToRedeem) public { + Market storage market = markets[marketId]; + require(market.outcome1Token != ExpandedIERC20(address(0)), "Market does not exist"); + + market.outcome1Token.burnFrom(msg.sender, tokensToRedeem); + market.outcome2Token.burnFrom(msg.sender, tokensToRedeem); + + currency.safeTransfer(msg.sender, tokensToRedeem); + + emit TokensRedeemed(marketId, msg.sender, tokensToRedeem); + } + + // If the market is resolved, then all of caller's outcome tokens are burned and currency payout is made depending + // on the resolved market outcome and the amount of outcome tokens burned. If the market was resolved to the first + // outcome, then the payout equals balance of outcome1Token while outcome2Token provides nothing. If the market was + // resolved to the second outcome, then the payout equals balance of outcome2Token while outcome1Token provides + // nothing. If the market was resolved to the split outcome, then both outcome tokens provides half of their balance + // as currency payout. + function settleOutcomeTokens(bytes32 marketId) public returns (uint256 payout) { + Market storage market = markets[marketId]; + require(market.resolved, "Market not resolved"); + + uint256 outcome1Balance = market.outcome1Token.balanceOf(msg.sender); + uint256 outcome2Balance = market.outcome2Token.balanceOf(msg.sender); + + if (market.assertedOutcomeId == keccak256(market.outcome1)) payout = outcome1Balance; + else if (market.assertedOutcomeId == keccak256(market.outcome2)) payout = outcome2Balance; + else payout = (outcome1Balance + outcome2Balance) / 2; + + market.outcome1Token.burnFrom(msg.sender, outcome1Balance); + market.outcome2Token.burnFrom(msg.sender, outcome2Balance); + currency.safeTransfer(msg.sender, payout); + + emit TokensSettled(marketId, msg.sender, payout, outcome1Balance, outcome2Balance); + } + + function _getCollateralWhitelist() internal view returns (AddressWhitelist) { + return AddressWhitelist(finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist)); + } + + function _composeClaim(string memory outcome, bytes memory description) internal view returns (bytes memory) { + return + abi.encodePacked( + "As of assertion timestamp ", + ClaimData.toUtf8BytesUint(block.timestamp), + ", the described prediction market outcome is: ", + outcome, + ". The market description is: ", + description + ); + } + + function _assertTruthWithDefaults(bytes memory claim, uint256 bond) internal returns (bytes32 assertionId) { + assertionId = oo.assertTruth( + claim, + msg.sender, // Asserter + address(this), // Receive callback in this contract. + address(0), // No sovereign security. + assertionLiveness, + currency, + bond, + defaultIdentifier, + bytes32(0) // No domain. + ); + } +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/test/AssertingCallerTest.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/test/AssertingCallerTest.sol new file mode 100644 index 000000000..52cd08bbf --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/test/AssertingCallerTest.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "../OptimisticOracleV3.sol"; + +// Test contract used to wrap assertions for integration testing. +contract AssertingCallerTest { + using SafeERC20 for IERC20; + + OptimisticOracleV3 immutable optimisticOracleV3; + + constructor(OptimisticOracleV3 _optimisticOracleV3) { + optimisticOracleV3 = _optimisticOracleV3; + } + + // Wraps the OptimisticOracleV3 assertTruth function by passing msg.sender as the asserter and transferring the bond. + function assertTruth( + bytes memory claim, + address callbackRecipient, + address escalationManager, + uint64 liveness, + IERC20 currency, + uint256 bond, + bytes32 identifier, + bytes32 domainId + ) public returns (bytes32 assertionId) { + currency.safeTransferFrom(msg.sender, address(this), bond); + currency.safeApprove(address(optimisticOracleV3), bond); + + assertionId = optimisticOracleV3.assertTruth( + claim, + msg.sender, + callbackRecipient, + escalationManager, + liveness, + currency, + bond, + identifier, + domainId + ); + } +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/test/OptimisticOracleV3Test.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/test/OptimisticOracleV3Test.sol new file mode 100644 index 000000000..7169d0cff --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/test/OptimisticOracleV3Test.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "../OptimisticOracleV3.sol"; +import "../../../common/implementation/Testable.sol"; + +// Test contract used to manage the time for the contract in tests. +contract OptimisticOracleV3Test is OptimisticOracleV3, Testable { + constructor( + FinderInterface _finder, + IERC20 _defaultCurrency, + uint64 _defaultLiveness, + address _timerAddress + ) OptimisticOracleV3(_finder, _defaultCurrency, _defaultLiveness) Testable(_timerAddress) {} + + function getCurrentTime() public view override(OptimisticOracleV3, Testable) returns (uint256) { + return uint256(Testable.getCurrentTime()); + } +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/EscalationManagerInterface.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/EscalationManagerInterface.sol new file mode 100644 index 000000000..755e8a4a5 --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/EscalationManagerInterface.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.16; + +import "./OptimisticOracleV3CallbackRecipientInterface.sol"; + +/** + * @title Escalation Manager Interface + * @notice Interface for contracts that manage the escalation policy for assertions. + */ +interface EscalationManagerInterface is OptimisticOracleV3CallbackRecipientInterface { + // Assertion policy parameters as returned by the escalation manager. + struct AssertionPolicy { + bool blockAssertion; // If true, the the assertion should be blocked. + bool arbitrateViaEscalationManager; // If true, the escalation manager will arbitrate the assertion. + bool discardOracle; // If true, the Optimistic Oracle V3 should discard the oracle price. + bool validateDisputers; // If true, the escalation manager will validate the disputers. + } + + /** + * @notice Returns the assertion policy for the given assertion. + * @param assertionId the assertion identifier to get the assertion policy for. + * @return the assertion policy for the given assertion identifier. + */ + function getAssertionPolicy(bytes32 assertionId) external view returns (AssertionPolicy memory); + + /** + * @notice Callback function that is called by Optimistic Oracle V3 when an assertion is disputed. Used to validate + * if the dispute should be allowed based on the escalation policy. + * @param assertionId the assertionId to validate the dispute for. + * @param disputeCaller the caller of the dispute function. + * @return bool true if the dispute is allowed, false otherwise. + */ + function isDisputeAllowed(bytes32 assertionId, address disputeCaller) external view returns (bool); + + /** + * @notice Implements price getting logic. This method is called by Optimistic Oracle V3 settling an assertion that + * is configured to use the escalation manager as the oracle. The interface is constructed to mimic the UMA DVM. + * @param identifier price identifier being requested. + * @param time timestamp of the price being requested. + * @param ancillaryData ancillary data of the price being requested. + * @return price from the escalation manager to inform the resolution of the dispute. + */ + function getPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData) external returns (int256); + + /** + * @notice Implements price requesting logic for the escalation manager. This function is called by the Optimistic + * Oracle V3 on dispute and is constructed to mimic that of the UMA DVM interface. + * @param identifier the identifier to fetch the price for. + * @param time the time to fetch the price for. + * @param ancillaryData ancillary data of the price being requested. + */ + function requestPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData) external; +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/OptimisticOracleV3CallbackRecipientInterface.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/OptimisticOracleV3CallbackRecipientInterface.sol new file mode 100644 index 000000000..c0744df08 --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/OptimisticOracleV3CallbackRecipientInterface.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.16; + +/** + * @title Optimistic Oracle V3 Callback Recipient Interface + * @notice Interface for contracts implementing callbacks to be received from the Optimistic Oracle V3. + */ +interface OptimisticOracleV3CallbackRecipientInterface { + /** + * @notice Callback function that is called by Optimistic Oracle V3 when an assertion is resolved. + * @param assertionId The identifier of the assertion that was resolved. + * @param assertedTruthfully Whether the assertion was resolved as truthful or not. + */ + function assertionResolvedCallback(bytes32 assertionId, bool assertedTruthfully) external; + + /** + * @notice Callback function that is called by Optimistic Oracle V3 when an assertion is disputed. + * @param assertionId The identifier of the assertion that was disputed. + */ + function assertionDisputedCallback(bytes32 assertionId) external; +} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/OptimisticOracleV3Interface.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/OptimisticOracleV3Interface.sol new file mode 100644 index 000000000..4d7da5b16 --- /dev/null +++ b/contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/OptimisticOracleV3Interface.sol @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.16; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; + +/** + * @title Optimistic Oracle V3 Interface that callers must use to assert truths about the world. + */ +interface OptimisticOracleV3Interface { + // Struct grouping together the settings related to the escalation manager stored in the assertion. + struct EscalationManagerSettings { + bool arbitrateViaEscalationManager; // False if the DVM is used as an oracle (EscalationManager on True). + bool discardOracle; // False if Oracle result is used for resolving assertion after dispute. + bool validateDisputers; // True if the EM isDisputeAllowed should be checked on disputes. + address assertingCaller; // Stores msg.sender when assertion was made. + address escalationManager; // Address of the escalation manager (zero address if not configured). + } + + // Struct for storing properties and lifecycle of an assertion. + struct Assertion { + EscalationManagerSettings escalationManagerSettings; // Settings related to the escalation manager. + address asserter; // Address of the asserter. + uint64 assertionTime; // Time of the assertion. + bool settled; // True if the request is settled. + IERC20 currency; // ERC20 token used to pay rewards and fees. + uint64 expirationTime; // Unix timestamp marking threshold when the assertion can no longer be disputed. + bool settlementResolution; // Resolution of the assertion (false till resolved). + bytes32 domainId; // Optional domain that can be used to relate the assertion to others in the escalationManager. + bytes32 identifier; // UMA DVM identifier to use for price requests in the event of a dispute. + uint256 bond; // Amount of currency that the asserter has bonded. + address callbackRecipient; // Address that receives the callback. + address disputer; // Address of the disputer. + } + + // Struct for storing cached currency whitelist. + struct WhitelistedCurrency { + bool isWhitelisted; // True if the currency is whitelisted. + uint256 finalFee; // Final fee of the currency. + } + + /** + * @notice Disputes an assertion. Depending on how the assertion was configured, this may either escalate to the UMA + * DVM or the configured escalation manager for arbitration. + * @dev The caller must approve this contract to spend at least bond amount of currency for the associated assertion. + * @param assertionId unique identifier for the assertion to dispute. + * @param disputer receives bonds back at settlement. + */ + function disputeAssertion(bytes32 assertionId, address disputer) external; + + /** + * @notice Returns the default identifier used by the Optimistic Oracle V3. + * @return The default identifier. + */ + function defaultIdentifier() external view returns (bytes32); + + /** + * @notice Fetches information about a specific assertion and returns it. + * @param assertionId unique identifier for the assertion to fetch information for. + * @return assertion information about the assertion. + */ + function getAssertion(bytes32 assertionId) external view returns (Assertion memory); + + /** + * @notice Asserts a truth about the world, using the default currency and liveness. No callback recipient or + * escalation manager is enabled. The caller is expected to provide a bond of finalFee/burnedBondPercentage + * (with burnedBondPercentage set to 50%, the bond is 2x final fee) of the default currency. + * @dev The caller must approve this contract to spend at least the result of getMinimumBond(defaultCurrency). + * @param claim the truth claim being asserted. This is an assertion about the world, and is verified by disputers. + * @param asserter receives bonds back at settlement. This could be msg.sender or + * any other account that the caller wants to receive the bond at settlement time. + * @return assertionId unique identifier for this assertion. + */ + function assertTruthWithDefaults(bytes memory claim, address asserter) external returns (bytes32); + + /** + * @notice Asserts a truth about the world, using a fully custom configuration. + * @dev The caller must approve this contract to spend at least bond amount of currency. + * @param claim the truth claim being asserted. This is an assertion about the world, and is verified by disputers. + * @param asserter receives bonds back at settlement. This could be msg.sender or + * any other account that the caller wants to receive the bond at settlement time. + * @param callbackRecipient if configured, this address will receive a function call assertionResolvedCallback and + * assertionDisputedCallback at resolution or dispute respectively. Enables dynamic responses to these events. The + * recipient _must_ implement these callbacks and not revert or the assertion resolution will be blocked. + * @param escalationManager if configured, this address will control escalation properties of the assertion. This + * means a) choosing to arbitrate via the UMA DVM, b) choosing to discard assertions on dispute, or choosing to + * validate disputes. Combining these, the asserter can define their own security properties for the assertion. + * escalationManager also _must_ implement the same callbacks as callbackRecipient. + * @param liveness time to wait before the assertion can be resolved. Assertion can be disputed in this time. + * @param currency bond currency pulled from the caller and held in escrow until the assertion is resolved. + * @param bond amount of currency to pull from the caller and hold in escrow until the assertion is resolved. This + * must be >= getMinimumBond(address(currency)). + * @param identifier UMA DVM identifier to use for price requests in the event of a dispute. Must be pre-approved. + * @param domainId optional domain that can be used to relate this assertion to others in the escalationManager and + * can be used by the configured escalationManager to define custom behavior for groups of assertions. This is + * typically used for "escalation games" by changing bonds or other assertion properties based on the other + * assertions that have come before. If not needed this value should be 0 to save gas. + * @return assertionId unique identifier for this assertion. + */ + function assertTruth( + bytes memory claim, + address asserter, + address callbackRecipient, + address escalationManager, + uint64 liveness, + IERC20 currency, + uint256 bond, + bytes32 identifier, + bytes32 domainId + ) external returns (bytes32); + + /** + * @notice Fetches information about a specific identifier & currency from the UMA contracts and stores a local copy + * of the information within this contract. This is used to save gas when making assertions as we can avoid an + * external call to the UMA contracts to fetch this. + * @param identifier identifier to fetch information for and store locally. + * @param currency currency to fetch information for and store locally. + */ + function syncUmaParams(bytes32 identifier, address currency) external; + + /** + * @notice Resolves an assertion. If the assertion has not been disputed, the assertion is resolved as true and the + * asserter receives the bond. If the assertion has been disputed, the assertion is resolved depending on the oracle + * result. Based on the result, the asserter or disputer receives the bond. If the assertion was disputed then an + * amount of the bond is sent to the UMA Store as an oracle fee based on the burnedBondPercentage. The remainder of + * the bond is returned to the asserter or disputer. + * @param assertionId unique identifier for the assertion to resolve. + */ + function settleAssertion(bytes32 assertionId) external; + + /** + * @notice Settles an assertion and returns the resolution. + * @param assertionId unique identifier for the assertion to resolve and return the resolution for. + * @return resolution of the assertion. + */ + function settleAndGetAssertionResult(bytes32 assertionId) external returns (bool); + + /** + * @notice Fetches the resolution of a specific assertion and returns it. If the assertion has not been settled then + * this will revert. If the assertion was disputed and configured to discard the oracle resolution return false. + * @param assertionId unique identifier for the assertion to fetch the resolution for. + * @return resolution of the assertion. + */ + function getAssertionResult(bytes32 assertionId) external view returns (bool); + + /** + * @notice Returns the minimum bond amount required to make an assertion. This is calculated as the final fee of the + * currency divided by the burnedBondPercentage. If burn percentage is 50% then the min bond is 2x the final fee. + * @param currency currency to calculate the minimum bond for. + * @return minimum bond amount. + */ + function getMinimumBond(address currency) external view returns (uint256); + + event AssertionMade( + bytes32 indexed assertionId, + bytes32 domainId, + bytes claim, + address indexed asserter, + address callbackRecipient, + address escalationManager, + address caller, + uint64 expirationTime, + IERC20 currency, + uint256 bond, + bytes32 indexed identifier + ); + + event AssertionDisputed(bytes32 indexed assertionId, address indexed caller, address indexed disputer); + + event AssertionSettled( + bytes32 indexed assertionId, + address indexed bondRecipient, + bool disputed, + bool settlementResolution, + address settleCaller + ); + + event AdminPropertiesSet(IERC20 defaultCurrency, uint64 defaultLiveness, uint256 burnedBondPercentage); +} diff --git a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/GovernorChildTunnel.sol b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/GovernorChildTunnel.sol new file mode 100644 index 000000000..587752f2f --- /dev/null +++ b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/GovernorChildTunnel.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@maticnetwork/fx-portal/contracts/tunnel/FxBaseChildTunnel.sol"; + +/** + * @title Governor contract deployed on sidechain that receives governance actions from Ethereum. + */ +contract GovernorChildTunnel is FxBaseChildTunnel { + event ExecutedGovernanceTransaction(address indexed to, bytes data); + + constructor(address _fxChild) FxBaseChildTunnel(_fxChild) {} + + /** + * @notice Executes governance transaction created on Ethereum. + * @dev The data will be received automatically from the state receiver when the state is synced between Ethereum + * and Polygon. This will revert if the Root chain sender is not the `fxRootTunnel` contract. + * @param sender The sender of `data` from the Root chain. + * @param data ABI encoded params to include in delegated transaction. + */ + function _processMessageFromRoot( + uint256 /* stateId */, + address sender, + bytes memory data + ) internal override validateSender(sender) { + (address to, bytes memory inputData) = abi.decode(data, (address, bytes)); + + require(_executeCall(to, inputData), "execute call failed"); + emit ExecutedGovernanceTransaction(to, inputData); + } + + // Note: this snippet of code is copied from Governor.sol. + function _executeCall(address to, bytes memory data) private returns (bool) { + // Note: this snippet of code is copied from Governor.sol. + // solhint-disable-next-line max-line-length + // https://github.com/gnosis/safe-contracts/blob/59cfdaebcd8b87a0a32f87b50fead092c10d3a05/contracts/base/Executor.sol#L23-L31 + // solhint-disable-next-line no-inline-assembly + + bool success; + assembly { + let inputData := add(data, 0x20) + let inputDataSize := mload(data) + // Hardcode value to be 0 for relayed governance calls in order to avoid addressing complexity of bridging + // value cross-chain. + success := call(gas(), to, 0, inputData, inputDataSize, 0, 0) + } + return success; + } +} diff --git a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/GovernorRootTunnel.sol b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/GovernorRootTunnel.sol new file mode 100644 index 000000000..187e64f74 --- /dev/null +++ b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/GovernorRootTunnel.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/access/Ownable.sol"; +import "@maticnetwork/fx-portal/contracts/tunnel/FxBaseRootTunnel.sol"; +import "../common/implementation/Lockable.sol"; + +/** + * @title Governance relayer contract to be deployed on Ethereum that receives messages from the owner (Governor) and + * sends them to sidechain. + */ +contract GovernorRootTunnel is Ownable, FxBaseRootTunnel, Lockable { + event RelayedGovernanceRequest(address indexed to, bytes data); + + constructor(address _checkpointManager, address _fxRoot) FxBaseRootTunnel(_checkpointManager, _fxRoot) {} + + /** + * @notice This should be called in order to relay a governance request to the `GovernorChildTunnel` contract + * deployed to the sidechain. Note: this can only be called by the owner (presumably the Ethereum Governor + * contract). + * @dev The transaction submitted to `to` on the sidechain with the calldata `data` is assumed to have 0 `value` + * in order to avoid the added complexity of sending cross-chain transactions with positive value. + */ + function relayGovernance(address to, bytes memory data) external nonReentrant onlyOwner { + _sendMessageToChild(abi.encode(to, data)); + emit RelayedGovernanceRequest(to, data); + } + + /** + * @notice Function called as callback from child tunnel. Should not do anything as governance actions should only + * be sent from root to child. + */ + function _processMessageFromChild(bytes memory data) internal override { + // no-op + } +} diff --git a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleBaseTunnel.sol b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleBaseTunnel.sol new file mode 100644 index 000000000..d9ae51ec2 --- /dev/null +++ b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleBaseTunnel.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../data-verification-mechanism/interfaces/FinderInterface.sol"; +import "../data-verification-mechanism/implementation/Constants.sol"; + +/** + * @notice Enforces lifecycle of price requests for deriving contract. + */ +abstract contract OracleBaseTunnel { + enum RequestState { + NeverRequested, + Requested, + Resolved + } + + struct Price { + RequestState state; + int256 price; + } + + // Mapping of encoded price requests {identifier, time, ancillaryData} to Price objects. + mapping(bytes32 => Price) internal prices; + + // Finder to provide addresses for DVM system contracts. + FinderInterface public finder; + + event PriceRequestAdded(bytes32 indexed identifier, uint256 time, bytes ancillaryData, bytes32 indexed requestHash); + event PushedPrice( + bytes32 indexed identifier, + uint256 time, + bytes ancillaryData, + int256 price, + bytes32 indexed requestHash + ); + + /** + * @notice Constructor. + * @param _finderAddress finder to use to get addresses of DVM contracts. + */ + constructor(address _finderAddress) { + finder = FinderInterface(_finderAddress); + } + + /** + * @notice Enqueues a request (if a request isn't already present) for the given (identifier, time, + * ancillary data) combination. Will only emit an event if the request has never been requested. + */ + function _requestPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData) internal { + bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); + Price storage lookup = prices[priceRequestId]; + if (lookup.state == RequestState.NeverRequested) { + lookup.state = RequestState.Requested; + emit PriceRequestAdded(identifier, time, ancillaryData, priceRequestId); + } + } + + /** + * @notice Publishes price for a requested query. Will only emit an event if the request has never been resolved. + */ + function _publishPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData, int256 price) internal { + bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); + Price storage lookup = prices[priceRequestId]; + if (lookup.state == RequestState.Resolved) return; + lookup.price = price; + lookup.state = RequestState.Resolved; + emit PushedPrice(identifier, time, ancillaryData, lookup.price, priceRequestId); + } + + /** + * @notice Returns the convenient way to store price requests, uniquely identified by {identifier, time, + * ancillaryData }. + */ + function _encodePriceRequest( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) internal pure returns (bytes32) { + return keccak256(abi.encode(identifier, time, ancillaryData)); + } +} diff --git a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleChildTunnel.sol b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleChildTunnel.sol new file mode 100644 index 000000000..8580fc462 --- /dev/null +++ b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleChildTunnel.sol @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@maticnetwork/fx-portal/contracts/tunnel/FxBaseChildTunnel.sol"; +import "../cross-chain-oracle/AncillaryDataCompression.sol"; +import "../data-verification-mechanism/interfaces/OracleAncillaryInterface.sol"; +import "../data-verification-mechanism/interfaces/RegistryInterface.sol"; +import "./OracleBaseTunnel.sol"; +import "../common/implementation/Lockable.sol"; + +/** + * @title Adapter deployed on sidechain to give financial contracts the ability to trigger cross-chain price requests to + * the mainnet DVM. Also has the ability to receive published prices from mainnet. This contract can be treated as the + * "DVM" for this network, because a calling contract can request and access a resolved price request from this + * contract. + * @dev The intended client of this contract is an OptimisticOracle on sidechain that needs price + * resolution secured by the DVM on mainnet. + */ +contract OracleChildTunnel is OracleBaseTunnel, OracleAncillaryInterface, FxBaseChildTunnel, Lockable { + using AncillaryDataCompression for bytes; + + // Mapping of parent request ID to child request ID. + mapping(bytes32 => bytes32) public childRequestIds; + + event PriceRequestBridged( + address indexed requester, + bytes32 identifier, + uint256 time, + bytes ancillaryData, + bytes32 indexed childRequestId, + bytes32 indexed parentRequestId + ); + event ResolvedLegacyRequest( + bytes32 indexed identifier, + uint256 time, + bytes ancillaryData, + int256 price, + bytes32 indexed requestHash, + bytes32 indexed legacyRequestHash + ); + + constructor( + address _fxChild, + address _finderAddress + ) OracleBaseTunnel(_finderAddress) FxBaseChildTunnel(_fxChild) {} + + // This assumes that the local network has a Registry that resembles the mainnet registry. + modifier onlyRegisteredContract() { + RegistryInterface registry = RegistryInterface(finder.getImplementationAddress(OracleInterfaces.Registry)); + require(registry.isContractRegistered(msg.sender), "Caller must be registered"); + _; + } + + /** + * @notice This should be called to bridge a price request to mainnet. + * @dev Can be called only by a registered contract that is allowed to make DVM price requests. Will mark this + * price request as Requested, and therefore able to receive the price resolution data from mainnet. Emits a message + * that will be included in regular checkpoint of all sidechain transactions to mainnet. + * @param identifier Identifier of price request. + * @param time Timestamp of price request. + * @param ancillaryData extra data of price request. + */ + function requestPrice( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) public override nonReentrant onlyRegisteredContract { + address requester = msg.sender; + bytes32 childRequestId = _encodePriceRequest(identifier, time, ancillaryData); + Price storage lookup = prices[childRequestId]; + + // Send the request to mainnet if it has not been requested yet. + if (lookup.state != RequestState.NeverRequested) return; + lookup.state = RequestState.Requested; + + // Only the compressed ancillary data is sent to the mainnet. As it includes the request block number that is + // not available when getting the resolved price, we map the derived request ID. + bytes memory parentAncillaryData = ancillaryData.compress(requester, block.number); + bytes32 parentRequestId = _encodePriceRequest(identifier, time, parentAncillaryData); + childRequestIds[parentRequestId] = childRequestId; + + // Emit all required information so that voters on mainnet can track the origin of the request and full + // ancillary data by using the parentRequestId that is derived from identifier, time and ancillary data as + // observed on mainnet. + emit PriceRequestBridged(requester, identifier, time, ancillaryData, childRequestId, parentRequestId); + emit PriceRequestAdded(identifier, time, parentAncillaryData, parentRequestId); + + _sendMessageToRoot(abi.encode(identifier, time, parentAncillaryData)); + } + + /** + * @notice Resolves a price request. + * @dev The data will be received automatically from the state receiver when the state is synced between Ethereum + * and Polygon. This will revert if the Root chain sender is not the `fxRootTunnel` contract. + * @param sender The sender of `data` from the Root chain. + * @param data ABI encoded params with which to call `_publishPrice`. + */ + function _processMessageFromRoot( + uint256 /* stateId */, + address sender, + bytes memory data + ) internal override validateSender(sender) { + (bytes32 identifier, uint256 time, bytes memory ancillaryData, int256 price) = abi.decode( + data, + (bytes32, uint256, bytes, int256) + ); + bytes32 parentRequestId = _encodePriceRequest(identifier, time, ancillaryData); + + // Resolve the requestId used when requesting and checking the price. The childRequestIds value in the mapping + // could be uninitialized if the request was originated from: + // - the previous implementation of this contract, or + // - another chain and was pushed to this chain by mistake. + bytes32 priceRequestId = childRequestIds[parentRequestId]; + if (priceRequestId == bytes32(0)) priceRequestId = parentRequestId; + Price storage lookup = prices[priceRequestId]; + + // In order to support resolving the requests initiated from the previous implementation of this contract, we + // only update the state and emit an event if it has not yet been resolved. + if (lookup.state == RequestState.Resolved) return; + lookup.price = price; + lookup.state = RequestState.Resolved; + emit PushedPrice(identifier, time, ancillaryData, price, priceRequestId); + } + + /** + * @notice This method handles a special case when a price request was originated on the previous implementation of + * this contract, but was not settled before the upgrade. + * @dev Duplicates the resolved state from the legacy request to the new request where original ancillary data is + * used for request ID derivation. Will revert if the legacy request has not been pushed from mainnet. + * @param identifier Identifier of price request to resolve. + * @param time Timestamp of price request to resolve. + * @param ancillaryData Original ancillary data passed by the requester before stamping by the legacy spoke. + * @param childRequester Address of the requester that initiated the price request. + */ + function resolveLegacyRequest( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData, + address childRequester + ) external { + bytes32 legacyRequestId = _encodePriceRequest( + identifier, + time, + _legacyStampAncillaryData(ancillaryData, childRequester) + ); + Price storage legacyLookup = prices[legacyRequestId]; + require(legacyLookup.state == RequestState.Resolved, "Price has not been resolved"); + + bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); + Price storage lookup = prices[priceRequestId]; + + // Update the state and emit an event only if the legacy request has not been resolved yet. + if (lookup.state == RequestState.Resolved) return; + lookup.price = legacyLookup.price; + lookup.state = RequestState.Resolved; + emit ResolvedLegacyRequest(identifier, time, ancillaryData, lookup.price, priceRequestId, legacyRequestId); + } + + /** + * @notice Returns whether a price has resolved for the request. + * @param identifier Identifier of price request. + * @param time Timestamp of price request + * @param ancillaryData extra data of price request. + * @return True if a price is available, False otherwise. If true, then getPrice will succeed for the request. + */ + function hasPrice( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) public view override nonReentrantView onlyRegisteredContract returns (bool) { + bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); + return prices[priceRequestId].state == RequestState.Resolved; + } + + /** + * @notice Returns resolved price for the request. + * @dev Reverts if price is not available. + * @param identifier Identifier of price request. + * @param time Timestamp of price request + * @param ancillaryData extra data of price request. + * @return int256 Price, or reverts if no resolved price for any reason. + */ + function getPrice( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) public view override nonReentrantView onlyRegisteredContract returns (int256) { + bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); + Price storage lookup = prices[priceRequestId]; + require(lookup.state == RequestState.Resolved, "Price has not been resolved"); + return lookup.price; + } + + /** + * @notice Compresses ancillary data by providing sufficient information to track back the original ancillary data + * mainnet. + * @dev This is expected to be used in offchain infrastructure when speeding up requests to the mainnet. + * @param ancillaryData original ancillary data to be processed. + * @param requester address of the requester who initiated the price request. + * @param requestBlockNumber block number when the price request was initiated. + * @return compressed ancillary data. + */ + function compressAncillaryData( + bytes memory ancillaryData, + address requester, + uint256 requestBlockNumber + ) external view returns (bytes memory) { + return ancillaryData.compress(requester, requestBlockNumber); + } + + /** + * @dev This replicates the implementation of `_stampAncillaryData` from the previous version of this contract for + * the purpose of resolving legacy requests if they had not been resolved before the upgrade. + */ + function _legacyStampAncillaryData( + bytes memory ancillaryData, + address requester + ) internal view returns (bytes memory) { + // Price requests that originate from this method, on Polygon, will ultimately be submitted to the DVM on + // Ethereum via the OracleRootTunnel. Therefore this contract should stamp its requester's address in the + // ancillary data so voters can conveniently track the requests path to the DVM. + return + AncillaryData.appendKeyValueUint( + AncillaryData.appendKeyValueAddress(ancillaryData, "childRequester", requester), + "childChainId", + block.chainid + ); + } +} diff --git a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleRootTunnel.sol b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleRootTunnel.sol new file mode 100644 index 000000000..97278a9d6 --- /dev/null +++ b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleRootTunnel.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@maticnetwork/fx-portal/contracts/tunnel/FxBaseRootTunnel.sol"; + +import "./OracleBaseTunnel.sol"; +import "../data-verification-mechanism/interfaces/OracleAncillaryInterface.sol"; +import "../common/implementation/Lockable.sol"; + +/** + * @title Adapter deployed on mainnet that validates and sends price requests from sidechain to the DVM on mainnet. + * @dev This contract must be a registered financial contract in order to make DVM price requests. + */ +contract OracleRootTunnel is OracleBaseTunnel, FxBaseRootTunnel, Lockable { + constructor( + address _checkpointManager, + address _fxRoot, + address _finderAddress + ) OracleBaseTunnel(_finderAddress) FxBaseRootTunnel(_checkpointManager, _fxRoot) {} + + /** + * @notice This is the first method that should be called in order to publish a price request to the sidechain. + * @dev Publishes the DVM resolved price for the price request, or reverts if not resolved yet. This contract must + * be registered with the DVM to query price requests. + * @param identifier Identifier of price request to resolve. + * @param time Timestamp of price request to resolve. + * @param ancillaryData extra data of price request to resolve. + */ + function publishPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData) public nonReentrant { + // `getPrice` will revert if there is no price. + int256 price = _getOracle().getPrice(identifier, time, ancillaryData); + // This implementation allows duplicate MessageSent events via _sendMessageToRoot. The child tunnel on the + // sidechain will not have a problem handling duplicate price resolutions (it will just ignore them). This is + // potentially a fallback in case the automatic state sync to the sidechain is missing the `publishPrice` + // transaction for some reason. There is little risk in duplicating MessageSent emissions because the sidechain + // bridge does not impose any rate-limiting. + _publishPrice(identifier, time, ancillaryData, price); + _sendMessageToChild(abi.encode(identifier, time, ancillaryData, price)); + } + + /** + * @notice Submits a price request. + * @dev This internal method will be called inside `receiveMessage(bytes memory inputData)`. The `inputData` is a + * proof of transaction that is derived from the transaction hash of the transaction on the child chain that + * originated the cross-chain price request via _sendMessageToRoot. This contract must be registered with the DVM + * to submit price requests. + * @param data ABI encoded params with which to call `requestPrice`. + */ + function _processMessageFromChild(bytes memory data) internal override { + (bytes32 identifier, uint256 time, bytes memory ancillaryData) = abi.decode(data, (bytes32, uint256, bytes)); + _requestPrice(identifier, time, ancillaryData); + _getOracle().requestPrice(identifier, time, ancillaryData); + } + + /** + * @notice Return DVM for this network. + */ + function _getOracle() internal view returns (OracleAncillaryInterface) { + return OracleAncillaryInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); + } +} diff --git a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/README.md b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/README.md new file mode 100644 index 000000000..abb4034c2 --- /dev/null +++ b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/README.md @@ -0,0 +1,42 @@ +# Polygon <> Ethereum State Transfer + +This document describes the architecture of how arbitrary messages can be passed between Ethereum and Polygon. For a more detailed explanation from the Polygon official docs site, go [here](https://docs.polygon.technology/docs/develop/l1-l2-communication/state-transfer/). + +# Two-way bridge between Root on Ethereum and Child on Polygon + +At a high level we are deploying "Root" and "Child" bridge contracts on both networks that communicate only with each other and the native [state sync](https://docs.polygon.technology/docs/contribute/state-sync/state-sync) infrastructure that Polygon uses to pass data between the Ethereum and Polygon EVM's. Polygon uses "tunnel" to describe what [other](https://docs.tokenbridge.net/amb-bridge/about-amb-bridge) [relayer](https://forum.makerdao.com/t/announcing-the-optimism-dai-bridge-with-fast-withdrawals/6938) [systems](https://developer.offchainlabs.com/docs/inside_arbitrum#bridging) call "bridges". + +Diagram of oracle tunnel system: ![image](https://user-images.githubusercontent.com/12886084/121140379-115e8e80-c83a-11eb-89e9-27a694e20814.png). + +# Root Tunnel Contract + +This contract is deployed on Ethereum and inherits from the official tunnel implementation called the ["FxBaseRootTunnel"](https://github.com/fx-portal/contracts/blob/baed24d22178201bca33140c303e0925661ec0ac/contracts/tunnel/FxBaseRootTunnel.sol) which implements `_processMessageFromChild(bytes memory data)` to receive messages from Polygon and enforces that the message originated from a Polygon transaction that has been provably [checkpointed](https://docs.matic.network/docs/contribute/heimdall/checkpoint/) to Ethereum. Notably, the root tunnel can only communicate with one child tunnel on Polygon, and the child tunnel address cannot be overwritten after being set. + +In order to send messages to Polygon, the tunnel contract must be initialized to point to a "FxRoot" contract that is [already deployed](https://etherscan.io/address/0xfe5e5D361b2ad62c541bAb87C45a0B9B018389a2#code) to Ethereum. The tunnel contract can send messages to Polygon via the "FxRoot" which has special permission to call `syncState(address receiver, bytes calldata data)` on the ["StateSender" contract](https://etherscan.io/address/0x28e4f3a7f651294b9564800b2d01f35189a5bfbe/advanced#code). + +To receive messages from Polygon, the tunnel contract is similarly initialized to point to a ["CheckpointManager"](https://etherscan.io/address/0x86e4dc95c7fbdbf52e33d563bbdb00823894c287) deployed on Ethereum. Checkpoints are snapshots of the [Polygon chain state](https://docs.polygon.technology/docs/contribute/heimdall/checkpoint) that are first validated by the Polygon validator set before being submitted to the "CheckpointManager" on Ethereum. Once a Polygon transaction is checkpointed to Ethereum, its arbitrary message can be used to trigger a function call on the Root Tunnel following an inclusion proof. This line in [FxBaseRootTunnel](https://github.com/fx-portal/contracts/blob/baed24d22178201bca33140c303e0925661ec0ac/contracts/tunnel/FxBaseRootTunnel.sol#L103) implements the inclusion proof verification, and the `_validateAndExtractMessage` internal method must pass before the Root Tunnel can submit a price request to the DVM. + +# Child Tunnel Contract + +This contract is deployed on Polygon and inherits from the official tunnel implementation called the ["FxBaseChildTunnel"](https://github.com/fx-portal/contracts/blob/baed24d22178201bca33140c303e0925661ec0ac/contracts/tunnel/FxBaseChildTunnel.sol) which implements `_processMessageFromRoot(bytes memory data)` to receive messages from Ethereum. Like the root tunnel, the child tunnel can only communicate with one root tunnel whose address cannot be overwritten after being set. + +To send messages to Ethereum, the tunnel contract emits a `MessageSent(bytes message)` event containing the message that can be [passed to the Root tunnel contract](https://github.com/fx-portal/contracts/blob/baed24d22178201bca33140c303e0925661ec0ac/contracts/tunnel/FxBaseRootTunnel.sol#L138) after the transaction that originally emitted the `MessageSent` event has been included in a Checkpoint. + +To receive messages from Ethereum, Polygon validators will automatically detect and submit `StateSynced(uint256 id, address contractAddress, bytes data)` to Polygon's "FxChild" contract and execute `onStateReceive(uint256 stateId, bytes _data)`, which will pass the message on to the Child tunnel contract. The Child tunnel must be initialized by pointing to the ["FxChild" contract](https://explorer-mainnet.maticvigil.com/address/0x8397259c983751DAf40400790063935a11afa28a/read-contract) deployed on Polygon which can only be called by the System [Superuser address](https://explorer-mainnet.maticvigil.com/address/0x0000000000000000000000000000000000001001/transactions) on Polygon. + +# Types of messages that can be relayed + +Any message can be relayed between Polygon and Ethereum provided that they are sent by contracts that implement the Polygon tunnel interface correctly (i.e. they can make calls to FxChild and FxRoot and they implement `_processMessage...` correctly). Each contract that wants to transfer state between Polygon and Ethereum must set up its own tunnel, so the relationship between Child and Root tunnels is 1-to-1. For example, UMA requires at least two tunnels to be set up: + +- Oracle Tunnel: Submitting price requests from Polygon to Ethereum and resolving prices from Ethereum to Polygon +- Governor Tunnel: Sending governance actions from Ethereum to Polygon that can be executed on Polygon + +# Special Permissions that Tunnel contracts need within UMA system + +- Oracle Tunnel: The Root tunnel must be able to submit price requests to the DVM and therefore must be registered with the `Registry`. + +# Security Considerations + +This system relies on the [Polygon consensus mechanism](https://docs.matic.network/docs/home/architecture/security-models#proof-of-stake-security) secured by validators in a Proof of Stake system. The validator set enforces the integrity of data passed between networks (i.e. downstream users need to trust that the validators are not modifying the arbitrary messages that are being sent between networks). + +Moreover, downstream users also rely on off-chain actors to relay messages in a timely fashion. Historically messages are sent once per hour. diff --git a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/FxChildMock.sol b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/FxChildMock.sol new file mode 100644 index 000000000..07edece58 --- /dev/null +++ b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/FxChildMock.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// IStateReceiver represents interface to receive state +interface IStateReceiver { + function onStateReceive(uint256 stateId, bytes calldata data) external; +} + +// IFxMessageProcessor represents interface to process message +interface IFxMessageProcessor { + function processMessageFromRoot(uint256 stateId, address rootMessageSender, bytes calldata data) external; +} + +/** + * @title FxChild child contract for state receiver + */ +contract FxChildMock is IStateReceiver { + address public fxRoot; + address public systemCaller; + + event NewFxMessage(address rootMessageSender, address receiver, bytes data); + + constructor(address _systemCaller) { + systemCaller = _systemCaller; + } + + function setFxRoot(address _fxRoot) public { + require(fxRoot == address(0x0)); + fxRoot = _fxRoot; + } + + function onStateReceive(uint256 stateId, bytes calldata _data) external override { + require(msg.sender == systemCaller, "Invalid sender: must be system super user"); + (address rootMessageSender, address receiver, bytes memory data) = abi.decode(_data, (address, address, bytes)); + emit NewFxMessage(rootMessageSender, receiver, data); + IFxMessageProcessor(receiver).processMessageFromRoot(stateId, rootMessageSender, data); + } +} diff --git a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/FxRootMock.sol b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/FxRootMock.sol new file mode 100644 index 000000000..de6831908 --- /dev/null +++ b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/FxRootMock.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IStateSender { + function syncState(address receiver, bytes calldata data) external; +} + +interface IFxStateSender { + function sendMessageToChild(address _receiver, bytes calldata _data) external; +} + +/** + * @title FxRoot root contract for fx-portal + */ +contract FxRootMock is IFxStateSender { + IStateSender public stateSender; + address public fxChild; + + constructor(address _stateSender) { + stateSender = IStateSender(_stateSender); + } + + function setFxChild(address _fxChild) public { + require(fxChild == address(0x0)); + fxChild = _fxChild; + } + + function sendMessageToChild(address _receiver, bytes calldata _data) public override { + bytes memory data = abi.encode(msg.sender, _receiver, _data); + stateSender.syncState(fxChild, data); + } +} diff --git a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/OracleBaseTunnelMock.sol b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/OracleBaseTunnelMock.sol new file mode 100644 index 000000000..bd44ca023 --- /dev/null +++ b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/OracleBaseTunnelMock.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../OracleBaseTunnel.sol"; + +/** + * @title Test implementation of OracleBaseTunnel enabling unit tests on internal methods. + * @dev Unit tests should ensure that internal methods `_requestPrice` and `_publishPrice` emit the correct events + * and modify state as expected. + */ +contract OracleBaseTunnelMock is OracleBaseTunnel { + constructor(address _finderAddress) OracleBaseTunnel(_finderAddress) {} + + function requestPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData) public { + _requestPrice(identifier, time, ancillaryData); + } + + function encodePriceRequest( + bytes32 identifier, + uint256 time, + bytes memory ancillaryData + ) public pure returns (bytes32) { + return _encodePriceRequest(identifier, time, ancillaryData); + } + + function publishPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData, int256 price) public { + _publishPrice(identifier, time, ancillaryData, price); + } +} diff --git a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/OracleRootTunnelMock.sol b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/OracleRootTunnelMock.sol new file mode 100644 index 000000000..43073ae88 --- /dev/null +++ b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/OracleRootTunnelMock.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../OracleRootTunnel.sol"; + +contract OracleRootTunnelMock is OracleRootTunnel { + // If set to true, then always revert on calls to receiveMessage. + bool public revertReceiveMessage; + + // Error message to emit when receiveMessage reverts. + string public errorMessage; + + event ReceivedMessage(bytes indexed inputData); + + constructor( + address _checkpointManager, + address _fxRoot, + address _finderAddress + ) OracleRootTunnel(_checkpointManager, _fxRoot, _finderAddress) { + revertReceiveMessage = false; + errorMessage = "generic error message"; + } + + // Helper method to test _processMessageFromChild directly without having to call internal + // _validateAndExtractMessage + function processMessageFromChild(bytes memory message) public { + _processMessageFromChild(message); + } + + // Helper method to test receiveMessage. Will always succeed unless `revertReceiveMessage` is True, then will + // always revert. + function receiveMessage(bytes memory inputData) public override { + if (!revertReceiveMessage) { + emit ReceivedMessage(inputData); + } else { + require(false, errorMessage); + } + } + + function setRevertReceiveMessage(bool _revertReceiveMessage) public { + revertReceiveMessage = _revertReceiveMessage; + } + + function setRevertErrorMessage(string calldata _errorMessage) public { + errorMessage = _errorMessage; + } +} diff --git a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/StateSyncMock.sol b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/StateSyncMock.sol new file mode 100644 index 000000000..1420e8b74 --- /dev/null +++ b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/StateSyncMock.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @notice Dummy State Sender contract to simulate plasma state sender while testing + */ +contract StateSyncMock { + /** + * @notice Event emitted when when syncState is called + * @dev Heimdall bridge listens to this event and sends the data to receiver contract on child chain + * @param id Id of the sync, increamented for each event in case of actual state sender contract + * @param contractAddress the contract receiving data on child chain + * @param data bytes data to be sent + */ + event StateSynced(uint256 indexed id, address indexed contractAddress, bytes data); + + /** + * @notice called to send data to child chain + * @dev sender and receiver contracts need to be registered in case of actual state sender contract + * @param receiver the contract receiving data on child chain + * @param data bytes data to be sent + */ + function syncState(address receiver, bytes calldata data) external { + emit StateSynced(1, receiver, data); + } +} diff --git a/contracts/external/uma/core/contracts/proxy-scripts/atomic-disputer/ReserveCurrencyDisputer.sol b/contracts/external/uma/core/contracts/proxy-scripts/atomic-disputer/ReserveCurrencyDisputer.sol new file mode 100644 index 000000000..248d9012c --- /dev/null +++ b/contracts/external/uma/core/contracts/proxy-scripts/atomic-disputer/ReserveCurrencyDisputer.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; +pragma abicoder v2; + +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; + +import "@uniswap/lib/contracts/libraries/TransferHelper.sol"; +import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol"; + +import "../../common/implementation/FixedPoint.sol"; + +/** + * @title ReserveCurrencyDisputer + * @notice Helper contract to enable a disputer to hold one reserver currency and dispute against any number of + * financial contracts. Is assumed to be called by a DSProxy which holds reserve currency. + */ + +contract ReserveCurrencyDisputer { + using SafeMath for uint256; + using FixedPoint for FixedPoint.Unsigned; + + /** + * @notice Swaps required amount of reserve currency to collateral currency which is then used to dispute a liquidation. + * @dev Any collateral the contract has will be used before anything is purchased on Uniswap. + * @param uniswapRouter address of the uniswap router used to facilitate trades. + * @param financialContract address of the financial contract on which the liquidation is occurring. + * @param reserveCurrency address of the token to swap for collateral. This is the common currency held by the DSProxy. + * @param sponsor address of the sponsor who's liquidation is disputed. + * @param liquidationId index of the liquidation for the given sponsor. + * @param maxReserveTokenSpent maximum number of reserve tokens to spend in the trade. Bounds slippage. + * @param deadline abort the trade and dispute if the transaction is mined after this timestamp. + **/ + function swapDispute( + address uniswapRouter, + address financialContract, + address reserveCurrency, + uint256 liquidationId, + address sponsor, + uint256 maxReserveTokenSpent, + uint256 deadline + ) public { + IFinancialContract fc = IFinancialContract(financialContract); + + // 1. Fetch information about the liquidation from the financial contract. + IFinancialContract.LiquidationData memory liquidationData = fc.liquidations(sponsor, liquidationId); + + // 2. Fetch the disputeBondPercentage from the financial contract. + FixedPoint.Unsigned memory disputeBondPercentage = fc.disputeBondPercentage(); + + // 3. Compute the disputeBondAmount. Multiply by the unit collateral so the dispute bond is a percentage of the + // locked collateral after fees. To add fees we simply multiply the rawUnitCollateral by the cumulativeFeeMultiplier. + FixedPoint.Unsigned memory disputeBondAmount = liquidationData.lockedCollateral.mul(disputeBondPercentage).mul( + (liquidationData.rawUnitCollateral).mul(fc.cumulativeFeeMultiplier()) + ); + + // 4. Calculate required collateral. Cost of a dispute is the dispute bond + the final fee. + FixedPoint.Unsigned memory totalCollateralRequired = disputeBondAmount.add(liquidationData.finalFee); + + // 5. Compute the collateral shortfall. This considers and collateral that is current in the contract. + FixedPoint.Unsigned memory collateralToBePurchased = subOrZero( + totalCollateralRequired, + getCollateralBalance(fc) + ); + + // 6. If there is collateral to be purchased, buy it on uniswap with the reserve currency. + if (collateralToBePurchased.isGreaterThan(0) && reserveCurrency != fc.collateralCurrency()) { + IUniswapV2Router01 router = IUniswapV2Router01(uniswapRouter); + address[] memory path = new address[](2); + path[0] = reserveCurrency; + path[1] = fc.collateralCurrency(); + + TransferHelper.safeApprove(reserveCurrency, address(router), maxReserveTokenSpent); + router.swapTokensForExactTokens( + collateralToBePurchased.rawValue, + maxReserveTokenSpent, + path, + address(this), + deadline + ); + } + + // 7. Finally, submit the dispute. + TransferHelper.safeApprove(fc.collateralCurrency(), address(fc), totalCollateralRequired.rawValue); + fc.dispute(liquidationId, sponsor); + } + + // Helper method to work around subtraction overflow in the case of: a - b with b > a. + function subOrZero( + FixedPoint.Unsigned memory a, + FixedPoint.Unsigned memory b + ) internal pure returns (FixedPoint.Unsigned memory) { + return b.isGreaterThanOrEqual(a) ? FixedPoint.fromUnscaledUint(0) : a.sub(b); + } + + // Helper method to return the collateral balance of this contract. + function getCollateralBalance(IFinancialContract fc) internal view returns (FixedPoint.Unsigned memory) { + return FixedPoint.Unsigned(IERC20(fc.collateralCurrency()).balanceOf(address(this))); + } +} + +// Define some simple interfaces for dealing with UMA contracts. +interface IFinancialContract { + enum Status { + Uninitialized, + NotDisputed, + Disputed, + DisputeSucceeded, + DisputeFailed + } + + struct LiquidationData { + address sponsor; + address liquidator; + Status state; + uint256 liquidationTime; + FixedPoint.Unsigned tokensOutstanding; + FixedPoint.Unsigned lockedCollateral; + FixedPoint.Unsigned liquidatedCollateral; + FixedPoint.Unsigned rawUnitCollateral; + address disputer; + FixedPoint.Unsigned settlementPrice; + FixedPoint.Unsigned finalFee; + } + + function liquidations(address sponsor, uint256 liquidationId) external view returns (LiquidationData memory); + + function disputeBondPercentage() external view returns (FixedPoint.Unsigned memory); + + function disputerDisputeRewardPct() external view returns (FixedPoint.Unsigned memory); + + function cumulativeFeeMultiplier() external view returns (FixedPoint.Unsigned memory); + + function collateralCurrency() external view returns (address); + + function dispute(uint256 liquidationId, address sponsor) external returns (FixedPoint.Unsigned memory totalPaid); +} diff --git a/contracts/external/uma/core/contracts/proxy-scripts/atomic-liquidator/ReserveCurrencyLiquidator.sol b/contracts/external/uma/core/contracts/proxy-scripts/atomic-liquidator/ReserveCurrencyLiquidator.sol new file mode 100644 index 000000000..3acafa0d9 --- /dev/null +++ b/contracts/external/uma/core/contracts/proxy-scripts/atomic-liquidator/ReserveCurrencyLiquidator.sol @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; + +import "@uniswap/lib/contracts/libraries/TransferHelper.sol"; +import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol"; + +import "../../common/implementation/FixedPoint.sol"; + +/** + * @title ReserveCurrencyLiquidator + * @notice Helper contract to enable a liquidator to hold one reserver currency and liquidate against any number of + * financial contracts. Is assumed to be called by a DSProxy which holds reserve currency. + */ + +contract ReserveCurrencyLiquidator { + using SafeMath for uint256; + using FixedPoint for FixedPoint.Unsigned; + + /** + * @notice Swaps required amount of reserve currency to collateral currency which is then used to mint tokens to + * liquidate a position within one transaction. + * @dev After the liquidation is done the DSProxy that called this method will have an open position AND pending + * liquidation within the financial contract. The bot using the DSProxy should withdraw the liquidation once it has + * passed liveness. At this point the position can be manually unwound. + * @dev Any synthetics & collateral that the DSProxy already has are considered in the amount swapped and minted. + * These existing tokens will be used first before any swaps or mints are done. + * @dev If there is a token shortfall (either from not enough reserve to buy sufficient collateral or not enough + * collateral to begins with or due to slippage) the script will liquidate as much as possible given the reserves. + * @param uniswapRouter address of the uniswap router used to facilitate trades. + * @param financialContract address of the financial contract on which the liquidation is occurring. + * @param reserveCurrency address of the token to swap for collateral. THis is the common currency held by the DSProxy. + * @param liquidatedSponsor address of the sponsor to be liquidated. + * @param maxSlippage max slip the trade on uniswap will accept before reverting. + * @param minCollateralPerTokenLiquidated abort the liquidation if the position's collateral per token is below this value. + * @param maxCollateralPerTokenLiquidated abort the liquidation if the position's collateral per token exceeds this value. + * @param maxTokensToLiquidate max number of tokens to liquidate. For a full liquidation this is the full position debt. + * @param deadline abort the trade and liquidation if the transaction is mined after this timestamp. + **/ + function swapMintLiquidate( + address uniswapRouter, + address financialContract, + address reserveCurrency, + address liquidatedSponsor, + FixedPoint.Unsigned calldata minCollateralPerTokenLiquidated, + FixedPoint.Unsigned calldata maxCollateralPerTokenLiquidated, + FixedPoint.Unsigned memory maxTokensToLiquidate, + uint256 maxSlippage, + uint256 deadline + ) public { + IFinancialContract fc = IFinancialContract(financialContract); + + // 1. Calculate the token shortfall. This is the synthetics to liquidate minus any synthetics the DSProxy already + // has. If this number is negative(balance large than synthetics to liquidate) the return 0 (no shortfall). + FixedPoint.Unsigned memory tokenShortfall = subOrZero(maxTokensToLiquidate, getSyntheticBalance(fc)); + + // 2. Calculate how much collateral is needed to make up the token shortfall from minting new synthetics. + FixedPoint.Unsigned memory gcr = fc.pfc().divCeil(fc.totalTokensOutstanding()); + FixedPoint.Unsigned memory collateralToMintShortfall = tokenShortfall.mulCeil(gcr); + + // 3. Calculate the total collateral required. This considers the final fee for the given collateral type + any + // collateral needed to mint the token short fall. + + FixedPoint.Unsigned memory totalCollateralRequired = getFinalFee(fc).add(collateralToMintShortfall); + + // 4.a. Calculate how much collateral needs to be purchased. If the DSProxy already has some collateral then this + // will factor this in. If the DSProxy has more collateral than the total amount required the purchased = 0. + uint256 collateralToBePurchased = subOrZero(totalCollateralRequired, getCollateralBalance(fc)).rawValue; + + // 4.b. If there is some collateral to be purchased, execute a trade on uniswap to meet the shortfall. + // Note the path assumes a direct route from the reserve currency to the collateral currency. + // Note the maxInputAmount is computed by taking the 1000000 wei trade as the spot price from the router, + // multiplied by collateral to be purchased to arrive at a "zero slippage input amount". This would be the amount of + // required input to buy the amountOut, assuming zero slippage. This is then scalded by maxSlippage & swap fees + // to find the amountInMax that factors in the max tolerable exchange slippage. The maxSlippage is divided by two + // as slippage in an AMM is constituted by the inputToken going up and the outputToken going down in proportion. + // the +1e18 is used to offset the slippage percentage provided. i.e a 5% will be input at 0.05e18, offset by 1e18 + // to bring it up to 1.05e18. the *997 and *1000 in the numerator and denominator respectively are for uniswap fees. + if (collateralToBePurchased > 0 && reserveCurrency != fc.collateralCurrency()) { + IUniswapV2Router01 router = IUniswapV2Router01(uniswapRouter); + address[] memory path = new address[](2); + path[0] = reserveCurrency; + path[1] = fc.collateralCurrency(); + + TransferHelper.safeApprove(reserveCurrency, address(router), type(uint256).max); + router.swapTokensForExactTokens( + collateralToBePurchased, // amountOut + (router.getAmountsIn(1000000, path)[0] * collateralToBePurchased * (1e18 + maxSlippage / 2) * 997) / + (1000000 * 1e18 * 1000), // amountInMax + path, + address(this), + deadline + ); + } + + // 4.c. If at this point we were not able to get `the required amount of collateral (due to insufficient reserve + // or not enough collateral in the contract) the script should try to liquidate as much as it can regardless. + // Update the values of total collateral to the current collateral balance and re-compute the tokenShortfall + // as the maximum tokens that could be liquidated at the current GCR. + if (totalCollateralRequired.isGreaterThan(getCollateralBalance(fc))) { + totalCollateralRequired = getCollateralBalance(fc); + collateralToMintShortfall = totalCollateralRequired.sub(getFinalFee(fc)); + tokenShortfall = collateralToMintShortfall.divCeil(gcr); + } + // 5. Mint the shortfall synthetics with collateral. Note we are minting at the GCR. + // If the DSProxy already has enough tokens (tokenShortfall = 0) we still preform the approval on the collateral + // currency as this is needed to pay the final fee in the liquidation tx. + TransferHelper.safeApprove(fc.collateralCurrency(), address(fc), totalCollateralRequired.rawValue); + if (tokenShortfall.isGreaterThan(0)) fc.create(collateralToMintShortfall, tokenShortfall); + + // The liquidatableTokens is either the maxTokensToLiquidate (if we were able to buy/mint enough) or the full + // token token balance at this point if there was a shortfall. + if (maxTokensToLiquidate.isGreaterThan(getSyntheticBalance(fc))) maxTokensToLiquidate = getSyntheticBalance(fc); + + // 6. Liquidate position with newly minted synthetics. + TransferHelper.safeApprove(fc.tokenCurrency(), address(fc), maxTokensToLiquidate.rawValue); + fc.createLiquidation( + liquidatedSponsor, + minCollateralPerTokenLiquidated, + maxCollateralPerTokenLiquidated, + maxTokensToLiquidate, + deadline + ); + } + + // Helper method to work around subtraction overflow in the case of: a - b with b > a. + function subOrZero( + FixedPoint.Unsigned memory a, + FixedPoint.Unsigned memory b + ) internal pure returns (FixedPoint.Unsigned memory) { + return b.isGreaterThanOrEqual(a) ? FixedPoint.fromUnscaledUint(0) : a.sub(b); + } + + // Helper method to return the current final fee for a given financial contract instance. + function getFinalFee(IFinancialContract fc) internal view returns (FixedPoint.Unsigned memory) { + return IStore(IFinder(fc.finder()).getImplementationAddress("Store")).computeFinalFee(fc.collateralCurrency()); + } + + // Helper method to return the collateral balance of this contract. + function getCollateralBalance(IFinancialContract fc) internal view returns (FixedPoint.Unsigned memory) { + return FixedPoint.Unsigned(IERC20(fc.collateralCurrency()).balanceOf(address(this))); + } + + // Helper method to return the synthetic balance of this contract. + function getSyntheticBalance(IFinancialContract fc) internal view returns (FixedPoint.Unsigned memory) { + return FixedPoint.Unsigned(IERC20(fc.tokenCurrency()).balanceOf(address(this))); + } +} + +// Define some simple interfaces for dealing with UMA contracts. +interface IFinancialContract { + struct PositionData { + FixedPoint.Unsigned tokensOutstanding; + uint256 withdrawalRequestPassTimestamp; + FixedPoint.Unsigned withdrawalRequestAmount; + FixedPoint.Unsigned rawCollateral; + uint256 transferPositionRequestPassTimestamp; + } + + function positions(address sponsor) external view returns (PositionData memory); + + function collateralCurrency() external view returns (address); + + function tokenCurrency() external view returns (address); + + function finder() external view returns (address); + + function pfc() external view returns (FixedPoint.Unsigned memory); + + function totalTokensOutstanding() external view returns (FixedPoint.Unsigned memory); + + function create(FixedPoint.Unsigned memory collateralAmount, FixedPoint.Unsigned memory numTokens) external; + + function createLiquidation( + address sponsor, + FixedPoint.Unsigned calldata minCollateralPerToken, + FixedPoint.Unsigned calldata maxCollateralPerToken, + FixedPoint.Unsigned calldata maxTokensToLiquidate, + uint256 deadline + ) + external + returns ( + uint256 liquidationId, + FixedPoint.Unsigned memory tokensLiquidated, + FixedPoint.Unsigned memory finalFeeBond + ); +} + +interface IStore { + function computeFinalFee(address currency) external view returns (FixedPoint.Unsigned memory); +} + +interface IFinder { + function getImplementationAddress(bytes32 interfaceName) external view returns (address); +} diff --git a/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/LiquidationWithdrawer.sol b/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/LiquidationWithdrawer.sol new file mode 100644 index 000000000..f2f10670a --- /dev/null +++ b/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/LiquidationWithdrawer.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/FixedPoint.sol"; + +// Simple contract used to withdraw liquidations using a DSProxy from legacy contracts (1.2.2 and below). +contract LiquidationWithdrawer { + function withdrawLiquidation( + address financialContractAddress, + uint256 liquidationId, + address sponsor + ) public returns (FixedPoint.Unsigned memory) { + return IFinancialContract(financialContractAddress).withdrawLiquidation(liquidationId, sponsor); + } +} + +interface IFinancialContract { + function withdrawLiquidation( + uint256 liquidationId, + address sponsor + ) external returns (FixedPoint.Unsigned memory amountWithdrawn); +} diff --git a/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/PositionSettler.sol b/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/PositionSettler.sol new file mode 100644 index 000000000..0b50cb368 --- /dev/null +++ b/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/PositionSettler.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/FixedPoint.sol"; + +// Simple contract used to Settle expired positions using a DSProxy. +contract PositionSettler { + function settleExpired(address financialContractAddress) public returns (FixedPoint.Unsigned memory) { + return IFinancialContract(financialContractAddress).settleExpired(); + } +} + +interface IFinancialContract { + function settleExpired() external returns (FixedPoint.Unsigned memory amountWithdrawn); +} diff --git a/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/TokenRedeemer.sol b/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/TokenRedeemer.sol new file mode 100644 index 000000000..9ae17baf0 --- /dev/null +++ b/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/TokenRedeemer.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/FixedPoint.sol"; +import "@uniswap/lib/contracts/libraries/TransferHelper.sol"; + +// Simple contract used to redeem tokens using a DSProxy from an emp. +contract TokenRedeemer { + function redeem( + address financialContractAddress, + FixedPoint.Unsigned memory numTokens + ) public returns (FixedPoint.Unsigned memory) { + IFinancialContract fc = IFinancialContract(financialContractAddress); + TransferHelper.safeApprove(fc.tokenCurrency(), financialContractAddress, numTokens.rawValue); + return fc.redeem(numTokens); + } +} + +interface IFinancialContract { + function redeem(FixedPoint.Unsigned memory numTokens) external returns (FixedPoint.Unsigned memory amountWithdrawn); + + function tokenCurrency() external returns (address); +} diff --git a/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/TokenSender.sol b/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/TokenSender.sol new file mode 100644 index 000000000..5268a8958 --- /dev/null +++ b/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/TokenSender.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../../common/implementation/ExpandedERC20.sol"; + +contract TokenSender { + function transferERC20(address tokenAddress, address recipientAddress, uint256 amount) public returns (bool) { + IERC20 token = IERC20(tokenAddress); + token.transfer(recipientAddress, amount); + return true; + } +} diff --git a/contracts/external/uma/core/contracts/proxy-scripts/lsp-broker/LspUniswapV2Broker.sol b/contracts/external/uma/core/contracts/proxy-scripts/lsp-broker/LspUniswapV2Broker.sol new file mode 100644 index 000000000..593b2f439 --- /dev/null +++ b/contracts/external/uma/core/contracts/proxy-scripts/lsp-broker/LspUniswapV2Broker.sol @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; + +import "@uniswap/lib/contracts/libraries/Babylonian.sol"; +import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; + +import "@uniswap/lib/contracts/libraries/TransferHelper.sol"; +import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol"; + +import "../../common/implementation/FixedPoint.sol"; + +import "../../financial-templates/long-short-pair/LongShortPair.sol"; + +/** + * @title LspUniswapV2Broker + * @notice Helper contract to facilitate batched LSP and UniswapV2 transactions, including Mint+Sell and Mint+LP. + */ +contract LspUniswapV2Broker { + using FixedPoint for FixedPoint.Signed; + using FixedPoint for FixedPoint.Unsigned; + + /** + * @notice Mint long and short tokens and deposit them all into a UniV2 Pool. + * @dev The caller of this method needs to approve `amountCollateral` collateral to be spent by this contract. + * @param callingAsEOA If True, caller has balance of collateral and expects to receive back all LP tokens + dust. + * @param longShortPair LSP contract address to mint position on. + * @param router Contract to call to exchange long and short tokens. + * @param amountCollateral Amount of collateral to deposit and mint long and short tokens against. + * @param minLpTokens Minimum number of LP tokens to mint + * @param deadline Unix timestamp that will force the transaction to revert if minded after this time. + */ + function atomicMintAddLiquidity( + bool callingAsEOA, + LongShortPair longShortPair, + IUniswapV2Router01 router, + uint256 amountCollateral, + uint256 minLpTokens, + uint256 deadline + ) public { + require(address(longShortPair) != address(0), "Invalid long short pair"); + require(address(router) != address(0), "Invalid router"); + require(amountCollateral != 0, "Collateral to mint with"); + + IERC20 collateralToken = IERC20(longShortPair.collateralToken()); + IERC20 longToken = IERC20(longShortPair.longToken()); + IERC20 shortToken = IERC20(longShortPair.shortToken()); + + // 0) Pull collateral from caller if necessary and approve LSP to spend it. + if (callingAsEOA) + TransferHelper.safeTransferFrom(address(collateralToken), msg.sender, address(this), amountCollateral); + + // 1) Approve collateral to be spent by the LSP from this contract at size of amountCollateral. + TransferHelper.safeApprove(address(collateralToken), address(longShortPair), amountCollateral); + + // 2) Deposit collateral into LSP and mint long and short tokens. + uint256 tokensToCreate = FixedPoint + .fromUnscaledUint(amountCollateral) + .div(longShortPair.collateralPerPair()) + .rawValue; + longShortPair.create(tokensToCreate); + + require( + longToken.balanceOf(address(this)) == shortToken.balanceOf(address(this)) && + longToken.balanceOf(address(this)) == tokensToCreate, + "Create invariant failed" + ); + + { + // 3) Calculate if we need to sell the long token for the short or the short token for the long. Calculate + // the total trade size to bring the balance of long/short in this contract to equal the pool ratio after + // the trade. Note this computation must consider how the resultant pool ratios is impacted by the trade. + bool aToB; + uint256 tradeSize; + (uint256 reserveA, uint256 reserveB) = getReserves( + router.factory(), + address(longToken), + address(shortToken) + ); + (aToB, tradeSize) = computeSwapToMintAtPoolRatio( + FixedPoint.Signed(int256(tokensToCreate)), + FixedPoint.Signed(int256(reserveA)), + FixedPoint.Signed(int256(reserveB)) + ); + address[] memory path = new address[](2); + + // 3.a) If the trade is token a to b then we are selling long tokens for short tokens. In this case, we + // know the exact output number of tokens we want (short tokens) and use the router's + // swapTokensForExactTokens with the exact output number of tokens specified. + if (aToB && tradeSize > 0) { + path[0] = address(longToken); + path[1] = address(shortToken); + TransferHelper.safeApprove(address(longToken), address(router), tradeSize); + router.swapTokensForExactTokens(tradeSize, type(uint256).max, path, address(this), deadline); + } + if (!aToB && tradeSize > 0) { + // Else, if the trade is b to a then we are selling short tokens for long tokens. In this case, we + // know the exact input number of tokens that we need to sell (short tokens) and can use the router's + // swapExactTokensForTokens with the exact input specified. + path[0] = address(shortToken); + path[1] = address(longToken); + TransferHelper.safeApprove(address(shortToken), address(router), tradeSize); + router.swapExactTokensForTokens(tradeSize, 0, path, address(this), deadline); + } + } + + // 4) Add liquidity to the pool via the router. Approve both long and short tokens with the full account balance + // in this contract (i.e the amount of tokens minted +- that traded in step 2.). + TransferHelper.safeApprove(address(longToken), address(router), longToken.balanceOf(address(this))); + TransferHelper.safeApprove(address(shortToken), address(router), shortToken.balanceOf(address(this))); + router.addLiquidity( + address(longToken), + address(shortToken), + longToken.balanceOf(address(this)), + shortToken.balanceOf(address(this)), + 0, + 0, + address(this), + deadline + ); + + // 5) Validate that at least the minimum number of tokens have been minted. If not, revert. + { + IERC20 lPToken = IERC20(address(pairFor(router.factory(), address(longToken), address(shortToken)))); + require(lPToken.balanceOf(address(this)) > minLpTokens, "Failed to mint min LP tokens"); + + // 5) Finally, if the caller is an EOA, send back the LP tokens from minting + any dust that was left over. + // The dust is in the long and short tokens and will be left over as a result of small rounding errors in + // the calculation on how many long/short should be bought/sold to meet the pool ratio before minting. + if (callingAsEOA) { + // Send the LP tokens back to the minter. + TransferHelper.safeTransfer(address(lPToken), msg.sender, lPToken.balanceOf(address(this))); + + // Send any dust left over back to the minter that may have happened due to small rounding errors in + // the computation methods within computeSwapToMintAtPoolRatio. + TransferHelper.safeTransfer(address(longToken), msg.sender, longToken.balanceOf(address(this))); + TransferHelper.safeTransfer(address(shortToken), msg.sender, shortToken.balanceOf(address(this))); + } + } + } + + // For a given mint size (m) and pool balances (ra & rb), compute the trade size such that the resulting ratio + // of tokens, considering the mint and trade,is equal to the pool ratio after the trade. This is computed using the + // following logic: Assume m tokens are minted in equal proportion of long and short. This contract will therefore + // hold t_l=m+△l long tokens and t_s=m-△s after a trade of s for l tokens. The ratio t_l/t_s must equal the pool + // ratio after the trade. The pool ratio, considering the trade can be expressed as (R_a-△a)(R_b+△bλ)=k for a + // trade of △a for △b and a swap fee of λ=(1-swapFee). Using this, with a bit of algebra and simplification, we can + // solve for the resultant ratio to be (m+△a)/(m-△b)=(R_a-△a)/(R_b+△b). i.e the ratio of mint+trade must equal the + // ratio of token A and B in the pool after the trade has concluded. We also know that △a can be solved for as + // △a=(△bλR_a)/(R_b+△bλ) by manipulating a known uniswap equation. Using this equation and the expression of ratios + // we can solve simultaneously for △b. I.e how many short tokens do we need to sell such that the ratio of long and + // short after the trade equal the pool ratios. Numerically, this works out to this form: + // △b=(sqrt(R_b(R_a+m)(R_a(R_b(λ^2+2λ+1)+4λm)+R_b(λ^2-2λ+1)m))+R_b(-λ-1)(R_a+m))/(2λ(R_a+m)). For how this was solved + // from the algebraic solution above see https://bit.ly/3jn7AF6 on wolfram alpha showing the derivation. + function computeSwapToMintAtPoolRatio( + FixedPoint.Signed memory m, + FixedPoint.Signed memory ra, + FixedPoint.Signed memory rb + ) private pure returns (bool, uint256) { + FixedPoint.Signed memory numerator1 = sqrt( + rb.mul(ra.add(m)).mul( + ra.mul(rb.mul(lambda2().add(num(2).mul(lambda())).add(num(1))).add(num(4).mul(lambda().mul(m)))).add( + rb.mul(lambda2().sub(num(2).mul(lambda())).add(num(1))).mul(m) + ) + ) + ); + + FixedPoint.Signed memory numerator2 = rb.mul(ra.add(m)).mul(num(-1).sub(lambda())); + + FixedPoint.Signed memory numerator = numerator1.mul(1e9).add(numerator2); + + FixedPoint.Signed memory denominator = num(2).mul(lambda()).mul(ra.add(m)); + + FixedPoint.Signed memory tradeSize = numerator.div(denominator); + + // If the trade size is negative then we are traversing the equation backwards. In this case the contract must + // swap between swapTokensForExactTokens and swapExactTokensForTokens methods as we are always solving for △b. + // In other words, we are always solving for how many short tokens are needed to be bought/sold. If the number + // is positive then we are buying them. if it is negative then we are selling them. In the case we are selling + // them (negative) then we swap the polarity and apply the swap fee to the trade. + bool aToB = tradeSize.isLessThan(0); + return (aToB, uint256(aToB ? applySwapFee(tradeSize.mul(-1)).rawValue : tradeSize.rawValue)); + } + + // Syntactical sugar to calculate square root of a number. + function sqrt(FixedPoint.Signed memory _num) private pure returns (FixedPoint.Signed memory) { + return FixedPoint.Signed(int256(Babylonian.sqrt(uint256(_num.rawValue)))); + } + + // Syntactical sugar to convert a int256 to a Fixedpoint.Signed. + function num(int256 _num) private pure returns (FixedPoint.Signed memory) { + return FixedPoint.fromUnscaledInt(_num); + } + + // Takes an input fixedPoint.Signed num and returns the num scaled by 0.997. 0.3% swap fee as in uniswap. + function applySwapFee(FixedPoint.Signed memory _num) private pure returns (FixedPoint.Signed memory) { + return _num.mul(997).div(1000); + } + + // 1 with 0.3% fees applied. + function lambda() private pure returns (FixedPoint.Signed memory) { + return applySwapFee(FixedPoint.fromUnscaledInt(1)); + } + + // 1 with 0.3% fees applied twice. + function lambda2() private pure returns (FixedPoint.Signed memory) { + return applySwapFee(lambda()); + } + + /** + * @notice Mint long and short tokens and convert all of one side into the other. + * @dev The caller of this method needs to approve `amountCollateral` collateral to be spent by this contract. + * @param callingAsEOA If True, caller has balance of collateral and expects to receive back all long/short tokens. + * @param sellLong If True, converts all long tokens into short, else the opposite. + * @param longShortPair LSP contract address to mint position on. + * @param router Contract to call to exchange long and short tokens. + * @param amountCollateral Amount of collateral to deposit and mint long and short tokens against. + * @param swapPath `Router.swapExactTokensForTokens` param: path with which to swap token to sell for the other. + * @param deadline `Router.swapExactTokensForTokens` param: time before transaction must be mined. + */ + function atomicMintSellOneSide( + bool callingAsEOA, + bool sellLong, + LongShortPair longShortPair, + IUniswapV2Router01 router /* TODO: Should we allow `router` to be any exchange, such as a Matcha multihop? */, + uint256 amountCollateral, + address[] memory swapPath, + uint256 deadline + ) public { + require(address(longShortPair) != address(0), "Invalid long short pair"); + require(address(router) != address(0), "Invalid router"); + require(amountCollateral != 0, "Collateral to mint with"); + + IERC20 collateralToken = IERC20(longShortPair.collateralToken()); + + // 0) Pull collateral from caller if necessary and approve LSP to spend it. + if (callingAsEOA) + TransferHelper.safeTransferFrom(address(collateralToken), msg.sender, address(this), amountCollateral); + + TransferHelper.safeApprove(address(collateralToken), address(longShortPair), amountCollateral); + + // 1) Deposit collateral into LSP and mint long and short tokens. + uint256 tokensToCreate = FixedPoint + .fromUnscaledUint(amountCollateral) + .div(longShortPair.collateralPerPair()) + .rawValue; + longShortPair.create(tokensToCreate); + + // 2) Determine which token we are selling and convert it all into the other. + IERC20 soldToken = IERC20(sellLong ? longShortPair.shortToken() : longShortPair.longToken()); + TransferHelper.safeApprove(address(soldToken), address(router), soldToken.balanceOf(address(this))); + require(swapPath[0] == address(soldToken), "Sold token != 0th swapPath"); + router.swapExactTokensForTokens( + soldToken.balanceOf(address(this)), // sell all of the sold tokens held by the contract. + 0, + swapPath, + address(this), + deadline + ); + + // 3) Send tokens back to caller if necessary. + if (callingAsEOA) { + IERC20 purchasedToken = IERC20(!sellLong ? longShortPair.shortToken() : longShortPair.longToken()); + TransferHelper.safeTransfer(address(purchasedToken), msg.sender, purchasedToken.balanceOf(address(this))); + } + } + + function getReserves( + address factory, + address tokenA, + address tokenB + ) public view returns (uint256 reserveA, uint256 reserveB) { + (address token0, ) = sortTokens(tokenA, tokenB); + (uint256 reserve0, uint256 reserve1, ) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves(); + (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); + } + + function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) { + require(tokenA != tokenB, "UniswapV2Library: IDENTICAL_ADDRESSES"); + (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + require(token0 != address(0), "UniswapV2Library: ZERO_ADDRESS"); + } + + // calculates the CREATE2 address for a pair without making any external calls + function pairFor(address factory, address tokenA, address tokenB) internal pure returns (IUniswapV2Pair pair) { + (address token0, address token1) = sortTokens(tokenA, tokenB); + pair = IUniswapV2Pair( + address( + uint160( + uint256( + keccak256( + abi.encodePacked( + hex"ff", + factory, + keccak256(abi.encodePacked(token0, token1)), + hex"96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" // init code hash + ) + ) + ) + ) + ) + ); + } +} diff --git a/contracts/external/uma/core/contracts/proxy-scripts/uniswap-broker/UniswapV2Broker.sol b/contracts/external/uma/core/contracts/proxy-scripts/uniswap-broker/UniswapV2Broker.sol new file mode 100644 index 000000000..5c5dc1e6e --- /dev/null +++ b/contracts/external/uma/core/contracts/proxy-scripts/uniswap-broker/UniswapV2Broker.sol @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; + +import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; + +import "@uniswap/lib/contracts/libraries/Babylonian.sol"; +import "@uniswap/lib/contracts/libraries/TransferHelper.sol"; +import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol"; + +/** + * @title UniswapV2Broker + * @notice Trading contract used to arb uniswap pairs to a desired "true" price. Intended use is to arb UMA perpetual + * synthetics that trade off peg. This implementation can ber used in conjunction with a DSProxy contract to atomically + * swap and move a uniswap market. + */ + +contract UniswapV2Broker { + using SafeMath for uint256; + + /** + * @notice Swaps an amount of either token such that the trade results in the uniswap pair's price being as close as + * possible to the truePrice. + * @dev True price is expressed in the ratio of token A to token B. + * @dev The caller must approve this contract to spend whichever token is intended to be swapped. + * @param tradingAsEOA bool to indicate if the UniswapV2Broker is being called by a DSProxy or an EOA. + * @param uniswapRouter address of the uniswap router used to facilitate trades. + * @param uniswapFactory address of the uniswap factory used to fetch current pair reserves. + * @param swappedTokens array of addresses which are to be swapped. The order does not matter as the function will figure + * out which tokens need to be exchanged to move the market to the desired "true" price. + * @param truePriceTokens array of unit used to represent the true price. 0th value is the numerator of the true price + * and the 1st value is the the denominator of the true price. + * @param maxSpendTokens array of unit to represent the max to spend in the two tokens. + * @param to recipient of the trade proceeds. + * @param deadline to limit when the trade can execute. If the tx is mined after this timestamp then revert. + */ + function swapToPrice( + bool tradingAsEOA, + address uniswapRouter, + address uniswapFactory, + address[2] memory swappedTokens, + uint256[2] memory truePriceTokens, + uint256[2] memory maxSpendTokens, + address to, + uint256 deadline + ) public { + IUniswapV2Router01 router = IUniswapV2Router01(uniswapRouter); + + // true price is expressed as a ratio, so both values must be non-zero + require(truePriceTokens[0] != 0 && truePriceTokens[1] != 0, "SwapToPrice: ZERO_PRICE"); + // caller can specify 0 for either if they wish to swap in only one direction, but not both + require(maxSpendTokens[0] != 0 || maxSpendTokens[1] != 0, "SwapToPrice: ZERO_SPEND"); + + bool aToB; + uint256 amountIn; + { + (uint256 reserveA, uint256 reserveB) = getReserves(uniswapFactory, swappedTokens[0], swappedTokens[1]); + (aToB, amountIn) = computeTradeToMoveMarket(truePriceTokens[0], truePriceTokens[1], reserveA, reserveB); + } + + require(amountIn > 0, "SwapToPrice: ZERO_AMOUNT_IN"); + + // spend up to the allowance of the token in + uint256 maxSpend = aToB ? maxSpendTokens[0] : maxSpendTokens[1]; + if (amountIn > maxSpend) { + amountIn = maxSpend; + } + + address tokenIn = aToB ? swappedTokens[0] : swappedTokens[1]; + address tokenOut = aToB ? swappedTokens[1] : swappedTokens[0]; + + TransferHelper.safeApprove(tokenIn, address(router), amountIn); + + if (tradingAsEOA) TransferHelper.safeTransferFrom(tokenIn, msg.sender, address(this), amountIn); + + address[] memory path = new address[](2); + path[0] = tokenIn; + path[1] = tokenOut; + + router.swapExactTokensForTokens( + amountIn, + 0, // amountOutMin: we can skip computing this number because the math is tested within the uniswap tests. + path, + to, + deadline + ); + } + + /** + * @notice Given the "true" price a token (represented by truePriceTokenA/truePriceTokenB) and the reservers in the + * uniswap pair, calculate: a) the direction of trade (aToB) and b) the amount needed to trade (amountIn) to move + * the pool price to be equal to the true price. + * @dev Note that this method uses the Babylonian square root method which has a small margin of error which will + * result in a small over or under estimation on the size of the trade needed. + * @param truePriceTokenA the nominator of the true price. + * @param truePriceTokenB the denominator of the true price. + * @param reserveA number of token A in the pair reserves + * @param reserveB number of token B in the pair reserves + */ + // + function computeTradeToMoveMarket( + uint256 truePriceTokenA, + uint256 truePriceTokenB, + uint256 reserveA, + uint256 reserveB + ) public pure returns (bool aToB, uint256 amountIn) { + aToB = FullMath.mulDiv(reserveA, truePriceTokenB, reserveB) < truePriceTokenA; + + uint256 invariant = reserveA.mul(reserveB); + + // The trade ∆a of token a required to move the market to some desired price P' from the current price P can be + // found with ∆a=(kP')^1/2-Ra. + uint256 leftSide = Babylonian.sqrt( + FullMath.mulDiv( + invariant, + aToB ? truePriceTokenA : truePriceTokenB, + aToB ? truePriceTokenB : truePriceTokenA + ) + ); + uint256 rightSide = (aToB ? reserveA : reserveB); + + if (leftSide < rightSide) return (false, 0); + + // compute the amount that must be sent to move the price back to the true price. + amountIn = leftSide.sub(rightSide); + } + + // The methods below are taken from https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/libraries/UniswapV2Library.sol + // We could import this library into this contract but this library is dependent Uniswap's SafeMath, which is bound + // to solidity 6.6.6. UMA uses 0.8.0 and so a modified version is needed to accomidate this solidity version. + function getReserves( + address factory, + address tokenA, + address tokenB + ) public view returns (uint256 reserveA, uint256 reserveB) { + (address token0, ) = sortTokens(tokenA, tokenB); + (uint256 reserve0, uint256 reserve1, ) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves(); + (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); + } + + function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) { + require(tokenA != tokenB, "UniswapV2Library: IDENTICAL_ADDRESSES"); + (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + require(token0 != address(0), "UniswapV2Library: ZERO_ADDRESS"); + } + + // calculates the CREATE2 address for a pair without making any external calls + function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) { + (address token0, address token1) = sortTokens(tokenA, tokenB); + pair = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + hex"ff", + factory, + keccak256(abi.encodePacked(token0, token1)), + hex"96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" // init code hash + ) + ) + ) + ) + ); + } +} + +// The library below is taken from @uniswap/lib/contracts/libraries/FullMath.sol. It has been modified to work with solidity 0.8 +library FullMath { + /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + /// @param a The multiplicand + /// @param b The multiplier + /// @param denominator The divisor + /// @return result The 256-bit result + /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv + function mulDiv(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) { + // 512-bit multiply [prod1 prod0] = a * b + // Compute the product mod 2**256 and mod 2**256 - 1 + // then use the Chinese Remainder Theorem to reconstruct + // the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2**256 + prod0 + uint256 prod0; // Least significant 256 bits of the product + uint256 prod1; // Most significant 256 bits of the product + assembly { + let mm := mulmod(a, b, not(0)) + prod0 := mul(a, b) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // Handle non-overflow cases, 256 by 256 division + if (prod1 == 0) { + require(denominator > 0); + assembly { + result := div(prod0, denominator) + } + return result; + } + + // Make sure the result is less than 2**256. + // Also prevents denominator == 0 + require(denominator > prod1); + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0] + // Compute remainder using mulmod + uint256 remainder; + assembly { + remainder := mulmod(a, b, denominator) + } + // Subtract 256 bit number from 512 bit number + assembly { + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Factor powers of two out of denominator + // Compute largest power of two divisor of denominator. + // Always >= 1. + uint256 twos = denominator & (~denominator + 1); + // Divide denominator by power of two + assembly { + denominator := div(denominator, twos) + } + + // Divide [prod1 prod0] by the factors of two + assembly { + prod0 := div(prod0, twos) + } + // Shift in bits from prod1 into prod0. For this we need + // to flip `twos` such that it is 2**256 / twos. + // If twos is zero, then it becomes one + assembly { + twos := add(div(sub(0, twos), twos), 1) + } + prod0 |= prod1 * twos; + + // Invert denominator mod 2**256 + // Now that denominator is an odd number, it has an inverse + // modulo 2**256 such that denominator * inv = 1 mod 2**256. + // Compute the inverse by starting with a seed that is correct + // correct for four bits. That is, denominator * inv = 1 mod 2**4 + uint256 inv = (3 * denominator) ^ 2; + // Now use Newton-Raphson iteration to improve the precision. + // Thanks to Hensel's lifting lemma, this also works in modular + // arithmetic, doubling the correct bits in each step. + inv *= 2 - denominator * inv; // inverse mod 2**8 + inv *= 2 - denominator * inv; // inverse mod 2**16 + inv *= 2 - denominator * inv; // inverse mod 2**32 + inv *= 2 - denominator * inv; // inverse mod 2**64 + inv *= 2 - denominator * inv; // inverse mod 2**128 + inv *= 2 - denominator * inv; // inverse mod 2**256 + + // Because the division is now exact we can divide by multiplying + // with the modular inverse of denominator. This will give us the + // correct result modulo 2**256. Since the precoditions guarantee + // that the outcome is less than 2**256, this is the final result. + // We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inv; + return result; + } + + /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + /// @param a The multiplicand + /// @param b The multiplier + /// @param denominator The divisor + /// @return result The 256-bit result + function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) { + result = mulDiv(a, b, denominator); + if (mulmod(a, b, denominator) > 0) { + require(result < type(uint256).max); + result++; + } + } +} diff --git a/contracts/external/uma/core/contracts/proxy-scripts/uniswap-broker/UniswapV3Broker.sol b/contracts/external/uma/core/contracts/proxy-scripts/uniswap-broker/UniswapV3Broker.sol new file mode 100644 index 000000000..634deb1ea --- /dev/null +++ b/contracts/external/uma/core/contracts/proxy-scripts/uniswap-broker/UniswapV3Broker.sol @@ -0,0 +1,623 @@ +pragma solidity ^0.8.0; + +import "@uniswap/lib/contracts/libraries/TransferHelper.sol"; + +import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; + +import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; + +import "@uniswap/v3-core/contracts/libraries/LowGasSafeMath.sol"; +import "@uniswap/v3-core/contracts/libraries/LiquidityMath.sol"; +import "@uniswap/v3-core/contracts/libraries/BitMath.sol"; +import "@uniswap/v3-core/contracts/libraries/UnsafeMath.sol"; +import "@uniswap/v3-core/contracts/libraries/SafeCast.sol"; + +/** + * @title UniswapV3Broker + * @notice Trading contract used to arb uniswapV3 pairs to a desired "true" price. Intended use is to arb UMA perpetual + * synthetics that trade off peg. This implementation can ber used in conjunction with a DSProxy contract to atomically + * swap and move a uniswap market. + */ + +contract UniswapV3Broker { + using SafeCast for uint256; + using LowGasSafeMath for uint256; + using LowGasSafeMath for int256; + + struct SwapState { + uint160 sqrtPriceX96; + int24 tick; + uint128 liquidity; + uint256 requiredInputAmount; + } + + struct StepComputations { + uint160 sqrtPriceStartX96; + int24 tickNext; + bool initialized; + uint160 sqrtPriceNextX96; + } + + /** + * @notice Swaps an amount of either pool tokens such that the trade results in the uniswap pair's price equaling a + * desired price. + * @dev The desired price is represented as sqrtRatioTargetX96. This is the Price^(1/2) * 96^2. + * @dev The caller must approve this contract to spend whichever token is intended to be swapped. + * @param tradingAsEOA bool to indicate if the UniswapBroker is being called by a DSProxy or an EOA. + * @param uniswapPool address of the pool to uniswap v3 trade against. + * @param uniswapRouter address of the uniswap v3 router to route the trade. + * @param sqrtRatioTargetX96 target, encoded price. + * @param recipient address that the output tokens should be sent to. + * @param deadline to limit when the trade can execute. If the tx is mined after this timestamp then revert. + */ + function swapToPrice( + bool tradingAsEOA, + address uniswapPool, + address uniswapRouter, + uint160 sqrtRatioTargetX96, + address recipient, + uint256 deadline + ) external returns (uint256) { + // Create an instance of the pool and load in the current token price and the active tick. + IUniswapV3Pool pool = IUniswapV3Pool(uniswapPool); + (uint160 sqrtPriceX96, int24 tick, , , , , ) = pool.slot0(); + + // Work out the direction we need to trade. If the current price is more than the target price then we are + // trading token0 for token1. Else, we are trading token1 for token0. + bool zeroForOne = sqrtPriceX96 >= sqrtRatioTargetX96; + + // Build a state object to store this information which can be re-used during. + SwapState memory state = SwapState({ + sqrtPriceX96: sqrtPriceX96, + tick: tick, + liquidity: pool.liquidity(), + requiredInputAmount: 0 + }); + + // Iterate in a while loop that breaks when we hit the target price. + while (true) { + // Compute the next initialized tick. We only need to traverse initialized ticks as uninitialized ticks + // have the same liquidity as the previous tick. + StepComputations memory step; + step.sqrtPriceStartX96 = state.sqrtPriceX96; + (step.tickNext, step.initialized) = TickBitmap.nextInitializedTickWithinOneWord( + pool, + state.tick, + pool.tickSpacing(), + zeroForOne + ); + + // Double check we are not over or underflow the ticks. + if (step.tickNext < TickMath.MIN_TICK) step.tickNext = TickMath.MIN_TICK; + else if (step.tickNext > TickMath.MAX_TICK) step.tickNext = TickMath.MAX_TICK; + + // Find the price at the next tick. Between the current state.sqrtPriceX96 and the nextTickPriceX96 we + // can find how much of the sold token is needed to sufficiently move the market over the interval. + + uint160 nextTickPriceX96 = TickMath.getSqrtRatioAtTick(step.tickNext); + uint256 inputAmountForStep; + + // If zeroForOne is true, then we are moving the price UP. In this case we need to ensure that if the next + // tick price is more than the target price, we set the set the the next step price to the target price. This + // ensures that the price does not undershoot when the next tick is the last tick. Else, traverse the whole tick. + if (zeroForOne) { + step.sqrtPriceNextX96 = nextTickPriceX96 > sqrtRatioTargetX96 ? sqrtRatioTargetX96 : nextTickPriceX96; + inputAmountForStep = SqrtPriceMath.getAmount0Delta( // As we are trading token0 for token1, calculate the token0 input. + step.sqrtPriceStartX96, + step.sqrtPriceNextX96, + state.liquidity, + false + ); + // Else, if zeroForOne is false, then we are moving the price DOWN. In this case we need to ensure that we + // don't overshoot the price on the next step. + } else { + step.sqrtPriceNextX96 = nextTickPriceX96 > sqrtRatioTargetX96 ? nextTickPriceX96 : sqrtRatioTargetX96; + inputAmountForStep = SqrtPriceMath.getAmount1Delta( // As we are trading token1 for token0, calculate the token1 input. + step.sqrtPriceStartX96, + step.sqrtPriceNextX96, + state.liquidity, + false + ); + } + + // Add amount for this step to the total required input. + state.requiredInputAmount = state.requiredInputAmount.add(inputAmountForStep); + + // If we have hit(or exceeded) our target price in the associate direction, then stop. + if (zeroForOne && state.sqrtPriceX96 <= sqrtRatioTargetX96) break; + if (!zeroForOne && state.sqrtPriceX96 >= sqrtRatioTargetX96) break; + + // If the next step is is initialized then we will need to update the liquidity for the current step. + if (step.initialized) { + // Fetch the net liquidity. this could be positive or negative depending on if a LP is turning on or off at this price. + (, int128 liquidityNet, , , , , , ) = pool.ticks(step.tickNext); + state.liquidity = LiquidityMath.addDelta(state.liquidity, zeroForOne ? liquidityNet : -liquidityNet); + } + + // Finally, set the state price to the next price for the next iteration. + state.sqrtPriceX96 = step.sqrtPriceNextX96; + state.tick = step.tickNext; + } + + // Based on the direction we are moving, set the input and output tokens. + (address tokenIn, address tokenOut) = zeroForOne + ? (pool.token0(), pool.token1()) + : (pool.token1(), pool.token0()); + + // If trading from an EOA pull tokens into this contract. If trading from a DSProxy this is redundant. + if (tradingAsEOA) + TransferHelper.safeTransferFrom(tokenIn, msg.sender, address(this), state.requiredInputAmount); + + // Approve the router and execute the swap. + TransferHelper.safeApprove(tokenIn, address(uniswapRouter), state.requiredInputAmount); + ISwapRouter(uniswapRouter).exactInputSingle( + ISwapRouter.ExactInputSingleParams({ + tokenIn: tokenIn, + tokenOut: tokenOut, + fee: pool.fee(), + recipient: recipient, + deadline: deadline, + amountIn: state.requiredInputAmount, + amountOutMinimum: 0, + sqrtPriceLimitX96: sqrtRatioTargetX96 + }) + ); + + return state.requiredInputAmount; + } +} + +// The code below are taken almost verbatim from https://github.com/Uniswap/uniswap-v3-core/blob/main/contracts/libraries/TickBitmap.sol. +// They was modified slightly to enable the them to be called on an external pool by passing in a pool address and +// to accommodate solidity 0.8. + +library TickBitmap { + function nextInitializedTickWithinOneWord( + IUniswapV3Pool pool, + int24 tick, + int24 tickSpacing, + bool lte + ) internal view returns (int24 next, bool initialized) { + int24 compressed = tick / tickSpacing; + if (tick < 0 && tick % tickSpacing != 0) compressed--; // round towards negative infinity + + if (lte) { + (int16 wordPos, uint8 bitPos) = position(compressed); + // all the 1s at or to the right of the current bitPos + uint256 mask = (1 << bitPos) - 1 + (1 << bitPos); + uint256 masked = pool.tickBitmap(wordPos) & mask; + + // if there are no initialized ticks to the right of or at the current tick, return rightmost in the word + initialized = masked != 0; + // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick + next = initialized + ? (compressed - int24(int24(uint24(bitPos)) - int24(uint24(BitMath.mostSignificantBit(masked))))) * + tickSpacing + : (compressed - int24(uint24(bitPos))) * tickSpacing; + } else { + // start from the word of the next tick, since the current tick state doesn't matter + (int16 wordPos, uint8 bitPos) = position(compressed + 1); + // all the 1s at or to the left of the bitPos + uint256 mask = ~((1 << bitPos) - 1); + uint256 masked = pool.tickBitmap(wordPos) & mask; + + // if there are no initialized ticks to the left of the current tick, return leftmost in the word + initialized = masked != 0; + // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick + next = initialized + ? (compressed + 1 + int24(BitMath.leastSignificantBit(masked) - uint24(bitPos))) * tickSpacing + : (compressed + 1 + int24(type(uint8).max - uint24(bitPos))) * tickSpacing; + } + } + + function position(int24 tick) private pure returns (int16 wordPos, uint8 bitPos) { + wordPos = int16(tick >> 8); + bitPos = uint8(int8(tick % 256)); + } + + function flipTick(mapping(int16 => uint256) storage self, int24 tick, int24 tickSpacing) internal { + require(tick % tickSpacing == 0); // ensure that the tick is spaced + (int16 wordPos, uint8 bitPos) = position(tick / tickSpacing); + uint256 mask = 1 << bitPos; + self[wordPos] ^= mask; + } +} + +// Taken from https://github.com/Uniswap/uniswap-v3-core/blob/main/contracts/libraries/TickMath.sol and update +// to work with solidity 0.8. +library TickMath { + /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128 + int24 internal constant MIN_TICK = -887272; + /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128 + int24 internal constant MAX_TICK = -MIN_TICK; + + /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK) + uint160 internal constant MIN_SQRT_RATIO = 4295128739; + /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK) + uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; + + /// @notice Calculates sqrt(1.0001^tick) * 2^96 + /// @dev Throws if |tick| > max tick + /// @param tick The input tick for the above formula + /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0) + /// at the given tick + function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) { + uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick)); + require(absTick <= uint256(int256(MAX_TICK)), "T"); + + uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000; + if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128; + if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128; + if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128; + if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128; + if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128; + if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128; + if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128; + if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128; + if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128; + if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128; + if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128; + if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128; + if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128; + if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128; + if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128; + if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128; + if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128; + if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128; + if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128; + + if (tick > 0) ratio = type(uint256).max / ratio; + + // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96. + // we then downcast because we know the result always fits within 160 bits due to our tick input constraint + // we round up in the division so getTickAtSqrtRatio of the output price is always consistent + sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1)); + } + + function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick) { + // second inequality must be < because the price can never reach the price at the max tick + require(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO, "R"); + uint256 ratio = uint256(sqrtPriceX96) << 32; + + uint256 r = ratio; + uint256 msb = 0; + + assembly { + let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(5, gt(r, 0xFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(4, gt(r, 0xFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(3, gt(r, 0xFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(2, gt(r, 0xF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(1, gt(r, 0x3)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := gt(r, 0x1) + msb := or(msb, f) + } + + if (msb >= 128) r = ratio >> (msb - 127); + else r = ratio << (127 - msb); + + int256 log_2 = (int256(msb) - 128) << 64; + + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(63, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(62, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(61, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(60, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(59, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(58, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(57, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(56, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(55, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(54, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(53, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(52, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(51, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(50, f)) + } + + int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number + + int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128); + int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128); + + tick = tickLow == tickHi + ? tickLow + : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 + ? tickHi + : tickLow; + } +} + +library SqrtPriceMath { + using SafeCast for uint256; + + function getAmount0Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint128 liquidity, + bool roundUp + ) internal pure returns (uint256 amount0) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + uint256 numerator2 = sqrtRatioBX96 - sqrtRatioAX96; + + require(sqrtRatioAX96 > 0); + + return + roundUp + ? UnsafeMath.divRoundingUp( + FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96), + sqrtRatioAX96 + ) + : FullMath.mulDiv(numerator1, numerator2, sqrtRatioBX96) / sqrtRatioAX96; + } + + /// @notice Gets the amount1 delta between two prices + /// @dev Calculates liquidity * (sqrt(upper) - sqrt(lower)) + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The amount of usable liquidity + /// @param roundUp Whether to round the amount up, or down + /// @return amount1 Amount of token1 required to cover a position of size liquidity between the two passed prices + function getAmount1Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint128 liquidity, + bool roundUp + ) internal pure returns (uint256 amount1) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + return + roundUp + ? FullMath.mulDivRoundingUp(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96) + : FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96); + } + + /// @notice Helper that gets signed token0 delta + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The change in liquidity for which to compute the amount0 delta + /// @return amount0 Amount of token0 corresponding to the passed liquidityDelta between the two prices + function getAmount0Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + int128 liquidity + ) internal pure returns (int256 amount0) { + return + liquidity < 0 + ? -getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() + : getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + } + + /// @notice Helper that gets signed token1 delta + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The change in liquidity for which to compute the amount1 delta + /// @return amount1 Amount of token1 corresponding to the passed liquidityDelta between the two prices + function getAmount1Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + int128 liquidity + ) internal pure returns (int256 amount1) { + return + liquidity < 0 + ? -getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() + : getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + } +} + +library FullMath { + /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + /// @param a The multiplicand + /// @param b The multiplier + /// @param denominator The divisor + /// @return result The 256-bit result + /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv + function mulDiv(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) { + // 512-bit multiply [prod1 prod0] = a * b + // Compute the product mod 2**256 and mod 2**256 - 1 + // then use the Chinese Remainder Theorem to reconstruct + // the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2**256 + prod0 + uint256 prod0; // Least significant 256 bits of the product + uint256 prod1; // Most significant 256 bits of the product + assembly { + let mm := mulmod(a, b, not(0)) + prod0 := mul(a, b) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // Handle non-overflow cases, 256 by 256 division + if (prod1 == 0) { + require(denominator > 0); + assembly { + result := div(prod0, denominator) + } + return result; + } + + // Make sure the result is less than 2**256. + // Also prevents denominator == 0 + require(denominator > prod1); + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0] + // Compute remainder using mulmod + uint256 remainder; + assembly { + remainder := mulmod(a, b, denominator) + } + // Subtract 256 bit number from 512 bit number + assembly { + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Factor powers of two out of denominator + // Compute largest power of two divisor of denominator. + // Always >= 1. + // NOTE: this is modified from the original Full math implementation to work with solidity 8 + uint256 twos = (type(uint256).max - denominator + 1) & denominator; + // Divide denominator by power of two + assembly { + denominator := div(denominator, twos) + } + + // Divide [prod1 prod0] by the factors of two + assembly { + prod0 := div(prod0, twos) + } + // Shift in bits from prod1 into prod0. For this we need + // to flip `twos` such that it is 2**256 / twos. + // If twos is zero, then it becomes one + assembly { + twos := add(div(sub(0, twos), twos), 1) + } + unchecked { + prod0 |= prod1 * twos; + + // Invert denominator mod 2**256 + // Now that denominator is an odd number, it has an inverse + // modulo 2**256 such that denominator * inv = 1 mod 2**256. + // Compute the inverse by starting with a seed that is correct + // correct for four bits. That is, denominator * inv = 1 mod 2**4 + uint256 inv = (3 * denominator) ^ 2; + // Now use Newton-Raphson iteration to improve the precision. + // Thanks to Hensel's lifting lemma, this also works in modular + // arithmetic, doubling the correct bits in each step. + // NOTE: this is modified from the original Full math implementation to work with solidity 8 with the unchecked syntax. + inv *= 2 - denominator * inv; // inverse mod 2**8 + inv *= 2 - denominator * inv; // inverse mod 2**16 + inv *= 2 - denominator * inv; // inverse mod 2**32 + inv *= 2 - denominator * inv; // inverse mod 2**64 + inv *= 2 - denominator * inv; // inverse mod 2**128 + inv *= 2 - denominator * inv; // inverse mod 2**256 + // Because the division is now exact we can divide by multiplying + // with the modular inverse of denominator. This will give us the + // correct result modulo 2**256. Since the precoditions guarantee + // that the outcome is less than 2**256, this is the final result. + // We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inv; + } + return result; + } + + /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + /// @param a The multiplicand + /// @param b The multiplier + /// @param denominator The divisor + /// @return result The 256-bit result + function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) { + result = mulDiv(a, b, denominator); + if (mulmod(a, b, denominator) > 0) { + require(result < type(uint256).max); + result++; + } + } +} + +library FixedPoint96 { + uint8 internal constant RESOLUTION = 96; + uint256 internal constant Q96 = 0x1000000000000000000000000; +} diff --git a/contracts/external/uma/core/contracts/snapshot-helpers/SnapshotVotingPower.sol b/contracts/external/uma/core/contracts/snapshot-helpers/SnapshotVotingPower.sol new file mode 100644 index 000000000..f00576fdd --- /dev/null +++ b/contracts/external/uma/core/contracts/snapshot-helpers/SnapshotVotingPower.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +/** + * @title VotingInterface + * @notice Minimum interface required to interact with VotingV2 contract. + */ +interface VotingInterface { + struct VoterStake { + uint128 stake; + uint128 pendingUnstake; + uint128 rewardsPaidPerToken; + uint128 outstandingRewards; + int128 unappliedSlash; + uint64 nextIndexToProcess; + uint64 unstakeTime; + address delegate; + } + + function voterStakes(address) external view returns (VoterStake memory); + + function getVoterFromDelegate(address) external view returns (address); +} + +/** + * @title SnapshotVotingPower + * @notice Helper contract to support offchain voting with Snapshot. + */ +contract SnapshotVotingPower { + VotingInterface public immutable votingV2 = VotingInterface(0x004395edb43EFca9885CEdad51EC9fAf93Bd34ac); + + /** + * @notice This is only used by Snapshot to calculate voting power and does not represent transferable tokens. + * @param user address of the user for whom to calculate voting power. + * @return uint256 value of user's voting power based on staked UMA at DVM2.0. + **/ + function balanceOf(address user) external view returns (uint256) { + address voter = votingV2.getVoterFromDelegate(user); + VotingInterface.VoterStake memory voterStake = votingV2.voterStakes(voter); + + // Avoid double counting in case of stake delegation. + if (voterStake.delegate != address(0) && user != voterStake.delegate) return 0; + + return uint256(voterStake.stake); + } +} diff --git a/contracts/external/uma/core/contracts/umip-helpers/OriginValidator.sol b/contracts/external/uma/core/contracts/umip-helpers/OriginValidator.sol new file mode 100644 index 000000000..992c45415 --- /dev/null +++ b/contracts/external/uma/core/contracts/umip-helpers/OriginValidator.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +/** + * @title An auxiliary contract that checks if the tx origin is the upgrader. + * @dev Note: the validate function can be used as the first transaction in a governance proposals to block any other + * transactions from being executed if the proposal is not initiated by the upgrader. + */ +contract OriginValidator { + /** + * @notice Checks if the caller is the upgrader. + * @dev This is used as the first transaction in the upgrade process to block any other transactions from being + * executed if the upgrade is not initiated by the upgrader. + */ + function validate(address upgrader) public view { + require(tx.origin == upgrader); + } +} diff --git a/contracts/external/uma/core/contracts/umip-helpers/Umip3Upgrader.sol b/contracts/external/uma/core/contracts/umip-helpers/Umip3Upgrader.sol new file mode 100644 index 000000000..981f9d4b6 --- /dev/null +++ b/contracts/external/uma/core/contracts/umip-helpers/Umip3Upgrader.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import "../data-verification-mechanism/implementation/Finder.sol"; +import "../data-verification-mechanism/implementation/Constants.sol"; +import "../data-verification-mechanism/implementation/Voting.sol"; + +/** + * @title A contract to track a whitelist of addresses. + */ +contract Umip3Upgrader { + // Existing governor is the only one who can initiate the upgrade. + address public existingGovernor; + + // Existing Voting contract needs to be informed of the address of the new Voting contract. + Voting public existingVoting; + + // New governor will be the new owner of the finder. + address public newGovernor; + + // Finder contract to push upgrades to. + Finder public finder; + + // Addresses to upgrade. + address public voting; + address public identifierWhitelist; + address public store; + address public financialContractsAdmin; + address public registry; + + constructor( + address _existingGovernor, + address _existingVoting, + address _finder, + address _voting, + address _identifierWhitelist, + address _store, + address _financialContractsAdmin, + address _registry, + address _newGovernor + ) { + existingGovernor = _existingGovernor; + existingVoting = Voting(_existingVoting); + finder = Finder(_finder); + voting = _voting; + identifierWhitelist = _identifierWhitelist; + store = _store; + financialContractsAdmin = _financialContractsAdmin; + registry = _registry; + newGovernor = _newGovernor; + } + + function upgrade() external { + require(msg.sender == existingGovernor, "Upgrade can only be initiated by the existing governor."); + + // Change the addresses in the Finder. + finder.changeImplementationAddress(OracleInterfaces.Oracle, voting); + finder.changeImplementationAddress(OracleInterfaces.IdentifierWhitelist, identifierWhitelist); + finder.changeImplementationAddress(OracleInterfaces.Store, store); + finder.changeImplementationAddress(OracleInterfaces.FinancialContractsAdmin, financialContractsAdmin); + finder.changeImplementationAddress(OracleInterfaces.Registry, registry); + + // Transfer the ownership of the Finder to the new Governor now that all the addresses have been updated. + finder.transferOwnership(newGovernor); + + // Inform the existing Voting contract of the address of the new Voting contract and transfer its + // ownership to the new governor to allow for any future changes to the migrated contract. + existingVoting.setMigrated(voting); + existingVoting.transferOwnership(newGovernor); + } +} diff --git a/contracts/external/uma/core/contracts/umip-helpers/VotingUpgrader.sol b/contracts/external/uma/core/contracts/umip-helpers/VotingUpgrader.sol new file mode 100644 index 000000000..cae70d937 --- /dev/null +++ b/contracts/external/uma/core/contracts/umip-helpers/VotingUpgrader.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "../data-verification-mechanism/implementation/Finder.sol"; +import "../data-verification-mechanism/implementation/Constants.sol"; +import "../data-verification-mechanism/implementation/Voting.sol"; + +/** + * @title A contract that executes a short series of upgrade calls that must be performed atomically as a part of the + * upgrade process for Voting.sol. + * @dev Note: the complete upgrade process requires more than just the transactions in this contract. These are only + * the ones that need to be performed atomically. + */ +contract VotingUpgrader { + // Existing governor is the only one who can initiate the upgrade. + address public governor; + + // Existing Voting contract needs to be informed of the address of the new Voting contract. + Voting public existingVoting; + + // New governor will be the new owner of the finder. + + // Finder contract to push upgrades to. + Finder public finder; + + // Addresses to upgrade. + address public newVoting; + + // Address to call setMigrated on the old voting contract. + address public setMigratedAddress; + + /** + * @notice Removes an address from the whitelist. + * @param _governor the Governor contract address. + * @param _existingVoting the current/existing Voting contract address. + * @param _newVoting the new Voting deployment address. + * @param _finder the Finder contract address. + * @param _setMigratedAddress the address to set migrated. This address will be able to continue making calls to + * old voting contract (used to claim rewards on others' behalf). Note: this address + * can always be changed by the voters. + */ + constructor( + address _governor, + address _existingVoting, + address _newVoting, + address _finder, + address _setMigratedAddress + ) { + governor = _governor; + existingVoting = Voting(_existingVoting); + newVoting = _newVoting; + finder = Finder(_finder); + setMigratedAddress = _setMigratedAddress; + } + + /** + * @notice Performs the atomic portion of the upgrade process. + * @dev This method updates the Voting address in the finder, sets the old voting contract to migrated state, and + * returns ownership of the existing Voting contract and Finder back to the Governor. + */ + function upgrade() external { + require(msg.sender == governor, "Upgrade can only be initiated by the existing governor."); + + // Change the addresses in the Finder. + finder.changeImplementationAddress(OracleInterfaces.Oracle, newVoting); + + // Set the preset "migrated" address to allow this address to claim rewards on voters' behalf. + // This also effectively shuts down the existing voting contract so new votes cannot be triggered. + existingVoting.setMigrated(setMigratedAddress); + + // Transfer back ownership of old voting contract and the finder to the governor. + existingVoting.transferOwnership(governor); + finder.transferOwnership(governor); + } +} diff --git a/contracts/external/uma/core/contracts/umip-helpers/VotingUpgraderV2.sol b/contracts/external/uma/core/contracts/umip-helpers/VotingUpgraderV2.sol new file mode 100644 index 000000000..6c59c666a --- /dev/null +++ b/contracts/external/uma/core/contracts/umip-helpers/VotingUpgraderV2.sol @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "../data-verification-mechanism/implementation/Finder.sol"; +import "../data-verification-mechanism/implementation/Constants.sol"; +import "../data-verification-mechanism/implementation/Voting.sol"; + +import "../common/implementation/MultiRole.sol"; +import "@openzeppelin/contracts-v4/access/Ownable.sol"; + +// Ownable contracts to transfer ownership of +struct OwnableContracts { + Ownable identifierWhitelist; + Ownable financialContractsAdmin; + Ownable addressWhitelist; + Ownable governorRootTunnel; + Ownable arbitrumParentMessenger; + Ownable oracleHub; + Ownable governorHub; + Ownable bobaParentMessenger; + Ownable optimismParentMessenger; + Ownable optimisticOracleV3; +} + +// Multirole contracts to transfer ownership of +struct MultiroleContracts { + MultiRole registry; + MultiRole store; +} + +/** + * @title A contract that executes a short series of upgrade calls that must be performed atomically as a part of the + * upgrade process for VotingV2.sol, GovernorV2.sol and ProposerV2.sol. + * @dev Note: the complete upgrade process requires more than just the transactions in this contract. These are only + * the ones that need to be performed atomically. + */ +contract VotingUpgraderV2 { + // The only one who can initiate the upgrade. + address public immutable upgrader; + + // Existing governor is the only one who can initiate the upgrade. + MultiRole public immutable existingGovernor; + + // New governor contract, set to be the UMA DVM owner post upgrade. + address public immutable newGovernor; + + // Existing Voting contract needs to be informed of the address of the new Voting contract. + Voting public immutable existingVoting; + + // New governor will be the new owner of the finder. + + // Finder contract to push upgrades to. + Finder public immutable finder; + + // Address to upgrade to. + address public immutable newVoting; + + // Proposer contract. + Ownable public immutable existingProposer; + + // Additional ownable contracts to transfer ownership of. + OwnableContracts public ownableContracts; + + // Additional multirole contracts to transfer ownership of. + MultiroleContracts public multiroleContracts; + + /** + * @notice Constructs the voting upgrader to upgrade to the DVM V2. This upgrades the voting, governor and proposer + * contracts. + * @param _existingGovernor the existing Governor contract address. + * @param _newGovernor the new Governor contract address. + * @param _existingVoting the current/existing Voting contract address. + * @param _newVoting the new Voting deployment address. + * @param _finder the Finder contract address. + * @param _ownableContracts additional ownable contracts to transfer ownership of. + * @param _multiroleContracts additional multirole contracts to transfer ownership of. + */ + constructor( + address _upgrader, + address _existingGovernor, + address _newGovernor, + address _existingVoting, + address _newVoting, + address _existingProposer, + address _finder, + OwnableContracts memory _ownableContracts, + MultiroleContracts memory _multiroleContracts + ) { + upgrader = _upgrader; + existingGovernor = MultiRole(_existingGovernor); + newGovernor = _newGovernor; + existingVoting = Voting(_existingVoting); + newVoting = _newVoting; + existingProposer = Ownable(_existingProposer); + finder = Finder(_finder); + ownableContracts = _ownableContracts; + multiroleContracts = _multiroleContracts; + } + + /** + * @notice Checks if the caller is the upgrader. + * @dev This is used as the first transaction in the upgrade process to block any other transactions from being + * executed if the upgrade is not initiated by the upgrader. + */ + function canRun() public view { + require(tx.origin == upgrader); + } + + /** + * @notice Performs the atomic portion of the upgrade process. + * @dev This method updates the Voting address in the finder, sets the old voting contract to migrated state, and + * transfers the required ownership of the contracts to GovernorV2. + */ + function upgrade() external { + require(msg.sender == address(existingGovernor), "Upgrade can only be initiated by the existing governor."); + + // Change the addresses in the Finder. + finder.changeImplementationAddress(OracleInterfaces.Oracle, newVoting); + + // Set the preset "migrated" address to allow this address to claim rewards on voters' behalf. + // This also effectively shuts down the existing voting contract so new votes cannot be triggered. + existingVoting.setMigrated(newVoting); + + // Transfer back ownership of old voting contract and the finder to the governor. + existingVoting.transferOwnership(newGovernor); + finder.transferOwnership(newGovernor); + + // Transfer ownership of existingProposer contract to the new governor. + existingProposer.transferOwnership(newGovernor); + + // Additional ownable contracts + ownableContracts.identifierWhitelist.transferOwnership(newGovernor); + ownableContracts.financialContractsAdmin.transferOwnership(newGovernor); + ownableContracts.addressWhitelist.transferOwnership(newGovernor); + ownableContracts.governorRootTunnel.transferOwnership(newGovernor); + ownableContracts.arbitrumParentMessenger.transferOwnership(newGovernor); + ownableContracts.oracleHub.transferOwnership(newGovernor); + ownableContracts.governorHub.transferOwnership(newGovernor); + ownableContracts.bobaParentMessenger.transferOwnership(newGovernor); + ownableContracts.optimismParentMessenger.transferOwnership(newGovernor); + ownableContracts.optimisticOracleV3.transferOwnership(newGovernor); + + // Set the new governor as the owner of the old governor + existingGovernor.resetMember(0, newGovernor); + + // Set governor as the owner of governor + MultiRole(newGovernor).resetMember(0, newGovernor); + + // Additional multirole contracts + multiroleContracts.registry.resetMember(0, newGovernor); + multiroleContracts.store.resetMember(0, newGovernor); + } +} diff --git a/contracts/handlers/MulticallHandler.sol b/contracts/handlers/MulticallHandler.sol index c4af8bcc4..47d2841fe 100644 --- a/contracts/handlers/MulticallHandler.sol +++ b/contracts/handlers/MulticallHandler.sol @@ -2,10 +2,10 @@ pragma solidity ^0.8.0; import "../interfaces/SpokePoolMessageHandler.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; -import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/utils/Address.sol"; +import "@openzeppelin/contracts-v4/security/ReentrancyGuard.sol"; /** * @title Across Multicall contract that allows a user to specify a series of calls that should be made by the handler diff --git a/contracts/interfaces/HubPoolInterface.sol b/contracts/interfaces/HubPoolInterface.sol index 128f906f5..7aca12679 100644 --- a/contracts/interfaces/HubPoolInterface.sol +++ b/contracts/interfaces/HubPoolInterface.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; /** * @notice Concise list of functions in HubPool implementation. @@ -106,11 +106,7 @@ interface HubPoolInterface { function setIdentifier(bytes32 newIdentifier) external; - function setCrossChainContracts( - uint256 l2ChainId, - address adapter, - address spokePool - ) external; + function setCrossChainContracts(uint256 l2ChainId, address adapter, address spokePool) external; function enableL1TokenForLiquidityProvision(address l1Token) external; @@ -118,11 +114,7 @@ interface HubPoolInterface { function addLiquidity(address l1Token, uint256 l1TokenAmount) external payable; - function removeLiquidity( - address l1Token, - uint256 lpTokenAmount, - bool sendEth - ) external; + function removeLiquidity(address l1Token, uint256 lpTokenAmount, bool sendEth) external; function exchangeRateCurrent(address l1Token) external returns (uint256); @@ -155,11 +147,7 @@ interface HubPoolInterface { function claimProtocolFeesCaptured(address l1Token) external; - function setPoolRebalanceRoute( - uint256 destinationChainId, - address l1Token, - address destinationToken - ) external; + function setPoolRebalanceRoute(uint256 destinationChainId, address l1Token, address destinationToken) external; function setDepositRoute( uint256 originChainId, @@ -168,10 +156,10 @@ interface HubPoolInterface { bool depositsEnabled ) external; - function poolRebalanceRoute(uint256 destinationChainId, address l1Token) - external - view - returns (address destinationToken); + function poolRebalanceRoute( + uint256 destinationChainId, + address l1Token + ) external view returns (address destinationToken); function loadEthForL2Calls() external payable; } diff --git a/contracts/interfaces/SpokePoolPeripheryInterface.sol b/contracts/interfaces/SpokePoolPeripheryInterface.sol index 680bf2256..823afc2e7 100644 --- a/contracts/interfaces/SpokePoolPeripheryInterface.sol +++ b/contracts/interfaces/SpokePoolPeripheryInterface.sol @@ -1,8 +1,8 @@ //SPDX-License-Identifier: Unlicense pragma solidity ^0.8.0; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; +import { IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import { IERC20Permit } from "@openzeppelin/contracts-v4/token/ERC20/extensions/IERC20Permit.sol"; import { IPermit2 } from "../external/interfaces/IPermit2.sol"; /** diff --git a/contracts/libraries/CircleCCTPAdapter.sol b/contracts/libraries/CircleCCTPAdapter.sol index a54ad9ae3..b68f79f58 100644 --- a/contracts/libraries/CircleCCTPAdapter.sol +++ b/contracts/libraries/CircleCCTPAdapter.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import "../external/interfaces/CCTPInterfaces.sol"; import { AddressToBytes32 } from "../libraries/AddressConverters.sol"; diff --git a/contracts/libraries/OFTTransportAdapter.sol b/contracts/libraries/OFTTransportAdapter.sol index 797dc7713..97a7acf28 100644 --- a/contracts/libraries/OFTTransportAdapter.sol +++ b/contracts/libraries/OFTTransportAdapter.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import { IOFT, SendParam, MessagingFee, OFTReceipt } from "../interfaces/IOFT.sol"; import { AddressToBytes32 } from "../libraries/AddressConverters.sol"; diff --git a/contracts/merkle-distributor/AcrossMerkleDistributor.sol b/contracts/merkle-distributor/AcrossMerkleDistributor.sol index 3fde38ece..3af0cab5d 100644 --- a/contracts/merkle-distributor/AcrossMerkleDistributor.sol +++ b/contracts/merkle-distributor/AcrossMerkleDistributor.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import "@uma/core/contracts/merkle-distributor/implementation/MerkleDistributor.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "../external/uma/core/contracts/merkle-distributor/implementation/MerkleDistributor.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; /** * @title Extended MerkleDistributor contract. diff --git a/contracts/permit2-order/Permit2Depositor.sol b/contracts/permit2-order/Permit2Depositor.sol index 6d512baca..783911d56 100644 --- a/contracts/permit2-order/Permit2Depositor.sol +++ b/contracts/permit2-order/Permit2Depositor.sol @@ -5,9 +5,9 @@ import "./Permit2OrderLib.sol"; import "../external/interfaces/IPermit2.sol"; import "../interfaces/V3SpokePoolInterface.sol"; -import "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/utils/math/SafeCast.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; import { AddressToBytes32 } from "../libraries/AddressConverters.sol"; /** @@ -33,11 +33,7 @@ contract Permit2Depositor { * @param _permit2 Permit2 contract * @param _quoteBeforeDeadline quoteBeforeDeadline is subtracted from the deadline to get the quote timestamp. */ - constructor( - V3SpokePoolInterface _spokePool, - IPermit2 _permit2, - uint256 _quoteBeforeDeadline - ) { + constructor(V3SpokePoolInterface _spokePool, IPermit2 _permit2, uint256 _quoteBeforeDeadline) { SPOKE_POOL = _spokePool; PERMIT2 = _permit2; QUOTE_BEFORE_DEADLINE = _quoteBeforeDeadline; diff --git a/contracts/test/ExpandedERC20WithBlacklist.sol b/contracts/test/ExpandedERC20WithBlacklist.sol index 607609eb7..137488eab 100644 --- a/contracts/test/ExpandedERC20WithBlacklist.sol +++ b/contracts/test/ExpandedERC20WithBlacklist.sol @@ -1,26 +1,18 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import "@uma/core/contracts/common/implementation/ExpandedERC20.sol"; +import "../external/uma/core/contracts/common/implementation/ExpandedERC20.sol"; contract ExpandedERC20WithBlacklist is ExpandedERC20 { mapping(address => bool) public isBlackListed; - constructor( - string memory name, - string memory symbol, - uint8 decimals - ) ExpandedERC20(name, symbol, decimals) {} + constructor(string memory name, string memory symbol, uint8 decimals) ExpandedERC20(name, symbol, decimals) {} function setBlacklistStatus(address account, bool status) external { isBlackListed[account] = status; } - function _beforeTokenTransfer( - address from, - address to, - uint256 amount - ) internal override { + function _beforeTokenTransfer(address from, address to, uint256 amount) internal override { require(!isBlackListed[to], "Recipient is blacklisted"); super._beforeTokenTransfer(from, to, amount); } diff --git a/contracts/test/MockERC1271.sol b/contracts/test/MockERC1271.sol index 3da5f2440..daf95884a 100644 --- a/contracts/test/MockERC1271.sol +++ b/contracts/test/MockERC1271.sol @@ -1,10 +1,10 @@ //SPDX-License-Identifier: Unlicense pragma solidity ^0.8.0; -import "@openzeppelin/contracts/interfaces/IERC1271.sol"; +import "@openzeppelin/contracts-v4/interfaces/IERC1271.sol"; -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts-v4/utils/cryptography/ECDSA.sol"; +import "@openzeppelin/contracts-v4/access/Ownable.sol"; /** * @title MockERC1271 diff --git a/contracts/test/MockERC20.sol b/contracts/test/MockERC20.sol index 84150eee4..044f7d31e 100644 --- a/contracts/test/MockERC20.sol +++ b/contracts/test/MockERC20.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.0; import { IERC20Auth } from "../external/interfaces/IERC20Auth.sol"; -import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; +import { ERC20Permit } from "@openzeppelin/contracts-v4/token/ERC20/extensions/ERC20Permit.sol"; +import { ERC20 } from "@openzeppelin/contracts-v4/token/ERC20/ERC20.sol"; +import { SignatureChecker } from "@openzeppelin/contracts-v4/utils/cryptography/SignatureChecker.sol"; /** * @title MockERC20 diff --git a/contracts/test/MockPermit2.sol b/contracts/test/MockPermit2.sol index eac9d31b3..bbe413bf6 100644 --- a/contracts/test/MockPermit2.sol +++ b/contracts/test/MockPermit2.sol @@ -1,10 +1,10 @@ pragma solidity ^0.8.0; import { IPermit2 } from "../external/interfaces/IPermit2.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IERC1271 } from "@openzeppelin/contracts/interfaces/IERC1271.sol"; -import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; +import { IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; +import { IERC1271 } from "@openzeppelin/contracts-v4/interfaces/IERC1271.sol"; +import { EIP712 } from "@openzeppelin/contracts-v4/utils/cryptography/EIP712.sol"; // Taken from https://github.com/Uniswap/permit2/blob/main/src/EIP712.sol contract Permit2EIP712 { diff --git a/contracts/test/MockSpokePool.sol b/contracts/test/MockSpokePool.sol index cca9e449f..d592072bd 100644 --- a/contracts/test/MockSpokePool.sol +++ b/contracts/test/MockSpokePool.sol @@ -1,7 +1,7 @@ //SPDX-License-Identifier: Unlicense pragma solidity ^0.8.0; -import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable-v4/access/OwnableUpgradeable.sol"; import "../SpokePool.sol"; import "./interfaces/MockV2SpokePoolInterface.sol"; import "./V2MerkleLib.sol"; @@ -33,11 +33,7 @@ contract MockSpokePool is SpokePool, MockV2SpokePoolInterface, OwnableUpgradeabl /// @custom:oz-upgrades-unsafe-allow constructor constructor(address _wrappedNativeTokenAddress) SpokePool(_wrappedNativeTokenAddress, 1 hours, 9 hours, 0, 0) {} // solhint-disable-line no-empty-blocks - function initialize( - uint32 _initialDepositId, - address _crossDomainAdmin, - address _hubPool - ) public initializer { + function initialize(uint32 _initialDepositId, address _crossDomainAdmin, address _hubPool) public initializer { __Ownable_init(); __SpokePool_init(_initialDepositId, _crossDomainAdmin, _hubPool); currentTime = block.timestamp; // solhint-disable-line not-rely-on-time diff --git a/contracts/test/PolygonERC20Test.sol b/contracts/test/PolygonERC20Test.sol index a0b3d8b18..110128d4f 100644 --- a/contracts/test/PolygonERC20Test.sol +++ b/contracts/test/PolygonERC20Test.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import "@uma/core/contracts/common/implementation/ExpandedERC20.sol"; +import "../external/uma/core/contracts/common/implementation/ExpandedERC20.sol"; import "../PolygonTokenBridger.sol"; /** diff --git a/contracts/test/PolygonMocks.sol b/contracts/test/PolygonMocks.sol index 69fbf567e..09b18893f 100644 --- a/contracts/test/PolygonMocks.sol +++ b/contracts/test/PolygonMocks.sol @@ -1,16 +1,12 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/ERC20.sol"; contract RootChainManagerMock { function depositEtherFor(address user) external payable {} // solhint-disable-line no-empty-blocks - function depositFor( - address user, - address rootToken, - bytes calldata depositData - ) external {} // solhint-disable-line no-empty-blocks + function depositFor(address user, address rootToken, bytes calldata depositData) external {} // solhint-disable-line no-empty-blocks } contract FxStateSenderMock { diff --git a/contracts/test/V2MerkleLib.sol b/contracts/test/V2MerkleLib.sol index b18af96cb..0bc7ec48b 100644 --- a/contracts/test/V2MerkleLib.sol +++ b/contracts/test/V2MerkleLib.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; +import "@openzeppelin/contracts-v4/utils/cryptography/MerkleProof.sol"; import "./interfaces/MockV2SpokePoolInterface.sol"; /** diff --git a/contracts/upgradeable/AddressLibUpgradeable.sol b/contracts/upgradeable/AddressLibUpgradeable.sol index d5b5980d6..036a8d8ad 100644 --- a/contracts/upgradeable/AddressLibUpgradeable.sol +++ b/contracts/upgradeable/AddressLibUpgradeable.sol @@ -6,7 +6,7 @@ pragma solidity ^0.8.0; /** * @title AddressUpgradeable * @dev Collection of functions related to the address type - * @notice Logic is 100% copied from "@openzeppelin/contracts-upgradeable/contracts/utils/AddressUpgradeable.sol" but one + * @notice Logic is 100% copied from "@openzeppelin/contracts-upgradeable-v4/contracts/utils/AddressUpgradeable.sol" but one * comment is added to clarify why we allow delegatecall() in this contract, which is typically unsafe for use in * upgradeable implementation contracts. * @dev See https://docs.openzeppelin.com/upgrades-plugins/1.x/faq#delegatecall-selfdestruct for more details. @@ -121,11 +121,7 @@ library AddressLibUpgradeable { * * _Available since v3.1._ */ - function functionCallWithValue( - address target, - bytes memory data, - uint256 value - ) internal returns (bytes memory) { + 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"); } diff --git a/contracts/upgradeable/EIP712CrossChainUpgradeable.sol b/contracts/upgradeable/EIP712CrossChainUpgradeable.sol index de1f436de..5a5cef507 100644 --- a/contracts/upgradeable/EIP712CrossChainUpgradeable.sol +++ b/contracts/upgradeable/EIP712CrossChainUpgradeable.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts-upgradeable-v4/utils/cryptography/ECDSAUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable-v4/proxy/utils/Initializable.sol"; /** * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data. diff --git a/contracts/upgradeable/MultiCallerUpgradeable.sol b/contracts/upgradeable/MultiCallerUpgradeable.sol index 22b9e1dcb..32b6112d1 100644 --- a/contracts/upgradeable/MultiCallerUpgradeable.sol +++ b/contracts/upgradeable/MultiCallerUpgradeable.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; /** * @title MultiCallerUpgradeable - * @notice Logic is 100% copied from "@uma/core/contracts/common/implementation/MultiCaller.sol" but one + * @notice Logic is 100% copied from "contracts/external/uma/core/contracts/common/implementation/MultiCaller.sol" but one * comment is added to clarify why we allow delegatecall() in this contract, which is typically unsafe for use in * upgradeable implementation contracts. * @dev See https://docs.openzeppelin.com/upgrades-plugins/1.x/faq#delegatecall-selfdestruct for more details. diff --git a/foundry.toml b/foundry.toml index d5d2b5e6c..8747790c4 100644 --- a/foundry.toml +++ b/foundry.toml @@ -16,7 +16,7 @@ remappings = [ "@matterlabs/=node_modules/@matterlabs/", "@openzeppelin/=node_modules/@openzeppelin/", "@scroll-tech/=node_modules/@scroll-tech/", - "@uma/=node_modules/@uma/", + "@uma/=contracts/external/uma/", "@uniswap/=node_modules/@uniswap/", "arb-bridge-eth/=node_modules/arb-bridge-eth/", "arb-bridge-peripherals/=node_modules/arb-bridge-peripherals/", @@ -28,11 +28,12 @@ remappings = [ ] via_ir = true optimizer_runs = 800 -solc_version = "0.8.23" +auto_detect_solc = true +# solc_version = "0.8.23" revert_strings = "strip" fs_permissions = [{ access = "read", path = "./"}] -solc = "0.8.23" +# solc = "0.8.23" evm_version = "prague" [profile.zksync.zksync] diff --git a/hardhat.config.ts b/hardhat.config.ts index ddd7712a5..0a9abd137 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -14,6 +14,7 @@ import "hardhat-gas-reporter"; import "solidity-coverage"; import "hardhat-deploy"; import "@openzeppelin/hardhat-upgrades"; +import "hardhat-preprocessor"; const getMnemonic = () => { // Publicly-disclosed mnemonic. This is required for hre deployments in test. @@ -33,6 +34,17 @@ const getDefaultHardhatConfig = (chainId: number, isTestnet: boolean = false): a }; }; +// Hardhat already resolves node_modules paths, so we only need to remap specific contracts where we need to apply a custom remapping rule. +const customRemappings: [string, string][] = [["@uma", "contracts/external/uma/"]]; + +function applyRemappings(line: string): string { + // split/join works on plain-string patterns and supports old Node versions + for (const [find, rep] of customRemappings) { + if (line.includes(find)) line = line.split(find).join(rep); + } + return line; +} + // Custom tasks to add to HRE. const tasks = [ "enableL1TokenAcrossEcosystem", @@ -56,7 +68,7 @@ const isTest = process.env.IS_TEST === "true" || process.env.CI === "true"; // the following config is true. const compileZk = process.env.COMPILE_ZK === "true"; -const solcVersion = "0.8.23"; +const solcVersion = "0.8.30"; // Compilation settings are overridden for large contracts to allow them to compile without going over the bytecode // limit. @@ -89,7 +101,13 @@ const LARGEST_CONTRACT_COMPILER_SETTINGS = { const config: HardhatUserConfig = { solidity: { - compilers: [DEFAULT_CONTRACT_COMPILER_SETTINGS], + compilers: [ + DEFAULT_CONTRACT_COMPILER_SETTINGS, + { + ...DEFAULT_CONTRACT_COMPILER_SETTINGS, + version: "0.8.16", + }, + ], overrides: { "contracts/HubPool.sol": LARGE_CONTRACT_COMPILER_SETTINGS, "contracts/Linea_SpokePool.sol": { @@ -115,6 +133,10 @@ const config: HardhatUserConfig = { "contracts/Cher_SpokePool.sol": LARGE_CONTRACT_COMPILER_SETTINGS, "contracts/Blast_SpokePool.sol": LARGEST_CONTRACT_COMPILER_SETTINGS, "contracts/Tatara_SpokePool.sol": LARGE_CONTRACT_COMPILER_SETTINGS, + "contracts/external/uma/**/*.sol": { + ...DEFAULT_CONTRACT_COMPILER_SETTINGS, + version: "0.8.16", + }, }, }, zksolc: { @@ -375,6 +397,11 @@ const config: HardhatUserConfig = { paths: { tests: "./test/evm/hardhat", }, + // preprocess: { + // eachLine: () => ({ + // transform: (line: string) => applyRemappings(line), + // }), + // }, }; export default config; diff --git a/package.json b/package.json index dbec6e189..c426e4d6c 100644 --- a/package.json +++ b/package.json @@ -59,8 +59,10 @@ "@ethersproject/abstract-provider": "5.7.0", "@ethersproject/abstract-signer": "5.7.0", "@ethersproject/bignumber": "5.7.0", - "@openzeppelin/contracts": "4.9.6", - "@openzeppelin/contracts-upgradeable": "4.9.6", + "@openzeppelin/contracts": "5.5.0", + "@openzeppelin/contracts-upgradeable": "5.5.0", + "@openzeppelin/contracts-upgradeable-v4": "npm:@openzeppelin/contracts-upgradeable@4.9.6", + "@openzeppelin/contracts-v4": "npm:@openzeppelin/contracts@4.9.6", "@openzeppelin/foundry-upgrades": "^0.4.0", "@safe-global/protocol-kit": "^6.1.1", "@scroll-tech/contracts": "^0.1.0", @@ -73,9 +75,9 @@ "@types/yargs": "^17.0.33", "@uma/common": "^2.37.3", "@uma/contracts-node": "^0.4.17", - "@uma/core": "^2.61.0", "axios": "^1.7.4", "bs58": "^6.0.0", + "hardhat-preprocessor": "^0.1.5", "prettier-plugin-rust": "^0.1.9", "yargs": "^17.7.2", "zksync-web3": "^0.14.3" diff --git a/script/001DeployHubPool.s.sol b/script/001DeployHubPool.s.sol index ad98dbb23..a324a5ddc 100644 --- a/script/001DeployHubPool.s.sol +++ b/script/001DeployHubPool.s.sol @@ -6,7 +6,7 @@ import { Test } from "forge-std/Test.sol"; import { console } from "forge-std/console.sol"; import { HubPool } from "../contracts/HubPool.sol"; import { LpTokenFactory } from "../contracts/LpTokenFactory.sol"; -import { FinderInterface } from "@uma/core/contracts/data-verification-mechanism/interfaces/FinderInterface.sol"; +import { FinderInterface } from "contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/FinderInterface.sol"; import { WETH9Interface } from "../contracts/external/interfaces/WETH9Interface.sol"; import { Constants } from "./utils/Constants.sol"; diff --git a/script/002DeployOptimismAdapter.s.sol b/script/002DeployOptimismAdapter.s.sol index e6c5aae90..b6e7f060c 100644 --- a/script/002DeployOptimismAdapter.s.sol +++ b/script/002DeployOptimismAdapter.s.sol @@ -6,7 +6,7 @@ import { Test } from "forge-std/Test.sol"; import { console } from "forge-std/console.sol"; import { Optimism_Adapter } from "../contracts/chain-adapters/Optimism_Adapter.sol"; import { Constants } from "./utils/Constants.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; import { ITokenMessenger } from "../contracts/external/interfaces/CCTPInterfaces.sol"; import { IL1StandardBridge } from "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; import { WETH9Interface } from "../contracts/external/interfaces/WETH9Interface.sol"; diff --git a/script/004DeployArbitrumAdapter.s.sol b/script/004DeployArbitrumAdapter.s.sol index 4b7f25b0b..5f4bbc456 100644 --- a/script/004DeployArbitrumAdapter.s.sol +++ b/script/004DeployArbitrumAdapter.s.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import { Script } from "forge-std/Script.sol"; import { Test } from "forge-std/Test.sol"; import { console } from "forge-std/console.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; import { Arbitrum_Adapter } from "../contracts/chain-adapters/Arbitrum_Adapter.sol"; import { Constants } from "./utils/Constants.sol"; diff --git a/script/009DeployPolygonAdapter.s.sol b/script/009DeployPolygonAdapter.s.sol index bb8bfd64c..220db9603 100644 --- a/script/009DeployPolygonAdapter.s.sol +++ b/script/009DeployPolygonAdapter.s.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import { Script } from "forge-std/Script.sol"; import { Test } from "forge-std/Test.sol"; import { console } from "forge-std/console.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; import { Polygon_Adapter } from "../contracts/chain-adapters/Polygon_Adapter.sol"; import { Constants } from "./utils/Constants.sol"; import { WETH9Interface } from "../contracts/external/interfaces/WETH9Interface.sol"; diff --git a/script/024DeployBaseAdapter.s.sol b/script/024DeployBaseAdapter.s.sol index 3cf0410e5..89a5cfebb 100644 --- a/script/024DeployBaseAdapter.s.sol +++ b/script/024DeployBaseAdapter.s.sol @@ -6,7 +6,7 @@ import { Test } from "forge-std/Test.sol"; import { console } from "forge-std/console.sol"; import { Base_Adapter } from "../contracts/chain-adapters/Base_Adapter.sol"; import { Constants } from "./utils/Constants.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; import { ITokenMessenger } from "../contracts/external/interfaces/CCTPInterfaces.sol"; import { IL1StandardBridge } from "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; import { WETH9Interface } from "../contracts/external/interfaces/WETH9Interface.sol"; diff --git a/script/061DeployUnichainAdapter.s.sol b/script/061DeployUnichainAdapter.s.sol index d5003f946..1c7b0841e 100644 --- a/script/061DeployUnichainAdapter.s.sol +++ b/script/061DeployUnichainAdapter.s.sol @@ -6,7 +6,7 @@ import { Test } from "forge-std/Test.sol"; import { console } from "forge-std/console.sol"; import { DoctorWho_Adapter as Unichain_Adapter } from "../contracts/chain-adapters/DoctorWho_Adapter.sol"; import { Constants } from "./utils/Constants.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; import { ITokenMessenger } from "../contracts/external/interfaces/CCTPInterfaces.sol"; import { IL1StandardBridge } from "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; import { WETH9Interface } from "../contracts/external/interfaces/WETH9Interface.sol"; diff --git a/script/utils/DeploymentUtils.sol b/script/utils/DeploymentUtils.sol index 61d569026..11b2fcc7d 100644 --- a/script/utils/DeploymentUtils.sol +++ b/script/utils/DeploymentUtils.sol @@ -6,7 +6,7 @@ import { Test } from "forge-std/Test.sol"; import { console } from "forge-std/console.sol"; import { Upgrades, Core, UnsafeUpgrades } from "@openzeppelin/foundry-upgrades/src/LegacyUpgrades.sol"; import { Options } from "@openzeppelin/foundry-upgrades/src/Options.sol"; -import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts-v4/proxy/ERC1967/ERC1967Proxy.sol"; import { Constants } from "./Constants.sol"; import { DeployedAddresses } from "./DeployedAddresses.sol"; diff --git a/test/evm/foundry/fork/BlacklistedRelayerRecipient.t.sol b/test/evm/foundry/fork/BlacklistedRelayerRecipient.t.sol index 65bbdbdb5..64a831672 100644 --- a/test/evm/foundry/fork/BlacklistedRelayerRecipient.t.sol +++ b/test/evm/foundry/fork/BlacklistedRelayerRecipient.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import { Test } from "forge-std/Test.sol"; import { MockSpokePool } from "../../../../contracts/test/MockSpokePool.sol"; import { AddressToBytes32 } from "../../../../contracts/libraries/AddressConverters.sol"; -import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable-v4/token/ERC20/IERC20Upgradeable.sol"; // Define a minimal interface for USDT. Note USDT does NOT return anything after a transfer. interface IUSDT { @@ -12,11 +12,7 @@ interface IUSDT { function transfer(address recipient, uint256 amount) external; - function transferFrom( - address from, - address to, - uint256 value - ) external; + function transferFrom(address from, address to, uint256 value) external; function addBlackList(address _evilUser) external; @@ -29,11 +25,7 @@ interface IUSDC { function transfer(address recipient, uint256 amount) external returns (bool); - function transferFrom( - address from, - address to, - uint256 value - ) external returns (bool); + function transferFrom(address from, address to, uint256 value) external returns (bool); function blacklist(address _account) external; @@ -48,7 +40,7 @@ contract MockSpokePoolTest is Test { address largeUSDTAccount = 0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1; address largeUSDCAccount = 0x37305B1cD40574E4C5Ce33f8e8306Be057fD7341; - uint256 seedAmount = 10_000 * 10**6; + uint256 seedAmount = 10_000 * 10 ** 6; address recipient1 = address(0x6969691111111420); address recipient2 = address(0x6969692222222420); @@ -78,7 +70,7 @@ contract MockSpokePoolTest is Test { assertEq(usdt.balanceOf(address(spokePool)), seedAmount, "SpokePool should have seed USDT balance"); uint256[] memory refundAmounts = new uint256[](1); - refundAmounts[0] = 420 * 10**6; + refundAmounts[0] = 420 * 10 ** 6; address[] memory refundAddresses = new address[](1); refundAddresses[0] = recipient1; @@ -110,8 +102,8 @@ contract MockSpokePoolTest is Test { assertEq(usdt.balanceOf(recipient2), 0, "Recipient2 should start with 0 USDT balance"); uint256[] memory refundAmounts = new uint256[](2); - refundAmounts[0] = 420 * 10**6; - refundAmounts[1] = 69 * 10**6; + refundAmounts[0] = 420 * 10 ** 6; + refundAmounts[1] = 69 * 10 ** 6; address[] memory refundAddresses = new address[](2); refundAddresses[0] = recipient1; @@ -137,8 +129,8 @@ contract MockSpokePoolTest is Test { assertEq(usdc.balanceOf(recipient2), 0, "Recipient2 should start with 0 USDc balance"); uint256[] memory refundAmounts = new uint256[](2); - refundAmounts[0] = 420 * 10**6; - refundAmounts[1] = 69 * 10**6; + refundAmounts[0] = 420 * 10 ** 6; + refundAmounts[1] = 69 * 10 ** 6; address[] memory refundAddresses = new address[](2); refundAddresses[0] = recipient1; diff --git a/test/evm/foundry/fork/UniversalAdapterOFT.t.sol b/test/evm/foundry/fork/UniversalAdapterOFT.t.sol index bd9b94a55..5a8d496d0 100644 --- a/test/evm/foundry/fork/UniversalAdapterOFT.t.sol +++ b/test/evm/foundry/fork/UniversalAdapterOFT.t.sol @@ -8,8 +8,8 @@ import { Universal_Adapter } from "../../../../contracts/chain-adapters/Universa import { AdapterStore, MessengerTypes } from "../../../../contracts/AdapterStore.sol"; import { HubPoolStore } from "../../../../contracts/chain-adapters/utilities/HubPoolStore.sol"; import { IOFT, SendParam, MessagingFee } from "../../../../contracts/interfaces/IOFT.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import { ITokenMessenger } from "../../../../contracts/external/interfaces/CCTPInterfaces.sol"; // A mock contract to simulate the HubPool's delegatecall to the Universal_Adapter @@ -20,12 +20,7 @@ contract MockHub { adapter = _adapter; } - function callRelayTokens( - address l1Token, - address l2Token, - uint256 amount, - address to - ) external payable { + function callRelayTokens(address l1Token, address l2Token, uint256 amount, address to) external payable { // This simulates HubPool's delegatecall to the adapter (bool success, ) = adapter.delegatecall( abi.encodeWithSignature("relayTokens(address,address,uint256,address)", l1Token, l2Token, amount, to) @@ -60,7 +55,7 @@ contract UniversalAdapterOFTTest is Test { // Test parameters uint256 forkId; - uint256 constant SEND_AMOUNT = 1 * 10**6; // 1 USDT (6 decimals) + uint256 constant SEND_AMOUNT = 1 * 10 ** 6; // 1 USDT (6 decimals) uint256 constant ETH_FUNDING = 0.1 ether; uint32 constant DST_EID = 30110; // Arbitrum EID address constant RECIPIENT = 0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D; // dev wallet diff --git a/test/evm/foundry/local/Blast_DaiRetriever.t.sol b/test/evm/foundry/local/Blast_DaiRetriever.t.sol index 71a93645b..2dfc10eab 100644 --- a/test/evm/foundry/local/Blast_DaiRetriever.t.sol +++ b/test/evm/foundry/local/Blast_DaiRetriever.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import { Test } from "forge-std/Test.sol"; -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import { ERC20 } from "@openzeppelin/contracts-v4/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts-upgradeable-v4/token/ERC20/IERC20Upgradeable.sol"; import { Blast_DaiRetriever } from "../../../../contracts/Blast_DaiRetriever.sol"; import { MockBlastUsdYieldManager } from "../../../../contracts/test/MockBlastUsdYieldManager.sol"; diff --git a/test/evm/foundry/local/Forwarder.t.sol b/test/evm/foundry/local/Forwarder.t.sol index a87874740..36f2023e5 100644 --- a/test/evm/foundry/local/Forwarder.t.sol +++ b/test/evm/foundry/local/Forwarder.t.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.0; import { Test } from "forge-std/Test.sol"; -import { ERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import { ERC20, IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/ERC20.sol"; +import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable-v4/token/ERC20/IERC20Upgradeable.sol"; import { IL1StandardBridge } from "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; -import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts-v4/proxy/ERC1967/ERC1967Proxy.sol"; import { Optimism_Adapter } from "../../../../contracts/chain-adapters/Optimism_Adapter.sol"; import { WETH9Interface } from "../../../../contracts/external/interfaces/WETH9Interface.sol"; diff --git a/test/evm/foundry/local/MultiCallerUpgradeable.t.sol b/test/evm/foundry/local/MultiCallerUpgradeable.t.sol index c50b87540..605ae03ca 100644 --- a/test/evm/foundry/local/MultiCallerUpgradeable.t.sol +++ b/test/evm/foundry/local/MultiCallerUpgradeable.t.sol @@ -6,8 +6,8 @@ import "forge-std/console.sol"; import { SpokePool } from "../../../../contracts/SpokePool.sol"; import { Ethereum_SpokePool } from "../../../../contracts/Ethereum_SpokePool.sol"; -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { ERC20 } from "@openzeppelin/contracts-v4/token/ERC20/ERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; import { AddressToBytes32 } from "../../../../contracts/libraries/AddressConverters.sol"; // This test does not require a mainnet fork (since it is testing contracts before deployment). @@ -35,13 +35,13 @@ contract MultiCallerUpgradeableTest is Test { rando2 = vm.addr(2); relayer = vm.addr(3); - deal(address(mockL2WETH), relayer, 10**22, true); + deal(address(mockL2WETH), relayer, 10 ** 22, true); vm.prank(relayer); - IERC20(address(mockL2WETH)).approve(address(ethereumSpokePool), 2**256 - 1); + IERC20(address(mockL2WETH)).approve(address(ethereumSpokePool), 2 ** 256 - 1); // Create Dummy Relay Data - uint256 depositAmount = 5 * (10**18); + uint256 depositAmount = 5 * (10 ** 18); uint256 mockRepaymentChainId = 1; uint32 fillDeadline = uint32(ethereumSpokePool.getCurrentTime()) + 1000; diff --git a/test/evm/foundry/local/MulticallHandler.t.sol b/test/evm/foundry/local/MulticallHandler.t.sol index b52d6c113..d6498602e 100644 --- a/test/evm/foundry/local/MulticallHandler.t.sol +++ b/test/evm/foundry/local/MulticallHandler.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import { Test } from "forge-std/Test.sol"; import { MulticallHandler } from "../../../../contracts/handlers/MulticallHandler.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; // Run this test to verify PermissionSplitter behavior when changing ownership of the HubPool // to it. Therefore this test should be run as a fork test via: diff --git a/test/evm/foundry/local/Router_Adapter.t.sol b/test/evm/foundry/local/Router_Adapter.t.sol index a415a9cad..1edd7e2d5 100644 --- a/test/evm/foundry/local/Router_Adapter.t.sol +++ b/test/evm/foundry/local/Router_Adapter.t.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.0; import { Test } from "forge-std/Test.sol"; -import { ERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import { ERC20, IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/ERC20.sol"; +import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable-v4/token/ERC20/IERC20Upgradeable.sol"; import { IL1StandardBridge } from "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; -import { FinderInterface } from "@uma/core/contracts/data-verification-mechanism/interfaces/FinderInterface.sol"; +import { FinderInterface } from "contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/FinderInterface.sol"; import { Router_Adapter } from "../../../../contracts/chain-adapters/Router_Adapter.sol"; import { Optimism_Adapter } from "../../../../contracts/chain-adapters/Optimism_Adapter.sol"; diff --git a/test/evm/foundry/local/SpokePoolDeprecatedMethods.t.sol b/test/evm/foundry/local/SpokePoolDeprecatedMethods.t.sol index f47ed02b0..8d2ea918a 100644 --- a/test/evm/foundry/local/SpokePoolDeprecatedMethods.t.sol +++ b/test/evm/foundry/local/SpokePoolDeprecatedMethods.t.sol @@ -5,7 +5,7 @@ import { Test } from "forge-std/Test.sol"; import { MockSpokePool } from "../../../../contracts/test/MockSpokePool.sol"; import { WETH9 } from "../../../../contracts/external/WETH9.sol"; import { AddressToBytes32 } from "../../../../contracts/libraries/AddressConverters.sol"; -import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts-v4/proxy/ERC1967/ERC1967Proxy.sol"; // Deprecated interface used to show that we can still call deposit() on the spoke, which should route internally to the // colliding function interface selector on depositDeprecated_5947912356 enabling legacy deposits to still work without @@ -33,7 +33,7 @@ contract SpokePoolOverloadedDeprecatedMethodsTest is Test { address owner; uint256 destinationChainId = 10; - uint256 depositAmount = 0.5 * (10**18); + uint256 depositAmount = 0.5 * (10 ** 18); function setUp() public { mockWETH = new WETH9(); diff --git a/test/evm/foundry/local/SpokePoolPeriphery.t.sol b/test/evm/foundry/local/SpokePoolPeriphery.t.sol index 5a3398da4..fa21dcf2a 100644 --- a/test/evm/foundry/local/SpokePoolPeriphery.t.sol +++ b/test/evm/foundry/local/SpokePoolPeriphery.t.sol @@ -14,9 +14,9 @@ import { IPermit2 } from "../../../../contracts/external/interfaces/IPermit2.sol import { MockPermit2, Permit2EIP712, SignatureVerification } from "../../../../contracts/test/MockPermit2.sol"; import { PeripherySigningLib } from "../../../../contracts/libraries/PeripherySigningLib.sol"; import { MockERC20 } from "../../../../contracts/test/MockERC20.sol"; -import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IERC1271 } from "@openzeppelin/contracts/interfaces/IERC1271.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts-v4/proxy/ERC1967/ERC1967Proxy.sol"; +import { IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import { IERC1271 } from "@openzeppelin/contracts-v4/interfaces/IERC1271.sol"; import { AddressToBytes32 } from "../../../../contracts/libraries/AddressConverters.sol"; contract Exchange { diff --git a/test/evm/foundry/local/SpokePoolVerifier.t.sol b/test/evm/foundry/local/SpokePoolVerifier.t.sol index 6aad99e01..3dd873788 100644 --- a/test/evm/foundry/local/SpokePoolVerifier.t.sol +++ b/test/evm/foundry/local/SpokePoolVerifier.t.sol @@ -7,8 +7,8 @@ import { SpokePoolVerifier } from "../../../../contracts/SpokePoolVerifier.sol"; import { Ethereum_SpokePool } from "../../../../contracts/Ethereum_SpokePool.sol"; import { V3SpokePoolInterface } from "../../../../contracts/interfaces/V3SpokePoolInterface.sol"; import { WETH9 } from "../../../../contracts/external/WETH9.sol"; -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { ERC20 } from "@openzeppelin/contracts-v4/token/ERC20/ERC20.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts-v4/proxy/ERC1967/ERC1967Proxy.sol"; import { AddressToBytes32 } from "../../../../contracts/libraries/AddressConverters.sol"; interface EthereumSpokePoolOnlyAddressInterface { @@ -41,8 +41,8 @@ contract SpokePoolVerifierTest is Test { address owner; uint256 destinationChainId = 10; - uint256 mintAmount = 10**22; - uint256 depositAmount = 5 * (10**18); + uint256 mintAmount = 10 ** 22; + uint256 depositAmount = 5 * (10 ** 18); uint32 fillDeadlineBuffer = 7200; function setUp() public { diff --git a/test/evm/foundry/local/SpokePool_EIP7702.t.sol b/test/evm/foundry/local/SpokePool_EIP7702.t.sol index 58f9907e0..6d8d5c37d 100644 --- a/test/evm/foundry/local/SpokePool_EIP7702.t.sol +++ b/test/evm/foundry/local/SpokePool_EIP7702.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import { Test } from "forge-std/Test.sol"; import { MockSpokePool } from "../../../../contracts/test/MockSpokePool.sol"; import { WETH9 } from "../../../../contracts/external/WETH9.sol"; -import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts-v4/proxy/ERC1967/ERC1967Proxy.sol"; import { AddressToBytes32, Bytes32ToAddress } from "../../../../contracts/libraries/AddressConverters.sol"; import { V3SpokePoolInterface } from "../../../../contracts/interfaces/V3SpokePoolInterface.sol"; diff --git a/test/evm/foundry/local/Universal_Adapter.t.sol b/test/evm/foundry/local/Universal_Adapter.t.sol index db5f09b16..60dabd9bd 100644 --- a/test/evm/foundry/local/Universal_Adapter.t.sol +++ b/test/evm/foundry/local/Universal_Adapter.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import { Test } from "forge-std/Test.sol"; import { Vm } from "forge-std/Vm.sol"; -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { ERC20 } from "@openzeppelin/contracts-v4/token/ERC20/ERC20.sol"; import { Universal_Adapter, HubPoolStore } from "../../../../contracts/chain-adapters/Universal_Adapter.sol"; import { MockHubPool } from "../../../../contracts/test/MockHubPool.sol"; import { HubPoolInterface } from "../../../../contracts/interfaces/HubPoolInterface.sol"; diff --git a/test/evm/foundry/local/Universal_SpokePool.t.sol b/test/evm/foundry/local/Universal_SpokePool.t.sol index eaca37105..8edb25c67 100644 --- a/test/evm/foundry/local/Universal_SpokePool.t.sol +++ b/test/evm/foundry/local/Universal_SpokePool.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import { Test } from "forge-std/Test.sol"; -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { ERC20 } from "@openzeppelin/contracts-v4/token/ERC20/ERC20.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts-v4/proxy/ERC1967/ERC1967Proxy.sol"; import { Universal_SpokePool, IHelios } from "../../../../contracts/Universal_SpokePool.sol"; import "../../../../contracts/SpokePool.sol"; diff --git a/test/evm/foundry/local/WithdrawalHelper.t.sol b/test/evm/foundry/local/WithdrawalHelper.t.sol index b6bdac8cc..162906e41 100644 --- a/test/evm/foundry/local/WithdrawalHelper.t.sol +++ b/test/evm/foundry/local/WithdrawalHelper.t.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.0; import { Test } from "forge-std/Test.sol"; -import { ERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import { ERC20, IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/ERC20.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts-v4/proxy/ERC1967/ERC1967Proxy.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts-v4/proxy/utils/UUPSUpgradeable.sol"; import { Lib_PredeployAddresses } from "@eth-optimism/contracts/libraries/constants/Lib_PredeployAddresses.sol"; import { ITokenMessenger } from "../../../../contracts/external/interfaces/CCTPInterfaces.sol"; import { Arbitrum_WithdrawalHelper } from "../../../../contracts/chain-adapters/l2/Arbitrum_WithdrawalHelper.sol"; diff --git a/test/evm/foundry/local/ZkStack_Adapter.t.sol b/test/evm/foundry/local/ZkStack_Adapter.t.sol index a0b89753a..504c5a41d 100644 --- a/test/evm/foundry/local/ZkStack_Adapter.t.sol +++ b/test/evm/foundry/local/ZkStack_Adapter.t.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.0; import { Test } from "forge-std/Test.sol"; -import { ERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import { ERC20, IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/ERC20.sol"; +import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable-v4/token/ERC20/IERC20Upgradeable.sol"; import { IL1StandardBridge } from "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; -import { FinderInterface } from "@uma/core/contracts/data-verification-mechanism/interfaces/FinderInterface.sol"; +import { FinderInterface } from "../../../../contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/FinderInterface.sol"; import { ZkStack_Adapter } from "../../../../contracts/chain-adapters/ZkStack_Adapter.sol"; import { ZkStack_CustomGasToken_Adapter, FunderInterface } from "../../../../contracts/chain-adapters/ZkStack_CustomGasToken_Adapter.sol"; diff --git a/yarn.lock b/yarn.lock index 5c5974a4f..53993f28d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2552,11 +2552,26 @@ dependencies: "@opentelemetry/core" "^1.1.0" -"@openzeppelin/contracts-upgradeable@4.9.6", "@openzeppelin/contracts-upgradeable@^4.8.1": +"@openzeppelin/contracts-upgradeable-v4@npm:@openzeppelin/contracts-upgradeable@4.9.6": version "4.9.6" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.6.tgz#38b21708a719da647de4bb0e4802ee235a0d24df" integrity sha512-m4iHazOsOCv1DgM7eD7GupTJ+NFVujRZt1wzddDPSVGpWdKq1SKkla5htKG7+IS4d2XOCtzkUNwRZ7Vq5aEUMA== +"@openzeppelin/contracts-upgradeable@5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-5.5.0.tgz#e35b3ededa5ccc13205c2b42e2058cd5cb7d424a" + integrity sha512-Va5hKG5oaK0EE5bXTVWugcGimMHazxL+SL523dH6WVbGiuLXwuWr9oxtLyPHQSVGtgmlIgtKNR5V+OUpCIUwFQ== + +"@openzeppelin/contracts-upgradeable@^4.8.1": + version "4.9.6" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.6.tgz#38b21708a719da647de4bb0e4802ee235a0d24df" + integrity sha512-m4iHazOsOCv1DgM7eD7GupTJ+NFVujRZt1wzddDPSVGpWdKq1SKkla5htKG7+IS4d2XOCtzkUNwRZ7Vq5aEUMA== + +"@openzeppelin/contracts-v4@npm:@openzeppelin/contracts@4.9.6": + version "4.9.6" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.6.tgz#2a880a24eb19b4f8b25adc2a5095f2aa27f39677" + integrity sha512-xSmezSupL+y9VkHZJGDoCBpmnB2ogM13ccaYDWqJTfS3dbuHkgjuwDFUmaFauBCboQMGB/S5UqUl2y54X99BmA== + "@openzeppelin/contracts@3.4.1-solc-0.7-2": version "3.4.1-solc-0.7-2" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.1-solc-0.7-2.tgz#371c67ebffe50f551c3146a9eec5fe6ffe862e92" @@ -2572,7 +2587,12 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.4.2.tgz#4e889c9c66e736f7de189a53f8ba5b8d789425c2" integrity sha512-NyJV7sJgoGYqbtNUWgzzOGW4T6rR19FmX1IJgXGdapGPWsuMelGJn9h03nos0iqfforCbCB0iYIR0MtIuIFLLw== -"@openzeppelin/contracts@4.9.6", "@openzeppelin/contracts@^4.2.0", "@openzeppelin/contracts@^4.8.1": +"@openzeppelin/contracts@5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.5.0.tgz#24e8a2f9598de484dcb223512af656edf52bc0e8" + integrity sha512-R8hq4zmKKWP2c7OxeRgAcjZwvF5W0Qq2OIX7degrtdM52Q9xYr4MLJdUAVPKGUewNJ1qo+M6YiZLLnNUnjP/gg== + +"@openzeppelin/contracts@^4.2.0", "@openzeppelin/contracts@^4.8.1": version "4.9.6" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.6.tgz#2a880a24eb19b4f8b25adc2a5095f2aa27f39677" integrity sha512-xSmezSupL+y9VkHZJGDoCBpmnB2ogM13ccaYDWqJTfS3dbuHkgjuwDFUmaFauBCboQMGB/S5UqUl2y54X99BmA== @@ -4510,22 +4530,6 @@ "@uniswap/v3-core" "^1.0.0-rc.2" "@uniswap/v3-periphery" "^1.0.0-beta.23" -"@uma/core@^2.61.0": - version "2.61.0" - resolved "https://registry.yarnpkg.com/@uma/core/-/core-2.61.0.tgz#29580736349a47af8fb10beb4bb3b50bfcf912f5" - integrity sha512-bnk+CWW+uWpRilrgUny/gDXHKomG+h1Ug84OXdx+AAvj1/BtlMDOCNNt1OX8LSAz+a0hkiN9s24/zgHclTC/sg== - dependencies: - "@gnosis.pm/safe-contracts" "^1.3.0" - "@gnosis.pm/zodiac" "3.2.0" - "@maticnetwork/fx-portal" "^1.0.4" - "@openzeppelin/contracts" "4.9.6" - "@uma/common" "^2.37.3" - "@uniswap/lib" "4.0.1-alpha" - "@uniswap/v2-core" "1.0.0" - "@uniswap/v2-periphery" "1.1.0-beta.0" - "@uniswap/v3-core" "^1.0.0-rc.2" - "@uniswap/v3-periphery" "^1.0.0-beta.23" - "@uma/merkle-distributor@^1.3.38": version "1.3.40" resolved "https://registry.yarnpkg.com/@uma/merkle-distributor/-/merkle-distributor-1.3.40.tgz#70ba0ebf0fe923851786396bcb9a59a5a931127b" @@ -10354,6 +10358,13 @@ hardhat-gas-reporter@^1.0.4, hardhat-gas-reporter@^1.0.8: eth-gas-reporter "^0.2.24" sha1 "^1.1.1" +hardhat-preprocessor@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/hardhat-preprocessor/-/hardhat-preprocessor-0.1.5.tgz#75b22641fd6a680739c995d03bd5f7868eb72144" + integrity sha512-j8m44mmPxpxAAd0G8fPHRHOas/INZdzptSur0TNJvMEGcFdLDhbHHxBcqZVQ/bmiW42q4gC60AP4CXn9EF018g== + dependencies: + murmur-128 "^0.2.1" + hardhat-typechain@^0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/hardhat-typechain/-/hardhat-typechain-0.3.5.tgz#8e50616a9da348b33bd001168c8fda9c66b7b4af" From d95273a1f6d225c010c2ff48ef6cddfd25c3363b Mon Sep 17 00:00:00 2001 From: Faisal Usmani Date: Sat, 1 Nov 2025 21:12:41 -0400 Subject: [PATCH 2/4] Removed unused uma contracts Signed-off-by: Faisal Usmani --- contracts/SpokePool.sol | 84 +- .../implementation/AddressWhitelist.sol | 92 -- .../common/implementation/AncillaryData.sol | 144 -- .../common/implementation/HasFinder.sol | 13 - .../common/implementation/Lockable.sol | 77 -- .../common/implementation/Multicall3.sol | 235 ---- .../common/implementation/Stakeable.sol | 60 - .../common/implementation/TestnetERC20.sol | 39 - .../common/implementation/Withdrawable.sol | 61 - .../implementation/dsproxy/DSGuardFactory.sol | 123 -- .../implementation/dsproxy/DSProxyFactory.sol | 195 --- .../contracts/common/interfaces/Balancer.sol | 10 - .../interfaces/HarvestVaultInterface.sol | 16 - .../common/interfaces/IERC20Standard.sol | 22 - .../contracts/common/interfaces/Multicall.sol | 15 - .../common/interfaces/Multicall2.sol | 25 - .../common/interfaces/TransactionBatcher.sol | 9 - .../contracts/common/interfaces/UniswapV2.sol | 15 - .../contracts/common/interfaces/UniswapV3.sol | 23 - .../common/interfaces/VaultInterface.sol | 16 - .../common/test/AncillaryDataTest.sol | 42 - .../contracts/common/test/BalancerMock.sol | 21 - .../core/contracts/common/test/BasicERC20.sol | 61 - .../common/test/HarvestVaultMock.sol | 25 - .../contracts/common/test/MintableERC721.sol | 13 - .../contracts/common/test/MultiCallerTest.sol | 15 - .../contracts/common/test/MultiRoleTest.sol | 22 - .../contracts/common/test/MulticallMock.sol | 23 - .../contracts/common/test/PerpetualMock.sol | 52 - .../common/test/ReentrancyAttack.sol | 12 - .../common/test/ReentrancyChecker.sol | 52 - .../contracts/common/test/ReentrancyMock.sol | 65 - .../common/test/SignedFixedPointTest.sol | 154 --- .../contracts/common/test/TestableTest.sol | 15 - .../contracts/common/test/UniswapV2Mock.sol | 18 - .../contracts/common/test/UniswapV3Mock.sol | 26 - .../common/test/UnsignedFixedPointTest.sol | 146 --- .../core/contracts/common/test/VaultMock.sol | 25 - .../common/test/WithdrawableTest.sol | 26 - .../AncillaryDataCompression.sol | 49 - .../cross-chain-oracle/GovernorHub.sol | 52 - .../cross-chain-oracle/GovernorSpoke.sol | 56 - .../cross-chain-oracle/OracleBase.sol | 78 -- .../cross-chain-oracle/OracleHub.sol | 148 --- .../cross-chain-oracle/OracleSpoke.sol | 255 ---- .../contracts/cross-chain-oracle/README.md | 325 ----- .../cross-chain-oracle/SpokeBase.sol | 26 - .../chain-adapters/Admin_ChildMessenger.sol | 57 - .../Arbitrum_ChildMessenger.sol | 84 -- .../Arbitrum_ParentMessenger.sol | 210 --- .../Optimism_ChildMessenger.sol | 102 -- .../Optimism_ParentMessenger.sol | 107 -- .../chain-adapters/ParentMessengerBase.sol | 98 -- .../chain-adapters/Polygon_ChildMessenger.sol | 69 - .../Polygon_ParentMessenger.sol | 66 - .../test/Arbitrum_inboxMock.sol | 51 - .../test/OVM_L1CrossDomainMessengerMock.sol | 12 - .../test/ParentMessengerBaseMock.sol | 12 - .../test/Polygon_ChildMessengerMock.sol | 16 - .../test/Polygon_ParentMessengerMock.sol | 16 - .../ChildMessengerConsumerInterface.sol | 7 - .../interfaces/ChildMessengerInterface.sol | 7 - .../ParentMessengerConsumerInterface.sol | 7 - .../interfaces/ParentMessengerInterface.sol | 10 - .../test/GovernorMessengerMock.sol | 23 - .../test/OracleBaseMock.sol | 27 - .../test/OracleMessengerMock.sol | 61 - .../data-verification-mechanism/README.md | 114 -- .../implementation/AdminIdentifierLib.sol | 63 - .../implementation/ContractCreator.sol | 32 - .../implementation/DesignatedVoting.sol | 117 -- .../DesignatedVotingFactory.sol | 53 - .../implementation/DesignatedVotingV2.sol | 55 - .../DesignatedVotingV2Factory.sol | 37 - .../implementation/EmergencyProposer.sol | 260 ---- .../FinancialContractsAdmin.sol | 29 - .../implementation/Finder.sol | 40 - .../FixedSlashSlashingLibrary.sol | 103 -- .../implementation/Governor.sol | 208 --- .../implementation/GovernorV2.sol | 234 ---- .../implementation/IdentifierWhitelist.sol | 64 - .../implementation/Proposer.sol | 95 -- .../implementation/ProposerV2.sol | 120 -- .../implementation/Registry.sol | 202 --- .../implementation/ResultComputation.sol | 94 -- .../implementation/ResultComputationV2.sol | 82 -- .../implementation/Staker.sol | 372 ------ .../implementation/Store.sol | 179 --- .../implementation/TokenMigrator.sol | 63 - .../implementation/VoteTiming.sol | 65 - .../implementation/Voting.sol | 977 -------------- .../implementation/VotingToken.sol | 52 - .../implementation/VotingV2.sol | 1156 ----------------- .../test/EmergencyProposerTest.sol | 20 - .../implementation/test/GovernorTest.sol | 22 - .../implementation/test/GovernorV2Test.sol | 17 - .../implementation/test/MockAdministratee.sol | 22 - .../PriceIdentifierSlashingLibaryTest.sol | 61 - .../implementation/test/ProposerV2Test.sol | 20 - .../test/PunitiveSlashingLibraryTest.sol | 48 - .../test/ResultComputationTest.sol | 28 - .../implementation/test/StakerTest.sol | 44 - .../implementation/test/VoteTimingTest.sol | 28 - .../implementation/test/VotingTest.sol | 32 - .../implementation/test/VotingV2Test.sol | 107 -- .../test/ZeroedSlashingLibaryTest.sol | 52 - .../interfaces/AdministrateeInterface.sol | 28 - .../MinimumVotingAncillaryInterface.sol | 20 - .../interfaces/OracleAncillaryInterface.sol | 43 - .../interfaces/OracleGovernanceInterface.sol | 20 - .../interfaces/OracleInterface.sol | 34 - .../interfaces/RegistryInterface.sol | 58 - .../interfaces/SlashingLibraryInterface.sol | 67 - .../interfaces/StakerInterface.sol | 38 - .../interfaces/VotingAncillaryInterface.sol | 183 --- .../interfaces/VotingInterface.sol | 170 --- .../interfaces/VotingV2Interface.sol | 187 --- .../test/MockOracle.sol | 138 -- .../test/MockOracleAncillary.sol | 154 --- .../test/MockOracleCombined.sol | 29 - .../test/MockOracleGovernance.sol | 28 - .../test/VotingAncillaryInterfaceTest.sol | 88 -- .../test/VotingInterfaceTest.sol | 88 -- .../uma/core/contracts/external/README.md | 3 - .../external/avm/AVM_CrossDomainEnabled.sol | 39 - .../avm/Arbitrum_CrossDomainEnabled.sol | 66 - .../external/avm/Arbitrum_Messenger.sol | 78 -- .../core/contracts/external/avm/CHANGELOG.md | 25 - .../external/avm/interfaces/ArbSys.sol | 79 -- .../avm/interfaces/iArbitrum_Inbox.sol | 28 - .../avm/interfaces/iArbitrum_Outbox.sol | 26 - .../external/boba/BobaAddressManager.sol | 11 - .../contracts/external/chainbridge/Bridge.sol | 451 ------- .../external/chainbridge/CHANGELOG.md | 26 - .../chainbridge/handlers/GenericHandler.sol | 237 ---- .../chainbridge/interfaces/IBridge.sol | 16 - .../interfaces/IDepositExecute.sol | 29 - .../chainbridge/interfaces/IERCHandler.sol | 29 - .../interfaces/IGenericHandler.sol | 23 - .../interfaces/OptimismL1StandardBridge.sol | 25 - .../interfaces/OptimismL2StandardBridge.sol | 19 - .../interfaces/OptimismL2StandardERC20.sol | 9 - .../common/EmergencyShutdownable.sol | 59 - .../financial-templates/common/FeePayer.sol | 322 ----- .../common/FundingRateApplier.sol | 338 ----- .../common/SyntheticToken.sol | 86 -- .../common/TokenFactory.sol | 30 - .../financial-templates/common/WETH9.sol | 72 - .../CoveredCallFinancialProductLibrary.sol | 94 -- .../FinancialProductLibrary.sol | 49 - .../KpiOptionsFinancialProductLibrary.sol | 43 - ...rTransformationFinancialProductLibrary.sol | 64 - ...rTransformationFinancialProductLibrary.sol | 64 - .../StructuredNoteFinancialProductLibrary.sol | 102 -- ...onLongShortPairFinancialProductLibrary.sol | 63 - ...arLongShortPairFinancialProductLibrary.sol | 79 -- ...llLongShortPairFinancialProductLibrary.sol | 69 - ...arLongShortPairFinancialProductLibrary.sol | 97 -- ...arLongShortPairFinancialProductLibrary.sol | 85 -- .../LongShortPairFinancialProductLibrary.sol | 12 - ...ndLongShortPairFinancialProductLibrary.sol | 101 -- ...enLongShortPairFinancialProductLibrary.sol | 85 -- ...enLongShortPairFinancialProductLibrary.sol | 86 -- .../ExpiringMultiParty.sol | 22 - .../ExpiringMultiPartyCreator.sol | 146 --- .../ExpiringMultiPartyLib.sol | 22 - .../expiring-multiparty/Liquidatable.sol | 634 --------- .../PricelessPositionManager.sol | 985 -------------- .../long-short-pair/LongShortPair.sol | 431 ------ .../long-short-pair/LongShortPairCreator.sol | 166 --- .../OptimisticDistributor.sol | 438 ------- .../OptimisticRewarder.sol | 114 -- .../OptimisticRewarderBase.sol | 400 ------ .../OptimisticRewarderCreator.sol | 91 -- .../OptimisticRewarderToken.sol | 33 - .../optimistic-rewarder/OptimisticStaker.sol | 91 -- .../test/OptimisticRewarderTest.sol | 59 - .../perpetual-multiparty/ConfigStore.sol | 184 --- .../ConfigStoreInterface.sol | 25 - .../perpetual-multiparty/Perpetual.sol | 22 - .../perpetual-multiparty/PerpetualCreator.sol | 167 --- .../perpetual-multiparty/PerpetualLib.sol | 22 - .../PerpetualLiquidatable.sol | 595 --------- .../PerpetualPositionManager.sol | 756 ----------- .../test/ExpiringMultiPartyMock.sol | 66 - .../test/FinancialProductLibraryTest.sol | 52 - .../test/FundingRateApplierTest.sol | 52 - ...ngShortPairFinancialProjectLibraryTest.sol | 18 - .../test/LongShortPairMock.sol | 12 - .../optimistic-governor/test/TestAvatar.sol | 34 - .../test/TestModuleProxyFactory.sol | 6 - .../implementation/OptimisticOracleV2.sol | 711 ---------- .../SkinnyOptimisticOracleV2.sol | 686 ---------- .../OptimisticOracleV2Interface.sol | 351 ----- .../SkinnyOptimisticOracleV2Interface.sol | 253 ---- .../previous-versions/OptimisticOracle.sol | 629 --------- .../SkinnyOptimisticOracle.sol | 695 ---------- .../test/OptimisticRequesterTest.sol | 139 -- .../test/SkinnyOptimisticRequesterTest.sol | 126 -- .../test/SkinnyOptimisticV2RequesterTest.sol | 123 -- .../implementation/ClaimData.sol | 4 - .../implementation/OptimisticOracleV3.sol | 488 ------- .../BaseEscalationManager.sol | 104 -- .../DisputeLimitingEscalationManager.sol | 59 - .../FullPolicyEscalationManager.sol | 222 ---- .../OwnerDiscardOracleEscalationManager.sol | 25 - .../OwnerSelectOracleEscalationManager.sol | 53 - .../SuperbondEscalationManager.sol | 35 - .../WhitelistAsserterEscalationManager.sol | 50 - .../WhitelistCallerEscalationManager.sol | 27 - .../WhitelistDisputerEscalationManager.sol | 29 - .../implementation/examples/DataAsserter.sol | 102 -- .../implementation/examples/Insurance.sol | 109 -- .../examples/PredictionMarket.sol | 258 ---- .../test/AssertingCallerTest.sol | 42 - .../test/OptimisticOracleV3Test.sol | 19 - .../interfaces/EscalationManagerInterface.sol | 53 - ...sticOracleV3CallbackRecipientInterface.sol | 21 - .../OptimisticOracleV3Interface.sol | 178 --- .../GovernorChildTunnel.sol | 49 - .../GovernorRootTunnel.sol | 36 - .../OracleBaseTunnel.sol | 81 -- .../OracleChildTunnel.sol | 229 ---- .../OracleRootTunnel.sol | 61 - .../polygon-cross-chain-oracle/README.md | 42 - .../test/FxChildMock.sol | 38 - .../test/FxRootMock.sol | 32 - .../test/OracleBaseTunnelMock.sol | 29 - .../test/OracleRootTunnelMock.sol | 47 - .../test/StateSyncMock.sol | 26 - .../ReserveCurrencyDisputer.sol | 137 -- .../ReserveCurrencyLiquidator.sol | 197 --- .../LiquidationWithdrawer.sol | 22 - .../bot-action-wrappers/PositionSettler.sol | 15 - .../bot-action-wrappers/TokenRedeemer.sol | 23 - .../bot-action-wrappers/TokenSender.sol | 12 - .../lsp-broker/LspUniswapV2Broker.sol | 305 ----- .../uniswap-broker/UniswapV2Broker.sol | 278 ---- .../uniswap-broker/UniswapV3Broker.sol | 623 --------- .../snapshot-helpers/SnapshotVotingPower.sol | 46 - .../umip-helpers/OriginValidator.sol | 18 - .../contracts/umip-helpers/Umip3Upgrader.sol | 71 - .../contracts/umip-helpers/VotingUpgrader.sol | 75 -- .../umip-helpers/VotingUpgraderV2.sol | 153 --- .../AcrossMerkleDistributor.sol | 2 +- foundry.toml | 3 - hardhat.config.ts | 16 +- 247 files changed, 10 insertions(+), 27967 deletions(-) delete mode 100644 contracts/external/uma/core/contracts/common/implementation/AddressWhitelist.sol delete mode 100644 contracts/external/uma/core/contracts/common/implementation/AncillaryData.sol delete mode 100644 contracts/external/uma/core/contracts/common/implementation/HasFinder.sol delete mode 100644 contracts/external/uma/core/contracts/common/implementation/Lockable.sol delete mode 100644 contracts/external/uma/core/contracts/common/implementation/Multicall3.sol delete mode 100644 contracts/external/uma/core/contracts/common/implementation/Stakeable.sol delete mode 100644 contracts/external/uma/core/contracts/common/implementation/TestnetERC20.sol delete mode 100644 contracts/external/uma/core/contracts/common/implementation/Withdrawable.sol delete mode 100644 contracts/external/uma/core/contracts/common/implementation/dsproxy/DSGuardFactory.sol delete mode 100644 contracts/external/uma/core/contracts/common/implementation/dsproxy/DSProxyFactory.sol delete mode 100644 contracts/external/uma/core/contracts/common/interfaces/Balancer.sol delete mode 100644 contracts/external/uma/core/contracts/common/interfaces/HarvestVaultInterface.sol delete mode 100644 contracts/external/uma/core/contracts/common/interfaces/IERC20Standard.sol delete mode 100644 contracts/external/uma/core/contracts/common/interfaces/Multicall.sol delete mode 100644 contracts/external/uma/core/contracts/common/interfaces/Multicall2.sol delete mode 100644 contracts/external/uma/core/contracts/common/interfaces/TransactionBatcher.sol delete mode 100644 contracts/external/uma/core/contracts/common/interfaces/UniswapV2.sol delete mode 100644 contracts/external/uma/core/contracts/common/interfaces/UniswapV3.sol delete mode 100644 contracts/external/uma/core/contracts/common/interfaces/VaultInterface.sol delete mode 100644 contracts/external/uma/core/contracts/common/test/AncillaryDataTest.sol delete mode 100644 contracts/external/uma/core/contracts/common/test/BalancerMock.sol delete mode 100644 contracts/external/uma/core/contracts/common/test/BasicERC20.sol delete mode 100644 contracts/external/uma/core/contracts/common/test/HarvestVaultMock.sol delete mode 100644 contracts/external/uma/core/contracts/common/test/MintableERC721.sol delete mode 100644 contracts/external/uma/core/contracts/common/test/MultiCallerTest.sol delete mode 100644 contracts/external/uma/core/contracts/common/test/MultiRoleTest.sol delete mode 100644 contracts/external/uma/core/contracts/common/test/MulticallMock.sol delete mode 100644 contracts/external/uma/core/contracts/common/test/PerpetualMock.sol delete mode 100644 contracts/external/uma/core/contracts/common/test/ReentrancyAttack.sol delete mode 100644 contracts/external/uma/core/contracts/common/test/ReentrancyChecker.sol delete mode 100644 contracts/external/uma/core/contracts/common/test/ReentrancyMock.sol delete mode 100644 contracts/external/uma/core/contracts/common/test/SignedFixedPointTest.sol delete mode 100644 contracts/external/uma/core/contracts/common/test/TestableTest.sol delete mode 100644 contracts/external/uma/core/contracts/common/test/UniswapV2Mock.sol delete mode 100644 contracts/external/uma/core/contracts/common/test/UniswapV3Mock.sol delete mode 100644 contracts/external/uma/core/contracts/common/test/UnsignedFixedPointTest.sol delete mode 100644 contracts/external/uma/core/contracts/common/test/VaultMock.sol delete mode 100644 contracts/external/uma/core/contracts/common/test/WithdrawableTest.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/AncillaryDataCompression.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/GovernorHub.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/GovernorSpoke.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/OracleBase.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/OracleHub.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/OracleSpoke.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/README.md delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/SpokeBase.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Admin_ChildMessenger.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Arbitrum_ChildMessenger.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Arbitrum_ParentMessenger.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Optimism_ChildMessenger.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Optimism_ParentMessenger.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/ParentMessengerBase.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Polygon_ChildMessenger.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Polygon_ParentMessenger.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Arbitrum_inboxMock.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/OVM_L1CrossDomainMessengerMock.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/ParentMessengerBaseMock.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Polygon_ChildMessengerMock.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Polygon_ParentMessengerMock.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ChildMessengerConsumerInterface.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ChildMessengerInterface.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ParentMessengerConsumerInterface.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ParentMessengerInterface.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/test/GovernorMessengerMock.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/test/OracleBaseMock.sol delete mode 100644 contracts/external/uma/core/contracts/cross-chain-oracle/test/OracleMessengerMock.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/README.md delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/AdminIdentifierLib.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ContractCreator.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVoting.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingFactory.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingV2.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingV2Factory.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/EmergencyProposer.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/FinancialContractsAdmin.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Finder.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/FixedSlashSlashingLibrary.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Governor.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/GovernorV2.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/IdentifierWhitelist.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Proposer.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ProposerV2.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Registry.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ResultComputation.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ResultComputationV2.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Staker.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Store.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/TokenMigrator.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VoteTiming.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Voting.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VotingToken.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VotingV2.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/EmergencyProposerTest.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/GovernorTest.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/GovernorV2Test.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/MockAdministratee.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/PriceIdentifierSlashingLibaryTest.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ProposerV2Test.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/PunitiveSlashingLibraryTest.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ResultComputationTest.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/StakerTest.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VoteTimingTest.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VotingTest.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VotingV2Test.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ZeroedSlashingLibaryTest.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/AdministrateeInterface.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/MinimumVotingAncillaryInterface.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleAncillaryInterface.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleGovernanceInterface.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleInterface.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/RegistryInterface.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/SlashingLibraryInterface.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/StakerInterface.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingAncillaryInterface.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingInterface.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingV2Interface.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracle.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleAncillary.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleCombined.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleGovernance.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/test/VotingAncillaryInterfaceTest.sol delete mode 100644 contracts/external/uma/core/contracts/data-verification-mechanism/test/VotingInterfaceTest.sol delete mode 100644 contracts/external/uma/core/contracts/external/README.md delete mode 100644 contracts/external/uma/core/contracts/external/avm/AVM_CrossDomainEnabled.sol delete mode 100644 contracts/external/uma/core/contracts/external/avm/Arbitrum_CrossDomainEnabled.sol delete mode 100644 contracts/external/uma/core/contracts/external/avm/Arbitrum_Messenger.sol delete mode 100644 contracts/external/uma/core/contracts/external/avm/CHANGELOG.md delete mode 100644 contracts/external/uma/core/contracts/external/avm/interfaces/ArbSys.sol delete mode 100644 contracts/external/uma/core/contracts/external/avm/interfaces/iArbitrum_Inbox.sol delete mode 100644 contracts/external/uma/core/contracts/external/avm/interfaces/iArbitrum_Outbox.sol delete mode 100644 contracts/external/uma/core/contracts/external/boba/BobaAddressManager.sol delete mode 100644 contracts/external/uma/core/contracts/external/chainbridge/Bridge.sol delete mode 100644 contracts/external/uma/core/contracts/external/chainbridge/CHANGELOG.md delete mode 100644 contracts/external/uma/core/contracts/external/chainbridge/handlers/GenericHandler.sol delete mode 100644 contracts/external/uma/core/contracts/external/chainbridge/interfaces/IBridge.sol delete mode 100644 contracts/external/uma/core/contracts/external/chainbridge/interfaces/IDepositExecute.sol delete mode 100644 contracts/external/uma/core/contracts/external/chainbridge/interfaces/IERCHandler.sol delete mode 100644 contracts/external/uma/core/contracts/external/chainbridge/interfaces/IGenericHandler.sol delete mode 100644 contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL1StandardBridge.sol delete mode 100644 contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL2StandardBridge.sol delete mode 100644 contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL2StandardERC20.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/common/EmergencyShutdownable.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/common/FeePayer.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/common/FundingRateApplier.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/common/SyntheticToken.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/common/TokenFactory.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/common/WETH9.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/CoveredCallFinancialProductLibrary.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/FinancialProductLibrary.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/KpiOptionsFinancialProductLibrary.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/PostExpirationIdentifierTransformationFinancialProductLibrary.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/PreExpirationIdentifierTransformationFinancialProductLibrary.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/StructuredNoteFinancialProductLibrary.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/BinaryOptionLongShortPairFinancialProductLibrary.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/CappedYieldDollarLongShortPairFinancialProductLibrary.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/CoveredCallLongShortPairFinancialProductLibrary.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/FlooredLinearLongShortPairFinancialProductLibrary.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/LinearLongShortPairFinancialProductLibrary.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/LongShortPairFinancialProductLibrary.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/RangeBondLongShortPairFinancialProductLibrary.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/SimpleSuccessTokenLongShortPairFinancialProductLibrary.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/SuccessTokenLongShortPairFinancialProductLibrary.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiParty.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiPartyCreator.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiPartyLib.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/Liquidatable.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/PricelessPositionManager.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/long-short-pair/LongShortPair.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/long-short-pair/LongShortPairCreator.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/optimistic-distributor/OptimisticDistributor.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarder.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderBase.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderCreator.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderToken.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticStaker.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/test/OptimisticRewarderTest.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/ConfigStore.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/ConfigStoreInterface.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/Perpetual.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualCreator.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualLib.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualLiquidatable.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualPositionManager.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/test/ExpiringMultiPartyMock.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/test/FinancialProductLibraryTest.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/test/FundingRateApplierTest.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/test/LongShortPairFinancialProjectLibraryTest.sol delete mode 100644 contracts/external/uma/core/contracts/financial-templates/test/LongShortPairMock.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-governor/test/TestAvatar.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-governor/test/TestModuleProxyFactory.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v2/implementation/OptimisticOracleV2.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v2/implementation/SkinnyOptimisticOracleV2.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/OptimisticOracleV2Interface.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/SkinnyOptimisticOracleV2Interface.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v2/previous-versions/OptimisticOracle.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v2/previous-versions/SkinnyOptimisticOracle.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v2/test/OptimisticRequesterTest.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v2/test/SkinnyOptimisticRequesterTest.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v2/test/SkinnyOptimisticV2RequesterTest.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/ClaimData.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/OptimisticOracleV3.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/BaseEscalationManager.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/DisputeLimitingEscalationManager.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/FullPolicyEscalationManager.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/OwnerDiscardOracleEscalationManager.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/OwnerSelectOracleEscalationManager.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/SuperbondEscalationManager.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistAsserterEscalationManager.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistCallerEscalationManager.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistDisputerEscalationManager.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/DataAsserter.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/Insurance.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/PredictionMarket.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/test/AssertingCallerTest.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/test/OptimisticOracleV3Test.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/EscalationManagerInterface.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/OptimisticOracleV3CallbackRecipientInterface.sol delete mode 100644 contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/OptimisticOracleV3Interface.sol delete mode 100644 contracts/external/uma/core/contracts/polygon-cross-chain-oracle/GovernorChildTunnel.sol delete mode 100644 contracts/external/uma/core/contracts/polygon-cross-chain-oracle/GovernorRootTunnel.sol delete mode 100644 contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleBaseTunnel.sol delete mode 100644 contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleChildTunnel.sol delete mode 100644 contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleRootTunnel.sol delete mode 100644 contracts/external/uma/core/contracts/polygon-cross-chain-oracle/README.md delete mode 100644 contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/FxChildMock.sol delete mode 100644 contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/FxRootMock.sol delete mode 100644 contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/OracleBaseTunnelMock.sol delete mode 100644 contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/OracleRootTunnelMock.sol delete mode 100644 contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/StateSyncMock.sol delete mode 100644 contracts/external/uma/core/contracts/proxy-scripts/atomic-disputer/ReserveCurrencyDisputer.sol delete mode 100644 contracts/external/uma/core/contracts/proxy-scripts/atomic-liquidator/ReserveCurrencyLiquidator.sol delete mode 100644 contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/LiquidationWithdrawer.sol delete mode 100644 contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/PositionSettler.sol delete mode 100644 contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/TokenRedeemer.sol delete mode 100644 contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/TokenSender.sol delete mode 100644 contracts/external/uma/core/contracts/proxy-scripts/lsp-broker/LspUniswapV2Broker.sol delete mode 100644 contracts/external/uma/core/contracts/proxy-scripts/uniswap-broker/UniswapV2Broker.sol delete mode 100644 contracts/external/uma/core/contracts/proxy-scripts/uniswap-broker/UniswapV3Broker.sol delete mode 100644 contracts/external/uma/core/contracts/snapshot-helpers/SnapshotVotingPower.sol delete mode 100644 contracts/external/uma/core/contracts/umip-helpers/OriginValidator.sol delete mode 100644 contracts/external/uma/core/contracts/umip-helpers/Umip3Upgrader.sol delete mode 100644 contracts/external/uma/core/contracts/umip-helpers/VotingUpgrader.sol delete mode 100644 contracts/external/uma/core/contracts/umip-helpers/VotingUpgraderV2.sol diff --git a/contracts/SpokePool.sol b/contracts/SpokePool.sol index f681417b0..fb4eb380a 100644 --- a/contracts/SpokePool.sol +++ b/contracts/SpokePool.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.18; +pragma solidity 0.8.25; import "./MerkleLib.sol"; import "./erc7683/ERC7683.sol"; @@ -1727,88 +1727,6 @@ abstract contract SpokePool is updatedMessage ); } - - _emitFilledRelayEvent(relayExecution, relayData, relayer, fillType); - _transferTokensToRecipient(relayExecution, relayData, isSlowFill); - } - - /** - * @notice Emits the FilledRelay event for a completed relay fill. - * @param relayExecution The relay execution parameters. - * @param relayData The relay data. - * @param relayer The relayer address. - * @param fillType The type of fill being executed. - */ - function _emitFilledRelayEvent( - V3RelayExecutionParams memory relayExecution, - V3RelayData memory relayData, - bytes32 relayer, - FillType fillType - ) private { - emit FilledRelay( - relayData.inputToken, - relayData.outputToken, - relayData.inputAmount, - relayData.outputAmount, - relayExecution.repaymentChainId, - relayData.originChainId, - relayData.depositId, - relayData.fillDeadline, - relayData.exclusivityDeadline, - relayData.exclusiveRelayer, - relayer, - relayData.depositor, - relayData.recipient, - _hashNonEmptyMessage(relayData.message), - V3RelayExecutionEventInfo({ - updatedRecipient: relayExecution.updatedRecipient, - updatedMessageHash: _hashNonEmptyMessage(relayExecution.updatedMessage), - updatedOutputAmount: relayExecution.updatedOutputAmount, - fillType: fillType - }) - ); - } - - /** - * @notice Transfers tokens to the recipient based on the relay execution parameters. - * @param relayExecution The relay execution parameters. - * @param relayData The relay data. - * @param isSlowFill Whether this is a slow fill execution. - */ - function _transferTokensToRecipient( - V3RelayExecutionParams memory relayExecution, - V3RelayData memory relayData, - bool isSlowFill - ) private { - address outputToken = relayData.outputToken.toAddress(); - uint256 amountToSend = relayExecution.updatedOutputAmount; - address recipientToSend = relayExecution.updatedRecipient.toAddress(); - - // If relay token is wrappedNativeToken then unwrap and send native token. - if (outputToken == address(wrappedNativeToken)) { - // Note: useContractFunds is True if we want to send funds to the recipient directly out of this contract, - // otherwise we expect the caller to send funds to the recipient. If useContractFunds is True and the - // recipient wants wrappedNativeToken, then we can assume that wrappedNativeToken is already in the - // contract, otherwise we'll need the user to send wrappedNativeToken to this contract. Regardless, we'll - // need to unwrap it to native token before sending to the user. - if (!isSlowFill) IERC20Upgradeable(outputToken).safeTransferFrom(msg.sender, address(this), amountToSend); - _unwrapwrappedNativeTokenTo(payable(recipientToSend), amountToSend); - // Else, this is a normal ERC20 token. Send to recipient. - } else { - // Note: Similar to note above, send token directly from the contract to the user in the slow relay case. - if (!isSlowFill) IERC20Upgradeable(outputToken).safeTransferFrom(msg.sender, recipientToSend, amountToSend); - else IERC20Upgradeable(outputToken).safeTransfer(recipientToSend, amountToSend); - } - - bytes memory updatedMessage = relayExecution.updatedMessage; - if (updatedMessage.length > 0 && recipientToSend.isContract()) { - AcrossMessageHandler(recipientToSend).handleV3AcrossMessage( - outputToken, - amountToSend, - msg.sender, - updatedMessage - ); - } } // Determine whether the exclusivityDeadline implies active exclusivity. diff --git a/contracts/external/uma/core/contracts/common/implementation/AddressWhitelist.sol b/contracts/external/uma/core/contracts/common/implementation/AddressWhitelist.sol deleted file mode 100644 index 8f30a1c8e..000000000 --- a/contracts/external/uma/core/contracts/common/implementation/AddressWhitelist.sol +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../interfaces/AddressWhitelistInterface.sol"; -import "@openzeppelin/contracts-v4/access/Ownable.sol"; -import "./Lockable.sol"; - -/** - * @title A contract to track a whitelist of addresses. - */ -contract AddressWhitelist is AddressWhitelistInterface, Ownable, Lockable { - enum Status { - None, - In, - Out - } - mapping(address => Status) public whitelist; - - address[] public whitelistIndices; - - event AddedToWhitelist(address indexed addedAddress); - event RemovedFromWhitelist(address indexed removedAddress); - - /** - * @notice Adds an address to the whitelist. - * @param newElement the new address to add. - */ - function addToWhitelist(address newElement) external override nonReentrant onlyOwner { - // Ignore if address is already included - if (whitelist[newElement] == Status.In) { - return; - } - - // Only append new addresses to the array, never a duplicate - if (whitelist[newElement] == Status.None) { - whitelistIndices.push(newElement); - } - - whitelist[newElement] = Status.In; - - emit AddedToWhitelist(newElement); - } - - /** - * @notice Removes an address from the whitelist. - * @param elementToRemove the existing address to remove. - */ - function removeFromWhitelist(address elementToRemove) external override nonReentrant onlyOwner { - if (whitelist[elementToRemove] != Status.Out) { - whitelist[elementToRemove] = Status.Out; - emit RemovedFromWhitelist(elementToRemove); - } - } - - /** - * @notice Checks whether an address is on the whitelist. - * @param elementToCheck the address to check. - * @return True if `elementToCheck` is on the whitelist, or False. - */ - function isOnWhitelist(address elementToCheck) external view override nonReentrantView returns (bool) { - return whitelist[elementToCheck] == Status.In; - } - - /** - * @notice Gets all addresses that are currently included in the whitelist. - * @dev Note: This method skips over, but still iterates through addresses. It is possible for this call to run out - * of gas if a large number of addresses have been removed. To reduce the likelihood of this unlikely scenario, we - * can modify the implementation so that when addresses are removed, the last addresses in the array is moved to - * the empty index. - * @return activeWhitelist the list of addresses on the whitelist. - */ - function getWhitelist() external view override nonReentrantView returns (address[] memory activeWhitelist) { - // Determine size of whitelist first - uint256 activeCount = 0; - for (uint256 i = 0; i < whitelistIndices.length; i++) { - if (whitelist[whitelistIndices[i]] == Status.In) { - activeCount++; - } - } - - // Populate whitelist - activeWhitelist = new address[](activeCount); - activeCount = 0; - for (uint256 i = 0; i < whitelistIndices.length; i++) { - address addr = whitelistIndices[i]; - if (whitelist[addr] == Status.In) { - activeWhitelist[activeCount] = addr; - activeCount++; - } - } - } -} diff --git a/contracts/external/uma/core/contracts/common/implementation/AncillaryData.sol b/contracts/external/uma/core/contracts/common/implementation/AncillaryData.sol deleted file mode 100644 index c48fb5ec0..000000000 --- a/contracts/external/uma/core/contracts/common/implementation/AncillaryData.sol +++ /dev/null @@ -1,144 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -/** - * @title Library for encoding and decoding ancillary data for DVM price requests. - * @notice We assume that on-chain ancillary data can be formatted directly from bytes to utf8 encoding via - * web3.utils.hexToUtf8, and that clients will parse the utf8-encoded ancillary data as a comma-delimitted key-value - * dictionary. Therefore, this library provides internal methods that aid appending to ancillary data from Solidity - * smart contracts. More details on UMA's ancillary data guidelines below: - * https://docs.google.com/document/d/1zhKKjgY1BupBGPPrY_WOJvui0B6DMcd-xDR8-9-SPDw/edit - */ -library AncillaryData { - // This converts the bottom half of a bytes32 input to hex in a highly gas-optimized way. - // Source: the brilliant implementation at https://gitter.im/ethereum/solidity?at=5840d23416207f7b0ed08c9b. - function toUtf8Bytes32Bottom(bytes32 bytesIn) private pure returns (bytes32) { - unchecked { - uint256 x = uint256(bytesIn); - - // Nibble interleave - x = x & 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff; - x = (x | (x * 2 ** 64)) & 0x0000000000000000ffffffffffffffff0000000000000000ffffffffffffffff; - x = (x | (x * 2 ** 32)) & 0x00000000ffffffff00000000ffffffff00000000ffffffff00000000ffffffff; - x = (x | (x * 2 ** 16)) & 0x0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff; - x = (x | (x * 2 ** 8)) & 0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff; - x = (x | (x * 2 ** 4)) & 0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f; - - // Hex encode - uint256 h = (x & 0x0808080808080808080808080808080808080808080808080808080808080808) / 8; - uint256 i = (x & 0x0404040404040404040404040404040404040404040404040404040404040404) / 4; - uint256 j = (x & 0x0202020202020202020202020202020202020202020202020202020202020202) / 2; - x = x + (h & (i | j)) * 0x27 + 0x3030303030303030303030303030303030303030303030303030303030303030; - - // Return the result. - return bytes32(x); - } - } - - /** - * @notice Returns utf8-encoded bytes32 string that can be read via web3.utils.hexToUtf8. - * @dev Will return bytes32 in all lower case hex characters and without the leading 0x. - * This has minor changes from the toUtf8BytesAddress to control for the size of the input. - * @param bytesIn bytes32 to encode. - * @return utf8 encoded bytes32. - */ - function toUtf8Bytes(bytes32 bytesIn) internal pure returns (bytes memory) { - return abi.encodePacked(toUtf8Bytes32Bottom(bytesIn >> 128), toUtf8Bytes32Bottom(bytesIn)); - } - - /** - * @notice Returns utf8-encoded address that can be read via web3.utils.hexToUtf8. - * Source: https://ethereum.stackexchange.com/questions/8346/convert-address-to-string/8447#8447 - * @dev Will return address in all lower case characters and without the leading 0x. - * @param x address to encode. - * @return utf8 encoded address bytes. - */ - function toUtf8BytesAddress(address x) internal pure returns (bytes memory) { - return - abi.encodePacked(toUtf8Bytes32Bottom(bytes32(bytes20(x)) >> 128), bytes8(toUtf8Bytes32Bottom(bytes20(x)))); - } - - /** - * @notice Converts a uint into a base-10, UTF-8 representation stored in a `string` type. - * @dev This method is based off of this code: https://stackoverflow.com/a/65707309. - */ - function toUtf8BytesUint(uint256 x) internal pure returns (bytes memory) { - if (x == 0) { - return "0"; - } - uint256 j = x; - uint256 len; - while (j != 0) { - len++; - j /= 10; - } - bytes memory bstr = new bytes(len); - uint256 k = len; - while (x != 0) { - k = k - 1; - uint8 temp = (48 + uint8(x - (x / 10) * 10)); - bytes1 b1 = bytes1(temp); - bstr[k] = b1; - x /= 10; - } - return bstr; - } - - function appendKeyValueBytes32( - bytes memory currentAncillaryData, - bytes memory key, - bytes32 value - ) internal pure returns (bytes memory) { - bytes memory prefix = constructPrefix(currentAncillaryData, key); - return abi.encodePacked(currentAncillaryData, prefix, toUtf8Bytes(value)); - } - - /** - * @notice Adds "key:value" to `currentAncillaryData` where `value` is an address that first needs to be converted - * to utf8 bytes. For example, if `utf8(currentAncillaryData)="k1:v1"`, then this function will return - * `utf8(k1:v1,key:value)`, and if `currentAncillaryData` is blank, then this will return `utf8(key:value)`. - * @param currentAncillaryData This bytes data should ideally be able to be utf8-decoded, but its OK if not. - * @param key Again, this bytes data should ideally be able to be utf8-decoded, but its OK if not. - * @param value An address to set as the value in the key:value pair to append to `currentAncillaryData`. - * @return Newly appended ancillary data. - */ - function appendKeyValueAddress( - bytes memory currentAncillaryData, - bytes memory key, - address value - ) internal pure returns (bytes memory) { - bytes memory prefix = constructPrefix(currentAncillaryData, key); - return abi.encodePacked(currentAncillaryData, prefix, toUtf8BytesAddress(value)); - } - - /** - * @notice Adds "key:value" to `currentAncillaryData` where `value` is a uint that first needs to be converted - * to utf8 bytes. For example, if `utf8(currentAncillaryData)="k1:v1"`, then this function will return - * `utf8(k1:v1,key:value)`, and if `currentAncillaryData` is blank, then this will return `utf8(key:value)`. - * @param currentAncillaryData This bytes data should ideally be able to be utf8-decoded, but its OK if not. - * @param key Again, this bytes data should ideally be able to be utf8-decoded, but its OK if not. - * @param value A uint to set as the value in the key:value pair to append to `currentAncillaryData`. - * @return Newly appended ancillary data. - */ - function appendKeyValueUint( - bytes memory currentAncillaryData, - bytes memory key, - uint256 value - ) internal pure returns (bytes memory) { - bytes memory prefix = constructPrefix(currentAncillaryData, key); - return abi.encodePacked(currentAncillaryData, prefix, toUtf8BytesUint(value)); - } - - /** - * @notice Helper method that returns the left hand side of a "key:value" pair plus the colon ":" and a leading - * comma "," if the `currentAncillaryData` is not empty. The return value is intended to be prepended as a prefix to - * some utf8 value that is ultimately added to a comma-delimited, key-value dictionary. - */ - function constructPrefix(bytes memory currentAncillaryData, bytes memory key) internal pure returns (bytes memory) { - if (currentAncillaryData.length > 0) { - return abi.encodePacked(",", key, ":"); - } else { - return abi.encodePacked(key, ":"); - } - } -} diff --git a/contracts/external/uma/core/contracts/common/implementation/HasFinder.sol b/contracts/external/uma/core/contracts/common/implementation/HasFinder.sol deleted file mode 100644 index bf64ebbb8..000000000 --- a/contracts/external/uma/core/contracts/common/implementation/HasFinder.sol +++ /dev/null @@ -1,13 +0,0 @@ -pragma solidity ^0.8.9; - -// SPDX-License-Identifier: UNLICENSED -import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; - -// Contract stores a reference to the DVM Finder contract which can be used to locate other important DVM contracts. -contract HasFinder { - FinderInterface public finder; - - constructor(address _finder) { - finder = FinderInterface(_finder); - } -} diff --git a/contracts/external/uma/core/contracts/common/implementation/Lockable.sol b/contracts/external/uma/core/contracts/common/implementation/Lockable.sol deleted file mode 100644 index 419d8119d..000000000 --- a/contracts/external/uma/core/contracts/common/implementation/Lockable.sol +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -/** - * @title A contract that provides modifiers to prevent reentrancy to state-changing and view-only methods. This contract - * is inspired by https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/ReentrancyGuard.sol - * and https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol. - */ -contract Lockable { - bool private _notEntered; - - constructor() { - // Storing an initial non-zero value makes deployment a bit more expensive, but in exchange the refund on every - // call to nonReentrant will be lower in amount. Since refunds are capped to a percentage of the total - // transaction's gas, it is best to keep them low in cases like this one, to increase the likelihood of the full - // refund coming into effect. - _notEntered = true; - } - - /** - * @dev Prevents a contract from calling itself, directly or indirectly. - * Calling a `nonReentrant` function from another `nonReentrant` function is not supported. It is possible to - * prevent this from happening by making the `nonReentrant` function external, and making it call a `private` - * function that does the actual state modification. - */ - modifier nonReentrant() { - _preEntranceCheck(); - _preEntranceSet(); - _; - _postEntranceReset(); - } - - /** - * @dev Designed to prevent a view-only method from being re-entered during a call to a `nonReentrant()` state-changing method. - */ - modifier nonReentrantView() { - _preEntranceCheck(); - _; - } - - // Internal methods are used to avoid copying the require statement's bytecode to every `nonReentrant()` method. - // On entry into a function, `_preEntranceCheck()` should always be called to check if the function is being - // re-entered. Then, if the function modifies state, it should call `_postEntranceSet()`, perform its logic, and - // then call `_postEntranceReset()`. - // View-only methods can simply call `_preEntranceCheck()` to make sure that it is not being re-entered. - function _preEntranceCheck() internal view { - // On the first call to nonReentrant, _notEntered will be true - require(_notEntered, "ReentrancyGuard: reentrant call"); - } - - function _preEntranceSet() internal { - // Any calls to nonReentrant after this point will fail - _notEntered = false; - } - - function _postEntranceReset() internal { - // By storing the original value once again, a refund is triggered (see - // https://eips.ethereum.org/EIPS/eip-2200) - _notEntered = true; - } - - // These functions are intended to be used by child contracts to temporarily disable and re-enable the guard. - // Intended use: - // _startReentrantGuardDisabled(); - // ... - // _endReentrantGuardDisabled(); - // - // IMPORTANT: these should NEVER be used in a method that isn't inside a nonReentrant block. Otherwise, it's - // possible to permanently lock your contract. - function _startReentrantGuardDisabled() internal { - _notEntered = true; - } - - function _endReentrantGuardDisabled() internal { - _notEntered = false; - } -} diff --git a/contracts/external/uma/core/contracts/common/implementation/Multicall3.sol b/contracts/external/uma/core/contracts/common/implementation/Multicall3.sol deleted file mode 100644 index bdba8fda4..000000000 --- a/contracts/external/uma/core/contracts/common/implementation/Multicall3.sol +++ /dev/null @@ -1,235 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -/// Source: https://github.com/mds1/multicall -/// @title Multicall3 -/// @notice Aggregate results from multiple function calls -/// @dev Multicall & Multicall2 backwards-compatible -/// @dev Aggregate methods are marked `payable` to save 24 gas per call -/// @author Michael Elliot -/// @author Joshua Levine -/// @author Nick Johnson -/// @author Andreas Bigger -/// @author Matt Solomon -contract Multicall3 { - struct Call { - address target; - bytes callData; - } - - struct Call3 { - address target; - bool allowFailure; - bytes callData; - } - - struct Call3Value { - address target; - bool allowFailure; - uint256 value; - bytes callData; - } - - struct Result { - bool success; - bytes returnData; - } - - /// @notice Backwards-compatible call aggregation with Multicall - /// @param calls An array of Call structs - /// @return blockNumber The block number where the calls were executed - /// @return returnData An array of bytes containing the responses - function aggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes[] memory returnData) { - blockNumber = block.number; - uint256 length = calls.length; - returnData = new bytes[](length); - Call calldata call; - for (uint256 i = 0; i < length; ) { - bool success; - call = calls[i]; - (success, returnData[i]) = call.target.call(call.callData); - require(success, "Multicall3: call failed"); - unchecked { - ++i; - } - } - } - - /// @notice Backwards-compatible with Multicall2 - /// @notice Aggregate calls without requiring success - /// @param requireSuccess If true, require all calls to succeed - /// @param calls An array of Call structs - /// @return returnData An array of Result structs - function tryAggregate( - bool requireSuccess, - Call[] calldata calls - ) public payable returns (Result[] memory returnData) { - uint256 length = calls.length; - returnData = new Result[](length); - Call calldata call; - for (uint256 i = 0; i < length; ) { - Result memory result = returnData[i]; - call = calls[i]; - (result.success, result.returnData) = call.target.call(call.callData); - if (requireSuccess) require(result.success, "Multicall3: call failed"); - unchecked { - ++i; - } - } - } - - /// @notice Backwards-compatible with Multicall2 - /// @notice Aggregate calls and allow failures using tryAggregate - /// @param calls An array of Call structs - /// @return blockNumber The block number where the calls were executed - /// @return blockHash The hash of the block where the calls were executed - /// @return returnData An array of Result structs - function tryBlockAndAggregate( - bool requireSuccess, - Call[] calldata calls - ) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) { - blockNumber = block.number; - blockHash = blockhash(block.number); - returnData = tryAggregate(requireSuccess, calls); - } - - /// @notice Backwards-compatible with Multicall2 - /// @notice Aggregate calls and allow failures using tryAggregate - /// @param calls An array of Call structs - /// @return blockNumber The block number where the calls were executed - /// @return blockHash The hash of the block where the calls were executed - /// @return returnData An array of Result structs - function blockAndAggregate( - Call[] calldata calls - ) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) { - (blockNumber, blockHash, returnData) = tryBlockAndAggregate(true, calls); - } - - /// @notice Aggregate calls, ensuring each returns success if required - /// @param calls An array of Call3 structs - /// @return returnData An array of Result structs - function aggregate3(Call3[] calldata calls) public payable returns (Result[] memory returnData) { - uint256 length = calls.length; - returnData = new Result[](length); - Call3 calldata calli; - for (uint256 i = 0; i < length; ) { - Result memory result = returnData[i]; - calli = calls[i]; - (result.success, result.returnData) = calli.target.call(calli.callData); - assembly { - // Revert if the call fails and failure is not allowed - // `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)` - if iszero(or(calldataload(add(calli, 0x20)), mload(result))) { - // set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)"))) - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - // set data offset - mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020) - // set length of revert string - mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017) - // set revert string: bytes32(abi.encodePacked("Multicall3: call failed")) - mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000) - revert(0x00, 0x64) - } - } - unchecked { - ++i; - } - } - } - - /// @notice Aggregate calls with a msg value - /// @notice Reverts if msg.value is less than the sum of the call values - /// @param calls An array of Call3Value structs - /// @return returnData An array of Result structs - function aggregate3Value(Call3Value[] calldata calls) public payable returns (Result[] memory returnData) { - uint256 valAccumulator; - uint256 length = calls.length; - returnData = new Result[](length); - Call3Value calldata calli; - for (uint256 i = 0; i < length; ) { - Result memory result = returnData[i]; - calli = calls[i]; - uint256 val = calli.value; - // Humanity will be a Type V Kardashev Civilization before this overflows - andreas - // ~ 10^25 Wei in existence << ~ 10^76 size uint fits in a uint256 - unchecked { - valAccumulator += val; - } - (result.success, result.returnData) = calli.target.call{ value: val }(calli.callData); - assembly { - // Revert if the call fails and failure is not allowed - // `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)` - if iszero(or(calldataload(add(calli, 0x20)), mload(result))) { - // set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)"))) - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - // set data offset - mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020) - // set length of revert string - mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017) - // set revert string: bytes32(abi.encodePacked("Multicall3: call failed")) - mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000) - revert(0x00, 0x84) - } - } - unchecked { - ++i; - } - } - // Finally, make sure the msg.value = SUM(call[0...i].value) - require(msg.value == valAccumulator, "Multicall3: value mismatch"); - } - - /// @notice Returns the block hash for the given block number - /// @param blockNumber The block number - function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) { - blockHash = blockhash(blockNumber); - } - - /// @notice Returns the block number - function getBlockNumber() public view returns (uint256 blockNumber) { - blockNumber = block.number; - } - - /// @notice Returns the block coinbase - function getCurrentBlockCoinbase() public view returns (address coinbase) { - coinbase = block.coinbase; - } - - /// @notice Returns the block difficulty - function getCurrentBlockDifficulty() public view returns (uint256 difficulty) { - difficulty = block.difficulty; - } - - /// @notice Returns the block gas limit - function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) { - gaslimit = block.gaslimit; - } - - /// @notice Returns the block timestamp - function getCurrentBlockTimestamp() public view returns (uint256 timestamp) { - timestamp = block.timestamp; - } - - /// @notice Returns the (ETH) balance of a given address - function getEthBalance(address addr) public view returns (uint256 balance) { - balance = addr.balance; - } - - /// @notice Returns the block hash of the last block - function getLastBlockHash() public view returns (bytes32 blockHash) { - unchecked { - blockHash = blockhash(block.number - 1); - } - } - - /// @notice Gets the base fee of the given block - /// @notice Can revert if the BASEFEE opcode is not implemented by the given chain - function getBasefee() public view returns (uint256 basefee) { - basefee = block.basefee; - } - - /// @notice Returns the chain id - function getChainId() public view returns (uint256 chainid) { - chainid = block.chainid; - } -} diff --git a/contracts/external/uma/core/contracts/common/implementation/Stakeable.sol b/contracts/external/uma/core/contracts/common/implementation/Stakeable.sol deleted file mode 100644 index 718ebea94..000000000 --- a/contracts/external/uma/core/contracts/common/implementation/Stakeable.sol +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Stakeable contract. - */ - -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../data-verification-mechanism/interfaces/StakerInterface.sol"; -import "./Withdrawable.sol"; - -/** - * @title Base contract that extends the Withdrawable contract enabling a specific role to stake ERC20 tokens against the - * Voting contract. Voting contract is fed in as a param rather than fetched from the finder to enable upgradability. - */ -abstract contract Stakeable is Withdrawable { - using SafeERC20 for IERC20; - - uint256 private roleId; - - /** - * @notice Stake ERC20 tokens from this contract to the votingContract. - * @param amount amount of tokens to stake. - * @param votingContract Address of the voting contract to stake into. - */ - function stake(uint128 amount, address votingContract) external onlyRoleHolder(roleId) { - StakerInterface voting = StakerInterface(votingContract); - IERC20 votingToken = IERC20(voting.votingToken()); - votingToken.approve(votingContract, amount); - voting.stake(amount); - } - - /** - * @notice Request unstaking of ERC20 tokens from this contract to the votingContract. - * @param amount amount of tokens to unstake. - * @param votingContract Address of the voting contract to unstake from. - */ - function requestUnstake(uint128 amount, address votingContract) external onlyRoleHolder(roleId) { - StakerInterface voting = StakerInterface(votingContract); - voting.requestUnstake(amount); - } - - /** - * @notice Execute an unstake request that has passed liveness on the voting contract. - * @param votingContract Address of the voting contract to execute the unstake from. - */ - function executeUnstake(address votingContract) external onlyRoleHolder(roleId) { - StakerInterface voting = StakerInterface(votingContract); - voting.executeUnstake(); - } - - /** - * @notice Internal method that allows derived contracts to choose the role for stakeable. - * @dev The role `setRoleId` must exist. Either this method or `_setStakeRole` must be - * called by the derived class for this contract to function properly. - * @param setRoleId ID corresponding to role whose members can stakeable. - */ - function _setStakeRole(uint256 setRoleId) internal onlyValidRole(setRoleId) { - roleId = setRoleId; - } -} diff --git a/contracts/external/uma/core/contracts/common/implementation/TestnetERC20.sol b/contracts/external/uma/core/contracts/common/implementation/TestnetERC20.sol deleted file mode 100644 index 19bc497ea..000000000 --- a/contracts/external/uma/core/contracts/common/implementation/TestnetERC20.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/token/ERC20/ERC20.sol"; - -/** - * @title An implementation of ERC20 with the same interface as the Compound project's testnet tokens (mainly DAI) - * @dev This contract can be deployed or the interface can be used to communicate with Compound's ERC20 tokens. Note: - * this token should never be used to store real value since it allows permissionless minting. - */ - -contract TestnetERC20 is ERC20 { - uint8 _decimals; - - /** - * @notice Constructs the TestnetERC20. - * @param _name The name which describes the new token. - * @param _symbol The ticker abbreviation of the name. Ideally < 5 chars. - * @param _tokenDecimals The number of decimals to define token precision. - */ - constructor(string memory _name, string memory _symbol, uint8 _tokenDecimals) ERC20(_name, _symbol) { - _decimals = _tokenDecimals; - } - - function decimals() public view virtual override(ERC20) returns (uint8) { - return _decimals; - } - - // Sample token information. - - /** - * @notice Mints value tokens to the owner address. - * @param ownerAddress the address to mint to. - * @param value the amount of tokens to mint. - */ - function allocateTo(address ownerAddress, uint256 value) external { - _mint(ownerAddress, value); - } -} diff --git a/contracts/external/uma/core/contracts/common/implementation/Withdrawable.sol b/contracts/external/uma/core/contracts/common/implementation/Withdrawable.sol deleted file mode 100644 index cca2acc1c..000000000 --- a/contracts/external/uma/core/contracts/common/implementation/Withdrawable.sol +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Withdrawable contract. - */ - -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/utils/Address.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; - -import "./MultiRole.sol"; - -/** - * @title Base contract that allows a specific role to withdraw any ETH and/or ERC20 tokens that the contract holds. - */ -abstract contract Withdrawable is MultiRole { - using SafeERC20 for IERC20; - - uint256 private roleId; - - /** - * @notice Withdraws ETH from the contract. - */ - function withdraw(uint256 amount) external onlyRoleHolder(roleId) { - Address.sendValue(payable(msg.sender), amount); - } - - /** - * @notice Withdraws ERC20 tokens from the contract. - * @param erc20Address ERC20 token to withdraw. - * @param amount amount of tokens to withdraw. - */ - function withdrawErc20(address erc20Address, uint256 amount) external onlyRoleHolder(roleId) { - IERC20 erc20 = IERC20(erc20Address); - erc20.safeTransfer(msg.sender, amount); - } - - /** - * @notice Internal method that allows derived contracts to create a role for withdrawal. - * @dev Either this method or `_setWithdrawRole` must be called by the derived class for this contract to function - * properly. - * @param newRoleId ID corresponding to role whose members can withdraw. - * @param managingRoleId ID corresponding to managing role who can modify the withdrawable role's membership. - * @param withdrawerAddress new manager of withdrawable role. - */ - function _createWithdrawRole(uint256 newRoleId, uint256 managingRoleId, address withdrawerAddress) internal { - roleId = newRoleId; - _createExclusiveRole(newRoleId, managingRoleId, withdrawerAddress); - } - - /** - * @notice Internal method that allows derived contracts to choose the role for withdrawal. - * @dev The role `setRoleId` must exist. Either this method or `_createWithdrawRole` must be - * called by the derived class for this contract to function properly. - * @param setRoleId ID corresponding to role whose members can withdraw. - */ - function _setWithdrawRole(uint256 setRoleId) internal onlyValidRole(setRoleId) { - roleId = setRoleId; - } -} diff --git a/contracts/external/uma/core/contracts/common/implementation/dsproxy/DSGuardFactory.sol b/contracts/external/uma/core/contracts/common/implementation/dsproxy/DSGuardFactory.sol deleted file mode 100644 index 395d88eb8..000000000 --- a/contracts/external/uma/core/contracts/common/implementation/dsproxy/DSGuardFactory.sol +++ /dev/null @@ -1,123 +0,0 @@ -// This file was originally taken from dapphub DSGuard and modified. Original source code can be found -// here: https://github.com/dapphub/ds-guard/blob/master/src/guard.sol -// Changes are limited to updating the Solidity version and some stylistic modifications. - -// guard.sol -- simple whitelist implementation of DSAuthority - -// Copyright (C) 2017 DappHub, LLC - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -pragma solidity ^0.8.0; - -abstract contract DSAuthority { - function canCall(address src, address dst, bytes4 sig) public view virtual returns (bool); -} - -contract DSAuthEvents { - event LogSetAuthority(address indexed authority); - event LogSetOwner(address indexed owner); -} - -contract DSAuth is DSAuthEvents { - DSAuthority public authority; - address public owner; - - constructor() { - owner = msg.sender; - emit LogSetOwner(msg.sender); - } - - function setOwner(address owner_) public auth { - owner = owner_; - emit LogSetOwner(owner); - } - - function setAuthority(DSAuthority authority_) public auth { - authority = authority_; - emit LogSetAuthority(address(authority)); - } - - modifier auth() { - require(isAuthorized(msg.sender, msg.sig)); - _; - } - - function isAuthorized(address src, bytes4 sig) internal view returns (bool) { - if (src == address(this)) { - return true; - } else if (src == owner) { - return true; - } else if (authority == DSAuthority(address(0))) { - return false; - } else { - return authority.canCall(src, address(this), sig); - } - } -} - -contract DSGuardEvents { - event LogPermit(bytes32 indexed src, bytes32 indexed dst, bytes32 indexed sig); - - event LogForbid(bytes32 indexed src, bytes32 indexed dst, bytes32 indexed sig); -} - -contract DSGuard is DSAuth, DSAuthority, DSGuardEvents { - bytes32 public constant ANY = bytes32(type(uint256).max); - - mapping(bytes32 => mapping(bytes32 => mapping(bytes32 => bool))) acl; - - function canCall(address src_, address dst_, bytes4 sig) public view override returns (bool) { - bytes32 src = bytes32(bytes20(src_)); - bytes32 dst = bytes32(bytes20(dst_)); - - return - acl[src][dst][sig] || - acl[src][dst][ANY] || - acl[src][ANY][sig] || - acl[src][ANY][ANY] || - acl[ANY][dst][sig] || - acl[ANY][dst][ANY] || - acl[ANY][ANY][sig] || - acl[ANY][ANY][ANY]; - } - - function permit(bytes32 src, bytes32 dst, bytes32 sig) public auth { - acl[src][dst][sig] = true; - emit LogPermit(src, dst, sig); - } - - function forbid(bytes32 src, bytes32 dst, bytes32 sig) public auth { - acl[src][dst][sig] = false; - emit LogForbid(src, dst, sig); - } - - function permit(address src, address dst, bytes32 sig) public { - permit(bytes32(bytes20(src)), bytes32(bytes20(dst)), sig); - } - - function forbid(address src, address dst, bytes32 sig) public { - forbid(bytes32(bytes20(src)), bytes32(bytes20(dst)), sig); - } -} - -contract DSGuardFactory { - mapping(address => bool) public isGuard; - - function newGuard() public returns (DSGuard guard) { - guard = new DSGuard(); - guard.setOwner(msg.sender); - isGuard[address(guard)] = true; - } -} diff --git a/contracts/external/uma/core/contracts/common/implementation/dsproxy/DSProxyFactory.sol b/contracts/external/uma/core/contracts/common/implementation/dsproxy/DSProxyFactory.sol deleted file mode 100644 index 97261b838..000000000 --- a/contracts/external/uma/core/contracts/common/implementation/dsproxy/DSProxyFactory.sol +++ /dev/null @@ -1,195 +0,0 @@ -// This file was originally taken from the Maker DS Proxy Factory and modified. The original source code can be found -// here: https://etherscan.io/address/0xa26e15c895efc0616177b7c1e7270a4c7d51c997#code -// Changes are limited to updating the Solidity version and some stylistic modifications. - -// proxy.sol - execute actions atomically through the proxy's identity - -// Copyright (C) 2017 DappHub, LLC - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -pragma solidity ^0.8.0; - -abstract contract DSAuthority { - function canCall(address src, address dst, bytes4 sig) public view virtual returns (bool); -} - -contract DSAuthEvents { - event LogSetAuthority(address indexed authority); - event LogSetOwner(address indexed owner); -} - -contract DSAuth is DSAuthEvents { - DSAuthority public authority; - address public owner; - - constructor() { - owner = msg.sender; - emit LogSetOwner(msg.sender); - } - - function setOwner(address owner_) public auth { - owner = owner_; - emit LogSetOwner(owner); - } - - function setAuthority(DSAuthority authority_) public auth { - authority = authority_; - emit LogSetAuthority(address(authority)); - } - - modifier auth() { - require(isAuthorized(msg.sender, msg.sig)); - _; - } - - function isAuthorized(address src, bytes4 sig) internal view returns (bool) { - if (src == address(this)) { - return true; - } else if (src == owner) { - return true; - } else if (authority == DSAuthority(address(0))) { - return false; - } else { - return authority.canCall(src, address(this), sig); - } - } -} - -contract DSNote { - event LogNote( - bytes4 indexed sig, - address indexed guy, - bytes32 indexed foo, - bytes32 indexed bar, - bytes fax - ) anonymous; - - modifier note() { - bytes32 foo; - bytes32 bar; - - assembly { - foo := calldataload(4) - bar := calldataload(36) - } - - emit LogNote(msg.sig, msg.sender, foo, bar, msg.data); - - _; - } -} - -// DSProxy -// Allows code execution using a persistant identity This can be very -// useful to execute a sequence of atomic actions. Since the owner of -// the proxy can be changed, this allows for dynamic ownership models -// i.e. a multisig -contract DSProxy is DSAuth, DSNote { - DSProxyCache public cache; // global cache for contracts - - constructor(address _cacheAddr) { - require(setCache(_cacheAddr)); - } - - fallback() external payable {} - - // use the proxy to execute calldata _data on contract _code - function execute(bytes memory _code, bytes memory _data) public payable returns (address target, bytes32 response) { - target = cache.read(_code); - if (target == address(0x0)) { - // deploy contract & store its address in cache - target = cache.write(_code); - } - - response = execute(target, _data); - } - - function execute(address _target, bytes memory _data) public payable auth note returns (bytes32 response) { - require(_target != address(0x0), "Target cant be 0x address"); - - // call contract in current context - assembly { - let succeeded := delegatecall(sub(gas(), 5000), _target, add(_data, 0x20), mload(_data), 0, 32) - response := mload(0) // load delegatecall output - switch iszero(succeeded) - case 1 { - // throw if delegatecall failed - revert(0, 0) - } - } - } - - //set new cache - function setCache(address _cacheAddr) public payable auth note returns (bool) { - require(_cacheAddr != address(0x0)); // invalid cache address - cache = DSProxyCache(_cacheAddr); // overwrite cache - return true; - } -} - -// DSProxyFactory -// This factory deploys new proxy instances through build() -// Deployed proxy addresses are logged -contract DSProxyFactory { - event Created(address indexed sender, address indexed owner, address proxy, address cache); - mapping(address => bool) public isProxy; - DSProxyCache public cache = new DSProxyCache(); - - // deploys a new proxy instance - // sets owner of proxy to caller - function build() public returns (DSProxy proxy) { - proxy = build(msg.sender); - } - - // deploys a new proxy instance - // sets custom owner of proxy - function build(address owner) public returns (DSProxy proxy) { - proxy = new DSProxy(address(cache)); - emit Created(msg.sender, owner, address(proxy), address(cache)); - proxy.setOwner(owner); - isProxy[address(proxy)] = true; - } -} - -// DSProxyCache -// This global cache stores addresses of contracts previously deployed -// by a proxy. This saves gas from repeat deployment of the same -// contracts and eliminates blockchain bloat. - -// By default, all proxies deployed from the same factory store -// contracts in the same cache. The cache a proxy instance uses can be -// changed. The cache uses the sha3 hash of a contract's bytecode to -// lookup the address -contract DSProxyCache { - mapping(bytes32 => address) cache; - - function read(bytes memory _code) public view returns (address) { - bytes32 hash = keccak256(_code); - return cache[hash]; - } - - function write(bytes memory _code) public returns (address target) { - assembly { - target := create(0, add(_code, 0x20), mload(_code)) - switch iszero(extcodesize(target)) - case 1 { - // throw if contract failed to deploy - revert(0, 0) - } - } - bytes32 hash = keccak256(_code); - cache[hash] = target; - } -} diff --git a/contracts/external/uma/core/contracts/common/interfaces/Balancer.sol b/contracts/external/uma/core/contracts/common/interfaces/Balancer.sol deleted file mode 100644 index 5b8cdb313..000000000 --- a/contracts/external/uma/core/contracts/common/interfaces/Balancer.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -/** - * @title Interface for Balancer. - * @dev This only contains the methods/events that we use in our contracts or offchain infrastructure. - */ -abstract contract Balancer { - function getSpotPriceSansFee(address tokenIn, address tokenOut) external view virtual returns (uint256 spotPrice); -} diff --git a/contracts/external/uma/core/contracts/common/interfaces/HarvestVaultInterface.sol b/contracts/external/uma/core/contracts/common/interfaces/HarvestVaultInterface.sol deleted file mode 100644 index 95eda77e8..000000000 --- a/contracts/external/uma/core/contracts/common/interfaces/HarvestVaultInterface.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; - -/** - * @title Interface for Harvest-style vaults. - * @dev This only contains the methods/events that we use in our contracts or offchain infrastructure. - */ -abstract contract HarvestVaultInterface { - // Return the underlying token. - function underlying() external view virtual returns (IERC20); - - // Gets the number of return tokens that a "share" of this vault is worth. - function getPricePerFullShare() external view virtual returns (uint256); -} diff --git a/contracts/external/uma/core/contracts/common/interfaces/IERC20Standard.sol b/contracts/external/uma/core/contracts/common/interfaces/IERC20Standard.sol deleted file mode 100644 index 07d523e73..000000000 --- a/contracts/external/uma/core/contracts/common/interfaces/IERC20Standard.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; - -/** - * @title ERC20 interface that includes the decimals read only method. - */ -interface IERC20Standard is IERC20 { - /** - * @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 {_setupDecimals} is called. - * - * 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() external view returns (uint8); -} diff --git a/contracts/external/uma/core/contracts/common/interfaces/Multicall.sol b/contracts/external/uma/core/contracts/common/interfaces/Multicall.sol deleted file mode 100644 index ddc44ba6e..000000000 --- a/contracts/external/uma/core/contracts/common/interfaces/Multicall.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity >=0.5.0; - -/** - * @title interface for MakerDao's Multicall contract. - * @dev This only contains the methods/events that we use in our contracts or offchain infrastructure. - */ -contract Multicall { - struct Call { - address target; - bytes callData; - } - - function aggregate(Call[] memory calls) public virtual returns (uint256 blockNumber, bytes[] memory returnData) {} -} diff --git a/contracts/external/uma/core/contracts/common/interfaces/Multicall2.sol b/contracts/external/uma/core/contracts/common/interfaces/Multicall2.sol deleted file mode 100644 index b93296d7e..000000000 --- a/contracts/external/uma/core/contracts/common/interfaces/Multicall2.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity >=0.5.0; - -/** - * @title interface for MakerDao's Multicall2 contract. - * @dev This adds method to allow calls within the batch to fail - * @dev Full implementation can be found here: https://github.com/makerdao/multicall/blob/16ec5e2859b3a4829ceed4ee1ef609e6e9a744ee/src/Multicall2.sol - */ -abstract contract Multicall2 { - struct Call { - address target; - bytes callData; - } - struct Result { - bool success; - bytes returnData; - } - - function aggregate(Call[] memory calls) public virtual returns (uint256 blockNumber, bytes[] memory returnData); - - function tryBlockAndAggregate( - bool requireSuccess, - Call[] memory calls - ) public virtual returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData); -} diff --git a/contracts/external/uma/core/contracts/common/interfaces/TransactionBatcher.sol b/contracts/external/uma/core/contracts/common/interfaces/TransactionBatcher.sol deleted file mode 100644 index 4aa32d8b3..000000000 --- a/contracts/external/uma/core/contracts/common/interfaces/TransactionBatcher.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -// This is an interface to interact with a deployed implementation by https://github.com/kleros/action-callback-bots for -// batching on-chain transactions. -// See deployed implementation here: https://etherscan.io/address/0x82458d1c812d7c930bb3229c9e159cbabd9aa8cb. -abstract contract TransactionBatcher { - function batchSend(address[] memory targets, uint256[] memory values, bytes[] memory datas) public payable virtual; -} diff --git a/contracts/external/uma/core/contracts/common/interfaces/UniswapV2.sol b/contracts/external/uma/core/contracts/common/interfaces/UniswapV2.sol deleted file mode 100644 index 13a0b98c7..000000000 --- a/contracts/external/uma/core/contracts/common/interfaces/UniswapV2.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -/** - * @title Interface for Uniswap v2. - * @dev This only contains the methods/events that we use in our contracts or offchain infrastructure. - */ -abstract contract UniswapV2 { - // Called after every swap showing the new uniswap "price" for this token pair. - event Sync(uint112 reserve0, uint112 reserve1); - // Base currency. - address public token0; - // Quote currency. - address public token1; -} diff --git a/contracts/external/uma/core/contracts/common/interfaces/UniswapV3.sol b/contracts/external/uma/core/contracts/common/interfaces/UniswapV3.sol deleted file mode 100644 index 4055ca94d..000000000 --- a/contracts/external/uma/core/contracts/common/interfaces/UniswapV3.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -/** - * @title Interface for Uniswap v3. - * @dev This only contains the methods/events that we use in our contracts or offchain infrastructure. - */ -abstract contract UniswapV3 { - // Called after every swap showing the new uniswap price for this token pair. - event Swap( - address indexed sender, - address indexed recipient, - int256 amount0, - int256 amount1, - uint160 sqrtPriceX96, - uint128 liquidity, - int24 tick - ); - // Base currency. - address public token0; - // Quote currency. - address public token1; -} diff --git a/contracts/external/uma/core/contracts/common/interfaces/VaultInterface.sol b/contracts/external/uma/core/contracts/common/interfaces/VaultInterface.sol deleted file mode 100644 index 1f3531b06..000000000 --- a/contracts/external/uma/core/contracts/common/interfaces/VaultInterface.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; - -/** - * @title Interface for Yearn-style vaults. - * @dev This only contains the methods/events that we use in our contracts or offchain infrastructure. - */ -abstract contract VaultInterface { - // Return the underlying token. - function token() external view virtual returns (IERC20); - - // Gets the number of return tokens that a "share" of this vault is worth. - function getPricePerFullShare() external view virtual returns (uint256); -} diff --git a/contracts/external/uma/core/contracts/common/test/AncillaryDataTest.sol b/contracts/external/uma/core/contracts/common/test/AncillaryDataTest.sol deleted file mode 100644 index 834184ad9..000000000 --- a/contracts/external/uma/core/contracts/common/test/AncillaryDataTest.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../implementation/AncillaryData.sol"; - -contract AncillaryDataTest { - function toUtf8BytesAddress(address x) external pure returns (bytes memory) { - return AncillaryData.toUtf8BytesAddress(x); - } - - function toUtf8BytesUint(uint256 v) external pure returns (bytes memory) { - return AncillaryData.toUtf8BytesUint(v); - } - - function appendKeyValueAddress( - bytes memory currentAncillaryData, - bytes memory key, - address value - ) external pure returns (bytes memory) { - return AncillaryData.appendKeyValueAddress(currentAncillaryData, key, value); - } - - function appendKeyValueBytes32( - bytes memory currentAncillaryData, - bytes memory key, - bytes32 value - ) external pure returns (bytes memory) { - return AncillaryData.appendKeyValueBytes32(currentAncillaryData, key, value); - } - - function appendKeyValueUint( - bytes memory currentAncillaryData, - bytes memory key, - uint256 value - ) external pure returns (bytes memory) { - return AncillaryData.appendKeyValueUint(currentAncillaryData, key, value); - } - - function constructPrefix(bytes memory currentAncillaryData, bytes memory key) external pure returns (bytes memory) { - return AncillaryData.constructPrefix(currentAncillaryData, key); - } -} diff --git a/contracts/external/uma/core/contracts/common/test/BalancerMock.sol b/contracts/external/uma/core/contracts/common/test/BalancerMock.sol deleted file mode 100644 index 27615909f..000000000 --- a/contracts/external/uma/core/contracts/common/test/BalancerMock.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../interfaces/Balancer.sol"; - -/** - * @title Balancer Mock - */ -contract BalancerMock is Balancer { - uint256 price = 0; - - // these params arent used in the mock, but this is to maintain compatibility with balancer API - function getSpotPriceSansFee(address, address) external view virtual override returns (uint256 spotPrice) { - return price; - } - - // this is not a balancer call, but for testing for changing price. - function setPrice(uint256 newPrice) external { - price = newPrice; - } -} diff --git a/contracts/external/uma/core/contracts/common/test/BasicERC20.sol b/contracts/external/uma/core/contracts/common/test/BasicERC20.sol deleted file mode 100644 index ef9a7cf63..000000000 --- a/contracts/external/uma/core/contracts/common/test/BasicERC20.sol +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; - -/** - * @title Implements only the required ERC20 methods. This contract is used - * test how contracts handle ERC20 contracts that have not implemented `decimals()` - * @dev Mostly copied from Consensys EIP-20 implementation: - * https://github.com/ConsenSys/Tokens/blob/fdf687c69d998266a95f15216b1955a4965a0a6d/contracts/eip20/EIP20.sol - */ -contract BasicERC20 is IERC20 { - uint256 private constant MAX_UINT256 = 2 ** 256 - 1; - mapping(address => uint256) public balances; - mapping(address => mapping(address => uint256)) public allowed; - - uint256 private _totalSupply; - - constructor(uint256 _initialAmount) { - balances[msg.sender] = _initialAmount; - _totalSupply = _initialAmount; - } - - function totalSupply() public view override returns (uint256) { - return _totalSupply; - } - - function transfer(address _to, uint256 _value) public override returns (bool success) { - require(balances[msg.sender] >= _value); - balances[msg.sender] -= _value; - balances[_to] += _value; - emit Transfer(msg.sender, _to, _value); - return true; - } - - function transferFrom(address _from, address _to, uint256 _value) public override returns (bool success) { - uint256 _allowance = allowed[_from][msg.sender]; - require(balances[_from] >= _value && _allowance >= _value); - balances[_to] += _value; - balances[_from] -= _value; - if (_allowance < MAX_UINT256) { - allowed[_from][msg.sender] -= _value; - } - emit Transfer(_from, _to, _value); //solhint-disable-line indent, no-unused-vars - return true; - } - - function balanceOf(address _owner) public view override returns (uint256 balance) { - return balances[_owner]; - } - - function approve(address _spender, uint256 _value) public override returns (bool success) { - allowed[msg.sender][_spender] = _value; - emit Approval(msg.sender, _spender, _value); //solhint-disable-line indent, no-unused-vars - return true; - } - - function allowance(address _owner, address _spender) public view override returns (uint256 remaining) { - return allowed[_owner][_spender]; - } -} diff --git a/contracts/external/uma/core/contracts/common/test/HarvestVaultMock.sol b/contracts/external/uma/core/contracts/common/test/HarvestVaultMock.sol deleted file mode 100644 index 473eb9b80..000000000 --- a/contracts/external/uma/core/contracts/common/test/HarvestVaultMock.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../interfaces/HarvestVaultInterface.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; - -/** - * @title Mock for Harvest-style vaults for use in tests. - */ -contract HarvestVaultMock is HarvestVaultInterface { - IERC20 public override underlying; - uint256 private pricePerFullShare = 0; - - constructor(IERC20 _underlying) { - underlying = _underlying; - } - - function getPricePerFullShare() external view override returns (uint256) { - return pricePerFullShare; - } - - function setPricePerFullShare(uint256 _pricePerFullShare) external { - pricePerFullShare = _pricePerFullShare; - } -} diff --git a/contracts/external/uma/core/contracts/common/test/MintableERC721.sol b/contracts/external/uma/core/contracts/common/test/MintableERC721.sol deleted file mode 100644 index e7655faac..000000000 --- a/contracts/external/uma/core/contracts/common/test/MintableERC721.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/token/ERC721/ERC721.sol"; - -// Test ERC721 contract that allows free minting -contract MintableERC721 is ERC721 { - constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {} - - function mint(address to, uint256 tokenId) public { - _safeMint(to, tokenId); - } -} diff --git a/contracts/external/uma/core/contracts/common/test/MultiCallerTest.sol b/contracts/external/uma/core/contracts/common/test/MultiCallerTest.sol deleted file mode 100644 index 9007ef898..000000000 --- a/contracts/external/uma/core/contracts/common/test/MultiCallerTest.sol +++ /dev/null @@ -1,15 +0,0 @@ -pragma solidity ^0.8.0; - -import "../implementation/MultiCaller.sol"; - -contract MultiCallerTest is MultiCaller { - uint256 public value; - - function call(bool shouldFail) public pure { - require(shouldFail, "shouldFail set to true"); - } - - function add(uint256 amount) public { - value += amount; - } -} diff --git a/contracts/external/uma/core/contracts/common/test/MultiRoleTest.sol b/contracts/external/uma/core/contracts/common/test/MultiRoleTest.sol deleted file mode 100644 index 5fdaf9cf7..000000000 --- a/contracts/external/uma/core/contracts/common/test/MultiRoleTest.sol +++ /dev/null @@ -1,22 +0,0 @@ -/* - MultiRoleTest contract. -*/ - -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../implementation/MultiRole.sol"; - -// The purpose of this contract is to make the MultiRole creation methods externally callable for testing purposes. -contract MultiRoleTest is MultiRole { - function createSharedRole(uint256 roleId, uint256 managingRoleId, address[] calldata initialMembers) external { - _createSharedRole(roleId, managingRoleId, initialMembers); - } - - function createExclusiveRole(uint256 roleId, uint256 managingRoleId, address initialMember) external { - _createExclusiveRole(roleId, managingRoleId, initialMember); - } - - // solhint-disable-next-line no-empty-blocks - function revertIfNotHoldingRole(uint256 roleId) external view onlyRoleHolder(roleId) {} -} diff --git a/contracts/external/uma/core/contracts/common/test/MulticallMock.sol b/contracts/external/uma/core/contracts/common/test/MulticallMock.sol deleted file mode 100644 index 38ad2ae8e..000000000 --- a/contracts/external/uma/core/contracts/common/test/MulticallMock.sol +++ /dev/null @@ -1,23 +0,0 @@ -pragma solidity >=0.5.0; - -/// @title Multicall - Aggregate results from multiple read-only function calls -/// @author Michael Elliot -/// @author Joshua Levine -/// @author Nick Johnson - -contract MulticallMock { - struct Call { - address target; - bytes callData; - } - - function aggregate(Call[] memory calls) public returns (uint256 blockNumber, bytes[] memory returnData) { - blockNumber = block.number; - returnData = new bytes[](calls.length); - for (uint256 i = 0; i < calls.length; i++) { - (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData); - require(success); - returnData[i] = ret; - } - } -} diff --git a/contracts/external/uma/core/contracts/common/test/PerpetualMock.sol b/contracts/external/uma/core/contracts/common/test/PerpetualMock.sol deleted file mode 100644 index b7929235b..000000000 --- a/contracts/external/uma/core/contracts/common/test/PerpetualMock.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/FixedPoint.sol"; - -/** - * @title Simple Perpetual Mock to serve trivial functions - */ -contract PerpetualMock { - struct FundingRate { - FixedPoint.Signed rate; - bytes32 identifier; - FixedPoint.Unsigned cumulativeMultiplier; - uint256 updateTime; - uint256 applicationTime; - uint256 proposalTime; - } - - using FixedPoint for FixedPoint.Unsigned; - using FixedPoint for FixedPoint.Signed; - - FundingRate public fundingRate; - - // Interface functions required to be implemented in order for an instance of this contract to be passed into the - // off-chain FinancialContractClient helper module: - FixedPoint.Unsigned public collateralRequirement; - uint256 public liquidationLiveness; - FixedPoint.Unsigned public cumulativeFeeMultiplier; - mapping(address => uint256) public positions; - mapping(address => uint256) public liquidations; - event NewSponsor(address indexed sponsor); - event EndedSponsorPosition(); - event LiquidationCreated(); - - function getCurrentTime() public view returns (uint256) { - return block.timestamp; - } - - // Public methods that are useful for tests: - function setFundingRate(FundingRate memory _fundingRate) external { - fundingRate = _fundingRate; - } - - function applyFundingRate() external { - fundingRate.applicationTime = block.timestamp; - // Simplified rate calcualtion. - // multiplier = multiplier * (1 + rate) - fundingRate.cumulativeMultiplier = fundingRate.cumulativeMultiplier.mul( - FixedPoint.fromSigned(FixedPoint.fromUnscaledInt(1).add(fundingRate.rate)) - ); - } -} diff --git a/contracts/external/uma/core/contracts/common/test/ReentrancyAttack.sol b/contracts/external/uma/core/contracts/common/test/ReentrancyAttack.sol deleted file mode 100644 index b6818a621..000000000 --- a/contracts/external/uma/core/contracts/common/test/ReentrancyAttack.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -// Tests reentrancy guards defined in Lockable.sol. -// Copied from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.1/contracts/mocks/ReentrancyAttack.sol. -contract ReentrancyAttack { - function callSender(bytes4 data) public { - // solhint-disable-next-line avoid-low-level-calls - (bool success, ) = msg.sender.call(abi.encodeWithSelector(data)); - require(success, "ReentrancyAttack: failed call"); - } -} diff --git a/contracts/external/uma/core/contracts/common/test/ReentrancyChecker.sol b/contracts/external/uma/core/contracts/common/test/ReentrancyChecker.sol deleted file mode 100644 index e844ced84..000000000 --- a/contracts/external/uma/core/contracts/common/test/ReentrancyChecker.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -// The Reentrancy Checker causes failures if it is successfully able to re-enter a contract. -// How to use: -// 1. Call setTransactionData with the transaction data you want the Reentrancy Checker to reenter the calling -// contract with. -// 2. Get the calling contract to call into the reentrancy checker with any call. The fallback function will receive -// this call and reenter the contract with the transaction data provided in 1. If that reentrancy call does not -// revert, then the reentrancy checker reverts the initial call, likely causeing the entire transaction to revert. -// -// Note: the reentrancy checker has a guard to prevent an infinite cycle of reentrancy. Inifinite cycles will run out -// of gas in all cases, potentially causing a revert when the contract is adequately protected from reentrancy. -contract ReentrancyChecker { - bytes public txnData; - bool hasBeenCalled; - - // Used to prevent infinite cycles where the reentrancy is cycled forever. - modifier skipIfReentered() { - if (hasBeenCalled) { - return; - } - hasBeenCalled = true; - _; - hasBeenCalled = false; - } - - function setTransactionData(bytes memory _txnData) public { - txnData = _txnData; - } - - function _executeCall(address to, uint256 value, bytes memory data) private returns (bool success) { - // Mostly copied from: - // solhint-disable-next-line max-line-length - // https://github.com/gnosis/safe-contracts/blob/59cfdaebcd8b87a0a32f87b50fead092c10d3a05/contracts/base/Executor.sol#L23-L31 - // solhint-disable-next-line no-inline-assembly - - assembly { - let inputData := add(data, 0x20) - let inputDataSize := mload(data) - success := call(gas(), to, value, inputData, inputDataSize, 0, 0) - } - } - - fallback() external skipIfReentered { - // Attampt to re-enter with the set txnData. - bool success = _executeCall(msg.sender, 0, txnData); - - // Fail if the call succeeds because that means the re-entrancy was successful. - require(!success, "Re-entrancy was successful"); - } -} diff --git a/contracts/external/uma/core/contracts/common/test/ReentrancyMock.sol b/contracts/external/uma/core/contracts/common/test/ReentrancyMock.sol deleted file mode 100644 index 2ece3ee4d..000000000 --- a/contracts/external/uma/core/contracts/common/test/ReentrancyMock.sol +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../implementation/Lockable.sol"; -import "./ReentrancyAttack.sol"; - -// Tests reentrancy guards defined in Lockable.sol. -// Extends https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.1/contracts/mocks/ReentrancyMock.sol. -contract ReentrancyMock is Lockable { - uint256 public counter; - - constructor() { - counter = 0; - } - - function callback() external nonReentrant { - _count(); - } - - function countAndSend(ReentrancyAttack attacker) external nonReentrant { - _count(); - bytes4 func = bytes4(keccak256("callback()")); - attacker.callSender(func); - } - - function countAndCall(ReentrancyAttack attacker) external nonReentrant { - _count(); - bytes4 func = bytes4(keccak256("getCount()")); - attacker.callSender(func); - } - - function countLocalRecursive(uint256 n) public nonReentrant { - if (n > 0) { - _count(); - countLocalRecursive(n - 1); - } - } - - function countThisRecursive(uint256 n) public nonReentrant { - if (n > 0) { - _count(); - // solhint-disable-next-line avoid-low-level-calls - (bool success, ) = address(this).call(abi.encodeWithSignature("countThisRecursive(uint256)", n - 1)); - require(success, "ReentrancyMock: failed call"); - } - } - - function countLocalCall() public nonReentrant { - getCount(); - } - - function countThisCall() public nonReentrant { - // solhint-disable-next-line avoid-low-level-calls - (bool success, ) = address(this).call(abi.encodeWithSignature("getCount()")); - require(success, "ReentrancyMock: failed call"); - } - - function getCount() public view nonReentrantView returns (uint256) { - return counter; - } - - function _count() private { - counter += 1; - } -} diff --git a/contracts/external/uma/core/contracts/common/test/SignedFixedPointTest.sol b/contracts/external/uma/core/contracts/common/test/SignedFixedPointTest.sol deleted file mode 100644 index e6148c33a..000000000 --- a/contracts/external/uma/core/contracts/common/test/SignedFixedPointTest.sol +++ /dev/null @@ -1,154 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../implementation/FixedPoint.sol"; - -// Wraps the FixedPoint library for testing purposes. -contract SignedFixedPointTest { - using FixedPoint for FixedPoint.Signed; - using FixedPoint for int256; - using SafeMath for int256; - - function wrapFromSigned(int256 a) external pure returns (uint256) { - return FixedPoint.fromSigned(FixedPoint.Signed(a)).rawValue; - } - - function wrapFromUnsigned(uint256 a) external pure returns (int256) { - return FixedPoint.fromUnsigned(FixedPoint.Unsigned(a)).rawValue; - } - - function wrapFromUnscaledInt(int256 a) external pure returns (int256) { - return FixedPoint.fromUnscaledInt(a).rawValue; - } - - function wrapIsEqual(int256 a, int256 b) external pure returns (bool) { - return FixedPoint.Signed(a).isEqual(FixedPoint.Signed(b)); - } - - function wrapMixedIsEqual(int256 a, int256 b) external pure returns (bool) { - return FixedPoint.Signed(a).isEqual(b); - } - - function wrapIsGreaterThan(int256 a, int256 b) external pure returns (bool) { - return FixedPoint.Signed(a).isGreaterThan(FixedPoint.Signed(b)); - } - - function wrapIsGreaterThanOrEqual(int256 a, int256 b) external pure returns (bool) { - return FixedPoint.Signed(a).isGreaterThanOrEqual(FixedPoint.Signed(b)); - } - - function wrapMixedIsGreaterThan(int256 a, int256 b) external pure returns (bool) { - return FixedPoint.Signed(a).isGreaterThan(b); - } - - function wrapMixedIsGreaterThanOrEqual(int256 a, int256 b) external pure returns (bool) { - return FixedPoint.Signed(a).isGreaterThanOrEqual(b); - } - - function wrapMixedIsGreaterThanOpposite(int256 a, int256 b) external pure returns (bool) { - return a.isGreaterThan(FixedPoint.Signed(b)); - } - - function wrapMixedIsGreaterThanOrEqualOpposite(int256 a, int256 b) external pure returns (bool) { - return a.isGreaterThanOrEqual(FixedPoint.Signed(b)); - } - - function wrapIsLessThan(int256 a, int256 b) external pure returns (bool) { - return FixedPoint.Signed(a).isLessThan(FixedPoint.Signed(b)); - } - - function wrapIsLessThanOrEqual(int256 a, int256 b) external pure returns (bool) { - return FixedPoint.Signed(a).isLessThanOrEqual(FixedPoint.Signed(b)); - } - - function wrapMixedIsLessThan(int256 a, int256 b) external pure returns (bool) { - return FixedPoint.Signed(a).isLessThan(b); - } - - function wrapMixedIsLessThanOrEqual(int256 a, int256 b) external pure returns (bool) { - return FixedPoint.Signed(a).isLessThanOrEqual(b); - } - - function wrapMixedIsLessThanOpposite(int256 a, int256 b) external pure returns (bool) { - return a.isLessThan(FixedPoint.Signed(b)); - } - - function wrapMixedIsLessThanOrEqualOpposite(int256 a, int256 b) external pure returns (bool) { - return a.isLessThanOrEqual(FixedPoint.Signed(b)); - } - - function wrapMin(int256 a, int256 b) external pure returns (int256) { - return FixedPoint.Signed(a).min(FixedPoint.Signed(b)).rawValue; - } - - function wrapMax(int256 a, int256 b) external pure returns (int256) { - return FixedPoint.Signed(a).max(FixedPoint.Signed(b)).rawValue; - } - - function wrapAdd(int256 a, int256 b) external pure returns (int256) { - return FixedPoint.Signed(a).add(FixedPoint.Signed(b)).rawValue; - } - - // The first int256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. - function wrapMixedAdd(int256 a, int256 b) external pure returns (int256) { - return FixedPoint.Signed(a).add(b).rawValue; - } - - function wrapSub(int256 a, int256 b) external pure returns (int256) { - return FixedPoint.Signed(a).sub(FixedPoint.Signed(b)).rawValue; - } - - // The first int256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. - function wrapMixedSub(int256 a, int256 b) external pure returns (int256) { - return FixedPoint.Signed(a).sub(b).rawValue; - } - - // The second int256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. - function wrapMixedSubOpposite(int256 a, int256 b) external pure returns (int256) { - return a.sub(FixedPoint.Signed(b)).rawValue; - } - - function wrapMul(int256 a, int256 b) external pure returns (int256) { - return FixedPoint.Signed(a).mul(FixedPoint.Signed(b)).rawValue; - } - - function wrapMulAwayFromZero(int256 a, int256 b) external pure returns (int256) { - return FixedPoint.Signed(a).mulAwayFromZero(FixedPoint.Signed(b)).rawValue; - } - - // The first int256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. - function wrapMixedMul(int256 a, int256 b) external pure returns (int256) { - return FixedPoint.Signed(a).mul(b).rawValue; - } - - function wrapMixedMulAwayFromZero(int256 a, int256 b) external pure returns (int256) { - return FixedPoint.Signed(a).mulAwayFromZero(b).rawValue; - } - - function wrapDiv(int256 a, int256 b) external pure returns (int256) { - return FixedPoint.Signed(a).div(FixedPoint.Signed(b)).rawValue; - } - - function wrapDivAwayFromZero(int256 a, int256 b) external pure returns (int256) { - return FixedPoint.Signed(a).divAwayFromZero(FixedPoint.Signed(b)).rawValue; - } - - // The first int256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. - function wrapMixedDiv(int256 a, int256 b) external pure returns (int256) { - return FixedPoint.Signed(a).div(b).rawValue; - } - - function wrapMixedDivAwayFromZero(int256 a, int256 b) external pure returns (int256) { - return FixedPoint.Signed(a).divAwayFromZero(b).rawValue; - } - - // The second int256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. - function wrapMixedDivOpposite(int256 a, int256 b) external pure returns (int256) { - return a.div(FixedPoint.Signed(b)).rawValue; - } - - // The first int256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. - function wrapPow(int256 a, uint256 b) external pure returns (int256) { - return FixedPoint.Signed(a).pow(b).rawValue; - } -} diff --git a/contracts/external/uma/core/contracts/common/test/TestableTest.sol b/contracts/external/uma/core/contracts/common/test/TestableTest.sol deleted file mode 100644 index 09a80d72c..000000000 --- a/contracts/external/uma/core/contracts/common/test/TestableTest.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../implementation/Testable.sol"; - -// TestableTest is derived from the abstract contract Testable for testing purposes. -contract TestableTest is Testable { - // solhint-disable-next-line no-empty-blocks - constructor(address _timerAddress) Testable(_timerAddress) {} - - function getTestableTimeAndBlockTime() external view returns (uint256 testableTime, uint256 blockTime) { - // solhint-disable-next-line not-rely-on-time - return (getCurrentTime(), block.timestamp); - } -} diff --git a/contracts/external/uma/core/contracts/common/test/UniswapV2Mock.sol b/contracts/external/uma/core/contracts/common/test/UniswapV2Mock.sol deleted file mode 100644 index d0359c309..000000000 --- a/contracts/external/uma/core/contracts/common/test/UniswapV2Mock.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../interfaces/UniswapV2.sol"; - -/** - * @title Uniswap v2 Mock that allows manual price injection. - */ -contract UniswapV2Mock is UniswapV2 { - function setTokens(address _token0, address _token1) external { - token0 = _token0; - token1 = _token1; - } - - function setPrice(uint112 reserve0, uint112 reserve1) external { - emit Sync(reserve0, reserve1); - } -} diff --git a/contracts/external/uma/core/contracts/common/test/UniswapV3Mock.sol b/contracts/external/uma/core/contracts/common/test/UniswapV3Mock.sol deleted file mode 100644 index 0e0bef183..000000000 --- a/contracts/external/uma/core/contracts/common/test/UniswapV3Mock.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../interfaces/UniswapV3.sol"; - -/** - * @title Uniswap v3 Mock that allows manual price injection. - */ -contract UniswapV3Mock is UniswapV3 { - function setTokens(address _token0, address _token1) external { - token0 = _token0; - token1 = _token1; - } - - function setPrice( - address sender, - address recipient, - int256 amount0, - int256 amount1, - uint160 sqrtPriceX96, - uint128 liquidity, - int24 tick - ) external { - emit Swap(sender, recipient, amount0, amount1, sqrtPriceX96, liquidity, tick); - } -} diff --git a/contracts/external/uma/core/contracts/common/test/UnsignedFixedPointTest.sol b/contracts/external/uma/core/contracts/common/test/UnsignedFixedPointTest.sol deleted file mode 100644 index 9230cdc81..000000000 --- a/contracts/external/uma/core/contracts/common/test/UnsignedFixedPointTest.sol +++ /dev/null @@ -1,146 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../implementation/FixedPoint.sol"; - -// Wraps the FixedPoint library for testing purposes. -contract UnsignedFixedPointTest { - using FixedPoint for FixedPoint.Unsigned; - using FixedPoint for uint256; - using SafeMath for uint256; - - function wrapFromUnscaledUint(uint256 a) external pure returns (uint256) { - return FixedPoint.fromUnscaledUint(a).rawValue; - } - - function wrapIsEqual(uint256 a, uint256 b) external pure returns (bool) { - return FixedPoint.Unsigned(a).isEqual(FixedPoint.Unsigned(b)); - } - - function wrapMixedIsEqual(uint256 a, uint256 b) external pure returns (bool) { - return FixedPoint.Unsigned(a).isEqual(b); - } - - function wrapIsGreaterThan(uint256 a, uint256 b) external pure returns (bool) { - return FixedPoint.Unsigned(a).isGreaterThan(FixedPoint.Unsigned(b)); - } - - function wrapIsGreaterThanOrEqual(uint256 a, uint256 b) external pure returns (bool) { - return FixedPoint.Unsigned(a).isGreaterThanOrEqual(FixedPoint.Unsigned(b)); - } - - function wrapMixedIsGreaterThan(uint256 a, uint256 b) external pure returns (bool) { - return FixedPoint.Unsigned(a).isGreaterThan(b); - } - - function wrapMixedIsGreaterThanOrEqual(uint256 a, uint256 b) external pure returns (bool) { - return FixedPoint.Unsigned(a).isGreaterThanOrEqual(b); - } - - function wrapMixedIsGreaterThanOpposite(uint256 a, uint256 b) external pure returns (bool) { - return a.isGreaterThan(FixedPoint.Unsigned(b)); - } - - function wrapMixedIsGreaterThanOrEqualOpposite(uint256 a, uint256 b) external pure returns (bool) { - return a.isGreaterThanOrEqual(FixedPoint.Unsigned(b)); - } - - function wrapIsLessThan(uint256 a, uint256 b) external pure returns (bool) { - return FixedPoint.Unsigned(a).isLessThan(FixedPoint.Unsigned(b)); - } - - function wrapIsLessThanOrEqual(uint256 a, uint256 b) external pure returns (bool) { - return FixedPoint.Unsigned(a).isLessThanOrEqual(FixedPoint.Unsigned(b)); - } - - function wrapMixedIsLessThan(uint256 a, uint256 b) external pure returns (bool) { - return FixedPoint.Unsigned(a).isLessThan(b); - } - - function wrapMixedIsLessThanOrEqual(uint256 a, uint256 b) external pure returns (bool) { - return FixedPoint.Unsigned(a).isLessThanOrEqual(b); - } - - function wrapMixedIsLessThanOpposite(uint256 a, uint256 b) external pure returns (bool) { - return a.isLessThan(FixedPoint.Unsigned(b)); - } - - function wrapMixedIsLessThanOrEqualOpposite(uint256 a, uint256 b) external pure returns (bool) { - return a.isLessThanOrEqual(FixedPoint.Unsigned(b)); - } - - function wrapMin(uint256 a, uint256 b) external pure returns (uint256) { - return FixedPoint.Unsigned(a).min(FixedPoint.Unsigned(b)).rawValue; - } - - function wrapMax(uint256 a, uint256 b) external pure returns (uint256) { - return FixedPoint.Unsigned(a).max(FixedPoint.Unsigned(b)).rawValue; - } - - function wrapAdd(uint256 a, uint256 b) external pure returns (uint256) { - return FixedPoint.Unsigned(a).add(FixedPoint.Unsigned(b)).rawValue; - } - - // The first uint256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. - function wrapMixedAdd(uint256 a, uint256 b) external pure returns (uint256) { - return FixedPoint.Unsigned(a).add(b).rawValue; - } - - function wrapSub(uint256 a, uint256 b) external pure returns (uint256) { - return FixedPoint.Unsigned(a).sub(FixedPoint.Unsigned(b)).rawValue; - } - - // The first uint256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. - function wrapMixedSub(uint256 a, uint256 b) external pure returns (uint256) { - return FixedPoint.Unsigned(a).sub(b).rawValue; - } - - // The second uint256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. - function wrapMixedSubOpposite(uint256 a, uint256 b) external pure returns (uint256) { - return a.sub(FixedPoint.Unsigned(b)).rawValue; - } - - function wrapMul(uint256 a, uint256 b) external pure returns (uint256) { - return FixedPoint.Unsigned(a).mul(FixedPoint.Unsigned(b)).rawValue; - } - - function wrapMulCeil(uint256 a, uint256 b) external pure returns (uint256) { - return FixedPoint.Unsigned(a).mulCeil(FixedPoint.Unsigned(b)).rawValue; - } - - // The first uint256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. - function wrapMixedMul(uint256 a, uint256 b) external pure returns (uint256) { - return FixedPoint.Unsigned(a).mul(b).rawValue; - } - - function wrapMixedMulCeil(uint256 a, uint256 b) external pure returns (uint256) { - return FixedPoint.Unsigned(a).mulCeil(b).rawValue; - } - - function wrapDiv(uint256 a, uint256 b) external pure returns (uint256) { - return FixedPoint.Unsigned(a).div(FixedPoint.Unsigned(b)).rawValue; - } - - function wrapDivCeil(uint256 a, uint256 b) external pure returns (uint256) { - return FixedPoint.Unsigned(a).divCeil(FixedPoint.Unsigned(b)).rawValue; - } - - // The first uint256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. - function wrapMixedDiv(uint256 a, uint256 b) external pure returns (uint256) { - return FixedPoint.Unsigned(a).div(b).rawValue; - } - - function wrapMixedDivCeil(uint256 a, uint256 b) external pure returns (uint256) { - return FixedPoint.Unsigned(a).divCeil(b).rawValue; - } - - // The second uint256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. - function wrapMixedDivOpposite(uint256 a, uint256 b) external pure returns (uint256) { - return a.div(FixedPoint.Unsigned(b)).rawValue; - } - - // The first uint256 is interpreted with a scaling factor and is converted to an `Unsigned` directly. - function wrapPow(uint256 a, uint256 b) external pure returns (uint256) { - return FixedPoint.Unsigned(a).pow(b).rawValue; - } -} diff --git a/contracts/external/uma/core/contracts/common/test/VaultMock.sol b/contracts/external/uma/core/contracts/common/test/VaultMock.sol deleted file mode 100644 index e4def688e..000000000 --- a/contracts/external/uma/core/contracts/common/test/VaultMock.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../interfaces/VaultInterface.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; - -/** - * @title Mock for yearn-style vaults for use in tests. - */ -contract VaultMock is VaultInterface { - IERC20 public override token; - uint256 private pricePerFullShare = 0; - - constructor(IERC20 _token) { - token = _token; - } - - function getPricePerFullShare() external view override returns (uint256) { - return pricePerFullShare; - } - - function setPricePerFullShare(uint256 _pricePerFullShare) external { - pricePerFullShare = _pricePerFullShare; - } -} diff --git a/contracts/external/uma/core/contracts/common/test/WithdrawableTest.sol b/contracts/external/uma/core/contracts/common/test/WithdrawableTest.sol deleted file mode 100644 index d98167aea..000000000 --- a/contracts/external/uma/core/contracts/common/test/WithdrawableTest.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../implementation/Withdrawable.sol"; - -// WithdrawableTest is derived from the abstract contract Withdrawable for testing purposes. -contract WithdrawableTest is Withdrawable { - enum Roles { - Governance, - Withdraw - } - - // solhint-disable-next-line no-empty-blocks - constructor() { - _createExclusiveRole(uint256(Roles.Governance), uint256(Roles.Governance), msg.sender); - _createWithdrawRole(uint256(Roles.Withdraw), uint256(Roles.Governance), msg.sender); - } - - function pay() external payable { - require(msg.value > 0); - } - - function setInternalWithdrawRole(uint256 setRoleId) public { - _setWithdrawRole(setRoleId); - } -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/AncillaryDataCompression.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/AncillaryDataCompression.sol deleted file mode 100644 index 359e3d62f..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/AncillaryDataCompression.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import { AncillaryData } from "../common/implementation/AncillaryData.sol"; - -/** - * @title Library for compressing ancillary data when bridging DVM price requests to mainnet. - * @notice This provides internal method for origin chain oracles to compress ancillary data by replacing it with the - * hash of the original ancillary data and adding additional information to track back the original ancillary data on - * mainnet. - */ -library AncillaryDataCompression { - /** - * @notice Compresses ancillary data by providing sufficient information to track back the original ancillary data - * on mainnet. - * @dev The compression replaces original ancillary data with its hash and adds address of origin chain oracle and - * block number so that it is more efficient to fetch original ancillary data from PriceRequestBridged event on - * origin chain indexed by parentRequestId. This parentRequestId can be reconstructed by taking keccak256 hash of - * ABI encoded price identifier, time and compressed ancillary data. - * @param ancillaryData original ancillary data to be processed. - * @param requester address of the requester who initiated the price request. - * @param requestBlockNumber block number when the price request was initiated. - * @return compressed ancillary data. - */ - function compress( - bytes memory ancillaryData, - address requester, - uint256 requestBlockNumber - ) internal view returns (bytes memory) { - return - AncillaryData.appendKeyValueUint( - AncillaryData.appendKeyValueAddress( - AncillaryData.appendKeyValueAddress( - AncillaryData.appendKeyValueUint( - AncillaryData.appendKeyValueBytes32("", "ancillaryDataHash", keccak256(ancillaryData)), - "childBlockNumber", - requestBlockNumber - ), - "childOracle", - address(this) - ), - "childRequester", - requester - ), - "childChainId", - block.chainid - ); - } -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/GovernorHub.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/GovernorHub.sol deleted file mode 100644 index 16d80dccc..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/GovernorHub.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../common/implementation/Lockable.sol"; -import "../common/implementation/MultiCaller.sol"; -import "./interfaces/ParentMessengerInterface.sol"; -import "./GovernorSpoke.sol"; -import "@openzeppelin/contracts-v4/access/Ownable.sol"; - -/** - * @title Cross-chain Oracle L1 Governor Hub. - * @notice Governance relayer contract to be deployed on Ethereum that receives messages from the owner (Governor) and - * sends them to spoke contracts on child chains. - */ - -contract GovernorHub is Ownable, Lockable, MultiCaller { - // Associates chain ID with ParentMessenger contract to use to send governance actions to that chain's GovernorSpoke - // contract. - mapping(uint256 => ParentMessengerInterface) public messengers; - - event RelayedGovernanceRequest( - uint256 indexed chainId, - address indexed messenger, - GovernorSpoke.Call[] calls, - bytes dataSentToChild - ); - event SetParentMessenger(uint256 indexed chainId, address indexed parentMessenger); - - /** - * @notice Set new ParentMessenger contract for chainId. - * @param chainId child network that messenger contract will communicate with. - * @param messenger ParentMessenger contract that sends messages to ChildMessenger on network with ID `chainId`. - * @dev Only callable by the owner (presumably the Ethereum Governor contract). - */ - function setMessenger(uint256 chainId, ParentMessengerInterface messenger) public nonReentrant onlyOwner { - messengers[chainId] = messenger; - emit SetParentMessenger(chainId, address(messenger)); - } - - /** - * @notice This should be called in order to relay a governance request to the `GovernorSpoke` contract deployed to - * the child chain associated with `chainId`. - * @param chainId network that messenger contract will communicate with - * @param calls the calls to be made by the GovernorSpoke. Should encode a `to` and `data` prop for each call. - * @dev Only callable by the owner (presumably the UMA DVM Governor contract, on L1 Ethereum). - */ - function relayGovernance(uint256 chainId, GovernorSpoke.Call[] memory calls) external nonReentrant onlyOwner { - bytes memory dataSentToChild = abi.encode(calls); - messengers[chainId].sendMessageToChild(dataSentToChild); - emit RelayedGovernanceRequest(chainId, address(messengers[chainId]), calls, dataSentToChild); - } -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/GovernorSpoke.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/GovernorSpoke.sol deleted file mode 100644 index 3c65c8a73..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/GovernorSpoke.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "./interfaces/ChildMessengerConsumerInterface.sol"; -import "../common/implementation/Lockable.sol"; -import "./SpokeBase.sol"; - -/** - * @title Cross-chain Oracle L2 Governor Spoke. - * @notice Governor contract deployed on L2 that receives governance actions from Ethereum. - */ -contract GovernorSpoke is Lockable, SpokeBase, ChildMessengerConsumerInterface { - struct Call { - address to; - bytes data; - } - - constructor(address _finderAddress) HasFinder(_finderAddress) {} - - event ExecutedGovernanceTransaction(address indexed to, bytes data); - - /** - * @notice Executes governance transaction created on Ethereum. - * @dev Can only be called by ChildMessenger contract that wants to execute governance action on this child chain - * that originated from DVM voters on root chain. ChildMessenger should only receive communication from - * ParentMessenger on mainnet. See the SpokeBase for the onlyMessenger modifier. - - * @param data Contains the target address and the encoded function selector + ABI encoded params to include in - * delegated transaction. - */ - function processMessageFromParent(bytes memory data) public override nonReentrant onlyMessenger { - Call[] memory calls = abi.decode(data, (Call[])); - - for (uint256 i = 0; i < calls.length; i++) { - (address to, bytes memory inputData) = (calls[i].to, calls[i].data); - require(_executeCall(to, inputData), "execute call failed"); - emit ExecutedGovernanceTransaction(to, inputData); - } - } - - // Note: this snippet of code is copied from Governor.sol. - function _executeCall(address to, bytes memory data) private returns (bool) { - // Note: this snippet of code is copied from Governor.sol and modified to not include any "value" field. - // solhint-disable-next-line no-inline-assembly - - bool success; - assembly { - let inputData := add(data, 0x20) - let inputDataSize := mload(data) - // Hardcode value to be 0 for relayed governance calls in order to avoid addressing complexity of bridging - // value cross-chain. - success := call(gas(), to, 0, inputData, inputDataSize, 0, 0) - } - return success; - } -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/OracleBase.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/OracleBase.sol deleted file mode 100644 index 0128c2d24..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/OracleBase.sol +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../data-verification-mechanism/interfaces/FinderInterface.sol"; -import "../data-verification-mechanism/implementation/Constants.sol"; -import "../common/implementation/HasFinder.sol"; - -/** - * @title Cross-chain Oracle L1 Oracle Base. - * @notice Enforces lifecycle of price requests for deriving contract. - */ -abstract contract OracleBase is HasFinder { - enum RequestState { - NeverRequested, - Requested, - Resolved - } - - struct Price { - RequestState state; - int256 price; - } - - // Mapping of encoded price requests {identifier, time, ancillaryData} to Price objects. - mapping(bytes32 => Price) internal prices; - - event PriceRequestAdded(bytes32 indexed identifier, uint256 time, bytes ancillaryData, bytes32 indexed requestHash); - event PushedPrice( - bytes32 indexed identifier, - uint256 time, - bytes ancillaryData, - int256 price, - bytes32 indexed requestHash - ); - - /** - * @notice Enqueues a request (if a request isn't already present) for the given (identifier, time, - * ancillary data) combination. Will only emit an event if the request has never been requested. - * @return True if price request is new, false otherwise. This is useful for caller to keep track of - * duplicate price requests. - */ - function _requestPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData) internal returns (bool) { - bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); - Price storage lookup = prices[priceRequestId]; - if (lookup.state == RequestState.NeverRequested) { - lookup.state = RequestState.Requested; - emit PriceRequestAdded(identifier, time, ancillaryData, priceRequestId); - return true; - } else { - return false; - } - } - - /** - * @notice Publishes price for a requested query. - * @dev Does not update price state if price is already resolved. - */ - function _publishPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData, int256 price) internal { - bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); - Price storage lookup = prices[priceRequestId]; - if (lookup.state == RequestState.Resolved) return; - lookup.price = price; - lookup.state = RequestState.Resolved; - emit PushedPrice(identifier, time, ancillaryData, lookup.price, priceRequestId); - } - - /** - * @notice Returns the convenient way to store price requests, uniquely identified by {identifier, time, - * ancillaryData }. - */ - function _encodePriceRequest( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) internal pure returns (bytes32) { - return keccak256(abi.encode(identifier, time, ancillaryData)); - } -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/OracleHub.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/OracleHub.sol deleted file mode 100644 index ea2b866ea..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/OracleHub.sol +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "./OracleBase.sol"; -import "../data-verification-mechanism/interfaces/OracleAncillaryInterface.sol"; -import "../data-verification-mechanism/interfaces/StoreInterface.sol"; -import "../common/implementation/Lockable.sol"; -import "../common/implementation/MultiCaller.sol"; -import "./interfaces/ParentMessengerInterface.sol"; -import "./interfaces/ParentMessengerConsumerInterface.sol"; -import "@openzeppelin/contracts-v4/access/Ownable.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; - -/** - * @title Cross-chain Oracle L1 Oracle Hub. - * @notice Gatekeeper contract deployed on mainnet that validates and sends price requests from sidechain to the DVM on - * mainnet. This is a "gate keeper" contract because it performs the final validation for any messages originating from - * a child chain's oracle before submitting price requests to the DVM. This contract also can publish DVM price - * resolution data to OracleSpokes on any chainId via the messenger for that chainId. - * @dev This contract must be a registered financial contract in order to make and query DVM price requests. - */ - -contract OracleHub is OracleBase, ParentMessengerConsumerInterface, Ownable, Lockable, MultiCaller { - using SafeERC20 for IERC20; - - // Currency that final fees are paid in. - IERC20 public token; - - // Associates chain ID with ParentMessenger contract to use to send price resolutions to that chain's OracleSpoke - // contract via its ChildMessenger contract. - mapping(uint256 => ParentMessengerInterface) public messengers; - - event SetParentMessenger(uint256 indexed chainId, address indexed parentMessenger); - - constructor(address _finderAddress, IERC20 _token) HasFinder(_finderAddress) { - token = _token; - } - - modifier onlyMessenger(uint256 chainId) { - require(msg.sender == address(messengers[chainId]), "Caller must be messenger for network"); - _; - } - - /** - * @notice Set new ParentMessenger contract for chainId. - * @param chainId network that has a child messenger contract that parent messenger contract will communicate with. - * @param messenger ParentMessenger contract that sends messages to ChildMessenger on network with ID `chainId`. - * @dev Only callable by the owner (presumably the Ethereum Governor contract). - */ - function setMessenger(uint256 chainId, ParentMessengerInterface messenger) public nonReentrant onlyOwner { - messengers[chainId] = messenger; - emit SetParentMessenger(chainId, address(messenger)); - } - - /** - * @notice Publishes a DVM resolved price to the OracleSpoke deployed on the network linked with `chainId`, or - * reverts if not resolved yet. This contract must be registered with the DVM to query price requests. - * The DVM price resolution is communicated to the OracleSpoke via the Parent-->Child messenger channel. - * @dev This method will always attempt to call `messenger.sendMessageToChild` even if it is a duplicate call for - * this price request. Therefore the Messenger contract for this `chainId` should determine how to handle duplicate - * calls. - * @dev This method is `payable` so that ETH can be forwarded to Messenger contracts that need to send ETH - * from L1 to L2, like Arbitrum messengers for example. For networks that do not use ETH, the caller will - * lose ETH, therefore it is the caller's responsibility to know when to send ETH. This is allowed to be - * `payable` because any EOA can call this function. - * @param chainId Network to resolve price for. - * @param identifier Identifier of price request to resolve. - * @param time Timestamp of price request to resolve. - * @param ancillaryData extra data of price request to resolve. - */ - function publishPrice( - uint256 chainId, - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) public payable nonReentrant { - // `getPrice` will revert if there is no price. - int256 price = _getOracle().getPrice(identifier, time, ancillaryData); - _publishPrice(identifier, time, ancillaryData, price); - - // Require caller to include enough ETH to pass to Messenger so that caller cannot take advantage of excess - // ETH held by the Messenger. Caller can easily query messenger to get exact amount of ETh to send. - uint256 requiredL1CallValue = messengers[chainId].getL1CallValue(); - require(msg.value == requiredL1CallValue, "Insufficient msg.value"); - - // Call returns a boolean value indicating success or failure. - // This is the current recommended method to use: https://solidity-by-example.org/sending-ether/ - if (msg.value > 0) { - (bool sent, ) = address(messengers[chainId]).call{ value: msg.value }(""); - require(sent, "Cannot send ETH to messenger"); - } - - // Pass all msg.value to Messenger: - messengers[chainId].sendMessageToChild(abi.encode(identifier, time, ancillaryData, price)); - } - - /** - * @notice Submits a price request originating from an OracleSpoke. Request data must be sent via the - * Child --> Parent Messenger communication channel. Returns silently if price request is a duplicate. - * @dev This contract must be registered to submit price requests to the DVM. Only the ParentMessenger - * can call this method. If the original requester on the child chain wants to expedite the Child --> Parent - * message, then they can call `requestPrice` on this contract for the same unique price request. - * @param chainId id of the child chain that sent the price request. - * @param data ABI encoded params with which to call `_requestPrice`. - */ - function processMessageFromChild( - uint256 chainId, - bytes memory data - ) public override nonReentrant onlyMessenger(chainId) { - (bytes32 identifier, uint256 time, bytes memory ancillaryData) = abi.decode(data, (bytes32, uint256, bytes)); - bool newPriceRequested = _requestPrice(identifier, time, ancillaryData); - if (newPriceRequested) { - _getOracle().requestPrice(identifier, time, ancillaryData); - } - } - - /** - * @notice Anyone can call this method to directly request a price to the DVM. This could be used by the child - * chain requester in the case where Child --> Parent communication takes too long and the requester wants to speed - * up the price resolution process. Returns silently if price request is a duplicate. Calling this method from - * the user's point of view is no different than calling the OptimisticOracle.requestPrice method, but with a - * different interface. - * @dev The caller must pay a final fee and have approved this contract to pull final fee from it. - * @dev If the price request params including the ancillary data does not match exactly the price request submitted - * on the child chain, then the child chain's price request will not resolve. The caller is recommended to use the - * `compressAncillaryData` method on the OracleSpoke to reconstruct the ancillary data. - * @param identifier Identifier for price request. - * @param time time for price request. - * @param ancillaryData Extra data for price request. - */ - function requestPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData) public nonReentrant { - bool newPriceRequested = _requestPrice(identifier, time, ancillaryData); - if (newPriceRequested) { - uint256 finalFee = _getStore().computeFinalFee(address(token)).rawValue; - token.safeTransferFrom(msg.sender, address(_getStore()), finalFee); - _getOracle().requestPrice(identifier, time, ancillaryData); - } - } - - function _getOracle() internal view returns (OracleAncillaryInterface) { - return OracleAncillaryInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); - } - - function _getStore() internal view returns (StoreInterface) { - return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store)); - } -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/OracleSpoke.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/OracleSpoke.sol deleted file mode 100644 index c9d0e2c74..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/OracleSpoke.sol +++ /dev/null @@ -1,255 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../data-verification-mechanism/interfaces/OracleAncillaryInterface.sol"; -import "../data-verification-mechanism/interfaces/OracleInterface.sol"; -import "../data-verification-mechanism/interfaces/RegistryInterface.sol"; -import "./AncillaryDataCompression.sol"; -import "./OracleBase.sol"; -import "../common/implementation/AncillaryData.sol"; -import "../common/implementation/Lockable.sol"; -import "./interfaces/ChildMessengerInterface.sol"; -import "./interfaces/ChildMessengerConsumerInterface.sol"; -import "./SpokeBase.sol"; - -/** - * @title Cross-chain Oracle L2 Oracle Spoke. - * @notice This contract is primarily intended to receive messages on the child chain from a parent chain and allow - * contracts deployed on the child chain to interact with this contract as an Oracle. Moreover, this contract gives - * child chain contracts the ability to trigger cross-chain price requests to the mainnet DVM. This Spoke knows how - * to communicate with the parent chain via a "ChildMessenger" contract which directly communicates with the - * "ParentMessenger" on mainnet. - * @dev The intended client of this contract is an OptimisticOracle on sidechain that needs price - * resolution secured by the DVM on mainnet. - */ -contract OracleSpoke is - OracleBase, - SpokeBase, - OracleAncillaryInterface, - OracleInterface, - ChildMessengerConsumerInterface, - Lockable -{ - using AncillaryDataCompression for bytes; - - // Mapping of parent request ID to child request ID. - mapping(bytes32 => bytes32) public childRequestIds; - - event PriceRequestBridged( - address indexed requester, - bytes32 identifier, - uint256 time, - bytes ancillaryData, - bytes32 indexed childRequestId, - bytes32 indexed parentRequestId - ); - event ResolvedLegacyRequest( - bytes32 indexed identifier, - uint256 time, - bytes ancillaryData, - int256 price, - bytes32 indexed requestHash, - bytes32 indexed legacyRequestHash - ); - - constructor(address _finderAddress) HasFinder(_finderAddress) {} - - // This assumes that the local network has a Registry that resembles the mainnet registry. - modifier onlyRegisteredContract() { - RegistryInterface registry = RegistryInterface(finder.getImplementationAddress(OracleInterfaces.Registry)); - require(registry.isContractRegistered(msg.sender), "Caller must be registered"); - _; - } - - /** - * @notice This is called to bridge a price request to mainnet. This method will enqueue a new price request - * or return silently if already requested. Price requests are relayed to mainnet (the "Parent" chain) via the - * ChildMessenger contract. - * @dev Can be called only by a registered contract that is allowed to make DVM price requests. Will mark this - * price request as Requested, and therefore able to receive the price resolution data from mainnet. - * @dev Contract registration enables the DVM to validate that the calling contract correctly pays final fees. - * Therefore, this function does not directly attempt to pull a final fee from the caller. - * @param identifier Identifier of price request. - * @param time Timestamp of price request. - * @param ancillaryData extra data of price request. - */ - function requestPrice( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) public override nonReentrant onlyRegisteredContract { - _requestPriceSpoke(identifier, time, ancillaryData); - } - - /** - * @notice Overloaded function to provide backwards compatibility for legacy financial contracts that do not use - * ancillary data. - */ - function requestPrice(bytes32 identifier, uint256 time) public override nonReentrant onlyRegisteredContract { - _requestPriceSpoke(identifier, time, ""); - } - - function _requestPriceSpoke(bytes32 identifier, uint256 time, bytes memory ancillaryData) internal { - address requester = msg.sender; - bytes32 childRequestId = _encodePriceRequest(identifier, time, ancillaryData); - Price storage lookup = prices[childRequestId]; - - // Send the request to mainnet only if it has not been requested yet. - if (lookup.state != RequestState.NeverRequested) return; - lookup.state = RequestState.Requested; - - // Only the compressed ancillary data is sent to the mainnet. As it includes the request block number that is - // not available when getting the resolved price, we map the derived request ID. - bytes memory parentAncillaryData = ancillaryData.compress(requester, block.number); - bytes32 parentRequestId = _encodePriceRequest(identifier, time, parentAncillaryData); - childRequestIds[parentRequestId] = childRequestId; - - // Emit all required information so that voters on mainnet can track the origin of the request and full - // ancillary data by using the parentRequestId that is derived from identifier, time and ancillary data as - // observed on mainnet. - emit PriceRequestBridged(requester, identifier, time, ancillaryData, childRequestId, parentRequestId); - emit PriceRequestAdded(identifier, time, parentAncillaryData, parentRequestId); - - getChildMessenger().sendMessageToParent(abi.encode(identifier, time, parentAncillaryData)); - } - - /** - * @notice Resolves a price request originating from a message sent by the DVM on the parent chain. - * @dev Can only be called by the ChildMessenger contract which is designed to communicate only with the - * ParentMessenger contract on Mainnet. See the SpokeBase for the onlyMessenger modifier. - * @param data ABI encoded params with which to call `_publishPrice`. - */ - function processMessageFromParent(bytes memory data) public override nonReentrant onlyMessenger { - (bytes32 identifier, uint256 time, bytes memory ancillaryData, int256 price) = abi.decode( - data, - (bytes32, uint256, bytes, int256) - ); - bytes32 parentRequestId = _encodePriceRequest(identifier, time, ancillaryData); - - // Resolve the requestId used when requesting and checking the price. The childRequestIds value in the mapping - // could be uninitialized if the request was originated from: - // - the previous implementation of this contract, or - // - another chain and was pushed to this chain by mistake. - bytes32 priceRequestId = childRequestIds[parentRequestId]; - if (priceRequestId == bytes32(0)) priceRequestId = parentRequestId; - Price storage lookup = prices[priceRequestId]; - - // In order to support resolving the requests initiated from the previous implementation of this contract, we - // only update the state and emit an event if it has not yet been resolved. - if (lookup.state == RequestState.Resolved) return; - lookup.price = price; - lookup.state = RequestState.Resolved; - emit PushedPrice(identifier, time, ancillaryData, price, priceRequestId); - } - - /** - * @notice This method handles a special case when a price request was originated on the previous implementation of - * this contract, but was not settled before the upgrade. - * @dev Duplicates the resolved state from the legacy request to the new request where original ancillary data is - * used for request ID derivation. Will revert if the legacy request has not been pushed from mainnet. - * @param identifier Identifier of price request to resolve. - * @param time Timestamp of price request to resolve. - * @param ancillaryData Original ancillary data passed by the requester before stamping by the legacy spoke. - */ - function resolveLegacyRequest(bytes32 identifier, uint256 time, bytes memory ancillaryData) external { - bytes32 legacyRequestId = _encodePriceRequest(identifier, time, _legacyStampAncillaryData(ancillaryData)); - Price storage legacyLookup = prices[legacyRequestId]; - require(legacyLookup.state == RequestState.Resolved, "Price has not been resolved"); - - bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); - Price storage lookup = prices[priceRequestId]; - - // Update the state and emit an event only if the legacy request has not been resolved yet. - if (lookup.state == RequestState.Resolved) return; - lookup.price = legacyLookup.price; - lookup.state = RequestState.Resolved; - emit ResolvedLegacyRequest(identifier, time, ancillaryData, lookup.price, priceRequestId, legacyRequestId); - } - - /** - * @notice Returns whether a price has resolved for the request. This method will not revert. - * @param identifier Identifier of price request. - * @param time Timestamp of price request - * @param ancillaryData extra data of price request. - * @return True if a price is available, False otherwise. If true, then getPrice will succeed for the request. - */ - function hasPrice( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) public view override nonReentrantView onlyRegisteredContract returns (bool) { - bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); - return prices[priceRequestId].state == RequestState.Resolved; - } - - /** - * @notice Overloaded function to provide backwards compatibility for legacy financial contracts that do not use - * ancillary data. - */ - function hasPrice( - bytes32 identifier, - uint256 time - ) public view override nonReentrantView onlyRegisteredContract returns (bool) { - bytes32 priceRequestId = _encodePriceRequest(identifier, time, ""); - return prices[priceRequestId].state == RequestState.Resolved; - } - - /** - * @notice Returns resolved price for the request. Reverts if price is not available. - * @param identifier Identifier of price request. - * @param time Timestamp of price request - * @param ancillaryData extra data of price request. - * @return int256 Price, or reverts if no resolved price for any reason. - */ - function getPrice( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) public view override nonReentrantView onlyRegisteredContract returns (int256) { - bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); - Price storage lookup = prices[priceRequestId]; - require(lookup.state == RequestState.Resolved, "Price has not been resolved"); - return lookup.price; - } - - /** - * @notice Overloaded function to provide backwards compatibility for legacy financial contracts that do not use - * ancillary data. - */ - function getPrice( - bytes32 identifier, - uint256 time - ) public view override nonReentrantView onlyRegisteredContract returns (int256) { - bytes32 priceRequestId = _encodePriceRequest(identifier, time, ""); - Price storage lookup = prices[priceRequestId]; - require(lookup.state == RequestState.Resolved, "Price has not been resolved"); - return lookup.price; - } - - /** - * @notice Compresses ancillary data by providing sufficient information to track back the original ancillary data - * mainnet. - * @dev This is expected to be used in offchain infrastructure when speeding up requests to the mainnet. - * @param ancillaryData original ancillary data to be processed. - * @param requester address of the requester who initiated the price request. - * @param requestBlockNumber block number when the price request was initiated. - * @return compressed ancillary data. - */ - function compressAncillaryData( - bytes memory ancillaryData, - address requester, - uint256 requestBlockNumber - ) external view returns (bytes memory) { - return ancillaryData.compress(requester, requestBlockNumber); - } - - /** - * @dev This replicates the implementation of `_stampAncillaryData` from the previous version of this contract for - * the purpose of resolving legacy requests if they had not been resolved before the upgrade. - */ - function _legacyStampAncillaryData(bytes memory ancillaryData) internal view returns (bytes memory) { - // This contract should stamp the child network's ID so that voters on the parent network can - // deterministically track unique price requests back to this contract. - return AncillaryData.appendKeyValueUint(ancillaryData, "childChainId", block.chainid); - } -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/README.md b/contracts/external/uma/core/contracts/cross-chain-oracle/README.md deleted file mode 100644 index fdff0cde3..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/README.md +++ /dev/null @@ -1,325 +0,0 @@ -# Deployments - -Follow these instructions to deploy two smart contract suites to an L2 that are secured by and can communicate with the core DVM contracts on L1: - -- Cross chain contracts that relay messages between L1 and L2. -- Optimistic oracle contracts that allow external contracts to make price requests from L2 to L1. - -## Setup - -Before running anything below, make sure you have run `yarn build` from the root of the repo. - -Note that the fork network instructions can be used as a test run prior to the public network -deployments. - -## Starting a fork - -To run a forked network deployment, run the following command: - -```sh -HARDHAT_CHAIN_ID= yarn hardhat node --fork --no-deploy --port -``` - -You'll need to run two forks in separate terminals on different ports to do the deployments below. - -Note: in the commands below, you'll need to set the relevant `NODE_URL_X` environment variable to the url of the locally forked network `http://localhost:`. - -If you're having trouble redeploying contracts because `hardhat` wants to "reuse" contracts, then run `yarn clean && yarn` in the `core` package to reset `deployments`. - -## Step-by-step guide to deployments on rollups (Arbitrum, Boba, Optimism, Base and Blast) - -The steps below explain how to deploy the cross-chain oracle onto supported rollups. - -1. Start by exporting some environment variables (or storing them in a .env): - -```sh -# Set only the following environment variables based on which L2 you're deploying to. For example, if you're deploying to mainnet and arbitrum, set NODE_URL_1 and NODE_URL_42161. -# When running against a forked network, set the URL to http://localhost: -export NODE_URL_1= -export NODE_URL_42161= -export NODE_URL_288= -export NODE_URL_10= -export NODE_URL_8453= -export NODE_URL_81457= -export MNEMONIC="Your 12-word mnemonic here" -export ETHERSCAN_API_KEY="Your L2 Etherscan API key" -``` - -2. Add new network variables in `@uma/common` and `@uma/financial-templates-lib` packages: - -- Hardhat `defaultConfig.networks` configuration in [packages/common/src/HardhatConfig.ts](/packages/common/src/HardhatConfig.ts). Adding `companionNetworks` parameter pointing to L1 mainnet is required for approved collateral migration task. -- `etherscan.customChains` configuration in [packages/common/src/HardhatConfig.ts](/packages/common/src/HardhatConfig.ts) if the L2 is not supported by `@nomicfoundation/hardhat-verify` library. -- `PublicNetworks` configuration in [packages/common/src/PublicNetworks.ts](/packages/common/src/PublicNetworks.ts) -- `averageBlockTimeSeconds` in [packages/common/src/TimeUtils.ts](/packages/common/src/TimeUtils.ts) -- Default RPC to `getNodeUrl` in [packages/common/src/ProviderUtils.ts](/packages/common/src/ProviderUtils.ts) -- Add L2 network specific task in the form `setup-l1--xchain` in [packages/common/src/hardhat/tasks/xchainSetup.ts](/packages/common/src/hardhat/tasks/xchainSetup.ts) -- Update `migrate-collateral-whitelist` task in [packages/common/src/hardhat/tasks/collateralWhitelist.ts](/packages/common/src/hardhat/tasks/collateralWhitelist.ts) to support target L2 network -- Gas estimator settings to `MAPPING_BY_NETWORK` in [packages/financial-templates-lib/src/helpers/GasEstimator.ts](/packages/financial-templates-lib/src/helpers/GasEstimator.ts) -- Rebuild `@uma/common` package: - - ```sh - cd packages/common - yarn build - ``` - -3. Update L1 contract deploy scripts under [packages/core/deploy](/packages/core/deploy): - -- Create `_ParentMessenger` deploy script and make sure to add L2 network specific tag in the form `l1--xchain`. -- Add L2 network specific tag in the form `l1--xchain` to [Oracle Hub deploy script](/packages/core/deploy/039_deploy_oracle_hub.js) -- Add L2 network specific tag in the form `l1--xchain` to [Governor Hub deploy script](/packages/core/deploy/041_deploy_governor_hub.js) - -4. Deploy L1 contracts. Use L2 network specific tag in the form `l1--xchain` as added in the step 3 above. - -```sh -yarn hardhat deploy --network mainnet --tags l1--xchain -``` - -Add the deployed `_ParentMessenger` contract to the associated networks file. - -5. Update L2 contract deploy scripts under [packages/core/deploy](/packages/core/deploy): - -- Create `_ChildMessenger` deploy script and make sure to add L2 network specific tag in the form `l2--xchain`. -- Add L2 network specific tag in the form `l2--xchain` to [Oracle Spoke deploy script](/packages/core/deploy/038_deploy_oracle_spoke.js) -- Add L2 network specific tag in the form `l2--xchain` to [Governor Spoke deploy script](/packages/core/deploy/040_deploy_governor_spoke.js) - -6. Deploy L2 contracts. Use L2 name specific tag in the form `l2--xchain` as added in the step 5 above. - -```sh -yarn hardhat deploy --network --tags l2--xchain,Registry - -``` - -Add the deployed L2 `Registry`, `Finder`, `OracleSpoke`, `GovernorSpoke` and `_ChildMessenger` contracts to the associated networks file. - -7. Setup mainnet contracts - -```sh -yarn hardhat setup-l1--xchain --network mainnet -``` - -8. Setup L2 contracts - -```sh -yarn hardhat setup-l2-xchain --network -``` - -9. We will now deploy the `OptimisticOracle`, `OptimisticOracleV2` and required contracts to the L2. `OptimisticOracleV3` contract is deployed in a later step. - -```sh -yarn hardhat deploy --network --tags OptimisticOracle,OptimisticOracleV2,IdentifierWhitelist,AddressWhitelist,Store -``` - -Add the deployed `OptimisticOracle`, `OptimisticOracleV2`, `IdentifierWhitelist`, `AddressWhitelist` and `Store` contracts to the associated networks file under [packages/core/networks](/packages/core/networks). - -10. Setup L2 `OptimisticOracle` and `OptimisticOracleV2`: - -```sh -# Seed IdentifierWhitelist with all identifiers already approved on mainnet. Note the --from address is the IdentifierWhitelist deployed on mainnet. -CROSS_CHAIN_NODE_URL=$NODE_URL_1 yarn hardhat migrate-identifiers --network --from 0xcF649d9Da4D1362C4DAEa67573430Bd6f945e570 --crosschain true - -# Seed Collateral whitelist with all collaterals already approved on mainnet. This will also pull the final fee from the L1 store and set it in the L2 Store. -# Replace with L2 chainId value. -yarn hardhat --network migrate-collateral-whitelist --l1chainid 1 --l2chainid - -# Alternatively, add individual tokens to L2 collateral whitelist: -yarn hardhat --network whitelist-collateral --address --finalfee - -# Point L2 Finder to remaining Optimistic Oracle system contracts. -yarn hardhat setup-finder --oraclespoke --identifierwhitelist --addresswhitelist --optimisticoracle --optimisticoraclev2 --store --network - -# Register OptimisticOracle as registered contract (repeat for OptimisticOracleV2). -yarn hardhat register-accounts --network --account -``` - -11. Deploy `OptimisticOracleV3` to the L2. - -First, update `ADDRESSES_FOR_NETWORK` variable in `OptimisticOracleV3` deploy script [057_deploy_optimistic_oracle_V3.js](/packages/core/deploy/057_deploy_optimistic_oracle_V3.js) with `defaultCurrency` (e.g. USDC) for selected network. - -Deploy `OptimisticOracleV3` instance: - -```sh -yarn hardhat deploy --network --tags OptimisticOracleV3 -``` - -Add the deployed `OptimisticOracleV3` contract to the associated networks file under [packages/core/networks](/packages/core/networks). - -12. Setup L2 `OptimisticOracleV3`: - -```sh -# Point L2 Finder to remaining OptimisticOracleV3 contract. -yarn hardhat setup-finder --optimisticoraclev3 --network - -# Register OptimisticOracleV3 as registered contract. -yarn hardhat register-accounts --network --account -``` - -13. Transfer ownership: - -Transfer deployed contract ownership for each of `Registry`, `Store`, `IdentifierWhitelist`, `AddressWhitelist`, `Finder` and `OptimisticOracleV3` instances to `GovernorSpoke` on L2: - -```sh -yarn hardhat transfer-owner --network --contract --owner -``` - -Also transfer the ownership of `_ParentMessenger` on mainnet to `GovernorV2`: - -```sh -yarn hardhat transfer-owner --network mainnet --contract --owner -``` - -14. Verify contracts: - -```sh -# mainnet -yarn hardhat --network mainnet etherscan-verify --api-key --license GPL-3.0 --force-license -# arbitrum -yarn hardhat --network arbitrum etherscan-verify --api-key --license GPL-3.0 --force-license -# boba -yarn hardhat --network boba sourcify -``` - -For networks that don't have `etherscan-verify` support, use `hardhat verify`: - -```sh -yarn hardhat --network verify --contract -``` - -In case of more complex constructor arguments, pass them from file `--constructor-args arguments.js` as discussed in [hardhat-verify docs](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify#complex-arguments). In order to get constructor argument values, check them in `args` property from respective contract deployment json file (appropriate network directory under `packages/core/deployments`). - -15. Run the following script to check all required steps: - -```sh -yarn hardhat verify-xchain --network mainnet --l2 -``` - -## L2->L1 Message passing and finalization - -The cross-chain-oracle contracts send messages from L1<->L2 under different situations. L1->L2 transactions are auto finalized on all recipient chains currently supported by the cross-chain oracle. However, L2->L1 transactions are not auto finalized on L1 in most cases. For example, when sending transactions from Optimism or Arbitrum back to Ethereum L1 the transaction needs to be executed manually on L1 after the 7 day liveness. In other UMA infrastructure, such as Across, there is an automatic finalizer bot that that picks up L2->L1 token transfers and automatically finalizes them on L1. This kind of infrastructure has not yet been built out for the cross-chain-oracle and therefore will need to be done manually if the situation arrives. Both Optimism and Arbitrum provide their own sets of scripts to facilitate this: - -To finalize **Arbitrum** transactions see the docs [here](https://github.com/OffchainLabs/arbitrum-tutorials/tree/master/packages/outbox-execute) on how to do this. Alternatively, Arbiscan provides a list of L2->L1 transactions and a UI for finalizing those that have passed liveness. This can also be used if you don't want to run the `outbox-execute` script. The relevant page can be found [here](https://arbiscan.io/txsExit). - -To finalize **Optimism** transactions see the equivalent script [here](https://github.com/ethereum-optimism/optimism/blob/34e7450873548f65bf3160ca58eed2328907310a/packages/sdk/tasks/finalize-withdrawal.ts#L14). Optimism's Etherscan also provides a UI, similar to Arbitrum, which can be found [here](https://optimistic.etherscan.io/txsExit). - -To finalize **Base** transactions see the equivalent script [here](https://github.com/ethereum-optimism/optimism/blob/develop/packages/message-relayer/src/exec/withdraw.ts). Base's Etherscan also provides a UI, similar to Optimism, which can be found [here](https://basescan.org/txsExit). - -**Boba** at present auto-finalizes all L2->L1 transactions without any required user intervention. - -## Step-by-step guide to deployments for Non-rollup chains - -The steps below explain how to deploy the cross-chain oracle onto non-rollup chains using the `Admin_ChildMessenger`. This enables a multisig to act as the "messenger" thereby enabling the UMA Optimistic oracle to be deployed onto chains that do not have a supporting canonical bridge. - -1. Start by exporting some environment variables (or storing them in a `.env`): - -```sh -# Set only the following environment variables based on which L2 you're deploying to. For example, if you're deploying to xDAI, set NODE_URL_100. -# When running against a forked network, set the URL to http://localhost: -export NODE_URL_100= -export MNEMONIC="Your 12-word mnemonic here" -export ETHERSCAN_API_KEY="Your L2 Etherscan API key" -``` - -2. Add new network variables in `@uma/common` and `@uma/financial-templates-lib` packages: - -- Hardhat `defaultConfig.networks` configuration in [packages/common/src/HardhatConfig.ts](/packages/common/src/HardhatConfig.ts). Adding `companionNetworks` parameter pointing to L1 mainnet is required for approved collateral migration task. -- `etherscan.customChains` configuration in [packages/common/src/HardhatConfig.ts](/packages/common/src/HardhatConfig.ts) if the L2 is not supported by `@nomicfoundation/hardhat-verify` library. -- `PublicNetworks` configuration in [packages/common/src/PublicNetworks.ts](/packages/common/src/PublicNetworks.ts) -- `averageBlockTimeSeconds` in [packages/common/src/TimeUtils.ts](/packages/common/src/TimeUtils.ts) -- Default RPC to `getNodeUrl` in [packages/common/src/ProviderUtils.ts](/packages/common/src/ProviderUtils.ts) -- Gas estimator settings to `MAPPING_BY_NETWORK` in [packages/financial-templates-lib/src/helpers/GasEstimator.ts](/packages/financial-templates-lib/src/helpers/GasEstimator.ts) -- Rebuild `@uma/common` package: - - ```sh - cd packages/common - yarn build - ``` - -3. Deploy L2 Contracts: - -```sh -# Replace with network name added in Hardhat configuration from Step 2 above. -yarn hardhat deploy --network --tags l2-admin-xchain,Registry -``` - -Add the deployed `Registry`, `Finder`, `OracleSpoke`, `GovernorSpoke` and `Admin_ChildMessenger` contracts to the associated networks file under [packages/core/networks](/packages/core/networks). - -4. Setup L2 contracts: - -```sh -yarn hardhat setup-l2-admin-xchain --network -``` - -5. We will now deploy the `OptimisticOracle`, `OptimisticOracleV2` and required contracts to the L2. - -```sh -yarn hardhat deploy --network --tags OptimisticOracle,OptimisticOracleV2,IdentifierWhitelist,AddressWhitelist,Store -``` - -Add the deployed `OptimisticOracle`, `OptimisticOracleV2`, `IdentifierWhitelist`, `AddressWhitelist` and `Store` contracts to the associated networks file under [packages/core/networks](/packages/core/networks). - -6. Setup L2 `OptimisticOracle` and `OptimisticOracleV2`: - -```sh -# Seed IdentifierWhitelist with all identifiers already approved on mainnet. Note the --from address is the IdentifierWhitelist deployed on mainnet. -CROSS_CHAIN_NODE_URL= yarn hardhat migrate-identifiers --network --from 0xcF649d9Da4D1362C4DAEa67573430Bd6f945e570 --crosschain true - -# Seed Collateral whitelist with all collaterals already approved on mainnet. This will also pull the final fee from the L1 store and set it in the L2 Store. -# Replace with L2 chainId value. -yarn hardhat --network migrate-collateral-whitelist --l1chainid 1 --l2chainid - -# Point L2 Finder to remaining Optimistic Oracle system contracts. -yarn hardhat setup-finder --oraclespoke --identifierwhitelist --addresswhitelist --optimisticoracle --optimisticoraclev2 --store --network - -# Register OptimisticOracle as registered contract (repeat for OptimisticOracleV2). -yarn hardhat register-accounts --network --account -``` - -7. Deploy `OptimisticOracleV3` to the L2. - -First, update `ADDRESSES_FOR_NETWORK` variable in `OptimisticOracleV3` deploy script [057_deploy_optimistic_oracle_V3.js](/packages/core/deploy/057_deploy_optimistic_oracle_V3.js) with `defaultCurrency` (e.g. USDC) for selected network. - -Deploy `OptimisticOracleV3` instance: - -```sh -yarn hardhat deploy --network --tags OptimisticOracleV3 -``` - -Add the deployed `OptimisticOracleV3` contract to the associated networks file under [packages/core/networks](/packages/core/networks). - -8. Setup L2 `OptimisticOracleV3`: - -```sh -# Point L2 Finder to remaining OptimisticOracleV3 contract. -yarn hardhat setup-finder --optimisticoraclev3 --network - -# Register OptimisticOracleV3 as registered contract. -yarn hardhat register-accounts --network --account -``` - -9. Verify contracts: - -```sh -# Use hardhat verify on each deployed contract above. -yarn hardhat --network verify --contract -``` - -In case of more complex constructor arguments, pass them from file `--constructor-args arguments.js` as discussed in [hardhat-verify docs](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify#complex-arguments). In order to get constructor argument values, check them in `args` property from respective contract deployment json file (appropriate network directory under `packages/core/deployments`). - -10. Transfer ownership: - -Transfer deployed contract ownership for each of `Registry`, `Store`, `IdentifierWhitelist`, `AddressWhitelist`, `Finder` and `OptimisticOracleV3` instances to `GovernorSpoke`: - -```sh -yarn hardhat transfer-owner --network --contract --owner -``` - -Also transfer `Admin_ChildMessenger` contract ownership to multisig: - -```sh -yarn hardhat transfer-owner --network --contract --owner -``` - -11. Run the following script to check all required steps: - -```sh -yarn hardhat verify-admin-xchain --network -``` diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/SpokeBase.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/SpokeBase.sol deleted file mode 100644 index 199c8f99a..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/SpokeBase.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; -import "./interfaces/ChildMessengerInterface.sol"; - -import "../data-verification-mechanism/implementation/Constants.sol"; -import "../common/implementation/HasFinder.sol"; - -/** - * @title Cross-chain Oracle L2 Spoke Base. - * @notice Provides access control to Governance and Oracle spoke L2 contracts. - */ - -abstract contract SpokeBase is HasFinder { - modifier onlyMessenger() { - require(msg.sender == address(getChildMessenger()), "Caller must be messenger"); - _; - } - - /** - * @notice Returns the child messenger address set in the finder. - * @return ChildMessengerInterface instance of child messenger deployed on L2. - */ - function getChildMessenger() public view returns (ChildMessengerInterface) { - return ChildMessengerInterface(finder.getImplementationAddress(OracleInterfaces.ChildMessenger)); - } -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Admin_ChildMessenger.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Admin_ChildMessenger.sol deleted file mode 100644 index 5ad5c2368..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Admin_ChildMessenger.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/access/Ownable.sol"; -import "../interfaces/ChildMessengerInterface.sol"; -import "../interfaces/ChildMessengerConsumerInterface.sol"; -import "../../common/implementation/Lockable.sol"; - -/** - * @notice A version of the child messenger that allows an admin to relay messages on its behalf. - * @dev No parent messenger is needed for this case, as the admin could be trusted to manually send DVM requests on - * mainnet. This is intended to be used as a "beta" deployment compatible with any EVM-compatible chains before - * implementing a full bridge adapter. Put simply, it is meant as a stop-gap. - */ -contract Admin_ChildMessenger is Ownable, Lockable, ChildMessengerInterface { - // The only child network contract that can send messages over the bridge via the messenger is the oracle spoke. - address public oracleSpoke; - - event SetOracleSpoke(address newOracleSpoke); - event MessageSentToParent(bytes data, address indexed oracleSpoke); - event MessageReceivedFromParent(bytes data, address indexed targetSpoke, address indexed caller); - - /** - * @notice Changes the stored address of the Oracle spoke, deployed on L2. - * @dev The caller of this function must be the admin. - * @param newOracleSpoke address of the new oracle spoke, deployed on L2. - */ - function setOracleSpoke(address newOracleSpoke) public onlyOwner nonReentrant { - oracleSpoke = newOracleSpoke; - emit SetOracleSpoke(newOracleSpoke); - } - - /** - * @notice Logs a message to be manually relayed to L1. - * @dev The caller must be the OracleSpoke on L2. No other contract is permissioned to call this function. - * @param data data message sent to the L1 messenger. Should be an encoded function call or packed data. - */ - function sendMessageToParent(bytes memory data) public override nonReentrant { - require(msg.sender == oracleSpoke, "Only callable by oracleSpoke"); - - // Note: only emit an event. These messages will be manually relayed. - emit MessageSentToParent(data, oracleSpoke); - } - - /** - * @notice Process a received message from the admin. - * @dev The caller must be the the admin. - * @param data data message sent from the admin. Should be an encoded function call or packed data. - * @param target desired recipient of `data`. Target must implement the `processMessageFromParent` function. Having - * this as a param enables the Admin to send messages to arbitrary addresses from the messenger contract. This is - * primarily used to send messages to the OracleSpoke and GovernorSpoke. - */ - function processMessageFromCrossChainParent(bytes memory data, address target) public onlyOwner nonReentrant { - ChildMessengerConsumerInterface(target).processMessageFromParent(data); - emit MessageReceivedFromParent(data, target, msg.sender); - } -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Arbitrum_ChildMessenger.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Arbitrum_ChildMessenger.sol deleted file mode 100644 index 3c9025eb5..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Arbitrum_ChildMessenger.sol +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../interfaces/ChildMessengerInterface.sol"; -import "../interfaces/ChildMessengerConsumerInterface.sol"; -import "../../common/implementation/Lockable.sol"; -import "../../external/avm/AVM_CrossDomainEnabled.sol"; - -/** - * @notice Sends and receives cross chain messages between Arbitrum L2 and Ethereum L1 network. - * @dev This contract is ownable via the onlyCrossDomainAccount modifier, restricting ownership to the cross-domain - * parent messenger contract that lives on L1. - */ -contract Arbitrum_ChildMessenger is AVM_CrossDomainEnabled, ChildMessengerInterface, Lockable { - // The only child network contract that can send messages over the bridge via the messenger is the oracle spoke. - address public oracleSpoke; - - // Messenger contract on the other side of the L1<->L2 bridge. - address public parentMessenger; - - event SetOracleSpoke(address newOracleSpoke); - event SetParentMessenger(address newParentMessenger); - event MessageSentToParent(bytes data, address indexed parentAddress, address indexed oracleSpoke, uint256 id); - event MessageReceivedFromParent(bytes data, address indexed targetSpoke, address indexed parentAddress); - - /** - * @notice Construct the Arbitrum_ChildMessenger contract. - * @param _parentMessenger The address of the L1 parent messenger. Acts as the "owner" of this contract. - */ - constructor(address _parentMessenger) { - parentMessenger = _parentMessenger; - } - - /** - * @notice Changes the stored address of the Oracle spoke, deployed on L2. - * @dev The caller of this function must be the parent messenger, over the canonical bridge. - * @param newOracleSpoke address of the new oracle spoke, deployed on L2. - */ - function setOracleSpoke(address newOracleSpoke) public onlyFromCrossDomainAccount(parentMessenger) nonReentrant { - oracleSpoke = newOracleSpoke; - emit SetOracleSpoke(newOracleSpoke); - } - - /** - * @notice Changes the stored address of the parent messenger, deployed on L1. - * @dev The caller of this function must be the parent messenger, over the canonical bridge. - * @param newParentMessenger address of the new parent messenger, deployed on L1. - */ - function setParentMessenger( - address newParentMessenger - ) public onlyFromCrossDomainAccount(parentMessenger) nonReentrant { - parentMessenger = newParentMessenger; - emit SetParentMessenger(newParentMessenger); - } - - /** - * @notice Sends a message to the parent messenger via the canonical message bridge. - * @dev The caller must be the OracleSpoke on L2. No other contract is permissioned to call this function. - * @dev The L1 target, the parent messenger, must implement processMessageFromChild to consume the message. - * @param data data message sent to the L1 messenger. Should be an encoded function call or packed data. - */ - function sendMessageToParent(bytes memory data) public override nonReentrant { - require(msg.sender == oracleSpoke, "Only callable by oracleSpoke"); - bytes memory dataSentToParent = abi.encodeWithSignature("processMessageFromCrossChainChild(bytes)", data); - uint256 id = sendCrossDomainMessage(msg.sender, parentMessenger, dataSentToParent); - emit MessageSentToParent(dataSentToParent, parentMessenger, oracleSpoke, id); - } - - /** - * @notice Process a received message from the parent messenger via the canonical message bridge. - * @dev The caller must be the the parent messenger, sent over the canonical message bridge. - * @param data data message sent from the L1 messenger. Should be an encoded function call or packed data. - * @param target desired recipient of `data`. Target must implement the `processMessageFromParent` function. Having - * this as a param enables the L1 Messenger to send messages to arbitrary addresses on the L1. This is primarily - * used to send messages to the OracleSpoke and GovernorSpoke on L2. - */ - function processMessageFromCrossChainParent( - bytes memory data, - address target - ) public onlyFromCrossDomainAccount(parentMessenger) nonReentrant { - ChildMessengerConsumerInterface(target).processMessageFromParent(data); - emit MessageReceivedFromParent(data, target, parentMessenger); - } -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Arbitrum_ParentMessenger.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Arbitrum_ParentMessenger.sol deleted file mode 100644 index f163e7d54..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Arbitrum_ParentMessenger.sol +++ /dev/null @@ -1,210 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../external/avm/Arbitrum_Messenger.sol"; -import "../interfaces/ParentMessengerInterface.sol"; -import "../interfaces/ParentMessengerConsumerInterface.sol"; -import "./ParentMessengerBase.sol"; -import "../../common/implementation/Lockable.sol"; - -/** - * @notice Sends cross chain messages from Ethereum L1 to Arbitrum L2 network. - * @dev This contract is ownable and should be owned by the DVM governor. - */ -contract Arbitrum_ParentMessenger is Arbitrum_Messenger, ParentMessengerInterface, ParentMessengerBase, Lockable { - event SetDefaultGasLimit(uint32 newDefaultGasLimit); - event SetDefaultMaxSubmissionCost(uint256 newMaxSubmissionCost); - event SetDefaultGasPrice(uint256 newDefaultGasPrice); - event SetRefundL2Address(address newRefundL2Address); - event MessageSentToChild( - bytes data, - address indexed targetSpoke, - uint256 l1CallValue, - uint32 gasLimit, - uint256 gasPrice, - uint256 maxSubmissionCost, - address refundL2Address, - address indexed childMessenger, - uint256 sequenceNumber - ); - event MessageReceivedFromChild(bytes data, address indexed childMessenger, address indexed targetHub); - - // Gas limit for immediate L2 execution attempt (can be estimated via NodeInterface.estimateRetryableTicket). - // NodeInterface precompile interface exists at L2 address 0x00000000000000000000000000000000000000C8 - uint32 public defaultGasLimit = 5_000_000; - - // Amount of ETH allocated to pay for the base submission fee. The base submission fee is a parameter unique to - // retryable transactions; the user is charged the base submission fee to cover the storage costs of keeping their - // ticket’s calldata in the retry buffer. (current base submission fee is queryable via - // ArbRetryableTx.getSubmissionPrice). ArbRetryableTicket precompile interface exists at L2 address - // 0x000000000000000000000000000000000000006E. - uint256 public defaultMaxSubmissionCost = 0.1e18; - - // L2 Gas price bid for immediate L2 execution attempt (queryable via standard eth*gasPrice RPC) - uint256 public defaultGasPrice = 10e9; // 10 gWei - - // This address on L2 receives extra ETH that is left over after relaying a message via the inbox. - address public refundL2Address; - - /** - * @notice Construct the Optimism_ParentMessenger contract. - * @param _inbox Contract that sends generalized messages to the Arbitrum chain. - * @param _childChainId The chain id of the Optimism L2 network this messenger should connect to. - **/ - constructor(address _inbox, uint256 _childChainId) Arbitrum_Messenger(_inbox) ParentMessengerBase(_childChainId) { - refundL2Address = owner(); - } - - /** - * @notice Changes the refund address on L2 that receives excess gas or the full msg.value if the retryable - * ticket reverts. - * @dev The caller of this function must be the owner, which should be set to the DVM governor. - * @param newRefundl2Address the new refund address to set. This should be set to an L2 address that is trusted by - * the owner as it can spend Arbitrum L2 refunds for excess gas when sending transactions on Arbitrum. - */ - function setRefundL2Address(address newRefundl2Address) public onlyOwner nonReentrant { - refundL2Address = newRefundl2Address; - emit SetRefundL2Address(refundL2Address); - } - - /** - * @notice Changes the default gas limit that is sent along with transactions to Arbitrum. - * @dev The caller of this function must be the owner, which should be set to the DVM governor. - * @param newDefaultGasLimit the new L2 gas limit to be set. - */ - function setDefaultGasLimit(uint32 newDefaultGasLimit) public onlyOwner nonReentrant { - defaultGasLimit = newDefaultGasLimit; - emit SetDefaultGasLimit(newDefaultGasLimit); - } - - /** - * @notice Changes the default gas price that is sent along with transactions to Arbitrum. - * @dev The caller of this function must be the owner, which should be set to the DVM governor. - * @param newDefaultGasPrice the new L2 gas price to be set. - */ - function setDefaultGasPrice(uint256 newDefaultGasPrice) public onlyOwner nonReentrant { - defaultGasPrice = newDefaultGasPrice; - emit SetDefaultGasPrice(newDefaultGasPrice); - } - - /** - * @notice Changes the default max submission cost that is sent along with transactions to Arbitrum. - * @dev The caller of this function must be the owner, which should be set to the DVM governor. - * @param newDefaultMaxSubmissionCost the new L2 max submission cost to be set. - */ - function setDefaultMaxSubmissionCost(uint256 newDefaultMaxSubmissionCost) public onlyOwner nonReentrant { - defaultMaxSubmissionCost = newDefaultMaxSubmissionCost; - emit SetDefaultMaxSubmissionCost(newDefaultMaxSubmissionCost); - } - - /** - * @notice Changes the address of the oracle spoke on L2 via the child messenger. - * @dev The caller of this function must be the owner, which should be set to the DVM governor. - * @dev This function will only succeed if this contract has enough ETH to cover the approximate L1 call value. - * @param newOracleSpoke the new oracle spoke address set on L2. - */ - function setChildOracleSpoke(address newOracleSpoke) public onlyOwner nonReentrant { - bytes memory dataSentToChild = abi.encodeWithSignature("setOracleSpoke(address)", newOracleSpoke); - _sendMessageToChild(dataSentToChild, childMessenger); - } - - /** - * @notice Changes the address of the parent messenger on L2 via the child messenger. - * @dev The caller of this function must be the owner, which should be set to the DVM governor. - * @dev This function will only succeed if this contract has enough ETH to cover the approximate L1 call value. - * @param newParentMessenger the new parent messenger contract to be set on L2. - */ - function setChildParentMessenger(address newParentMessenger) public onlyOwner nonReentrant { - bytes memory dataSentToChild = abi.encodeWithSignature("setParentMessenger(address)", newParentMessenger); - _sendMessageToChild(dataSentToChild, childMessenger); - } - - /** - * @notice Sends a message to the child messenger via the canonical message bridge. - * @dev The caller must be the either the OracleHub or the GovernorHub. This is to send either a - * price or initiate a governance action to the OracleSpoke or GovernorSpoke on the child network. - * @dev The recipient of this message is the child messenger. The messenger must implement processMessageFromParent - * which then forwards the data to the target either the OracleSpoke or the governorSpoke depending on the caller. - * @dev This function will only succeed if this contract has enough ETH to cover the approximate L1 call value. - * @param data data message sent to the child messenger. Should be an encoded function call or packed data. - */ - function sendMessageToChild(bytes memory data) external override onlyHubContract nonReentrant { - address target = msg.sender == oracleHub ? oracleSpoke : governorSpoke; - bytes memory dataSentToChild = abi.encodeWithSignature( - "processMessageFromCrossChainParent(bytes,address)", - data, - target - ); - _sendMessageToChild(dataSentToChild, target); - } - - /** - * @notice Process a received message from the child messenger via the canonical message bridge. - * @dev The caller must be the the child messenger, sent over the canonical message bridge. - * @dev Note that only the OracleHub can receive messages from the child messenger. Therefore we can always forward - * these messages to this contract. The OracleHub must implement processMessageFromChild to handle this message. - * @param data data message sent from the child messenger. Should be an encoded function call or packed data. - */ - function processMessageFromCrossChainChild( - bytes memory data - ) public onlyFromCrossDomainAccount(childMessenger) nonReentrant { - ParentMessengerConsumerInterface(oracleHub).processMessageFromChild(childChainId, data); - emit MessageReceivedFromChild(data, childMessenger, oracleHub); - } - - /** - * @notice This function is expected to be queried by Hub contracts that need to determine how much ETH - * to include in msg.value when calling `sendMessageToChild`. - * @return Amount of msg.value to include to send cross-chain message. - */ - function getL1CallValue() - public - view - override(ParentMessengerBase, ParentMessengerInterface) - nonReentrantView - returns (uint256) - { - return _getL1CallValue(); - } - - // We need to allow this contract to receive ETH, so that it can include some msg.value amount on external calls - // to the `sendMessageToChild` function. We shouldn't expect the owner of this contract to send - // ETH because the owner is intended to be a contract (e.g. the Governor) and we don't want to change the - // Governor interface. - fallback() external payable {} - - // Used to determine how much ETH to include in msg.value when calling admin functions like - // `setChildParentMessenger` and sending messages across the bridge. - function _getL1CallValue() internal view returns (uint256) { - // This could overflow if these values are set too high, but since they are configurable by trusted owner - // we won't catch this case. - return defaultMaxSubmissionCost + defaultGasPrice * defaultGasLimit; - } - - // This function will only succeed if this contract has enough ETH to cover the approximate L1 call value. - function _sendMessageToChild(bytes memory data, address target) internal { - uint256 requiredL1CallValue = _getL1CallValue(); - require(address(this).balance >= requiredL1CallValue, "Insufficient ETH balance"); - - uint256 seqNumber = sendTxToL2NoAliassing( - childMessenger, - refundL2Address, - requiredL1CallValue, - defaultMaxSubmissionCost, - defaultGasLimit, - defaultGasPrice, - data - ); - emit MessageSentToChild( - data, - target, - requiredL1CallValue, - defaultGasLimit, - defaultGasPrice, - defaultMaxSubmissionCost, - refundL2Address, - childMessenger, - seqNumber - ); - } -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Optimism_ChildMessenger.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Optimism_ChildMessenger.sol deleted file mode 100644 index 814d40ad7..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Optimism_ChildMessenger.sol +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -// This should be replaced with a "real" import when Optimism release their new contract versions. -import "@eth-optimism/contracts/libraries/bridge/CrossDomainEnabled.sol"; -import "@eth-optimism/contracts/libraries/constants/Lib_PredeployAddresses.sol"; -import "../interfaces/ChildMessengerInterface.sol"; -import "../interfaces/ChildMessengerConsumerInterface.sol"; -import "../../common/implementation/Lockable.sol"; - -/** - * @notice Sends cross chain messages from Optimism L2 to Ethereum L1 network. - * @dev This contract is ownable via the onlyFromCrossDomainAccount. modifier, restricting ownership to the cross-domain - * parent messenger contract that lives on L1. - */ -contract Optimism_ChildMessenger is CrossDomainEnabled, ChildMessengerInterface, Lockable { - // The only child network contract that can send messages over the bridge via the messenger is the oracle spoke. - address public oracleSpoke; - - // Messenger contract on the other side of the L1<->L2 bridge. - address public parentMessenger; - - // Hard coded default gas limit for L1 transactions. - uint32 public defaultGasLimit = 5_000_000; - - event SetOracleSpoke(address newOracleSpoke); - event SetParentMessenger(address newParentMessenger); - event SetDefaultGasLimit(uint32 newDefaultGasLimit); - event MessageSentToParent(bytes data, address indexed parentAddress, address oracleSpoke, uint32 gasLimit); - event MessageReceivedFromParent(bytes data, address indexed targetSpoke, address indexed parentAddress); - - /** - * @notice Construct the Optimism_ChildMessenger contract. - * @param _parentMessenger The address of the L1 parent messenger. Acts as the "owner" of this contract. - */ - constructor(address _parentMessenger) CrossDomainEnabled(Lib_PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER) { - parentMessenger = _parentMessenger; - } - - /** - * @notice Changes the stored address of the Oracle spoke, deployed on L2. - * @dev The caller of this function must be the parent messenger, over the canonical bridge. - * @param newOracleSpoke address of the new oracle spoke, deployed on L2. - */ - function setOracleSpoke(address newOracleSpoke) public onlyFromCrossDomainAccount(parentMessenger) nonReentrant { - oracleSpoke = newOracleSpoke; - emit SetOracleSpoke(newOracleSpoke); - } - - /** - * @notice Changes the stored address of the parent messenger, deployed on L1. - * @dev The caller of this function must be the parent messenger, over the canonical bridge. - * @param newParentMessenger address of the new parent messenger, deployed on L1. - */ - function setParentMessenger( - address newParentMessenger - ) public onlyFromCrossDomainAccount(parentMessenger) nonReentrant { - parentMessenger = newParentMessenger; - emit SetParentMessenger(newParentMessenger); - } - - /** - * @notice Changes the default gas limit that is sent along with transactions to Ethereum. - * @dev The caller of this function must be the parent messenger, over the canonical bridge. - * @param newDefaultGasLimit the new L1 gas limit to be set. - */ - function setDefaultGasLimit( - uint32 newDefaultGasLimit - ) public onlyFromCrossDomainAccount(parentMessenger) nonReentrant { - defaultGasLimit = newDefaultGasLimit; - emit SetDefaultGasLimit(newDefaultGasLimit); - } - - /** - * @notice Sends a message to the parent messenger via the canonical message bridge. - * @dev The caller must be the OracleSpoke on L2. No other contract is permissioned to call this function. - * @dev The L1 target, the parent messenger, must implement processMessageFromChild to consume the message. - * @param data data message sent to the L1 messenger. Should be an encoded function call or packed data. - */ - function sendMessageToParent(bytes memory data) public override nonReentrant { - require(msg.sender == oracleSpoke, "Only callable by oracleSpoke"); - bytes memory dataSentToParent = abi.encodeWithSignature("processMessageFromCrossChainChild(bytes)", data); - sendCrossDomainMessage(parentMessenger, defaultGasLimit, dataSentToParent); - emit MessageSentToParent(dataSentToParent, parentMessenger, oracleSpoke, defaultGasLimit); - } - - /** - * @notice Process a received message from the parent messenger via the canonical message bridge. - * @dev The caller must be the the parent messenger, sent over the canonical message bridge. - * @param data data message sent from the L1 messenger. Should be an encoded function call or packed data. - * @param target desired recipient of `data`. Target must implement the `processMessageFromParent` function. Having - * this as a param enables the L1 Messenger to send messages to arbitrary addresses on the L2. This is primarily - * used to send messages to the OracleSpoke and GovernorSpoke on L2. - */ - function processMessageFromCrossChainParent( - bytes memory data, - address target - ) public onlyFromCrossDomainAccount(parentMessenger) nonReentrant { - ChildMessengerConsumerInterface(target).processMessageFromParent(data); - emit MessageReceivedFromParent(data, target, parentMessenger); - } -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Optimism_ParentMessenger.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Optimism_ParentMessenger.sol deleted file mode 100644 index f7983b296..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Optimism_ParentMessenger.sol +++ /dev/null @@ -1,107 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -// This should be replaced with a "real" import when Optimism release their new contract versions. -import "@eth-optimism/contracts/libraries/bridge/CrossDomainEnabled.sol"; -import "../interfaces/ParentMessengerInterface.sol"; -import "../interfaces/ParentMessengerConsumerInterface.sol"; -import "./ParentMessengerBase.sol"; -import "../../common/implementation/Lockable.sol"; - -/** - * @notice Sends cross chain messages from Ethereum L1 to Optimism L2 network. - * @dev This contract is ownable and should be owned by the DVM governor. - */ -contract Optimism_ParentMessenger is CrossDomainEnabled, ParentMessengerInterface, ParentMessengerBase, Lockable { - event SetDefaultGasLimit(uint32 newDefaultGasLimit); - event MessageSentToChild(bytes data, address indexed targetSpoke, uint32 gasLimit, address indexed childMessenger); - event MessageReceivedFromChild(bytes data, address indexed childMessenger, address indexed targetHub); - - uint32 public defaultGasLimit = 5_000_000; - - /** - * @notice Construct the Optimism_ParentMessenger contract. - * @param _crossDomainMessenger The address of the Optimism cross domain messenger contract. - * @param _childChainId The chain id of the Optimism L2 network this messenger should connect to. - **/ - constructor( - address _crossDomainMessenger, - uint256 _childChainId - ) CrossDomainEnabled(_crossDomainMessenger) ParentMessengerBase(_childChainId) {} - - /** - * @notice Changes the default gas limit that is sent along with transactions to Optimism. - * @dev The caller of this function must be the owner. This should be set to the DVM governor. - * @param newDefaultGasLimit the new L2 gas limit to be set. - */ - function setDefaultGasLimit(uint32 newDefaultGasLimit) public onlyOwner nonReentrant { - defaultGasLimit = newDefaultGasLimit; - emit SetDefaultGasLimit(newDefaultGasLimit); - } - - /** - * @notice Changes the address of the oracle spoke on L2 via the child messenger. - * @dev The caller of this function must be the owner. This should be set to the DVM governor. - * @param newOracleSpoke the new oracle spoke address set on L2. - */ - function setChildOracleSpoke(address newOracleSpoke) public onlyOwner nonReentrant { - bytes memory dataSentToChild = abi.encodeWithSignature("setOracleSpoke(address)", newOracleSpoke); - sendCrossDomainMessage(childMessenger, defaultGasLimit, dataSentToChild); - emit MessageSentToChild(dataSentToChild, address(0), defaultGasLimit, childMessenger); - } - - /** - * @notice Changes the address of the parent messenger on L2 via the child messenger. - * @dev The caller of this function must be the owner. This should be set to the DVM governor. - * @param newParentMessenger the new parent messenger contract to be set on L2. - */ - function setChildParentMessenger(address newParentMessenger) public onlyOwner nonReentrant { - bytes memory dataSentToChild = abi.encodeWithSignature("setParentMessenger(address)", newParentMessenger); - sendCrossDomainMessage(childMessenger, defaultGasLimit, dataSentToChild); - emit MessageSentToChild(dataSentToChild, address(0), defaultGasLimit, childMessenger); - } - - /** - * @notice Changes the Optimism_ChildMessenger default gas limit on L2 via the child messenger. - * @dev The caller of this function must be the owner. This should be set to the DVM governor. - * @param newDefaultGasLimit the new default gas limit set on L2. - */ - function setChildDefaultGasLimit(uint32 newDefaultGasLimit) public onlyOwner nonReentrant { - bytes memory dataSentToChild = abi.encodeWithSignature("setDefaultGasLimit(uint32)", newDefaultGasLimit); - sendCrossDomainMessage(childMessenger, defaultGasLimit, dataSentToChild); - emit MessageSentToChild(dataSentToChild, address(0), defaultGasLimit, childMessenger); - } - - /** - * @notice Sends a message to the child messenger via the canonical message bridge. - * @dev The caller must be the either the OracleHub or the GovernorHub. This is to send either a - * price or initiate a governance action to the OracleSpoke or GovernorSpoke on the child network. - * @dev The recipient of this message is the child messenger. The messenger must implement processMessageFromParent - * which then forwards the data to the target either the OracleSpoke or the governorSpoke depending on the caller. - * @param data data message sent to the child messenger. Should be an encoded function call or packed data. - */ - function sendMessageToChild(bytes memory data) public override onlyHubContract nonReentrant { - address target = msg.sender == oracleHub ? oracleSpoke : governorSpoke; - bytes memory dataSentToChild = abi.encodeWithSignature( - "processMessageFromCrossChainParent(bytes,address)", - data, - target - ); - sendCrossDomainMessage(childMessenger, defaultGasLimit, dataSentToChild); - emit MessageSentToChild(dataSentToChild, target, defaultGasLimit, childMessenger); - } - - /** - * @notice Process a received message from the child messenger via the canonical message bridge. - * @dev The caller must be the the child messenger, sent over the canonical message bridge. - * @dev Note that only the OracleHub can receive messages from the child messenger. Therefore we can always forward - * these messages to this contract. The OracleHub must implement processMessageFromChild to handle this message. - * @param data data message sent from the child messenger. Should be an encoded function call or packed data. - */ - function processMessageFromCrossChainChild( - bytes memory data - ) public onlyFromCrossDomainAccount(childMessenger) nonReentrant { - ParentMessengerConsumerInterface(oracleHub).processMessageFromChild(childChainId, data); - emit MessageReceivedFromChild(data, childMessenger, oracleHub); - } -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/ParentMessengerBase.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/ParentMessengerBase.sol deleted file mode 100644 index 8b52f7bf8..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/ParentMessengerBase.sol +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/access/Ownable.sol"; -import "../interfaces/ParentMessengerInterface.sol"; - -abstract contract ParentMessengerBase is Ownable, ParentMessengerInterface { - uint256 public childChainId; - - address public childMessenger; - - address public oracleHub; - address public governorHub; - - address public oracleSpoke; - address public governorSpoke; - - event SetChildMessenger(address indexed childMessenger); - event SetOracleHub(address indexed oracleHub); - event SetGovernorHub(address indexed governorHub); - event SetOracleSpoke(address indexed oracleSpoke); - event SetGovernorSpoke(address indexed governorSpoke); - - modifier onlyHubContract() { - require(msg.sender == oracleHub || msg.sender == governorHub, "Only privileged caller"); - _; - } - - /** - * @notice Construct the ParentMessengerBase contract. - * @param _childChainId The chain id of the L2 network this messenger should connect to. - **/ - constructor(uint256 _childChainId) { - childChainId = _childChainId; - } - - /******************* - * OWNER METHODS * - *******************/ - - /** - * @notice Changes the stored address of the child messenger, deployed on L2. - * @dev The caller of this function must be the owner. This should be set to the DVM governor. - * @param newChildMessenger address of the new child messenger, deployed on L2. - */ - function setChildMessenger(address newChildMessenger) public onlyOwner { - childMessenger = newChildMessenger; - emit SetChildMessenger(childMessenger); - } - - /** - * @notice Changes the stored address of the Oracle hub, deployed on L1. - * @dev The caller of this function must be the owner. This should be set to the DVM governor. - * @param newOracleHub address of the new oracle hub, deployed on L1 Ethereum. - */ - function setOracleHub(address newOracleHub) public onlyOwner { - oracleHub = newOracleHub; - emit SetOracleHub(oracleHub); - } - - /** - * @notice Changes the stored address of the Governor hub, deployed on L1. - * @dev The caller of this function must be the owner. This should be set to the DVM governor. - * @param newGovernorHub address of the new governor hub, deployed on L1 Ethereum. - */ - function setGovernorHub(address newGovernorHub) public onlyOwner { - governorHub = newGovernorHub; - emit SetGovernorHub(governorHub); - } - - /** - * @notice Changes the stored address of the oracle spoke, deployed on L2. - * @dev The caller of this function must be the owner. This should be set to the DVM governor. - * @param newOracleSpoke address of the new oracle spoke, deployed on L2. - */ - function setOracleSpoke(address newOracleSpoke) public onlyOwner { - oracleSpoke = newOracleSpoke; - emit SetOracleSpoke(oracleSpoke); - } - - /** - * @notice Changes the stored address of the governor spoke, deployed on L2. - * @dev The caller of this function must be the owner. This should be set to the DVM governor. - * @param newGovernorSpoke address of the new governor spoke, deployed on L2. - */ - function setGovernorSpoke(address newGovernorSpoke) public onlyOwner { - governorSpoke = newGovernorSpoke; - emit SetGovernorSpoke(governorSpoke); - } - - /** - * @notice Returns the amount of ETH required for a caller to pass as msg.value when calling `sendMessageToChild`. - * @return The amount of ETH required for a caller to pass as msg.value when calling `sendMessageToChild`. - */ - function getL1CallValue() external view virtual override returns (uint256) { - return 0; - } -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Polygon_ChildMessenger.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Polygon_ChildMessenger.sol deleted file mode 100644 index e93dd0b86..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Polygon_ChildMessenger.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@maticnetwork/fx-portal/contracts/tunnel/FxBaseChildTunnel.sol"; -import "../interfaces/ChildMessengerInterface.sol"; -import "../interfaces/ChildMessengerConsumerInterface.sol"; -import "../../common/implementation/Lockable.sol"; -import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; -import "../../data-verification-mechanism/implementation/Constants.sol"; -import "../../common/implementation/HasFinder.sol"; - -/** - * @notice Sends cross chain messages from Polygon to Ethereum network. - * @dev This contract extends the `FxBaseChildTunnel` contract and therefore is 1-to-1 mapped with the - * `FxBaseRootTunnel` extended by the `Polygon_ParentMessenger` contract deployed on Polygon. This mapping ensures that - * the internal `_processMessageFromRoot` function is only callable indirectly by the `Polygon_ParentMessenger`. - */ -contract Polygon_ChildMessenger is FxBaseChildTunnel, ChildMessengerInterface, Lockable, HasFinder { - event MessageSentToParent(bytes data, address indexed targetHub, address indexed oracleSpoke); - event MessageReceivedFromParent(address indexed targetSpoke, bytes dataToSendToTarget); - - /** - * @notice Construct the Polygon_ChildMessenger contract. - * @param _finder Used to locate contracts for this network. - * @param _fxChild Polygon system contract deployed on Mainnet, required to construct new FxBaseRootTunnel - * that can send messages via native Polygon data tunnel. - */ - constructor(address _fxChild, address _finder) FxBaseChildTunnel(_fxChild) HasFinder(_finder) {} - - /** - * @notice Sends a message to the OracleSpoke via the parent messenger and the canonical message bridge. - * @dev The caller must be the OracleSpoke on child network. No other contract is permissioned to call this - * function. - * @dev The L1 target, the parent messenger, must implement processMessageFromChild to consume the message. - * @param data data message sent to the L1 messenger. Should be an encoded function call or packed data. - */ - function sendMessageToParent(bytes memory data) public override nonReentrant { - require(msg.sender == getOracleSpoke(), "Only callable by oracleSpoke"); - _sendMessageToRoot(abi.encode(data, getOracleHub())); - emit MessageSentToParent(data, getOracleHub(), getOracleSpoke()); - } - - /** - * @notice Process a received message from the parent messenger via the canonical message bridge. - * @dev The data will be received automatically from the state receiver when the state is synced between Ethereum - * and Polygon. This will revert if the Root chain sender is not the `fxRootTunnel` contract. - * @dev This call will revert if `setFxRoot` has not been called and the `sender` is not set to the - * FxRoot contract address. FxRoot should be set to Polygon_ParentMessenger. - * @param sender The sender of `data` from the Root chain. - * @param data ABI encoded params with which to call function on OracleHub or GovernorHub. - */ - function _processMessageFromRoot( - uint256 /* stateId */, - address sender, - bytes memory data - ) internal override validateSender(sender) nonReentrant { - (bytes memory dataToSendToTarget, address target) = abi.decode(data, (bytes, address)); - ChildMessengerConsumerInterface(target).processMessageFromParent(dataToSendToTarget); - emit MessageReceivedFromParent(target, dataToSendToTarget); - } - - function getOracleSpoke() public view returns (address) { - return finder.getImplementationAddress(OracleInterfaces.OracleSpoke); - } - - function getOracleHub() public view returns (address) { - return finder.getImplementationAddress(OracleInterfaces.OracleHub); - } -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Polygon_ParentMessenger.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Polygon_ParentMessenger.sol deleted file mode 100644 index efdf82f85..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/Polygon_ParentMessenger.sol +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@maticnetwork/fx-portal/contracts/tunnel/FxBaseRootTunnel.sol"; -import "../interfaces/ParentMessengerInterface.sol"; -import "../interfaces/ParentMessengerConsumerInterface.sol"; -import "./ParentMessengerBase.sol"; -import "../../common/implementation/Lockable.sol"; - -/** - * @notice Sends cross chain messages from Ethereum to Polygon network. - * @dev This contract extends the `FxBaseRootTunnel` contract and therefore is 1-to-1 mapped with the - * `FxBaseChildTunnel` extended by the `Polygon_ChildMessenger` contract deployed on Polygon. This mapping ensures that - * the internal `_processMessageFromChild` function is only callable indirectly by the `Polygon_ChildMessenger`. - */ -contract Polygon_ParentMessenger is FxBaseRootTunnel, ParentMessengerInterface, ParentMessengerBase, Lockable { - event MessageSentToChild(bytes data, address indexed targetSpoke); - event MessageReceivedFromChild(address indexed targetHub, bytes dataToSendToTarget); - - /** - * @notice Construct the Optimism_ParentMessenger contract. - * @param _checkpointManager The address of the Polygon checkpoint manager deployed on Mainnet. Required to - * construct new FxBaseRootTunnel that can connect to native Polygon data tunnel. - * @param _fxRoot Polygon system contract deployed on Mainnet, required to construct new FxBaseRootTunnel - * that can send messages via native Polygon data tunnel. - * @param _childChainId The chain id of the Optimism L2 network this messenger should connect to. - **/ - constructor( - address _checkpointManager, - address _fxRoot, - uint256 _childChainId - ) FxBaseRootTunnel(_checkpointManager, _fxRoot) ParentMessengerBase(_childChainId) {} - - /** - * @notice Sends a message to the child messenger via the canonical message bridge. - * @dev The caller must be the either the OracleHub or the GovernorHub. This is to send either a - * price or initiate a governance action to the OracleSpoke or GovernorSpoke on the child chain. - * @dev The recipient of this message is the child messenger. The messenger must implement _processMessageFromRoot - * which then forwards the data to the target either the OracleSpoke or the governorSpoke depending on the caller. - * @param data data message sent to the child messenger. Should be an encoded function call or packed data. - */ - function sendMessageToChild(bytes memory data) public override onlyHubContract nonReentrant { - address target = msg.sender == oracleHub ? oracleSpoke : governorSpoke; - bytes memory dataToSendToChild = abi.encode(data, target); - _sendMessageToChild(dataToSendToChild); - emit MessageSentToChild(dataToSendToChild, target); - } - - /** - * @notice Process a received message from the child messenger via the canonical message bridge. - * @dev This internal method will be called inside `FxBaseRootTunnel.receiveMessage(bytes memory inputData)`, - * which must be called by an EOA to finalize the relay of the message from Polygon to Ethereum. - * The `inputData` is a proof of transaction that is derived from the transaction hash of the transaction on the - * child chain that originated the cross-chain price request via _sendMessageToRoot. - * @dev This call will revert if `setFxChild` has not been called. Fx Child should be set to Polygon_ChildMessenger. - * @param data ABI encoded params with which to call function on OracleHub or GovernorHub. - */ - function _processMessageFromChild(bytes memory data) internal override nonReentrant { - // We know that this internal execution can only be triggered by the ChildMessenger, which inherits - // FxBaseChildTunnel and is mapped 1-to-1 with this contract's FxBaseRootTunnel via - // `setFxRootTunnel/setFxChildTunnel`. - (bytes memory dataToSendToTarget, address target) = abi.decode(data, (bytes, address)); - ParentMessengerConsumerInterface(target).processMessageFromChild(childChainId, dataToSendToTarget); - emit MessageReceivedFromChild(target, dataToSendToTarget); - } -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Arbitrum_inboxMock.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Arbitrum_inboxMock.sol deleted file mode 100644 index 480622a7e..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Arbitrum_inboxMock.sol +++ /dev/null @@ -1,51 +0,0 @@ -pragma solidity ^0.8.0; - -import "../../../external/avm/interfaces/iArbitrum_Inbox.sol"; -import "../../../cross-chain-oracle/chain-adapters/Arbitrum_ParentMessenger.sol"; - -contract Arbitrum_OutboxMock { - function l2ToL1Sender() external view returns (address) { - // Function not called in tests, only smocked. - return address(this); - } -} - -contract Arbitrum_BridgeMock { - address public outbox; - - function setOutbox(address _outbox) external { - outbox = _outbox; - } - - function activeOutbox() external view returns (address) { - return outbox; - } - - // This function can be called by an EOA to send a call to the parent messenger, which is important in tests - // because `processMessageFromCrossChainChild` can only be called by the Bridge contract. - function processMessageFromCrossChainChild(address payable messengerToCall, bytes memory data) external { - Arbitrum_ParentMessenger(messengerToCall).processMessageFromCrossChainChild(data); - } -} - -contract Arbitrum_InboxMock is iArbitrum_Inbox { - // We leave these unused function parameters named because this contract is used with smockit and makes testing - // this function's call inputs easier. - function createRetryableTicketNoRefundAliasRewrite( - address destAddr, - uint256 l2CallValue, - uint256 maxSubmissionCost, - address excessFeeRefundAddress, - address callValueRefundAddress, - uint256 maxGas, - uint256 gasPriceBid, - bytes calldata data - ) external payable override returns (uint256) { - return 0; - } - - function bridge() external view returns (address) { - // Function not called in tests, only smocked. - return address(this); - } -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/OVM_L1CrossDomainMessengerMock.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/OVM_L1CrossDomainMessengerMock.sol deleted file mode 100644 index ce35bdfb1..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/OVM_L1CrossDomainMessengerMock.sol +++ /dev/null @@ -1,12 +0,0 @@ -pragma solidity ^0.8.0; -import "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol"; - -contract OVM_L1CrossDomainMessengerMock is ICrossDomainMessenger { - function xDomainMessageSender() external view override returns (address) { - // Trivial return this contract's address. - return address(this); - } - - function sendMessage(address _target, bytes calldata _message, uint32 _gasLimit) external override {} - // Do nothing. -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/ParentMessengerBaseMock.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/ParentMessengerBaseMock.sol deleted file mode 100644 index 6df37ee28..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/ParentMessengerBaseMock.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../ParentMessengerBase.sol"; - -contract ParentMessengerBaseMock is ParentMessengerBase { - constructor(uint256 _childChainId) ParentMessengerBase(_childChainId) {} - - function sendMessageToChild(bytes memory) public view override { - require(false, "unused function"); - } -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Polygon_ChildMessengerMock.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Polygon_ChildMessengerMock.sol deleted file mode 100644 index dc590cf2e..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Polygon_ChildMessengerMock.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../Polygon_ChildMessenger.sol"; - -contract Polygon_ChildMessengerMock is Polygon_ChildMessenger { - constructor(address _fxChild, address _finder) Polygon_ChildMessenger(_fxChild, _finder) {} - - function processMessageFromRoot(address sender, bytes memory data) external { - _processMessageFromRoot( - 1, // Unused param set to arbitrary value. - sender, - data - ); - } -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Polygon_ParentMessengerMock.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Polygon_ParentMessengerMock.sol deleted file mode 100644 index 386b0755d..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/chain-adapters/test/Polygon_ParentMessengerMock.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../Polygon_ParentMessenger.sol"; - -contract Polygon_ParentMessengerMock is Polygon_ParentMessenger { - constructor( - address _checkpointManager, - address _fxRoot, - uint256 _childChainId - ) Polygon_ParentMessenger(_checkpointManager, _fxRoot, _childChainId) {} - - function processMessageFromChild(bytes memory data) external { - _processMessageFromChild(data); - } -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ChildMessengerConsumerInterface.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ChildMessengerConsumerInterface.sol deleted file mode 100644 index ed5448a23..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ChildMessengerConsumerInterface.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -interface ChildMessengerConsumerInterface { - // Called on L2 by child messenger. - function processMessageFromParent(bytes memory data) external; -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ChildMessengerInterface.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ChildMessengerInterface.sol deleted file mode 100644 index 5e7a0d759..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ChildMessengerInterface.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -interface ChildMessengerInterface { - // Should send cross-chain message to Parent messenger contract or revert. - function sendMessageToParent(bytes memory data) external; -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ParentMessengerConsumerInterface.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ParentMessengerConsumerInterface.sol deleted file mode 100644 index 24fcf3209..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ParentMessengerConsumerInterface.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -interface ParentMessengerConsumerInterface { - // Function called on Oracle hub to pass in data send from L2, with chain ID. - function processMessageFromChild(uint256 chainId, bytes memory data) external; -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ParentMessengerInterface.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ParentMessengerInterface.sol deleted file mode 100644 index 5d909c76a..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/interfaces/ParentMessengerInterface.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -interface ParentMessengerInterface { - // Should send cross-chain message to Child messenger contract or revert. - function sendMessageToChild(bytes memory data) external; - - // Informs Hub how much msg.value they need to include to call `sendMessageToChild`. - function getL1CallValue() external view returns (uint256); -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/test/GovernorMessengerMock.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/test/GovernorMessengerMock.sol deleted file mode 100644 index cf9b646c4..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/test/GovernorMessengerMock.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../GovernorSpoke.sol"; - -/** - * @notice Can be used as either a Parent or Child messenger mock in unit tests for the Governor Hub and Spoke - * cross-chain contracts. The reason that this can't also be used for the Oracle Hub and Spoke is that the - * sendMessageToChild is called with different encoded data in the Oracle versus the Governor. - */ -contract GovernorMessengerMock { - GovernorSpoke.Call[] private _latestCalls; - - function latestCalls() external view returns (GovernorSpoke.Call[] memory) { - return _latestCalls; - } - - function sendMessageToChild(bytes memory data) external { - delete _latestCalls; - GovernorSpoke.Call[] memory calls = abi.decode(data, (GovernorSpoke.Call[])); - for (uint256 i = 0; i < calls.length; i++) _latestCalls.push(calls[i]); - } -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/test/OracleBaseMock.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/test/OracleBaseMock.sol deleted file mode 100644 index 237613fad..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/test/OracleBaseMock.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../OracleBase.sol"; - -/** - * @title Test implementation of OracleBase enabling unit tests on internal methods. - */ -contract OracleBaseMock is OracleBase { - constructor(address _finderAddress) HasFinder(_finderAddress) {} - - function requestPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData) public returns (bool) { - return _requestPrice(identifier, time, ancillaryData); - } - - function encodePriceRequest( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) public pure returns (bytes32) { - return _encodePriceRequest(identifier, time, ancillaryData); - } - - function publishPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData, int256 price) public { - _publishPrice(identifier, time, ancillaryData, price); - } -} diff --git a/contracts/external/uma/core/contracts/cross-chain-oracle/test/OracleMessengerMock.sol b/contracts/external/uma/core/contracts/cross-chain-oracle/test/OracleMessengerMock.sol deleted file mode 100644 index 3d90b4907..000000000 --- a/contracts/external/uma/core/contracts/cross-chain-oracle/test/OracleMessengerMock.sol +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../OracleHub.sol"; -import "../OracleSpoke.sol"; -import "../interfaces/ParentMessengerInterface.sol"; - -/** - * @notice Can be used as either a Parent or Child messenger mock in unit tests for the Oracle Hub and Spoke - * cross-chain contracts. The reason that this can't also be used for the Governor Hub and Spoke is that the - * sendMessageToChild is called with different encoded data in the Oracle versus the Governor. - */ -contract OracleMessengerMock is ParentMessengerInterface { - bytes public latestAncillaryData; - uint256 public latestTime; - bytes32 public latestIdentifier; - int256 public latestPrice; - - uint256 public messageCount; - - // OracleHub calls `sendMessageToChild` - function sendMessageToChild(bytes memory data) external override { - (latestIdentifier, latestTime, latestAncillaryData, latestPrice) = abi.decode( - data, - (bytes32, uint256, bytes, int256) - ); - messageCount++; - } - - function getL1CallValue() public pure override returns (uint256) { - return 0; - } - - // This calls `processMessageFromChild` on OracleHub - function requestPrice( - address oracleHub, - uint256 chainId, - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) external { - OracleHub(oracleHub).processMessageFromChild(chainId, abi.encode(identifier, time, ancillaryData)); - } - - // OracleSpoke calls `sendMessageToParent` - function sendMessageToParent(bytes memory data) external { - (latestIdentifier, latestTime, latestAncillaryData) = abi.decode(data, (bytes32, uint256, bytes)); - messageCount++; - } - - // This calls `processMessageFromParent` on OracleSpoke - function publishPrice( - address oracleSpoke, - bytes32 identifier, - uint256 time, - bytes memory ancillaryData, - int256 price - ) external { - OracleSpoke(oracleSpoke).processMessageFromParent(abi.encode(identifier, time, ancillaryData, price)); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/README.md b/contracts/external/uma/core/contracts/data-verification-mechanism/README.md deleted file mode 100644 index 8d11499f5..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/README.md +++ /dev/null @@ -1,114 +0,0 @@ -# Deployments - -Follow these instructions to deploy DVMv2 on a new L1 testnet. Note that mainnet deployments used their dedicated -upgrade scripts when migrating from DVMv1. - -Cross chain setup between testnets is outside the scope of these instructions. - -## Setup - -Before running anything below, make sure you have run `yarn build` from the root of the repo. - -Note that the fork network instructions can be used as a test run prior to the public network deployments. - -## Starting a fork - -To run a forked network deployment, run the following command: - -```sh -HARDHAT_CHAIN_ID= yarn hardhat node --fork --no-deploy --port -``` - -You'll need to run two forks in separate terminals on different ports to do the deployments below. - -Note: in the commands below, you'll need to set the relevant `NODE_URL_X` environment variable to the url of the locally -forked network `http://localhost:`. - -If you're having trouble redeploying contracts because `hardhat` wants to "reuse" contracts, then delete the associated -networks file under [packages/core/networks](/packages/core/networks) and run `yarn clean && yarn` in the `core` -package to reset `deployments`. - -## Step-by-step guide to deployment on Sepolia testnet - -The steps below explain how to deploy DVMv2 on Sepolia testnet. - -1. Start by exporting some environment variables (or storing them in a .env): - -```sh -# Set only the following environment variables based on which L1 testnet you're deploying to. For example, if you're -# deploying to Sepolia, set NODE_URL_11155111. -# When running against a forked network, set the URL to http://localhost: -export NODE_URL_11155111= -export MNEMONIC="Your 12-word mnemonic here" -export ETHERSCAN_API_KEY="Your testnet Etherscan API key" -``` - -2. Add new network variables in `@uma/common` package: - -- Hardhat `defaultConfig.networks` configuration in [packages/common/src/HardhatConfig.ts](/packages/common/src/HardhatConfig.ts). -- `etherscan.customChains` configuration in [packages/common/src/HardhatConfig.ts](/packages/common/src/HardhatConfig.ts) - if the testnet is not supported by `@nomicfoundation/hardhat-verify` library (not required for Sepolia). -- `PublicNetworks` configuration in [packages/common/src/PublicNetworks.ts](/packages/common/src/PublicNetworks.ts) -- Default RPC to `getNodeUrl` in [packages/common/src/ProviderUtils.ts](/packages/common/src/ProviderUtils.ts) if the - testnet is not supported by Infura (not required for Sepolia). -- Rebuild `@uma/common` package: - - ```sh - cd packages/common - yarn build - ``` - -3. Deploy L1 testnet contracts: - -```sh -# Replace --network parameter if deploying in other testnet than Sepolia. -yarn hardhat deploy --network sepolia --tags dvmv2,MockOracle -``` - -Add all the deployed contracts to the associated networks file under [packages/core/networks](/packages/core/networks). - -4. Setup DVM contracts with Mock Oracle: - -```sh -# Replace --network parameter if deploying in other testnet than Sepolia. -yarn hardhat setup-dvmv2-testnet --network sepolia --mockoracle -``` - -This would resolve price requests via mocked Oracle contract. If instead you need to resolve them via `VotingV2`, just -skip the `--mockoracle` flag. - -5. Deploy and setup `OptimisticOracleV3` contract. - -Before deploying `OptimisticOracleV3` first, approve its default price identifier and bonding currency, e.g.: - -```sh -yarn hardhat whitelist-identifiers --network sepolia --id "ASSERT_TRUTH" -yarn hardhat whitelist-collateral --network sepolia --address 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238 -``` - -Now deploy `OptimisticOracleV3`: - -```sh -# Replace --network parameter if deploying in other testnet than Sepolia. -yarn hardhat deploy --network sepolia --tags OptimisticOracleV3 -``` - -Add the deployed `OptimisticOracleV3` contract to the associated networks file under [packages/core/networks](/packages/core/networks). - -Also re-run DVM setup script from step 4 above, so that `OptimisticOracleV3` gets registered and synced: - -```sh -yarn hardhat setup-dvmv2-testnet --network sepolia --mockoracle -``` - -6. Verify contracts: - -```sh -# etherscan-verify does not work on Sepolia so we use hardhat verify on each deployed contract above. -yarn hardhat --network sepolia verify --contract -``` - -In case of more complex constructor arguments, pass them from file `--constructor-args arguments.js` as discussed in -[hardhat-verify docs](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify#complex-arguments). -In order to get constructor argument values, check them in `args` property from respective contract deployment json -file (appropriate network directory under `packages/core/deployments`). diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/AdminIdentifierLib.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/AdminIdentifierLib.sol deleted file mode 100644 index 42f82b7f8..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/AdminIdentifierLib.sol +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -/** - * @title Library to construct admin identifiers. - */ -library AdminIdentifierLib { - // Returns a UTF-8 identifier representing a particular admin proposal. - // The identifier is of the form "Admin n", where n is the proposal id provided. - function _constructIdentifier(uint256 id) internal pure returns (bytes32) { - bytes32 bytesId = _uintToUtf8(id); - return _addPrefix(bytesId, "Admin ", 6); - } - - // This method converts the integer `v` into a base-10, UTF-8 representation stored in a `bytes32` type. - // If the input cannot be represented by 32 base-10 digits, it returns only the highest 32 digits. - // This method is based off of this code: https://ethereum.stackexchange.com/a/6613/47801. - function _uintToUtf8(uint256 v) internal pure returns (bytes32) { - bytes32 ret; - if (v == 0) { - // Handle 0 case explicitly. - ret = "0"; - } else { - // Constants. - uint256 bitsPerByte = 8; - uint256 base = 10; // Note: the output should be base-10. The below implementation will not work for bases > 10. - uint256 utf8NumberOffset = 48; - while (v > 0) { - // Downshift the entire bytes32 to allow the new digit to be added at the "front" of the bytes32, which - // translates to the beginning of the UTF-8 representation. - ret = ret >> bitsPerByte; - - // Separate the last digit that remains in v by modding by the base of desired output representation. - uint256 leastSignificantDigit = v % base; - - // Digits 0-9 are represented by 48-57 in UTF-8, so an offset must be added to create the character. - bytes32 utf8Digit = bytes32(leastSignificantDigit + utf8NumberOffset); - - // The top byte of ret has already been cleared to make room for the new digit. - // Upshift by 31 bytes to put it in position, and OR it with ret to leave the other characters untouched. - ret |= utf8Digit << (31 * bitsPerByte); - - // Divide v by the base to remove the digit that was just added. - v /= base; - } - } - return ret; - } - - // This method takes two UTF-8 strings represented as bytes32 and outputs one as a prefixed by the other. - // `input` is the UTF-8 that should have the prefix prepended. - // `prefix` is the UTF-8 that should be prepended onto input. - // `prefixLength` is number of UTF-8 characters represented by `prefix`. - // Notes: - // 1. If the resulting UTF-8 is larger than 32 characters, then only the first 32 characters will be represented - // by the bytes32 output. - // 2. If `prefix` has more characters than `prefixLength`, the function will produce an invalid result. - function _addPrefix(bytes32 input, bytes32 prefix, uint256 prefixLength) internal pure returns (bytes32) { - // Downshift `input` to open space at the "front" of the bytes32 - bytes32 shiftedInput = input >> (prefixLength * 8); - return shiftedInput | prefix; - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ContractCreator.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ContractCreator.sol deleted file mode 100644 index 522b998c8..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ContractCreator.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../interfaces/FinderInterface.sol"; -import "../../common/implementation/AddressWhitelist.sol"; -import "./Registry.sol"; -import "./Constants.sol"; - -/** - * @title Base contract for all financial contract creators - */ -abstract contract ContractCreator { - address internal finderAddress; - - constructor(address _finderAddress) { - finderAddress = _finderAddress; - } - - function _requireWhitelistedCollateral(address collateralAddress) internal view { - FinderInterface finder = FinderInterface(finderAddress); - AddressWhitelist collateralWhitelist = AddressWhitelist( - finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist) - ); - require(collateralWhitelist.isOnWhitelist(collateralAddress), "Collateral not whitelisted"); - } - - function _registerContract(address[] memory parties, address contractToRegister) internal { - FinderInterface finder = FinderInterface(finderAddress); - Registry registry = Registry(finder.getImplementationAddress(OracleInterfaces.Registry)); - registry.registerContract(parties, contractToRegister); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVoting.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVoting.sol deleted file mode 100644 index 643e33317..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVoting.sol +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/MultiRole.sol"; -import "../../common/implementation/Withdrawable.sol"; -import "../interfaces/VotingAncillaryInterface.sol"; -import "../interfaces/FinderInterface.sol"; -import "./Constants.sol"; - -/** - * @title Proxy to allow voting from another address. - * @dev Allows a UMA token holder to designate another address to vote on their behalf. - * Each voter must deploy their own instance of this contract. - */ -contract DesignatedVoting is Withdrawable { - /**************************************** - * INTERNAL VARIABLES AND STORAGE * - ****************************************/ - - enum Roles { - Owner, // Can set the Voter role. Is also permanently permissioned as the minter role. - Voter // Can vote through this contract. - } - - // Reference to the UMA Finder contract, allowing Voting upgrades to be performed - // without requiring any calls to this contract. - FinderInterface private finder; - - /** - * @notice Construct the DesignatedVoting contract. - * @param finderAddress keeps track of all contracts within the system based on their interfaceName. - * @param ownerAddress address of the owner of the DesignatedVoting contract. - * @param voterAddress address to which the owner has delegated their voting power. - */ - constructor(address finderAddress, address ownerAddress, address voterAddress) { - _createExclusiveRole(uint256(Roles.Owner), uint256(Roles.Owner), ownerAddress); - _createExclusiveRole(uint256(Roles.Voter), uint256(Roles.Owner), voterAddress); - _setWithdrawRole(uint256(Roles.Owner)); - - finder = FinderInterface(finderAddress); - } - - /**************************************** - * VOTING AND REWARD FUNCTIONALITY * - ****************************************/ - - /** - * @notice Forwards a commit to Voting. - * @param identifier uniquely identifies the feed for this vote. EG BTC/USD price pair. - * @param time specifies the unix timestamp of the price being voted on. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - * @param hash the keccak256 hash of the price you want to vote for and a random integer salt value. - */ - function commitVote( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData, - bytes32 hash - ) external onlyRoleHolder(uint256(Roles.Voter)) { - _getVotingAddress().commitVote(identifier, time, ancillaryData, hash); - } - - /** - * @notice Forwards a batch commit to Voting. - * @param commits struct to encapsulate an `identifier`, `time`, `hash` and optional `encryptedVote`. - */ - function batchCommit( - VotingAncillaryInterface.CommitmentAncillary[] calldata commits - ) external onlyRoleHolder(uint256(Roles.Voter)) { - _getVotingAddress().batchCommit(commits); - } - - /** - * @notice Forwards a reveal to Voting. - * @param identifier voted on in the commit phase. EG BTC/USD price pair. - * @param time specifies the unix timestamp of the price being voted on. - * @param price used along with the `salt` to produce the `hash` during the commit phase. - * @param salt used along with the `price` to produce the `hash` during the commit phase. - */ - function revealVote( - bytes32 identifier, - uint256 time, - int256 price, - bytes memory ancillaryData, - int256 salt - ) external onlyRoleHolder(uint256(Roles.Voter)) { - _getVotingAddress().revealVote(identifier, time, price, ancillaryData, salt); - } - - /** - * @notice Forwards a batch reveal to Voting. - * @param reveals is an array of the Reveal struct which contains an identifier, time, price and salt. - */ - function batchReveal( - VotingAncillaryInterface.RevealAncillary[] calldata reveals - ) external onlyRoleHolder(uint256(Roles.Voter)) { - _getVotingAddress().batchReveal(reveals); - } - - /** - * @notice Forwards a reward retrieval to Voting. - * @dev Rewards are added to the tokens already held by this contract. - * @param roundId defines the round from which voting rewards will be retrieved from. - * @param toRetrieve an array of PendingRequests which rewards are retrieved from. - * @return amount of rewards that the user should receive. - */ - function retrieveRewards( - uint256 roundId, - VotingAncillaryInterface.PendingRequestAncillary[] memory toRetrieve - ) public onlyRoleHolder(uint256(Roles.Voter)) returns (FixedPoint.Unsigned memory) { - return _getVotingAddress().retrieveRewards(address(this), roundId, toRetrieve); - } - - function _getVotingAddress() private view returns (VotingAncillaryInterface) { - return VotingAncillaryInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingFactory.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingFactory.sol deleted file mode 100644 index 6ff29591f..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingFactory.sol +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/Withdrawable.sol"; -import "./DesignatedVoting.sol"; - -/** - * @title Factory to deploy new instances of DesignatedVoting and look up previously deployed instances. - * @dev Allows off-chain infrastructure to look up a hot wallet's deployed DesignatedVoting contract. - */ -contract DesignatedVotingFactory is Withdrawable { - /**************************************** - * INTERNAL VARIABLES AND STORAGE * - ****************************************/ - - enum Roles { - Withdrawer // Can withdraw any ETH or ERC20 sent accidentally to this contract. - } - - address private finder; - mapping(address => DesignatedVoting) public designatedVotingContracts; - - /** - * @notice Construct the DesignatedVotingFactory contract. - * @param finderAddress keeps track of all contracts within the system based on their interfaceName. - */ - constructor(address finderAddress) { - finder = finderAddress; - - _createWithdrawRole(uint256(Roles.Withdrawer), uint256(Roles.Withdrawer), msg.sender); - } - - /** - * @notice Deploys a new `DesignatedVoting` contract. - * @param ownerAddress defines who will own the deployed instance of the designatedVoting contract. - * @return designatedVoting a new DesignatedVoting contract. - */ - function newDesignatedVoting(address ownerAddress) external returns (DesignatedVoting) { - DesignatedVoting designatedVoting = new DesignatedVoting(finder, ownerAddress, msg.sender); - designatedVotingContracts[msg.sender] = designatedVoting; - return designatedVoting; - } - - /** - * @notice Associates a `DesignatedVoting` instance with `msg.sender`. - * @param designatedVotingAddress address to designate voting to. - * @dev This is generally only used if the owner of a `DesignatedVoting` contract changes their `voter` - * address and wants that reflected here. - */ - function setDesignatedVoting(address designatedVotingAddress) external { - designatedVotingContracts[msg.sender] = DesignatedVoting(designatedVotingAddress); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingV2.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingV2.sol deleted file mode 100644 index 712072e3d..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingV2.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "../../common/implementation/MultiCaller.sol"; -import "../../common/implementation/Stakeable.sol"; -import "../interfaces/FinderInterface.sol"; -import "./Constants.sol"; - -/** - * @title Proxy to allow voting from another address. - * @dev Allows a UMA token holder to designate another address to vote on their behalf. - * Each voter must deploy their own instance of this contract. - */ -contract DesignatedVotingV2 is Stakeable, MultiCaller { - /**************************************** - * INTERNAL VARIABLES AND STORAGE * - ****************************************/ - - enum Roles { - Owner, // Can set the Voter role. - Voter // Can vote through this contract. - } - - // Reference to UMA Finder contract, allowing Voting upgrades to be without requiring any calls to this contract. - FinderInterface public immutable finder; - - /** - * @notice Construct the DesignatedVotingV2 contract. - * @param finderAddress keeps track of all contracts within the system based on their interfaceName. - * @param ownerAddress address of the owner of the DesignatedVotingV2 contract. - * @param voterAddress address to which the owner has delegated their voting power. - */ - constructor(address finderAddress, address ownerAddress, address voterAddress) { - _createExclusiveRole(uint256(Roles.Owner), uint256(Roles.Owner), ownerAddress); - _createExclusiveRole(uint256(Roles.Voter), uint256(Roles.Owner), voterAddress); - _setWithdrawRole(uint256(Roles.Owner)); - _setStakeRole(uint256(Roles.Owner)); - - finder = FinderInterface(finderAddress); - } - - /** - * @notice This method essentially syncs the voter role with the current voting delegate. - * @dev Because this is essentially a state sync method, there is no reason to restrict its permissioning. - */ - function delegateToVoter() public { - address voter = getMember(uint256(Roles.Voter)); - _getVotingContract().setDelegate(voter); - } - - // Returns the Voting contract address, named "Oracle" in the finder. - function _getVotingContract() private view returns (StakerInterface) { - return StakerInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingV2Factory.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingV2Factory.sol deleted file mode 100644 index 04b64f238..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/DesignatedVotingV2Factory.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "./DesignatedVotingV2.sol"; -import "../../common/implementation/MultiCaller.sol"; - -/** - * @title Factory to deploy new instances of DesignatedVotingV2 and look up previously deployed instances. - * @dev Allows off-chain infrastructure to look up a hot wallet's deployed DesignatedVoting contract. - */ -contract DesignatedVotingV2Factory is MultiCaller { - address public immutable finder; // Finder contract that stores addresses of UMA system contracts. - - event NewDesignatedVoting(address indexed voter, address indexed owner, address indexed designatedVoting); - - /** - * @notice Construct the DesignatedVotingFactory contract. - * @param _finder keeps track of all contracts within the system based on their interfaceName. - */ - constructor(address _finder) { - finder = _finder; - } - - /** - * @notice Deploys a new `DesignatedVoting` contract. - * @param owner defines who will own the deployed instance of the designatedVoting contract. - * @param voter defines who will be able to vote on behalf of the owner, using the designatedVoting contract. - * @return designatedVoting a new DesignatedVoting contract. - */ - function newDesignatedVoting(address owner, address voter) external returns (DesignatedVotingV2) { - DesignatedVotingV2 designatedVoting = new DesignatedVotingV2(finder, owner, voter); - - emit NewDesignatedVoting(voter, owner, address(designatedVoting)); - - return designatedVoting; - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/EmergencyProposer.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/EmergencyProposer.sol deleted file mode 100644 index 0ce2f69ca..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/EmergencyProposer.sol +++ /dev/null @@ -1,260 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "./GovernorV2.sol"; -import "../../common/implementation/Lockable.sol"; -import "../../common/implementation/MultiCaller.sol"; -import "@openzeppelin/contracts-v4/access/Ownable.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; - -/** - * @title Emergency Proposer contract - * @dev This is a contract that allows anyone to construct an emergency recovery transaction to bypass the - * standard voting process by submitting a very large bond, which is considered a quorum in this case. This bond is - * expected to be about as large as the GAT in the VotingV2 contract. If a proposal is considered invalid, UMA token - * holders can vote to slash and remove this proposal through the standard governance flow. If valid, a proposal must - * wait minimumWaitTime before it can be executed and it can only be executed by a privileged account, executor. This - * includes three tiers of protection to ensure that abuse is extremely risky both from creating market volatility in - * the underlying token and the threat of the locked tokens being slashed. - */ -contract EmergencyProposer is Ownable, Lockable, MultiCaller { - using SafeERC20 for IERC20; - - /**************************************** - * EMERGENCY PROPOSAL STATE * - ****************************************/ - - // Identifies a unique emergency proposal. - struct EmergencyProposal { - address sender; // Sender of the proposal. Address that receives the bond refund in the case of execution. - uint64 expiryTime; // Time at which the proposal expires and can be executed. - uint256 lockedTokens; // Tokens locked for the proposal. Tokens are returned when proposal is executed. - GovernorV2.Transaction[] transactions; // Array of transactions to be executed in the emergency action. - } - - // Array of all proposed emergency proposals. - EmergencyProposal[] public emergencyProposals; - - // UMA Governor, used to execute transactions. The Governor is the owner of all other UMA ecosystem contracts. - GovernorV2 public immutable governor; - - // Voting token, used to bond proposes. - IERC20 public immutable token; - - // The number of tokens needed to propose an emergency action. - uint256 public quorum; - - // The minimum time that must elapse between from when a proposal is created to when it can be executed. - uint64 public minimumWaitTime; - - // The only address that can execute an emergency proposal. Will be set to a multisig. Acts to guardrail the - // emergency recovery mechanism and ensure that only valid proposals can be executed. Note that while this address - // is somewhat privileged, it can't unilaterally push through proposals as a proposal must pass the minimum wait - // time without the DVM voters voting to slash the proposal. - address public executor; - - /**************************************** - * EVENTS * - ****************************************/ - - event QuorumSet(uint256 quorum); - event ExecutorSet(address executor); - event MinimumWaitTimeSet(uint256 minimumWaitTime); - event EmergencyTransactionsProposed( - uint256 indexed id, - address indexed sender, - address indexed caller, - uint64 expiryTime, - uint256 lockedTokens, - GovernorV2.Transaction[] transactions - ); - event EmergencyProposalRemoved( - uint256 indexed id, - address indexed sender, - address indexed caller, - uint64 expiryTime, - uint256 lockedTokens, - GovernorV2.Transaction[] transactions - ); - event EmergencyProposalSlashed( - uint256 indexed id, - address indexed sender, - address indexed caller, - uint64 expiryTime, - uint256 lockedTokens, - GovernorV2.Transaction[] transactions - ); - event EmergencyProposalExecuted( - uint256 indexed id, - address indexed sender, - address indexed caller, - uint64 expiryTime, - uint256 lockedTokens, - GovernorV2.Transaction[] transactions - ); - - /** - * @notice Construct the EmergencyProposer contract. - * @param _token the ERC20 token that the quorum is in. - * @param _quorum the tokens needed to propose an emergency action. - * @param _governor the governor contract that this contract makes proposals to. - * @param _executor the address that can execute an emergency proposal. - * @param _minimumWaitTime the minimum time that must elapse between from when a proposal is created to when it can - * be executed. - */ - constructor(IERC20 _token, uint256 _quorum, GovernorV2 _governor, address _executor, uint64 _minimumWaitTime) { - token = _token; - governor = _governor; - setExecutor(_executor); - setQuorum(_quorum); - - setMinimumWaitTime(_minimumWaitTime); - transferOwnership(address(_governor)); - } - - /** - * @notice Propose an emergency admin action to execute on the DVM as a set of proposed transactions. - * @dev Caller of this method must approve (and have) quorum amount of token to be pulled from their wallet. - * @param transactions array of transactions to be executed in the emergency action. When executed, will be sent - * via the governor contract. - * @return uint256 the emergency proposal id. - */ - function emergencyPropose(GovernorV2.Transaction[] memory transactions) external nonReentrant returns (uint256) { - require(msg.sender != address(governor), "Governor can't propose"); // The governor should never be the proposer. - require(transactions.length > 0, "No transactions to propose"); - token.safeTransferFrom(msg.sender, address(this), quorum); - uint256 id = emergencyProposals.length; - EmergencyProposal storage proposal = emergencyProposals.push(); - proposal.sender = msg.sender; - proposal.lockedTokens = quorum; - proposal.expiryTime = uint64(getCurrentTime()) + minimumWaitTime; - - for (uint256 i = 0; i < transactions.length; i++) proposal.transactions.push(transactions[i]); - - emit EmergencyTransactionsProposed(id, msg.sender, msg.sender, proposal.expiryTime, quorum, transactions); - return id; - } - - /** - * @notice After the proposal is executable, the executor or owner can use this function to remove the proposal - * without slashing. - * @dev This means that the DVM didn't explicitly reject the proposal. Allowing the executor to slash the quorum - * would give the executor too much power. So the only control either party has is to remove the proposal, - * releasing the bond. The proposal should not be removable before its liveness/expiry to ensure the regular Voting - * system's slash cannot be frontrun. - * @param id id of the proposal. - */ - function removeProposal(uint256 id) external nonReentrant { - EmergencyProposal storage proposal = emergencyProposals[id]; - require(proposal.expiryTime <= getCurrentTime(), "must be expired to remove"); - require(msg.sender == owner() || msg.sender == executor, "owner or executor"); - require(proposal.lockedTokens != 0, "invalid proposal"); - token.safeTransfer(proposal.sender, proposal.lockedTokens); - emit EmergencyProposalRemoved( - id, - proposal.sender, - msg.sender, - proposal.expiryTime, - proposal.lockedTokens, - proposal.transactions - ); - delete emergencyProposals[id]; - } - - /** - * @notice Before a proposal expires (or after), this method can be used by the owner, which should generally be - * the GovernorV2 contract, to slash the proposer. - * @dev The slash results in the proposer's tokens being sent to the Governor contract. - * @param id id of the proposal. - */ - function slashProposal(uint256 id) external nonReentrant onlyOwner { - EmergencyProposal storage proposal = emergencyProposals[id]; - require(proposal.lockedTokens != 0, "invalid proposal"); - token.safeTransfer(address(governor), proposal.lockedTokens); - emit EmergencyProposalSlashed( - id, - proposal.sender, - msg.sender, - proposal.expiryTime, - proposal.lockedTokens, - proposal.transactions - ); - delete emergencyProposals[id]; - } - - /** - * @notice After a proposal expires, this method can be used by the executor to execute the proposal. - * @dev This method effectively gives the executor veto power over any proposal. - * @dev The first transaction execution sends the total amount of ETH required to complete all payable - * transactions in the Governor. The EmergencyProposer must receive this amount of ETH in advance. - * The executed transactions are then able to use this ETH by including a nonzero value. - * @param id id of the proposal. - */ - function executeEmergencyProposal(uint256 id) external payable nonReentrant { - require(msg.sender == executor, "must be called by executor"); - - EmergencyProposal storage proposal = emergencyProposals[id]; - require(proposal.lockedTokens != 0, "invalid proposal"); - require(proposal.expiryTime <= getCurrentTime(), "must be expired to execute"); - - for (uint256 i = 0; i < proposal.transactions.length; i++) - governor.emergencyExecute{ value: address(this).balance }(proposal.transactions[i]); - - token.safeTransfer(proposal.sender, proposal.lockedTokens); - emit EmergencyProposalExecuted( - id, - proposal.sender, - msg.sender, - proposal.expiryTime, - proposal.lockedTokens, - proposal.transactions - ); - delete emergencyProposals[id]; - } - - /** - * @notice Admin method to set the quorum (bond) size. - * @dev Admin is intended to be the governance system. - * @param newQuorum the new quorum. - */ - function setQuorum(uint256 newQuorum) public nonReentrant onlyOwner { - require(newQuorum != 0, "quorum must be > 0"); - require(newQuorum < token.totalSupply(), "quorum must be < totalSupply"); - quorum = newQuorum; - emit QuorumSet(newQuorum); - } - - /** - * @notice Admin method to set the executor address. - * @dev Admin is intended to be the governance system. - * @param newExecutor the new executor address. - */ - function setExecutor(address newExecutor) public nonReentrant onlyOwner { - executor = newExecutor; - emit ExecutorSet(newExecutor); - } - - /** - * @notice Admin method to set the minimum wait time for a proposal to be executed. - * @dev Admin is intended to be the governance system. The minimum wait time is added to the current time at the - * time of the proposal to determine when the proposal will be executable. Any changes to this value after that - * point will have no impact on the proposal. - * @param newMinimumWaitTime the new minimum wait time. - */ - function setMinimumWaitTime(uint64 newMinimumWaitTime) public nonReentrant onlyOwner { - require(newMinimumWaitTime != 0, "minimumWaitTime == 0"); - require(newMinimumWaitTime <= 4 weeks, "minimumWaitTime > 1 month"); - minimumWaitTime = newMinimumWaitTime; - emit MinimumWaitTimeSet(newMinimumWaitTime); - } - - /** - * @notice Returns the current block timestamp. - * @dev Can be overridden to control contract time. - * @return the current block timestamp. - */ - function getCurrentTime() public view virtual returns (uint256) { - return block.timestamp; - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/FinancialContractsAdmin.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/FinancialContractsAdmin.sol deleted file mode 100644 index ff4a89d0f..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/FinancialContractsAdmin.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../interfaces/AdministrateeInterface.sol"; -import "@openzeppelin/contracts-v4/access/Ownable.sol"; - -/** - * @title Admin for financial contracts in the UMA system. - * @dev Allows appropriately permissioned admin roles to interact with financial contracts. - */ -contract FinancialContractsAdmin is Ownable { - /** - * @notice Calls emergency shutdown on the provided financial contract. - * @param financialContract address of the FinancialContract to be shut down. - */ - function callEmergencyShutdown(address financialContract) external onlyOwner { - AdministrateeInterface administratee = AdministrateeInterface(financialContract); - administratee.emergencyShutdown(); - } - - /** - * @notice Calls remargin on the provided financial contract. - * @param financialContract address of the FinancialContract to be remargined. - */ - function callRemargin(address financialContract) external onlyOwner { - AdministrateeInterface administratee = AdministrateeInterface(financialContract); - administratee.remargin(); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Finder.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Finder.sol deleted file mode 100644 index 44eaf5081..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Finder.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/access/Ownable.sol"; -import "../interfaces/FinderInterface.sol"; - -/** - * @title Provides addresses of the live contracts implementing certain interfaces. - * @dev Examples of interfaces with implementations that Finder locates are the Oracle and Store interfaces. - */ -contract Finder is FinderInterface, Ownable { - mapping(bytes32 => address) public interfacesImplemented; - - event InterfaceImplementationChanged(bytes32 indexed interfaceName, address indexed newImplementationAddress); - - /** - * @notice Updates the address of the contract that implements `interfaceName`. - * @param interfaceName bytes32 of the interface name that is either changed or registered. - * @param implementationAddress address of the implementation contract. - */ - function changeImplementationAddress( - bytes32 interfaceName, - address implementationAddress - ) external override onlyOwner { - interfacesImplemented[interfaceName] = implementationAddress; - - emit InterfaceImplementationChanged(interfaceName, implementationAddress); - } - - /** - * @notice Gets the address of the contract that implements the given `interfaceName`. - * @param interfaceName queried interface. - * @return implementationAddress address of the defined interface. - */ - function getImplementationAddress(bytes32 interfaceName) external view override returns (address) { - address implementationAddress = interfacesImplemented[interfaceName]; - require(implementationAddress != address(0x0), "Implementation not found"); - return implementationAddress; - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/FixedSlashSlashingLibrary.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/FixedSlashSlashingLibrary.sol deleted file mode 100644 index 7a6314f0d..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/FixedSlashSlashingLibrary.sol +++ /dev/null @@ -1,103 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "../interfaces/SlashingLibraryInterface.sol"; - -/** - * @title Slashing Library contract. Returns the how much a voter should be slashed per staked token as a function of - * the total staked, total votes and total correct votes. Can be upgraded to a new implementation to enable more - elaborate slashing algorithms via UMA governance. - */ - -contract FixedSlashSlashingLibrary is SlashingLibraryInterface { - uint256 public immutable baseSlashAmount; // Slash amount per token for missed votes and wrong non-governance votes. - uint256 public immutable governanceSlashAmount; // Slash amount per token for wrong governance votes. - - /** - * @notice Construct the FixedSlashSlashingLibrary contract. - * @param _baseSlashAmount Slash amount per token for missed votes and wrong non-governance votes. - * @param _governanceSlashAmount Slash amount per token for wrong governance votes. - */ - constructor(uint256 _baseSlashAmount, uint256 _governanceSlashAmount) { - require(_baseSlashAmount < 1e18, "Invalid base slash amount"); - require(_governanceSlashAmount < 1e18, "Invalid governance slash amount"); - baseSlashAmount = _baseSlashAmount; // Slash amount per token for missed votes and wrong non-governance votes. - governanceSlashAmount = _governanceSlashAmount; // Slash amount per token for wrong governance votes. - } - - /** - * @notice Calculates the wrong vote slash per token. - * @param totalStaked The total amount of tokens staked. - * @param totalVotes The total amount of votes. - * @param totalCorrectVotes The total amount of correct votes. - * @param priceRequestIndex The price request index within the resolvedPriceRequestIds array. - * @return uint256 The amount of tokens to slash per token staked. - */ - function calcWrongVoteSlashPerToken( - uint256 totalStaked, - uint256 totalVotes, - uint256 totalCorrectVotes, - uint256 priceRequestIndex - ) public view returns (uint256) { - return baseSlashAmount; - } - - /** - * @notice Calculates the wrong vote slash per token for governance requests. - * @param totalStaked The total amount of tokens staked. - * @param totalVotes The total amount of votes. - * @param totalCorrectVotes The total amount of correct votes. - * @param priceRequestIndex The price request index within the resolvedPriceRequestIds array. - * @return uint256 The amount of tokens to slash per token staked. - */ - function calcWrongVoteSlashPerTokenGovernance( - uint256 totalStaked, - uint256 totalVotes, - uint256 totalCorrectVotes, - uint256 priceRequestIndex - ) public view returns (uint256) { - return governanceSlashAmount; - } - - /** - * @notice Calculates the no vote slash per token. - * @param totalStaked The total amount of tokens staked. - * @param totalVotes The total amount of votes. - * @param totalCorrectVotes The total amount of correct votes. - * @param priceRequestIndex The price request index within the resolvedPriceRequestIds array. - * @return uint256 The amount of tokens to slash per token staked. - */ - function calcNoVoteSlashPerToken( - uint256 totalStaked, - uint256 totalVotes, - uint256 totalCorrectVotes, - uint256 priceRequestIndex - ) public view returns (uint256) { - return baseSlashAmount; - } - - /** - * @notice Calculates all slashing trackers in one go to decrease cross-contract calls needed. - * @param totalStaked The total amount of tokens staked. - * @param totalVotes The total amount of votes. - * @param totalCorrectVotes The total amount of correct votes. - * @param priceRequestIndex The price request index within the resolvedPriceRequestIds array. - * @param isGovernance Whether the request is a governance request. - * @return wrongVoteSlashPerToken The amount of tokens to slash for voting wrong. - * @return noVoteSlashPerToken The amount of tokens to slash for not voting. - */ - function calcSlashing( - uint256 totalStaked, - uint256 totalVotes, - uint256 totalCorrectVotes, - uint256 priceRequestIndex, - bool isGovernance - ) external view returns (uint256 wrongVoteSlashPerToken, uint256 noVoteSlashPerToken) { - return ( - isGovernance - ? calcWrongVoteSlashPerTokenGovernance(totalStaked, totalVotes, totalCorrectVotes, priceRequestIndex) - : calcWrongVoteSlashPerToken(totalStaked, totalVotes, totalCorrectVotes, priceRequestIndex), - calcNoVoteSlashPerToken(totalStaked, totalVotes, totalCorrectVotes, priceRequestIndex) - ); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Governor.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Governor.sol deleted file mode 100644 index e9514a020..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Governor.sol +++ /dev/null @@ -1,208 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/MultiRole.sol"; -import "../../common/implementation/FixedPoint.sol"; -import "../../common/implementation/Testable.sol"; -import "../interfaces/FinderInterface.sol"; -import "../interfaces/IdentifierWhitelistInterface.sol"; -import "../interfaces/OracleInterface.sol"; -import "./Constants.sol"; -import "./AdminIdentifierLib.sol"; - -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; -import "@openzeppelin/contracts-v4/utils/Address.sol"; - -/** - * @title Takes proposals for certain governance actions and allows UMA token holders to vote on them. - */ -contract Governor is MultiRole, Testable { - using SafeMath for uint256; - using Address for address; - - /**************************************** - * INTERNAL VARIABLES AND STORAGE * - ****************************************/ - - enum Roles { - Owner, // Can set the proposer. - Proposer // Address that can make proposals. - } - - struct Transaction { - address to; - uint256 value; - bytes data; - } - - struct Proposal { - Transaction[] transactions; - uint256 requestTime; - } - - FinderInterface private finder; - Proposal[] public proposals; - - /**************************************** - * EVENTS * - ****************************************/ - - // Emitted when a new proposal is created. - event NewProposal(uint256 indexed id, Transaction[] transactions); - - // Emitted when an existing proposal is executed. - event ProposalExecuted(uint256 indexed id, uint256 transactionIndex); - - /** - * @notice Construct the Governor contract. - * @param _finderAddress keeps track of all contracts within the system based on their interfaceName. - * @param _startingId the initial proposal id that the contract will begin incrementing from. - * @param _timerAddress Contract that stores the current time in a testing environment. - * Must be set to 0x0 for production environments that use live time. - */ - constructor(address _finderAddress, uint256 _startingId, address _timerAddress) Testable(_timerAddress) { - finder = FinderInterface(_finderAddress); - _createExclusiveRole(uint256(Roles.Owner), uint256(Roles.Owner), msg.sender); - _createExclusiveRole(uint256(Roles.Proposer), uint256(Roles.Owner), msg.sender); - - // Ensure the startingId is not set unreasonably high to avoid it being set such that new proposals overwrite - // other storage slots in the contract. - uint256 maxStartingId = 10 ** 18; - require(_startingId <= maxStartingId, "Cannot set startingId larger than 10^18"); - - // This just sets the initial length of the array to the startingId since modifying length directly has been - // disallowed in solidity 0.6. - assembly { - sstore(proposals.slot, _startingId) - } - } - - /**************************************** - * PROPOSAL ACTIONS * - ****************************************/ - - /** - * @notice Proposes a new governance action. Can only be called by the holder of the Proposer role. - * @param transactions list of transactions that are being proposed. - * @dev You can create the data portion of each transaction by doing the following: - * ``` - * const truffleContractInstance = await TruffleContract.deployed() - * const data = truffleContractInstance.methods.methodToCall(arg1, arg2).encodeABI() - * ``` - * Note: this method must be public because of a solidity limitation that - * disallows structs arrays to be passed to external functions. - */ - function propose(Transaction[] memory transactions) public onlyRoleHolder(uint256(Roles.Proposer)) { - uint256 id = proposals.length; - uint256 time = getCurrentTime(); - - // Note: doing all of this array manipulation manually is necessary because directly setting an array of - // structs in storage to an an array of structs in memory is currently not implemented in solidity :/. - - // Add a zero-initialized element to the proposals array. - proposals.push(); - - // Initialize the new proposal. - Proposal storage proposal = proposals[id]; - proposal.requestTime = time; - - // Initialize the transaction array. - for (uint256 i = 0; i < transactions.length; i++) { - require(transactions[i].to != address(0), "The `to` address cannot be 0x0"); - // If the transaction has any data with it the recipient must be a contract, not an EOA. - if (transactions[i].data.length > 0) { - require(transactions[i].to.isContract(), "EOA can't accept tx with data"); - } - proposal.transactions.push(transactions[i]); - } - - bytes32 identifier = AdminIdentifierLib._constructIdentifier(id); - - // Request a vote on this proposal in the DVM. - OracleInterface oracle = _getOracle(); - IdentifierWhitelistInterface supportedIdentifiers = _getIdentifierWhitelist(); - supportedIdentifiers.addSupportedIdentifier(identifier); - - oracle.requestPrice(identifier, time); - supportedIdentifiers.removeSupportedIdentifier(identifier); - - emit NewProposal(id, transactions); - } - - /** - * @notice Executes a proposed governance action that has been approved by voters. - * @dev This can be called by any address. Caller is expected to send enough ETH to execute payable transactions. - * @param id unique id for the executed proposal. - * @param transactionIndex unique transaction index for the executed proposal. - */ - function executeProposal(uint256 id, uint256 transactionIndex) external payable { - Proposal storage proposal = proposals[id]; - int256 price = _getOracle().getPrice(AdminIdentifierLib._constructIdentifier(id), proposal.requestTime); - - Transaction memory transaction = proposal.transactions[transactionIndex]; - - require( - transactionIndex == 0 || proposal.transactions[transactionIndex.sub(1)].to == address(0), - "Previous tx not yet executed" - ); - require(transaction.to != address(0), "Tx already executed"); - require(price != 0, "Proposal was rejected"); - require(msg.value == transaction.value, "Must send exact amount of ETH"); - - // Delete the transaction before execution to avoid any potential re-entrancy issues. - delete proposal.transactions[transactionIndex]; - - require(_executeCall(transaction.to, transaction.value, transaction.data), "Tx execution failed"); - - emit ProposalExecuted(id, transactionIndex); - } - - /**************************************** - * GOVERNOR STATE GETTERS * - ****************************************/ - - /** - * @notice Gets the total number of proposals (includes executed and non-executed). - * @return uint256 representing the current number of proposals. - */ - function numProposals() external view returns (uint256) { - return proposals.length; - } - - /** - * @notice Gets the proposal data for a particular id. - * @dev after a proposal is executed, its data will be zeroed out, except for the request time. - * @param id uniquely identify the identity of the proposal. - * @return proposal struct containing transactions[] and requestTime. - */ - function getProposal(uint256 id) external view returns (Proposal memory) { - return proposals[id]; - } - - /**************************************** - * PRIVATE GETTERS AND FUNCTIONS * - ****************************************/ - - function _executeCall(address to, uint256 value, bytes memory data) private returns (bool) { - // Mostly copied from: - // solhint-disable-next-line max-line-length - // https://github.com/gnosis/safe-contracts/blob/59cfdaebcd8b87a0a32f87b50fead092c10d3a05/contracts/base/Executor.sol#L23-L31 - // solhint-disable-next-line no-inline-assembly - - bool success; - assembly { - let inputData := add(data, 0x20) - let inputDataSize := mload(data) - success := call(gas(), to, value, inputData, inputDataSize, 0, 0) - } - return success; - } - - function _getOracle() private view returns (OracleInterface) { - return OracleInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); - } - - function _getIdentifierWhitelist() private view returns (IdentifierWhitelistInterface supportedIdentifiers) { - return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/GovernorV2.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/GovernorV2.sol deleted file mode 100644 index f537e78cd..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/GovernorV2.sol +++ /dev/null @@ -1,234 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "../../common/implementation/Lockable.sol"; -import "../../common/implementation/MultiCaller.sol"; -import "../../common/implementation/MultiRole.sol"; -import "../interfaces/FinderInterface.sol"; -import "../interfaces/IdentifierWhitelistInterface.sol"; -import "../interfaces/OracleGovernanceInterface.sol"; -import "./Constants.sol"; -import "./AdminIdentifierLib.sol"; - -import "@openzeppelin/contracts-v4/utils/Address.sol"; - -/** - * @title Takes proposals for certain governance actions and allows UMA token holders to vote on them. - */ -contract GovernorV2 is MultiRole, Lockable, MultiCaller { - using Address for address; - - /**************************************** - * GOVERNOR STATE * - ****************************************/ - - // Permissioned governor rolls. - enum Roles { - Owner, // Can set the proposer. - Proposer, // Address that can make proposals. - EmergencyProposer // Address that can make emergency proposals. - } - - // Structure to represent a transaction. - struct Transaction { - address to; // Target. - uint256 value; // value, in eth, to be sent as the msg.value. - bytes data; // payload data to be sent to the target. Would include encoded function call data usually. - } - - // Structure to represent a governance proposal. - struct Proposal { - Transaction[] transactions; // Set of transactions to be sent, if the proposal is executed. - uint256 requestTime; // Time at which the proposal was proposed. - bytes ancillaryData; // Extra data appended to a proposal to enhance the voters information. - } - - // Reference to UMA finder, used to find addresses of other UMA ecosystem contracts. - FinderInterface public immutable finder; - - // Array of all proposals. - Proposal[] public proposals; - - /**************************************** - * EVENTS * - ****************************************/ - - event NewProposal(uint256 indexed id, Transaction[] transactions); - - event ProposalExecuted(uint256 indexed id, uint256 transactionIndex); - event EmergencyExecution(address indexed to, uint256 value, bytes data); - - /** - * @notice Construct the Governor contract. - * @param _finderAddress keeps track of all contracts within the system based on their interfaceName. - * @param _startingId the initial proposal id that the contract will begin incrementing from. - */ - constructor(address _finderAddress, uint256 _startingId) { - finder = FinderInterface(_finderAddress); - _createExclusiveRole(uint256(Roles.Owner), uint256(Roles.Owner), msg.sender); - _createExclusiveRole(uint256(Roles.Proposer), uint256(Roles.Owner), msg.sender); - _createExclusiveRole(uint256(Roles.EmergencyProposer), uint256(Roles.Owner), msg.sender); - - // Ensure the startingId is not set unreasonably high to avoid it being set such that new proposals overwrite - // other storage slots in the contract. - uint256 maxStartingId = 10 ** 18; - require(_startingId <= maxStartingId, "Cannot set startingId larger than 10^18"); - - // Sets the initial length of the array to the startingId. Modifying length directly has been disallowed in solidity 0.6. - assembly { - sstore(proposals.slot, _startingId) - } - } - - /**************************************** - * PROPOSAL ACTIONS * - ****************************************/ - - /** - * @notice Proposes a new governance action. Can only be called by the holder of the Proposer role. - * @param transactions list of transactions that are being proposed. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - */ - function propose( - Transaction[] memory transactions, - bytes memory ancillaryData - ) external nonReentrant onlyRoleHolder(uint256(Roles.Proposer)) { - require(transactions.length > 0, "Empty transactions array"); - uint256 id = proposals.length; - uint256 time = getCurrentTime(); - - // Note: doing all of this array manipulation manually is necessary because directly setting an array of - // structs in storage to an array of structs in memory is currently not implemented in solidity :/. - - // Add a zero-initialized element to the proposals array. - proposals.push(); - - // Initialize the new proposal. - Proposal storage proposal = proposals[id]; - proposal.requestTime = time; - proposal.ancillaryData = ancillaryData; - - // Initialize the transaction array. - for (uint256 i = 0; i < transactions.length; i++) { - require(transactions[i].to != address(0), "The `to` address cannot be 0x0"); - // If the transaction has any data with it the recipient must be a contract, not an EOA. - if (transactions[i].data.length > 0) { - require(transactions[i].to.isContract(), "EOA can't accept tx with data"); - } - proposal.transactions.push(transactions[i]); - } - - bytes32 identifier = AdminIdentifierLib._constructIdentifier(id); - - // Request a vote on this proposal in the DVM. - _getOracle().requestGovernanceAction(identifier, time, ancillaryData); - - emit NewProposal(id, transactions); - } - - /** - * @notice Executes a proposed governance action that has been approved by voters. - * @dev This can be called by any address. Caller is expected to send enough ETH to execute payable transactions. - * @param id unique id for the executed proposal. - * @param transactionIndex unique transaction index for the executed proposal. - */ - function executeProposal(uint256 id, uint256 transactionIndex) external payable nonReentrant { - Proposal storage proposal = proposals[id]; - int256 price = _getOracle().getPrice( - AdminIdentifierLib._constructIdentifier(id), - proposal.requestTime, - proposal.ancillaryData - ); - - Transaction memory transaction = proposal.transactions[transactionIndex]; - - require( - transactionIndex == 0 || proposal.transactions[transactionIndex - 1].to == address(0), - "Previous tx not yet executed" - ); - require(transaction.to != address(0), "Tx already executed"); - require(price != 0, "Proposal was rejected"); - require(msg.value == transaction.value, "Must send exact amount of ETH"); - - // Delete the transaction before execution to avoid any potential re-entrancy issues. - delete proposal.transactions[transactionIndex]; - - require(_executeCall(transaction.to, transaction.value, transaction.data), "Tx execution failed"); - - emit ProposalExecuted(id, transactionIndex); - } - - /** - * @notice Emergency execution method that bypasses the voting system to execute a transaction. - * @dev This can only be called by the EmergencyProposer. - * @param transaction a single transaction to execute. - */ - function emergencyExecute( - Transaction memory transaction - ) external payable nonReentrant onlyRoleHolder(uint256(Roles.EmergencyProposer)) { - require(_executeCall(transaction.to, transaction.value, transaction.data), "Tx execution failed"); - - emit EmergencyExecution(transaction.to, transaction.value, transaction.data); - } - - /** - * @notice Returns the current block timestamp. - * @dev Can be overridden to control contract time. - * @return the current block timestamp. - */ - function getCurrentTime() public view virtual returns (uint256) { - return block.timestamp; - } - - /**************************************** - * GOVERNOR STATE GETTERS * - ****************************************/ - - /** - * @notice Gets the total number of proposals (includes executed and non-executed). - * @return uint256 representing the current number of proposals. - */ - function numProposals() external view returns (uint256) { - return proposals.length; - } - - /** - * @notice Gets the proposal data for a particular id. - * @dev after a proposal is executed, its data will be zeroed out, except for the request time and ancillary data. - * @param id uniquely identify the identity of the proposal. - * @return proposal struct containing transactions[] and requestTime. - */ - function getProposal(uint256 id) external view returns (Proposal memory) { - return proposals[id]; - } - - /**************************************** - * PRIVATE GETTERS AND FUNCTIONS * - ****************************************/ - - // Runs a function call on to, with value eth sent and data payload. - function _executeCall(address to, uint256 value, bytes memory data) private returns (bool) { - // Mostly copied from: - // solhint-disable-next-line max-line-length - // https://github.com/gnosis/safe-contracts/blob/59cfdaebcd8b87a0a32f87b50fead092c10d3a05/contracts/base/Executor.sol#L23-L31 - // solhint-disable-next-line no-inline-assembly - - bool success; - assembly { - let inputData := add(data, 0x20) - let inputDataSize := mload(data) - success := call(gas(), to, value, inputData, inputDataSize, 0, 0) - } - return success; - } - - // Returns the Voting contract address, named "Oracle" in the finder. - function _getOracle() private view returns (OracleGovernanceInterface) { - return OracleGovernanceInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); - } - - // Returns the IdentifierWhitelist contract address, named "IdentifierWhitelist" in the finder. - function _getIdentifierWhitelist() private view returns (IdentifierWhitelistInterface) { - return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/IdentifierWhitelist.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/IdentifierWhitelist.sol deleted file mode 100644 index 2461558f3..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/IdentifierWhitelist.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../interfaces/IdentifierWhitelistInterface.sol"; -import "@openzeppelin/contracts-v4/access/Ownable.sol"; - -/** - * @title Stores a whitelist of supported identifiers that the oracle can provide prices for. - */ -contract IdentifierWhitelist is IdentifierWhitelistInterface, Ownable { - /**************************************** - * INTERNAL VARIABLES AND STORAGE * - ****************************************/ - - mapping(bytes32 => bool) private supportedIdentifiers; - - /**************************************** - * EVENTS * - ****************************************/ - - event SupportedIdentifierAdded(bytes32 indexed identifier); - event SupportedIdentifierRemoved(bytes32 indexed identifier); - - /**************************************** - * ADMIN STATE MODIFYING FUNCTIONS * - ****************************************/ - - /** - * @notice Adds the provided identifier as a supported identifier. - * @dev Price requests using this identifier will succeed after this call. - * @param identifier unique UTF-8 representation for the feed being added. Eg: BTC/USD. - */ - function addSupportedIdentifier(bytes32 identifier) external override onlyOwner { - if (!supportedIdentifiers[identifier]) { - supportedIdentifiers[identifier] = true; - emit SupportedIdentifierAdded(identifier); - } - } - - /** - * @notice Removes the identifier from the whitelist. - * @dev Price requests using this identifier will no longer succeed after this call. - * @param identifier unique UTF-8 representation for the feed being removed. Eg: BTC/USD. - */ - function removeSupportedIdentifier(bytes32 identifier) external override onlyOwner { - if (supportedIdentifiers[identifier]) { - supportedIdentifiers[identifier] = false; - emit SupportedIdentifierRemoved(identifier); - } - } - - /**************************************** - * WHITELIST GETTERS FUNCTIONS * - ****************************************/ - - /** - * @notice Checks whether an identifier is on the whitelist. - * @param identifier unique UTF-8 representation for the feed being queried. Eg: BTC/USD. - * @return bool if the identifier is supported (or not). - */ - function isIdentifierSupported(bytes32 identifier) external view override returns (bool) { - return supportedIdentifiers[identifier]; - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Proposer.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Proposer.sol deleted file mode 100644 index 1e13fe8eb..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Proposer.sol +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "./Finder.sol"; -import "./Governor.sol"; -import "./Constants.sol"; -import "./Voting.sol"; -import "./AdminIdentifierLib.sol"; -import "../../common/implementation/Lockable.sol"; -import "@openzeppelin/contracts-v4/access/Ownable.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; - -/** - * @title Proposer contract that allows anyone to make governance proposals with a bond. - */ -contract Proposer is Ownable, Testable, Lockable { - using SafeERC20 for IERC20; - IERC20 public token; - uint256 public bond; - Governor public governor; - Finder public finder; - - struct BondedProposal { - address sender; - // 64 bits to save a storage slot. - uint64 time; - uint256 lockedBond; - } - mapping(uint256 => BondedProposal) public bondedProposals; - - event BondSet(uint256 bond); - event ProposalResolved(uint256 indexed id, bool success); - - /** - * @notice Construct the Proposer contract. - * @param _token the ERC20 token that the bond is paid in. - * @param _bond the bond amount. - * @param _governor the governor contract that this contract makes proposals to. - * @param _finder the finder contract used to look up addresses. - * @param _timer the timer contract to control the output of getCurrentTime(). Set to 0x0 if in production. - */ - constructor(IERC20 _token, uint256 _bond, Governor _governor, Finder _finder, address _timer) Testable(_timer) { - token = _token; - governor = _governor; - finder = _finder; - setBond(_bond); - transferOwnership(address(_governor)); - } - - /** - * @notice Propose a new set of governance transactions for vote. - * @dev Pulls bond from the caller. - * @param transactions list of transactions for the governor to execute. - * @return id the id of the governor proposal. - */ - function propose(Governor.Transaction[] memory transactions) external nonReentrant returns (uint256 id) { - id = governor.numProposals(); - token.safeTransferFrom(msg.sender, address(this), bond); - bondedProposals[id] = BondedProposal({ sender: msg.sender, lockedBond: bond, time: uint64(getCurrentTime()) }); - governor.propose(transactions); - } - - /** - * @notice Resolves a proposal by checking the status of the request in the Voting contract. - * @dev For the resolution to work correctly, this contract must be a registered contract in the DVM. - * @param id proposal id. - */ - function resolveProposal(uint256 id) external nonReentrant { - BondedProposal storage bondedProposal = bondedProposals[id]; - Voting voting = Voting(finder.getImplementationAddress(OracleInterfaces.Oracle)); - require( - voting.hasPrice(AdminIdentifierLib._constructIdentifier(id), bondedProposal.time, ""), - "No price resolved" - ); - if (voting.getPrice(AdminIdentifierLib._constructIdentifier(id), bondedProposal.time, "") != 0) { - token.safeTransfer(bondedProposal.sender, bondedProposal.lockedBond); - emit ProposalResolved(id, true); - } else { - token.safeTransfer(finder.getImplementationAddress(OracleInterfaces.Store), bondedProposal.lockedBond); - emit ProposalResolved(id, false); - } - delete bondedProposals[id]; - } - - /** - * @notice Admin method to set the bond amount. - * @dev Admin is intended to be the governance system, itself. - * @param _bond the new bond. - */ - function setBond(uint256 _bond) public nonReentrant onlyOwner { - bond = _bond; - emit BondSet(_bond); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ProposerV2.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ProposerV2.sol deleted file mode 100644 index 8739d87de..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ProposerV2.sol +++ /dev/null @@ -1,120 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "./Finder.sol"; -import "./GovernorV2.sol"; -import "./Constants.sol"; -import "../interfaces/OracleAncillaryInterface.sol"; -import "./AdminIdentifierLib.sol"; -import "../../common/implementation/Lockable.sol"; -import "../../common/implementation/MultiCaller.sol"; -import "@openzeppelin/contracts-v4/access/Ownable.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; - -/** - * @title Proposer contract that allows anyone to make governance proposals with a bond. - */ -contract ProposerV2 is Ownable, Lockable, MultiCaller { - using SafeERC20 for IERC20; - IERC20 public immutable token; // The ERC20 token that the bond is paid in. - uint256 public bond; // The bond amount for making a proposal. - GovernorV2 public immutable governor; // The governor contract that this contract makes proposals to. - Finder public immutable finder; // Finder contract that stores addresses of UMA system contracts. - - struct BondedProposal { - address sender; - // 64 bits to save a storage slot. - uint64 time; - uint256 lockedBond; - bytes ancillaryData; - } - mapping(uint256 => BondedProposal) public bondedProposals; - - event BondSet(uint256 bond); - event ProposalResolved(uint256 indexed id, bool success); - - /** - * @notice Construct the Proposer contract. - * @param _token the ERC20 token that the bond is paid in. - * @param _bond the bond amount. - * @param _governor the governor contract that this contract makes proposals to. - * @param _finder the finder contract used to look up addresses. - */ - constructor(IERC20 _token, uint256 _bond, GovernorV2 _governor, Finder _finder) { - token = _token; - governor = _governor; - finder = _finder; - setBond(_bond); - transferOwnership(address(_governor)); - } - - /** - * @notice Propose a new set of governance transactions for vote. - * @dev Pulls bond from the caller. - * @param transactions list of transactions for the governor to execute. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - * @return id the id of the governor proposal. - */ - function propose( - GovernorV2.Transaction[] memory transactions, - bytes memory ancillaryData - ) external nonReentrant returns (uint256) { - uint256 id = governor.numProposals(); - token.safeTransferFrom(msg.sender, address(this), bond); - bondedProposals[id] = BondedProposal({ - sender: msg.sender, - lockedBond: bond, - time: uint64(getCurrentTime()), - ancillaryData: ancillaryData - }); - governor.propose(transactions, ancillaryData); - return id; - } - - /** - * @notice Resolves a proposal by checking the status of the request in the Voting contract. - * @dev For the resolution to work correctly, this contract must be a registered contract in the DVM. - * @param id proposal id. - */ - function resolveProposal(uint256 id) external nonReentrant { - BondedProposal memory bondedProposal = bondedProposals[id]; - require(bondedProposal.sender != address(0), "Invalid proposal id"); - OracleAncillaryInterface voting = OracleAncillaryInterface( - finder.getImplementationAddress(OracleInterfaces.Oracle) - ); - bytes32 adminIdentifier = AdminIdentifierLib._constructIdentifier(id); - - require( - voting.hasPrice(adminIdentifier, bondedProposal.time, bondedProposal.ancillaryData), - "No price resolved" - ); - if (voting.getPrice(adminIdentifier, bondedProposal.time, bondedProposal.ancillaryData) != 0) { - token.safeTransfer(bondedProposal.sender, bondedProposal.lockedBond); - emit ProposalResolved(id, true); - } else { - token.safeTransfer(finder.getImplementationAddress(OracleInterfaces.Store), bondedProposal.lockedBond); - emit ProposalResolved(id, false); - } - delete bondedProposals[id]; - } - - /** - * @notice Admin method to set the bond amount. - * @dev Admin is intended to be the governance system itself. - * @param _bond the new bond. - */ - function setBond(uint256 _bond) public nonReentrant onlyOwner { - bond = _bond; - emit BondSet(_bond); - } - - /** - * @notice Returns the current block timestamp. - * @dev Can be overridden to control contract time. - * @return the current block timestamp. - */ - function getCurrentTime() public view virtual returns (uint256) { - return block.timestamp; - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Registry.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Registry.sol deleted file mode 100644 index 504c11166..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Registry.sol +++ /dev/null @@ -1,202 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/MultiRole.sol"; -import "../interfaces/RegistryInterface.sol"; - -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; - -/** - * @title Registry for financial contracts and approved financial contract creators. - * @dev Maintains a whitelist of financial contract creators that are allowed - * to register new financial contracts and stores party members of a financial contract. - */ -contract Registry is RegistryInterface, MultiRole { - using SafeMath for uint256; - - /**************************************** - * INTERNAL VARIABLES AND STORAGE * - ****************************************/ - - enum Roles { - Owner, // The owner manages the set of ContractCreators. - ContractCreator // Can register financial contracts. - } - - // This enum is required because a `WasValid` state is required - // to ensure that financial contracts cannot be re-registered. - enum Validity { - Invalid, - Valid - } - - // Local information about a contract. - struct FinancialContract { - Validity valid; - uint128 index; - } - - struct Party { - address[] contracts; // Each financial contract address is stored in this array. - // The address of each financial contract is mapped to its index for constant time look up and deletion. - mapping(address => uint256) contractIndex; - } - - // Array of all contracts that are approved to use the UMA Oracle. - address[] public registeredContracts; - - // Map of financial contract contracts to the associated FinancialContract struct. - mapping(address => FinancialContract) public contractMap; - - // Map each party member to their their associated Party struct. - mapping(address => Party) private partyMap; - - /**************************************** - * EVENTS * - ****************************************/ - - event NewContractRegistered(address indexed contractAddress, address indexed creator, address[] parties); - event PartyAdded(address indexed contractAddress, address indexed party); - event PartyRemoved(address indexed contractAddress, address indexed party); - - /** - * @notice Construct the Registry contract. - */ - constructor() { - _createExclusiveRole(uint256(Roles.Owner), uint256(Roles.Owner), msg.sender); - // Start with no contract creators registered. - _createSharedRole(uint256(Roles.ContractCreator), uint256(Roles.Owner), new address[](0)); - } - - /**************************************** - * REGISTRATION FUNCTIONS * - ****************************************/ - - /** - * @notice Registers a new financial contract. - * @dev Only authorized contract creators can call this method. - * @param parties array of addresses who become parties in the contract. - * @param contractAddress address of the contract against which the parties are registered. - */ - function registerContract( - address[] calldata parties, - address contractAddress - ) external override onlyRoleHolder(uint256(Roles.ContractCreator)) { - FinancialContract storage financialContract = contractMap[contractAddress]; - require(contractMap[contractAddress].valid == Validity.Invalid, "Can only register once"); - - // Store contract address as a registered contract. - registeredContracts.push(contractAddress); - - // No length check necessary because we should never hit (2^127 - 1) contracts. - financialContract.index = uint128(registeredContracts.length.sub(1)); - - // For all parties in the array add them to the contract's parties. - financialContract.valid = Validity.Valid; - for (uint256 i = 0; i < parties.length; i = i.add(1)) { - _addPartyToContract(parties[i], contractAddress); - } - - emit NewContractRegistered(contractAddress, msg.sender, parties); - } - - /** - * @notice Adds a party member to the calling contract. - * @dev msg.sender will be used to determine the contract that this party is added to. - * @param party new party for the calling contract. - */ - function addPartyToContract(address party) external override { - address contractAddress = msg.sender; - require(contractMap[contractAddress].valid == Validity.Valid, "Can only add to valid contract"); - - _addPartyToContract(party, contractAddress); - } - - /** - * @notice Removes a party member from the calling contract. - * @dev msg.sender will be used to determine the contract that this party is removed from. - * @param partyAddress address to be removed from the calling contract. - */ - function removePartyFromContract(address partyAddress) external override { - address contractAddress = msg.sender; - Party storage party = partyMap[partyAddress]; - uint256 numberOfContracts = party.contracts.length; - - require(numberOfContracts != 0, "Party has no contracts"); - require(contractMap[contractAddress].valid == Validity.Valid, "Remove only from valid contract"); - require(isPartyMemberOfContract(partyAddress, contractAddress), "Can only remove existing party"); - - // Index of the current location of the contract to remove. - uint256 deleteIndex = party.contractIndex[contractAddress]; - - // Store the last contract's address to update the lookup map. - address lastContractAddress = party.contracts[numberOfContracts - 1]; - - // Swap the contract to be removed with the last contract. - party.contracts[deleteIndex] = lastContractAddress; - - // Update the lookup index with the new location. - party.contractIndex[lastContractAddress] = deleteIndex; - - // Pop the last contract from the array and update the lookup map. - party.contracts.pop(); - delete party.contractIndex[contractAddress]; - - emit PartyRemoved(contractAddress, partyAddress); - } - - /**************************************** - * REGISTRY STATE GETTERS * - ****************************************/ - - /** - * @notice Returns whether the contract has been registered with the registry. - * @dev If it is registered, it is an authorized participant in the UMA system. - * @param contractAddress address of the financial contract. - * @return bool indicates whether the contract is registered. - */ - function isContractRegistered(address contractAddress) external view override returns (bool) { - return contractMap[contractAddress].valid == Validity.Valid; - } - - /** - * @notice Returns a list of all contracts that are associated with a particular party. - * @param party address of the party. - * @return an array of the contracts the party is registered to. - */ - function getRegisteredContracts(address party) external view override returns (address[] memory) { - return partyMap[party].contracts; - } - - /** - * @notice Returns all registered contracts. - * @return all registered contract addresses within the system. - */ - function getAllRegisteredContracts() external view override returns (address[] memory) { - return registeredContracts; - } - - /** - * @notice checks if an address is a party of a contract. - * @param party party to check. - * @param contractAddress address to check against the party. - * @return bool indicating if the address is a party of the contract. - */ - function isPartyMemberOfContract(address party, address contractAddress) public view override returns (bool) { - uint256 index = partyMap[party].contractIndex[contractAddress]; - return partyMap[party].contracts.length > index && partyMap[party].contracts[index] == contractAddress; - } - - /**************************************** - * INTERNAL FUNCTIONS * - ****************************************/ - - function _addPartyToContract(address party, address contractAddress) internal { - require(!isPartyMemberOfContract(party, contractAddress), "Can only register a party once"); - uint256 contractIndex = partyMap[party].contracts.length; - partyMap[party].contracts.push(contractAddress); - partyMap[party].contractIndex[contractAddress] = contractIndex; - - emit PartyAdded(contractAddress, party); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ResultComputation.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ResultComputation.sol deleted file mode 100644 index 8b67f0251..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ResultComputation.sol +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/FixedPoint.sol"; - -/** - * @title Computes vote results. - * @dev The result is the mode of the added votes. Otherwise, the vote is unresolved. - */ -library ResultComputation { - using FixedPoint for FixedPoint.Unsigned; - - /**************************************** - * INTERNAL LIBRARY DATA STRUCTURE * - ****************************************/ - - struct Data { - // Maps price to number of tokens that voted for that price. - mapping(int256 => FixedPoint.Unsigned) voteFrequency; - // The total votes that have been added. - FixedPoint.Unsigned totalVotes; - // The price that is the current mode, i.e., the price with the highest frequency in `voteFrequency`. - int256 currentMode; - } - - /**************************************** - * VOTING FUNCTIONS * - ****************************************/ - - /** - * @notice Adds a new vote to be used when computing the result. - * @param data contains information to which the vote is applied. - * @param votePrice value specified in the vote for the given `numberTokens`. - * @param numberTokens number of tokens that voted on the `votePrice`. - */ - function addVote(Data storage data, int256 votePrice, FixedPoint.Unsigned memory numberTokens) internal { - data.totalVotes = data.totalVotes.add(numberTokens); - data.voteFrequency[votePrice] = data.voteFrequency[votePrice].add(numberTokens); - if ( - votePrice != data.currentMode && - data.voteFrequency[votePrice].isGreaterThan(data.voteFrequency[data.currentMode]) - ) data.currentMode = votePrice; - } - - /**************************************** - * VOTING STATE GETTERS * - ****************************************/ - - /** - * @notice Returns whether the result is resolved, and if so, what value it resolved to. - * @dev `price` should be ignored if `isResolved` is false. - * @param data contains information against which the `minVoteThreshold` is applied. - * @param minVoteThreshold min (exclusive) number of tokens that must have voted for the result to be valid. Can be - * used to enforce a minimum voter participation rate, regardless of how the votes are distributed. - * @return isResolved indicates if the price has been resolved correctly. - * @return price the price that the dvm resolved to. - */ - function getResolvedPrice( - Data storage data, - FixedPoint.Unsigned memory minVoteThreshold - ) internal view returns (bool isResolved, int256 price) { - FixedPoint.Unsigned memory modeThreshold = FixedPoint.fromUnscaledUint(50).div(100); - - if ( - data.totalVotes.isGreaterThan(minVoteThreshold) && - data.voteFrequency[data.currentMode].div(data.totalVotes).isGreaterThan(modeThreshold) - ) { - // `modeThreshold` and `minVoteThreshold` are exceeded, so the current mode is the resolved price. - isResolved = true; - price = data.currentMode; - } else isResolved = false; - } - - /** - * @notice Checks whether a `voteHash` is considered correct. - * @dev Should only be called after a vote is resolved, i.e., via `getResolvedPrice`. - * @param data contains information against which the `voteHash` is checked. - * @param voteHash committed hash submitted by the voter. - * @return bool true if the vote was correct. - */ - function wasVoteCorrect(Data storage data, bytes32 voteHash) internal view returns (bool) { - return voteHash == keccak256(abi.encode(data.currentMode)); - } - - /** - * @notice Gets the total number of tokens whose votes are considered correct. - * @dev Should only be called after a vote is resolved, i.e., via `getResolvedPrice`. - * @param data contains all votes against which the correctly voted tokens are counted. - * @return FixedPoint.Unsigned which indicates the frequency of the correctly voted tokens. - */ - function getTotalCorrectlyVotedTokens(Data storage data) internal view returns (FixedPoint.Unsigned memory) { - return data.voteFrequency[data.currentMode]; - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ResultComputationV2.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ResultComputationV2.sol deleted file mode 100644 index cf7eb8c74..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/ResultComputationV2.sol +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -/** - * @title Computes vote results. - * @dev The result is the mode of the added votes. Otherwise, the vote is unresolved. - */ -library ResultComputationV2 { - /**************************************** - * INTERNAL LIBRARY DATA STRUCTURE * - ****************************************/ - - struct Data { - mapping(int256 => uint128) voteFrequency; // Maps price to number of tokens that voted for that price. - uint128 totalVotes; // The total votes that have been added. - int256 currentMode; // The price that is the current mode, i.e., the price with the highest frequency. - } - - /**************************************** - * VOTING FUNCTIONS * - ****************************************/ - - /** - * @notice Adds a new vote to be used when computing the result. - * @param data contains information to which the vote is applied. - * @param votePrice value specified in the vote for the given `numberTokens`. - * @param numberTokens number of tokens that voted on the `votePrice`. - */ - function addVote(Data storage data, int256 votePrice, uint128 numberTokens) internal { - data.totalVotes += numberTokens; - data.voteFrequency[votePrice] += numberTokens; - if (votePrice != data.currentMode && data.voteFrequency[votePrice] > data.voteFrequency[data.currentMode]) - data.currentMode = votePrice; - } - - /**************************************** - * VOTING STATE GETTERS * - ****************************************/ - - /** - * @notice Returns whether the result is resolved, and if so, what value it resolved to. - * @dev `price` should be ignored if `isResolved` is false. - * @param data contains information against which the `minTotalVotes` and `minModalVotes` thresholds are applied. - * @param minTotalVotes min (exclusive) number of tokens that must have voted (in any direction) for the result - * to be valid. Used to enforce a minimum voter participation rate, regardless of how the votes are distributed. - * @param minModalVotes min (exclusive) number of tokens that must have voted for the modal outcome for it to result - * in a resolution. This is used to avoid cases where the mode is a very small plurality. - * @return isResolved indicates if the price has been resolved correctly. - * @return price the price that the dvm resolved to. - */ - function getResolvedPrice( - Data storage data, - uint128 minTotalVotes, - uint128 minModalVotes - ) internal view returns (bool isResolved, int256 price) { - if (data.totalVotes > minTotalVotes && data.voteFrequency[data.currentMode] > minModalVotes) { - isResolved = true; // minTotalVotes and minModalVotes are exceeded, so the resolved price is the mode. - price = data.currentMode; - } - } - - /** - * @notice Checks whether a `voteHash` is considered correct. - * @dev Should only be called after a vote is resolved, i.e., via `getResolvedPrice`. - * @param data contains information against which the `voteHash` is checked. - * @param voteHash committed hash submitted by the voter. - * @return bool true if the vote was correct. - */ - function wasVoteCorrect(Data storage data, bytes32 voteHash) internal view returns (bool) { - return voteHash == keccak256(abi.encode(data.currentMode)); - } - - /** - * @notice Gets the total number of tokens whose votes are considered correct. - * @dev Should only be called after a vote is resolved, i.e., via `getResolvedPrice`. - * @param data contains all votes against which the correctly voted tokens are counted. - * @return uint128 which indicates the frequency of the correctly voted tokens. - */ - function getTotalCorrectlyVotedTokens(Data storage data) internal view returns (uint128) { - return data.voteFrequency[data.currentMode]; - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Staker.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Staker.sol deleted file mode 100644 index aa8e52f09..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Staker.sol +++ /dev/null @@ -1,372 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/Lockable.sol"; -import "../../common/implementation/MultiCaller.sol"; - -import "../../common/interfaces/ExpandedIERC20.sol"; -import "../interfaces/StakerInterface.sol"; - -import "@openzeppelin/contracts-v4/access/Ownable.sol"; -import "@openzeppelin/contracts-v4/utils/math/SafeCast.sol"; - -/** - * @title Staking contract enabling UMA to be locked up by stakers to earn a pro rata share of a fixed emission rate. - * @dev Handles the staking, unstaking and reward retrieval logic. - */ -abstract contract Staker is StakerInterface, Ownable, Lockable, MultiCaller { - /**************************************** - * STAKING STATE * - ****************************************/ - - // Identifies a "stake" for a given voter. Each staker has an instance of this struct. - struct VoterStake { - uint128 stake; // UMA staked by the staker. - uint128 pendingUnstake; // UMA in unstake cooldown period, waiting to be unstaked. - mapping(uint32 => uint128) pendingStakes; // If a voter stakes during an active reveal, stake is pending. - uint128 rewardsPaidPerToken; // Internal tracker used in the calculation of pro-rata share of rewards. - uint128 outstandingRewards; // Accumulated rewards that have not yet been claimed. - int128 unappliedSlash; // Used to track unapplied slashing in the case of bisected rounds. - uint64 nextIndexToProcess; // The next request index that a staker is susceptible to be slashed on. - uint64 unstakeTime; // Time that a staker can unstake. Used to determine if cooldown has passed. - address delegate; // Address a staker has delegated to. The delegate can commit/reveal/claimRestake rewards. - } - - mapping(address => VoterStake) public voterStakes; // Each voter is mapped to staker struct for their position. - - mapping(address => address) public delegateToStaker; // Mapping of delegates to their delegators (staker). - - uint128 public emissionRate; // Number of UMA emitted per second to incentivize stakers. - - uint128 public cumulativeStake; // Total number of UMA staked within the system. - - uint128 public rewardPerTokenStored; // Tracker used to allocate pro-rata share of rewards to stakers. - - uint64 public unstakeCoolDown; // Delay, in seconds, a staker must wait when trying to unstake their UMA. - - uint64 public lastUpdateTime; // Tracks the last time the reward rate was updated, used in reward allocation. - - ExpandedIERC20 public immutable votingToken; // An instance of the UMA voting token to mint rewards for stakers - - /**************************************** - * EVENTS * - ****************************************/ - - event Staked( - address indexed voter, - address indexed from, - uint128 amount, - uint128 voterStake, - uint128 voterPendingUnstake, - uint128 cumulativeStake - ); - - event RequestedUnstake(address indexed voter, uint128 amount, uint64 unstakeTime, uint128 voterStake); - - event ExecutedUnstake(address indexed voter, uint128 tokensSent, uint128 voterStake); - - event WithdrawnRewards(address indexed voter, address indexed delegate, uint128 tokensWithdrawn); - - event UpdatedReward(address indexed voter, uint128 newReward, uint64 lastUpdateTime); - - event SetNewEmissionRate(uint128 newEmissionRate); - - event SetNewUnstakeCoolDown(uint64 newUnstakeCoolDown); - - event DelegateSet(address indexed delegator, address indexed delegate); - - event DelegatorSet(address indexed delegate, address indexed delegator); - - /** - * @notice Construct the Staker contract - * @param _emissionRate amount of voting tokens that are emitted per second, split pro rata to stakers. - * @param _unstakeCoolDown time that a voter must wait to unstake after requesting to unstake. - * @param _votingToken address of the UMA token contract used to commit votes. - */ - constructor(uint128 _emissionRate, uint64 _unstakeCoolDown, address _votingToken) { - setEmissionRate(_emissionRate); - setUnstakeCoolDown(_unstakeCoolDown); - votingToken = ExpandedIERC20(_votingToken); - } - - /**************************************** - * STAKER FUNCTIONS * - ****************************************/ - - /** - * @notice Pulls tokens from the sender's wallet and stakes them on his behalf. - * @param amount the amount of tokens to stake. - */ - function stake(uint128 amount) external { - _stakeTo(msg.sender, msg.sender, amount); - } - - /** - * @notice Pulls tokens from the sender's wallet and stakes them for the recipient. - * @param recipient the recipient address. - * @param amount the amount of tokens to stake. - */ - function stakeTo(address recipient, uint128 amount) external { - _stakeTo(msg.sender, recipient, amount); - } - - // Pull an amount of votingToken from the from address and stakes them for the recipient address. - // If we are in an active reveal phase the stake amount will be added to the pending stake. - // If not, the stake amount will be added to the stake. - function _stakeTo(address from, address recipient, uint128 amount) internal { - require(amount > 0, "Cannot stake 0"); - - VoterStake storage voterStake = voterStakes[recipient]; - - // If the staker has a cumulative staked balance of 0 then we can shortcut their nextIndexToProcess to - // the most recent index. This means we don't need to traverse requests where the staker was not staked. - // _getStartingIndexForStaker returns the appropriate index to start at. - if (voterStake.stake == 0) voterStake.nextIndexToProcess = _getStartingIndexForStaker(); - _updateTrackers(recipient); - - // Compute pending stakes when needed. - _computePendingStakes(recipient, amount); - - voterStake.stake += amount; - cumulativeStake += amount; - - // Tokens are pulled from the from address and sent to this contract. - // During withdrawAndRestake, from is the same as the address of this contract, so there is no need to transfer. - if (from != address(this)) votingToken.transferFrom(from, address(this), amount); - emit Staked(recipient, from, amount, voterStake.stake, voterStake.pendingUnstake, cumulativeStake); - } - - /** - * @notice Request a certain number of tokens to be unstaked. After the unstake time expires, the user may execute - * the unstake. Tokens requested to unstake are not slashable nor subject to earning rewards. - * This function cannot be called during an active reveal phase. - * Note there is no way to cancel an unstake request, you must wait until after unstakeTime and re-stake. - * @param amount the amount of tokens to request to be unstaked. - */ - function requestUnstake(uint128 amount) external nonReentrant { - require(!_inActiveReveal(), "In an active reveal phase"); - require(amount > 0, "Cannot unstake 0"); - _updateTrackers(msg.sender); - VoterStake storage voterStake = voterStakes[msg.sender]; - - require(voterStake.stake >= amount && voterStake.pendingUnstake == 0, "Bad amount or pending unstake"); - - cumulativeStake -= amount; - voterStake.pendingUnstake = amount; - voterStake.stake -= amount; - voterStake.unstakeTime = uint64(getCurrentTime()) + unstakeCoolDown; - - emit RequestedUnstake(msg.sender, amount, voterStake.unstakeTime, voterStake.stake); - } - - /** - * @notice Execute a previously requested unstake. Requires the unstake time to have passed. - * @dev If a staker requested an unstake and time > unstakeTime then send funds to staker. If unstakeCoolDown is - * set to 0 then the unstake can be executed immediately. - */ - function executeUnstake() external nonReentrant { - VoterStake storage voterStake = voterStakes[msg.sender]; - require( - voterStake.unstakeTime != 0 && (getCurrentTime() >= voterStake.unstakeTime || unstakeCoolDown == 0), - "Unstake time not passed" - ); - uint128 tokensToSend = voterStake.pendingUnstake; - - if (tokensToSend > 0) { - voterStake.pendingUnstake = 0; - voterStake.unstakeTime = 0; - votingToken.transfer(msg.sender, tokensToSend); - } - - emit ExecutedUnstake(msg.sender, tokensToSend, voterStake.stake); - } - - /** - * @notice Send accumulated rewards to the voter. Note that these rewards do not include slashing balance changes. - * @return uint128 the amount of tokens sent to the voter. - */ - function withdrawRewards() external returns (uint128) { - return _withdrawRewards(msg.sender, msg.sender); - } - - // Withdraws rewards for a given voter and sends them to the recipient. - function _withdrawRewards(address voter, address recipient) internal returns (uint128) { - _updateTrackers(voter); - VoterStake storage voterStake = voterStakes[voter]; - - uint128 tokensToMint = voterStake.outstandingRewards; - if (tokensToMint > 0) { - voterStake.outstandingRewards = 0; - require(votingToken.mint(recipient, tokensToMint), "Voting token issuance failed"); - emit WithdrawnRewards(voter, msg.sender, tokensToMint); - } - return tokensToMint; - } - - /** - * @notice Stake accumulated rewards. This is merely a convenience mechanism that combines the voter's withdrawal - * and stake in the same transaction if requested by a delegate or the voter. - * @dev The rewarded tokens simply pass through this contract before being staked on the voter's behalf. - * The balance of the delegate remains unchanged. - * @return uint128 the amount of tokens that the voter is staking. - */ - function withdrawAndRestake() external returns (uint128) { - address voter = getVoterFromDelegate(msg.sender); - uint128 rewards = _withdrawRewards(voter, address(this)); - _stakeTo(address(this), voter, rewards); - return rewards; - } - - /** - * @notice Sets the delegate of a voter. This delegate can vote on behalf of the staker. The staker will still own - * all staked balances, receive rewards and be slashed based on the actions of the delegate. Intended use is using a - * low-security available wallet for voting while keeping access to staked amounts secure by a more secure wallet. - * @param delegate the address of the delegate. - */ - function setDelegate(address delegate) external { - voterStakes[msg.sender].delegate = delegate; - emit DelegateSet(msg.sender, delegate); - } - - /** - * @notice Sets the delegator of a voter. Acts to accept a delegation. The delegate can only vote for the delegator - * if the delegator also selected the delegate to do so (two-way relationship needed). - * @param delegator the address of the delegator. - */ - function setDelegator(address delegator) external { - delegateToStaker[msg.sender] = delegator; - emit DelegatorSet(msg.sender, delegator); - } - - /**************************************** - * OWNER ADMIN FUNCTIONS * - ****************************************/ - - /** - * @notice Set the token's emission rate, the number of voting tokens that are emitted per second. - * @param newEmissionRate the new amount of voting tokens that are emitted per second, split pro rata to stakers. - */ - function setEmissionRate(uint128 newEmissionRate) public onlyOwner { - _updateReward(address(0)); - emissionRate = newEmissionRate; - emit SetNewEmissionRate(newEmissionRate); - } - - /** - * @notice Set the amount of time a voter must wait to unstake after submitting a request to do so. - * @param newUnstakeCoolDown the new duration of the cool down period in seconds. - */ - function setUnstakeCoolDown(uint64 newUnstakeCoolDown) public onlyOwner { - unstakeCoolDown = newUnstakeCoolDown; - emit SetNewUnstakeCoolDown(newUnstakeCoolDown); - } - - // Updates an account internal trackers. - function _updateTrackers(address voter) internal virtual { - _updateReward(voter); - } - - /**************************************** - * VIEW FUNCTIONS * - ****************************************/ - - /** - * @notice Gets the pending stake for a voter for a given round. - * @param voter the voter address. - * @param roundId round id. - * @return uint128 amount of the pending stake. - */ - function getVoterPendingStake(address voter, uint32 roundId) external view returns (uint128) { - return voterStakes[voter].pendingStakes[roundId]; - } - - /** - * @notice Gets the voter from the delegate. - * @param caller caller of the function or the address to check in the mapping between a voter and their delegate. - * @return address voter that corresponds to the delegate. - */ - function getVoterFromDelegate(address caller) public view returns (address) { - address delegator = delegateToStaker[caller]; - // The delegate chose to be a delegate for the staker. - if (delegator != address(0) && voterStakes[delegator].delegate == caller) return delegator; - else return caller; // The staker chose the delegate. - } - - /** - * @notice Determine the number of outstanding token rewards that can be withdrawn by a voter. - * @param voter the address of the voter. - * @return uint256 the outstanding rewards. - */ - function outstandingRewards(address voter) public view returns (uint256) { - VoterStake storage voterStake = voterStakes[voter]; - - return - ((voterStake.stake * (rewardPerToken() - voterStake.rewardsPaidPerToken)) / 1e18) + - voterStake.outstandingRewards; - } - - /** - * @notice Calculate the reward per token based on the last time the reward was updated. - * @return uint256 the reward per token. - */ - function rewardPerToken() public view returns (uint256) { - if (cumulativeStake == 0) return rewardPerTokenStored; - return rewardPerTokenStored + ((getCurrentTime() - lastUpdateTime) * emissionRate * 1e18) / cumulativeStake; - } - - /** - * @notice Returns the total amount of tokens staked by the voter, after applying updateTrackers. Specifically used - * by offchain apps to simulate the cumulative stake + unapplied slashing updates without sending a transaction. - * @param voter the address of the voter. - * @return uint128 the total stake. - */ - function getVoterStakePostUpdate(address voter) external returns (uint128) { - _updateTrackers(voter); - return voterStakes[voter].stake; - } - - /** - * @notice Returns the current block timestamp. - * @dev Can be overridden to control contract time. - * @return the current block timestamp. - */ - function getCurrentTime() public view virtual returns (uint256) { - return block.timestamp; - } - - /**************************************** - * INTERNAL FUNCTIONS * - ****************************************/ - - // This function must be called before any tokens are staked. Update the voter's pending stakes when necessary. - // The contract that inherits from Staker (e.g. VotingV2) must implement this logic by overriding this function. - function _computePendingStakes(address voter, uint128 amount) internal virtual; - - // Add a new stake amount to the voter's pending stake for a specific round id. - function _incrementPendingStake(address voter, uint32 roundId, uint128 amount) internal { - voterStakes[voter].pendingStakes[roundId] += amount; - } - - // Determine if we are in an active reveal phase. This function should be overridden by the child contract. - function _inActiveReveal() internal view virtual returns (bool) { - return false; - } - - // Returns the starting index for a staker. This function should be overridden by the implementing contract. - function _getStartingIndexForStaker() internal virtual returns (uint64) { - return 0; - } - - // Calculate the reward per token based on last time the reward was updated. - function _updateReward(address voter) internal { - uint128 newRewardPerToken = uint128(rewardPerToken()); - rewardPerTokenStored = newRewardPerToken; - lastUpdateTime = uint64(getCurrentTime()); - if (voter != address(0)) { - VoterStake storage voterStake = voterStakes[voter]; - voterStake.outstandingRewards = uint128(outstandingRewards(voter)); - voterStake.rewardsPaidPerToken = newRewardPerToken; - } - emit UpdatedReward(voter, newRewardPerToken, lastUpdateTime); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Store.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Store.sol deleted file mode 100644 index 7762123da..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Store.sol +++ /dev/null @@ -1,179 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; -import "../../common/implementation/FixedPoint.sol"; -import "../../common/implementation/MultiRole.sol"; -import "../../common/implementation/Withdrawable.sol"; -import "../../common/implementation/Testable.sol"; -import "../interfaces/StoreInterface.sol"; - -/** - * @title An implementation of Store that can accept Oracle fees in ETH or any arbitrary ERC20 token. - */ -contract Store is StoreInterface, Withdrawable, Testable { - using SafeMath for uint256; - using FixedPoint for FixedPoint.Unsigned; - using FixedPoint for uint256; - using SafeERC20 for IERC20; - - /**************************************** - * INTERNAL VARIABLES AND STORAGE * - ****************************************/ - - enum Roles { - Owner, - Withdrawer - } - - FixedPoint.Unsigned public fixedOracleFeePerSecondPerPfc; // Percentage of 1 E.g., .1 is 10% Oracle fee. - FixedPoint.Unsigned public weeklyDelayFeePerSecondPerPfc; // Percentage of 1 E.g., .1 is 10% weekly delay fee. - - mapping(address => FixedPoint.Unsigned) public finalFees; - uint256 public constant SECONDS_PER_WEEK = 604800; - - /**************************************** - * EVENTS * - ****************************************/ - - event NewFixedOracleFeePerSecondPerPfc(FixedPoint.Unsigned newOracleFee); - event NewWeeklyDelayFeePerSecondPerPfc(FixedPoint.Unsigned newWeeklyDelayFeePerSecondPerPfc); - event NewFinalFee(FixedPoint.Unsigned newFinalFee); - - /** - * @notice Construct the Store contract. - */ - constructor( - FixedPoint.Unsigned memory _fixedOracleFeePerSecondPerPfc, - FixedPoint.Unsigned memory _weeklyDelayFeePerSecondPerPfc, - address _timerAddress - ) Testable(_timerAddress) { - _createExclusiveRole(uint256(Roles.Owner), uint256(Roles.Owner), msg.sender); - _createWithdrawRole(uint256(Roles.Withdrawer), uint256(Roles.Owner), msg.sender); - setFixedOracleFeePerSecondPerPfc(_fixedOracleFeePerSecondPerPfc); - setWeeklyDelayFeePerSecondPerPfc(_weeklyDelayFeePerSecondPerPfc); - } - - /**************************************** - * ORACLE FEE CALCULATION AND PAYMENT * - ****************************************/ - - /** - * @notice Pays Oracle fees in ETH to the store. - * @dev To be used by contracts whose margin currency is ETH. - */ - function payOracleFees() external payable override { - require(msg.value > 0, "Value sent can't be zero"); - } - - /** - * @notice Pays oracle fees in the margin currency, erc20Address, to the store. - * @dev To be used if the margin currency is an ERC20 token rather than ETH. - * @param erc20Address address of the ERC20 token used to pay the fee. - * @param amount number of tokens to transfer. An approval for at least this amount must exist. - */ - function payOracleFeesErc20(address erc20Address, FixedPoint.Unsigned calldata amount) external override { - IERC20 erc20 = IERC20(erc20Address); - require(amount.isGreaterThan(0), "Amount sent can't be zero"); - erc20.safeTransferFrom(msg.sender, address(this), amount.rawValue); - } - - /** - * @notice Computes the regular oracle fees that a contract should pay for a period. - * @dev The late penalty is similar to the regular fee in that is is charged per second over the period between - * startTime and endTime. - * - * The late penalty percentage increases over time as follows: - * - * - 0-1 week since startTime: no late penalty - * - * - 1-2 weeks since startTime: 1x late penalty percentage is applied - * - * - 2-3 weeks since startTime: 2x late penalty percentage is applied - * - * - ... - * - * @param startTime defines the beginning time from which the fee is paid. - * @param endTime end time until which the fee is paid. - * @param pfc "profit from corruption", or the maximum amount of margin currency that a - * token sponsor could extract from the contract through corrupting the price feed in their favor. - * @return regularFee amount owed for the duration from start to end time for the given pfc. - * @return latePenalty penalty percentage, if any, for paying the fee after the deadline. - */ - function computeRegularFee( - uint256 startTime, - uint256 endTime, - FixedPoint.Unsigned calldata pfc - ) external view override returns (FixedPoint.Unsigned memory regularFee, FixedPoint.Unsigned memory latePenalty) { - uint256 timeDiff = endTime.sub(startTime); - - // Multiply by the unscaled `timeDiff` first, to get more accurate results. - regularFee = pfc.mul(timeDiff).mul(fixedOracleFeePerSecondPerPfc); - - // Compute how long ago the start time was to compute the delay penalty. - uint256 paymentDelay = getCurrentTime().sub(startTime); - - // Compute the additional percentage (per second) that will be charged because of the penalty. - // Note: if less than a week has gone by since the startTime, paymentDelay / SECONDS_PER_WEEK will truncate to - // 0, causing no penalty to be charged. - FixedPoint.Unsigned memory penaltyPercentagePerSecond = weeklyDelayFeePerSecondPerPfc.mul( - paymentDelay.div(SECONDS_PER_WEEK) - ); - - // Apply the penaltyPercentagePerSecond to the payment period. - latePenalty = pfc.mul(timeDiff).mul(penaltyPercentagePerSecond); - } - - /** - * @notice Computes the final oracle fees that a contract should pay at settlement. - * @param currency token used to pay the final fee. - * @return finalFee amount due denominated in units of `currency`. - */ - function computeFinalFee(address currency) external view override returns (FixedPoint.Unsigned memory) { - return finalFees[currency]; - } - - /**************************************** - * ADMIN STATE MODIFYING FUNCTIONS * - ****************************************/ - - /** - * @notice Sets a new oracle fee per second. - * @param newFixedOracleFeePerSecondPerPfc new fee per second charged to use the oracle. - */ - function setFixedOracleFeePerSecondPerPfc( - FixedPoint.Unsigned memory newFixedOracleFeePerSecondPerPfc - ) public onlyRoleHolder(uint256(Roles.Owner)) { - // Oracle fees at or over 100% don't make sense. - require(newFixedOracleFeePerSecondPerPfc.isLessThan(1), "Fee must be < 100% per second."); - fixedOracleFeePerSecondPerPfc = newFixedOracleFeePerSecondPerPfc; - emit NewFixedOracleFeePerSecondPerPfc(newFixedOracleFeePerSecondPerPfc); - } - - /** - * @notice Sets a new weekly delay fee. - * @param newWeeklyDelayFeePerSecondPerPfc fee escalation per week of late fee payment. - */ - function setWeeklyDelayFeePerSecondPerPfc( - FixedPoint.Unsigned memory newWeeklyDelayFeePerSecondPerPfc - ) public onlyRoleHolder(uint256(Roles.Owner)) { - require(newWeeklyDelayFeePerSecondPerPfc.isLessThan(1), "weekly delay fee must be < 100%"); - weeklyDelayFeePerSecondPerPfc = newWeeklyDelayFeePerSecondPerPfc; - emit NewWeeklyDelayFeePerSecondPerPfc(newWeeklyDelayFeePerSecondPerPfc); - } - - /** - * @notice Sets a new final fee for a particular currency. - * @param currency defines the token currency used to pay the final fee. - * @param newFinalFee final fee amount. - */ - function setFinalFee( - address currency, - FixedPoint.Unsigned memory newFinalFee - ) public onlyRoleHolder(uint256(Roles.Owner)) { - finalFees[currency] = newFinalFee; - emit NewFinalFee(newFinalFee); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/TokenMigrator.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/TokenMigrator.sol deleted file mode 100644 index 0f0790f60..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/TokenMigrator.sol +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/FixedPoint.sol"; -import "../../common/interfaces/ExpandedIERC20.sol"; -import "./VotingToken.sol"; - -/** - * @title Migration contract for VotingTokens. - * @dev Handles migrating token holders from one token to the next. - */ -contract TokenMigrator { - using FixedPoint for FixedPoint.Unsigned; - - /**************************************** - * INTERNAL VARIABLES AND STORAGE * - ****************************************/ - - VotingToken public oldToken; - ExpandedIERC20 public newToken; - - uint256 public snapshotId; - FixedPoint.Unsigned public rate; - - mapping(address => bool) public hasMigrated; - - /** - * @notice Construct the TokenMigrator contract. - * @dev This function triggers the snapshot upon which all migrations will be based. - * @param _rate the number of old tokens it takes to generate one new token. - * @param _oldToken address of the token being migrated from. - * @param _newToken address of the token being migrated to. - */ - constructor(FixedPoint.Unsigned memory _rate, address _oldToken, address _newToken) { - // Prevents division by 0 in migrateTokens(). - // Also it doesn’t make sense to have “0 old tokens equate to 1 new token”. - require(_rate.isGreaterThan(0), "Rate can't be 0"); - rate = _rate; - newToken = ExpandedIERC20(_newToken); - oldToken = VotingToken(_oldToken); - snapshotId = oldToken.snapshot(); - } - - /** - * @notice Migrates the tokenHolder's old tokens to new tokens. - * @dev This function can only be called once per `tokenHolder`. Anyone can call this method - * on behalf of any other token holder since there is no disadvantage to receiving the tokens earlier. - * @param tokenHolder address of the token holder to migrate. - */ - function migrateTokens(address tokenHolder) external { - require(!hasMigrated[tokenHolder], "Already migrated tokens"); - hasMigrated[tokenHolder] = true; - - FixedPoint.Unsigned memory oldBalance = FixedPoint.Unsigned(oldToken.balanceOfAt(tokenHolder, snapshotId)); - - if (!oldBalance.isGreaterThan(0)) { - return; - } - - FixedPoint.Unsigned memory newBalance = oldBalance.div(rate); - require(newToken.mint(tokenHolder, newBalance.rawValue), "Mint failed"); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VoteTiming.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VoteTiming.sol deleted file mode 100644 index 6fdfa853a..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VoteTiming.sol +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../interfaces/VotingInterface.sol"; - -/** - * @title Library to compute rounds and phases for an equal length commit-reveal voting cycle. - */ -library VoteTiming { - struct Data { - uint256 phaseLength; - } - - /** - * @notice Initializes the data object. Sets the phase length based on the input. - * @param data reference to the this library's data object. - * @param phaseLength length of voting phase in seconds. - */ - function init(Data storage data, uint256 phaseLength) internal { - // This should have a require message but this results in an internal Solidity error. - require(phaseLength > 0); - data.phaseLength = phaseLength; - } - - /** - * @notice Computes the roundID based off the current time as floor(timestamp/roundLength). - * @dev The round ID depends on the global timestamp but not on the lifetime of the system. - * The consequence is that the initial round ID starts at an arbitrary number (that increments, as expected, for subsequent rounds) instead of zero or one. - * @param data input data object. - * @param currentTime input unix timestamp used to compute the current roundId. - * @return roundId defined as a function of the currentTime and `phaseLength` from `data`. - */ - function computeCurrentRoundId(Data storage data, uint256 currentTime) internal view returns (uint256) { - uint256 roundLength = data.phaseLength * uint256(VotingAncillaryInterface.Phase.NUM_PHASES); - return currentTime / roundLength; - } - - /** - * @notice compute the round end time as a function of the round Id. - * @param data input data object. - * @param roundId uniquely identifies the current round. - * @return timestamp unix time of when the current round will end. - */ - function computeRoundEndTime(Data storage data, uint256 roundId) internal view returns (uint256) { - uint256 roundLength = data.phaseLength * uint256(VotingAncillaryInterface.Phase.NUM_PHASES); - return roundLength * (roundId + 1); - } - - /** - * @notice Computes the current phase based only on the current time. - * @param data input data object. - * @param currentTime input unix timestamp used to compute the current roundId. - * @return current voting phase based on current time and vote phases configuration. - */ - function computeCurrentPhase( - Data storage data, - uint256 currentTime - ) internal view returns (VotingAncillaryInterface.Phase) { - // This employs some hacky casting. We could make this an if-statement if we're worried about type safety. - return - VotingAncillaryInterface.Phase( - (currentTime / data.phaseLength) % uint256(VotingAncillaryInterface.Phase.NUM_PHASES) - ); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Voting.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Voting.sol deleted file mode 100644 index 76e4f7d25..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/Voting.sol +++ /dev/null @@ -1,977 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/FixedPoint.sol"; -import "../../common/implementation/Testable.sol"; -import "../interfaces/FinderInterface.sol"; -import "../interfaces/OracleInterface.sol"; -import "../interfaces/OracleAncillaryInterface.sol"; -import "../interfaces/VotingInterface.sol"; -import "../interfaces/VotingAncillaryInterface.sol"; -import "../interfaces/IdentifierWhitelistInterface.sol"; -import "./Registry.sol"; -import "./ResultComputation.sol"; -import "./VoteTiming.sol"; -import "./VotingToken.sol"; -import "./Constants.sol"; - -import "@openzeppelin/contracts-v4/access/Ownable.sol"; -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; -import "@openzeppelin/contracts-v4/utils/cryptography/ECDSA.sol"; - -/** - * @title Voting system for Oracle. - * @dev Handles receiving and resolving price requests via a commit-reveal voting scheme. - */ -contract Voting is - Testable, - Ownable, - OracleInterface, - OracleAncillaryInterface, // Interface to support ancillary data with price requests. - VotingInterface, - VotingAncillaryInterface // Interface to support ancillary data with voting rounds. -{ - using FixedPoint for FixedPoint.Unsigned; - using SafeMath for uint256; - using VoteTiming for VoteTiming.Data; - using ResultComputation for ResultComputation.Data; - - /**************************************** - * VOTING DATA STRUCTURES * - ****************************************/ - - // Identifies a unique price request for which the Oracle will always return the same value. - // Tracks ongoing votes as well as the result of the vote. - struct PriceRequest { - bytes32 identifier; - uint256 time; - // A map containing all votes for this price in various rounds. - mapping(uint256 => VoteInstance) voteInstances; - // If in the past, this was the voting round where this price was resolved. If current or the upcoming round, - // this is the voting round where this price will be voted on, but not necessarily resolved. - uint256 lastVotingRound; - // The index in the `pendingPriceRequests` that references this PriceRequest. A value of UINT_MAX means that - // this PriceRequest is resolved and has been cleaned up from `pendingPriceRequests`. - uint256 index; - bytes ancillaryData; - } - - struct VoteInstance { - // Maps (voterAddress) to their submission. - mapping(address => VoteSubmission) voteSubmissions; - // The data structure containing the computed voting results. - ResultComputation.Data resultComputation; - } - - struct VoteSubmission { - // A bytes32 of `0` indicates no commit or a commit that was already revealed. - bytes32 commit; - // The hash of the value that was revealed. - // Note: this is only used for computation of rewards. - bytes32 revealHash; - } - - struct Round { - uint256 snapshotId; // Voting token snapshot ID for this round. 0 if no snapshot has been taken. - FixedPoint.Unsigned inflationRate; // Inflation rate set for this round. - FixedPoint.Unsigned gatPercentage; // Gat rate set for this round. - uint256 rewardsExpirationTime; // Time that rewards for this round can be claimed until. - } - - // Represents the status a price request has. - enum RequestStatus { - NotRequested, // Was never requested. - Active, // Is being voted on in the current round. - Resolved, // Was resolved in a previous round. - Future // Is scheduled to be voted on in a future round. - } - - // Only used as a return value in view methods -- never stored in the contract. - struct RequestState { - RequestStatus status; - uint256 lastVotingRound; - } - - /**************************************** - * INTERNAL TRACKING * - ****************************************/ - - // Maps round numbers to the rounds. - mapping(uint256 => Round) public rounds; - - // Maps price request IDs to the PriceRequest struct. - mapping(bytes32 => PriceRequest) private priceRequests; - - // Price request ids for price requests that haven't yet been marked as resolved. - // These requests may be for future rounds. - bytes32[] internal pendingPriceRequests; - - VoteTiming.Data public voteTiming; - - // Percentage of the total token supply that must be used in a vote to - // create a valid price resolution. 1 == 100%. - FixedPoint.Unsigned public gatPercentage; - - // Global setting for the rate of inflation per vote. This is the percentage of the snapshotted total supply that - // should be split among the correct voters. - // Note: this value is used to set per-round inflation at the beginning of each round. 1 = 100%. - FixedPoint.Unsigned public inflationRate; - - // Time in seconds from the end of the round in which a price request is - // resolved that voters can still claim their rewards. - uint256 public rewardsExpirationTimeout; - - // Reference to the voting token. - VotingToken public votingToken; - - // Reference to the Finder. - FinderInterface private finder; - - // If non-zero, this contract has been migrated to this address. All voters and - // financial contracts should query the new address only. - address public migratedAddress; - - // Max value of an unsigned integer. - uint256 private constant UINT_MAX = ~uint256(0); - - // Max length in bytes of ancillary data that can be appended to a price request. - // As of December 2020, the current Ethereum gas limit is 12.5 million. This requestPrice function's gas primarily - // comes from computing a Keccak-256 hash in _encodePriceRequest and writing a new PriceRequest to - // storage. We have empirically determined an ancillary data limit of 8192 bytes that keeps this function - // well within the gas limit at ~8 million gas. To learn more about the gas limit and EVM opcode costs go here: - // - https://etherscan.io/chart/gaslimit - // - https://github.com/djrtwo/evm-opcode-gas-costs - uint256 public constant ancillaryBytesLimit = 8192; - - bytes32 public snapshotMessageHash = ECDSA.toEthSignedMessageHash(keccak256(bytes("Sign For Snapshot"))); - - /*************************************** - * EVENTS * - ****************************************/ - - event VoteCommitted( - address indexed voter, - uint256 indexed roundId, - bytes32 indexed identifier, - uint256 time, - bytes ancillaryData - ); - - event EncryptedVote( - address indexed voter, - uint256 indexed roundId, - bytes32 indexed identifier, - uint256 time, - bytes ancillaryData, - bytes encryptedVote - ); - - event VoteRevealed( - address indexed voter, - uint256 indexed roundId, - bytes32 indexed identifier, - uint256 time, - int256 price, - bytes ancillaryData, - uint256 numTokens - ); - - event RewardsRetrieved( - address indexed voter, - uint256 indexed roundId, - bytes32 indexed identifier, - uint256 time, - bytes ancillaryData, - uint256 numTokens - ); - - event PriceRequestAdded(uint256 indexed roundId, bytes32 indexed identifier, uint256 time); - - event PriceResolved( - uint256 indexed roundId, - bytes32 indexed identifier, - uint256 time, - int256 price, - bytes ancillaryData - ); - - /** - * @notice Construct the Voting contract. - * @param _phaseLength length of the commit and reveal phases in seconds. - * @param _gatPercentage of the total token supply that must be used in a vote to create a valid price resolution. - * @param _inflationRate percentage inflation per round used to increase token supply of correct voters. - * @param _rewardsExpirationTimeout timeout, in seconds, within which rewards must be claimed. - * @param _votingToken address of the UMA token contract used to commit votes. - * @param _finder keeps track of all contracts within the system based on their interfaceName. - * @param _timerAddress Contract that stores the current time in a testing environment. - * Must be set to 0x0 for production environments that use live time. - */ - constructor( - uint256 _phaseLength, - FixedPoint.Unsigned memory _gatPercentage, - FixedPoint.Unsigned memory _inflationRate, - uint256 _rewardsExpirationTimeout, - address _votingToken, - address _finder, - address _timerAddress - ) Testable(_timerAddress) { - voteTiming.init(_phaseLength); - require(_gatPercentage.isLessThanOrEqual(1), "GAT percentage must be <= 100%"); - gatPercentage = _gatPercentage; - inflationRate = _inflationRate; - votingToken = VotingToken(_votingToken); - finder = FinderInterface(_finder); - rewardsExpirationTimeout = _rewardsExpirationTimeout; - } - - /*************************************** - MODIFIERS - ****************************************/ - - modifier onlyRegisteredContract() { - if (migratedAddress != address(0)) { - require(msg.sender == migratedAddress, "Caller must be migrated address"); - } else { - Registry registry = Registry(finder.getImplementationAddress(OracleInterfaces.Registry)); - require(registry.isContractRegistered(msg.sender), "Called must be registered"); - } - _; - } - - modifier onlyIfNotMigrated() { - require(migratedAddress == address(0), "Only call this if not migrated"); - _; - } - - /**************************************** - * PRICE REQUEST AND ACCESS FUNCTIONS * - ****************************************/ - - /** - * @notice Enqueues a request (if a request isn't already present) for the given `identifier`, `time` pair. - * @dev Time must be in the past and the identifier must be supported. The length of the ancillary data - * is limited such that this method abides by the EVM transaction gas limit. - * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. - * @param time unix timestamp for the price request. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - */ - function requestPrice( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) public override onlyRegisteredContract { - uint256 blockTime = getCurrentTime(); - require(time <= blockTime, "Can only request in past"); - require(_getIdentifierWhitelist().isIdentifierSupported(identifier), "Unsupported identifier request"); - require(ancillaryData.length <= ancillaryBytesLimit, "Invalid ancillary data"); - - bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); - PriceRequest storage priceRequest = priceRequests[priceRequestId]; - uint256 currentRoundId = voteTiming.computeCurrentRoundId(blockTime); - - RequestStatus requestStatus = _getRequestStatus(priceRequest, currentRoundId); - - if (requestStatus == RequestStatus.NotRequested) { - // Price has never been requested. - // Price requests always go in the next round, so add 1 to the computed current round. - uint256 nextRoundId = currentRoundId.add(1); - - PriceRequest storage newPriceRequest = priceRequests[priceRequestId]; - newPriceRequest.identifier = identifier; - newPriceRequest.time = time; - newPriceRequest.lastVotingRound = nextRoundId; - newPriceRequest.index = pendingPriceRequests.length; - newPriceRequest.ancillaryData = ancillaryData; - - pendingPriceRequests.push(priceRequestId); - emit PriceRequestAdded(nextRoundId, identifier, time); - } - } - - // Overloaded method to enable short term backwards compatibility. Will be deprecated in the next DVM version. - function requestPrice(bytes32 identifier, uint256 time) public override { - requestPrice(identifier, time, ""); - } - - /** - * @notice Whether the price for `identifier` and `time` is available. - * @dev Time must be in the past and the identifier must be supported. - * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. - * @param time unix timestamp of for the price request. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - * @return _hasPrice bool if the DVM has resolved to a price for the given identifier and timestamp. - */ - function hasPrice( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) public view override onlyRegisteredContract returns (bool) { - (bool _hasPrice, , ) = _getPriceOrError(identifier, time, ancillaryData); - return _hasPrice; - } - - // Overloaded method to enable short term backwards compatibility. Will be deprecated in the next DVM version. - function hasPrice(bytes32 identifier, uint256 time) public view override returns (bool) { - return hasPrice(identifier, time, ""); - } - - /** - * @notice Gets the price for `identifier` and `time` if it has already been requested and resolved. - * @dev If the price is not available, the method reverts. - * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. - * @param time unix timestamp of for the price request. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - * @return int256 representing the resolved price for the given identifier and timestamp. - */ - function getPrice( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) public view override onlyRegisteredContract returns (int256) { - (bool _hasPrice, int256 price, string memory message) = _getPriceOrError(identifier, time, ancillaryData); - - // If the price wasn't available, revert with the provided message. - require(_hasPrice, message); - return price; - } - - // Overloaded method to enable short term backwards compatibility. Will be deprecated in the next DVM version. - function getPrice(bytes32 identifier, uint256 time) public view override returns (int256) { - return getPrice(identifier, time, ""); - } - - /** - * @notice Gets the status of a list of price requests, identified by their identifier and time. - * @dev If the status for a particular request is NotRequested, the lastVotingRound will always be 0. - * @param requests array of type PendingRequest which includes an identifier and timestamp for each request. - * @return requestStates a list, in the same order as the input list, giving the status of each of the specified price requests. - */ - function getPriceRequestStatuses( - PendingRequestAncillary[] memory requests - ) public view returns (RequestState[] memory) { - RequestState[] memory requestStates = new RequestState[](requests.length); - uint256 currentRoundId = voteTiming.computeCurrentRoundId(getCurrentTime()); - for (uint256 i = 0; i < requests.length; i++) { - PriceRequest storage priceRequest = _getPriceRequest( - requests[i].identifier, - requests[i].time, - requests[i].ancillaryData - ); - - RequestStatus status = _getRequestStatus(priceRequest, currentRoundId); - - // If it's an active request, its true lastVotingRound is the current one, even if it hasn't been updated. - if (status == RequestStatus.Active) { - requestStates[i].lastVotingRound = currentRoundId; - } else { - requestStates[i].lastVotingRound = priceRequest.lastVotingRound; - } - requestStates[i].status = status; - } - return requestStates; - } - - // Overloaded method to enable short term backwards compatibility. Will be deprecated in the next DVM version. - function getPriceRequestStatuses(PendingRequest[] memory requests) public view returns (RequestState[] memory) { - PendingRequestAncillary[] memory requestsAncillary = new PendingRequestAncillary[](requests.length); - - for (uint256 i = 0; i < requests.length; i++) { - requestsAncillary[i].identifier = requests[i].identifier; - requestsAncillary[i].time = requests[i].time; - requestsAncillary[i].ancillaryData = ""; - } - return getPriceRequestStatuses(requestsAncillary); - } - - /**************************************** - * VOTING FUNCTIONS * - ****************************************/ - - /** - * @notice Commit a vote for a price request for `identifier` at `time`. - * @dev `identifier`, `time` must correspond to a price request that's currently in the commit phase. - * Commits can be changed. - * @dev Since transaction data is public, the salt will be revealed with the vote. While this is the system’s expected behavior, - * voters should never reuse salts. If someone else is able to guess the voted price and knows that a salt will be reused, then - * they can determine the vote pre-reveal. - * @param identifier uniquely identifies the committed vote. EG BTC/USD price pair. - * @param time unix timestamp of the price being voted on. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - * @param hash keccak256 hash of the `price`, `salt`, voter `address`, `time`, current `roundId`, and `identifier`. - */ - function commitVote( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData, - bytes32 hash - ) public override onlyIfNotMigrated { - require(hash != bytes32(0), "Invalid provided hash"); - // Current time is required for all vote timing queries. - uint256 blockTime = getCurrentTime(); - require( - voteTiming.computeCurrentPhase(blockTime) == VotingAncillaryInterface.Phase.Commit, - "Cannot commit in reveal phase" - ); - - // At this point, the computed and last updated round ID should be equal. - uint256 currentRoundId = voteTiming.computeCurrentRoundId(blockTime); - - PriceRequest storage priceRequest = _getPriceRequest(identifier, time, ancillaryData); - require( - _getRequestStatus(priceRequest, currentRoundId) == RequestStatus.Active, - "Cannot commit inactive request" - ); - - priceRequest.lastVotingRound = currentRoundId; - VoteInstance storage voteInstance = priceRequest.voteInstances[currentRoundId]; - voteInstance.voteSubmissions[msg.sender].commit = hash; - - emit VoteCommitted(msg.sender, currentRoundId, identifier, time, ancillaryData); - } - - // Overloaded method to enable short term backwards compatibility. Will be deprecated in the next DVM version. - function commitVote(bytes32 identifier, uint256 time, bytes32 hash) public override onlyIfNotMigrated { - commitVote(identifier, time, "", hash); - } - - /** - * @notice Snapshot the current round's token balances and lock in the inflation rate and GAT. - * @dev This function can be called multiple times, but only the first call per round into this function or `revealVote` - * will create the round snapshot. Any later calls will be a no-op. Will revert unless called during reveal period. - * @param signature signature required to prove caller is an EOA to prevent flash loans from being included in the - * snapshot. - */ - function snapshotCurrentRound( - bytes calldata signature - ) external override(VotingInterface, VotingAncillaryInterface) onlyIfNotMigrated { - uint256 blockTime = getCurrentTime(); - require(voteTiming.computeCurrentPhase(blockTime) == Phase.Reveal, "Only snapshot in reveal phase"); - // Require public snapshot require signature to ensure caller is an EOA. - require(ECDSA.recover(snapshotMessageHash, signature) == msg.sender, "Signature must match sender"); - uint256 roundId = voteTiming.computeCurrentRoundId(blockTime); - _freezeRoundVariables(roundId); - } - - /** - * @notice Reveal a previously committed vote for `identifier` at `time`. - * @dev The revealed `price`, `salt`, `address`, `time`, `roundId`, and `identifier`, must hash to the latest `hash` - * that `commitVote()` was called with. Only the committer can reveal their vote. - * @param identifier voted on in the commit phase. EG BTC/USD price pair. - * @param time specifies the unix timestamp of the price being voted on. - * @param price voted on during the commit phase. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - * @param salt value used to hide the commitment price during the commit phase. - */ - function revealVote( - bytes32 identifier, - uint256 time, - int256 price, - bytes memory ancillaryData, - int256 salt - ) public override onlyIfNotMigrated { - require(voteTiming.computeCurrentPhase(getCurrentTime()) == Phase.Reveal, "Cannot reveal in commit phase"); - // Note: computing the current round is required to disallow people from revealing an old commit after the round is over. - uint256 roundId = voteTiming.computeCurrentRoundId(getCurrentTime()); - - PriceRequest storage priceRequest = _getPriceRequest(identifier, time, ancillaryData); - VoteInstance storage voteInstance = priceRequest.voteInstances[roundId]; - VoteSubmission storage voteSubmission = voteInstance.voteSubmissions[msg.sender]; - - // Scoping to get rid of a stack too deep error. - { - // 0 hashes are disallowed in the commit phase, so they indicate a different error. - // Cannot reveal an uncommitted or previously revealed hash - require(voteSubmission.commit != bytes32(0), "Invalid hash reveal"); - require( - keccak256(abi.encodePacked(price, salt, msg.sender, time, ancillaryData, roundId, identifier)) == - voteSubmission.commit, - "Revealed data != commit hash" - ); - // To protect against flash loans, we require snapshot be validated as EOA. - require(rounds[roundId].snapshotId != 0, "Round has no snapshot"); - } - - // Get the frozen snapshotId - uint256 snapshotId = rounds[roundId].snapshotId; - - delete voteSubmission.commit; - - // Get the voter's snapshotted balance. Since balances are returned pre-scaled by 10**18, we can directly - // initialize the Unsigned value with the returned uint. - FixedPoint.Unsigned memory balance = FixedPoint.Unsigned(votingToken.balanceOfAt(msg.sender, snapshotId)); - - // Set the voter's submission. - voteSubmission.revealHash = keccak256(abi.encode(price)); - - // Add vote to the results. - voteInstance.resultComputation.addVote(price, balance); - - emit VoteRevealed(msg.sender, roundId, identifier, time, price, ancillaryData, balance.rawValue); - } - - // Overloaded method to enable short term backwards compatibility. Will be deprecated in the next DVM version. - function revealVote(bytes32 identifier, uint256 time, int256 price, int256 salt) public override { - revealVote(identifier, time, price, "", salt); - } - - /** - * @notice commits a vote and logs an event with a data blob, typically an encrypted version of the vote - * @dev An encrypted version of the vote is emitted in an event `EncryptedVote` to allow off-chain infrastructure to - * retrieve the commit. The contents of `encryptedVote` are never used on chain: it is purely for convenience. - * @param identifier unique price pair identifier. Eg: BTC/USD price pair. - * @param time unix timestamp of for the price request. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - * @param hash keccak256 hash of the price you want to vote for and a `int256 salt`. - * @param encryptedVote offchain encrypted blob containing the voters amount, time and salt. - */ - function commitAndEmitEncryptedVote( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData, - bytes32 hash, - bytes memory encryptedVote - ) public override { - commitVote(identifier, time, ancillaryData, hash); - - uint256 roundId = voteTiming.computeCurrentRoundId(getCurrentTime()); - emit EncryptedVote(msg.sender, roundId, identifier, time, ancillaryData, encryptedVote); - } - - // Overloaded method to enable short term backwards compatibility. Will be deprecated in the next DVM version. - function commitAndEmitEncryptedVote( - bytes32 identifier, - uint256 time, - bytes32 hash, - bytes memory encryptedVote - ) public override { - commitVote(identifier, time, "", hash); - - commitAndEmitEncryptedVote(identifier, time, "", hash, encryptedVote); - } - - /** - * @notice Submit a batch of commits in a single transaction. - * @dev Using `encryptedVote` is optional. If included then commitment is emitted in an event. - * Look at `project-root/common/Constants.js` for the tested maximum number of - * commitments that can fit in one transaction. - * @param commits struct to encapsulate an `identifier`, `time`, `hash` and optional `encryptedVote`. - */ - function batchCommit(CommitmentAncillary[] memory commits) public override { - for (uint256 i = 0; i < commits.length; i++) { - if (commits[i].encryptedVote.length == 0) { - commitVote(commits[i].identifier, commits[i].time, commits[i].ancillaryData, commits[i].hash); - } else { - commitAndEmitEncryptedVote( - commits[i].identifier, - commits[i].time, - commits[i].ancillaryData, - commits[i].hash, - commits[i].encryptedVote - ); - } - } - } - - // Overloaded method to enable short term backwards compatibility. Will be deprecated in the next DVM version. - function batchCommit(Commitment[] memory commits) public override { - CommitmentAncillary[] memory commitsAncillary = new CommitmentAncillary[](commits.length); - - for (uint256 i = 0; i < commits.length; i++) { - commitsAncillary[i].identifier = commits[i].identifier; - commitsAncillary[i].time = commits[i].time; - commitsAncillary[i].ancillaryData = ""; - commitsAncillary[i].hash = commits[i].hash; - commitsAncillary[i].encryptedVote = commits[i].encryptedVote; - } - batchCommit(commitsAncillary); - } - - /** - * @notice Reveal multiple votes in a single transaction. - * Look at `project-root/common/Constants.js` for the tested maximum number of reveals. - * that can fit in one transaction. - * @dev For more info on reveals, review the comment for `revealVote`. - * @param reveals array of the Reveal struct which contains an identifier, time, price and salt. - */ - function batchReveal(RevealAncillary[] memory reveals) public override { - for (uint256 i = 0; i < reveals.length; i++) { - revealVote( - reveals[i].identifier, - reveals[i].time, - reveals[i].price, - reveals[i].ancillaryData, - reveals[i].salt - ); - } - } - - // Overloaded method to enable short term backwards compatibility. Will be deprecated in the next DVM version. - function batchReveal(Reveal[] memory reveals) public override { - RevealAncillary[] memory revealsAncillary = new RevealAncillary[](reveals.length); - - for (uint256 i = 0; i < reveals.length; i++) { - revealsAncillary[i].identifier = reveals[i].identifier; - revealsAncillary[i].time = reveals[i].time; - revealsAncillary[i].price = reveals[i].price; - revealsAncillary[i].ancillaryData = ""; - revealsAncillary[i].salt = reveals[i].salt; - } - batchReveal(revealsAncillary); - } - - /** - * @notice Retrieves rewards owed for a set of resolved price requests. - * @dev Can only retrieve rewards if calling for a valid round and if the call is done within the timeout threshold - * (not expired). Note that a named return value is used here to avoid a stack to deep error. - * @param voterAddress voter for which rewards will be retrieved. Does not have to be the caller. - * @param roundId the round from which voting rewards will be retrieved from. - * @param toRetrieve array of PendingRequests which rewards are retrieved from. - * @return totalRewardToIssue total amount of rewards returned to the voter. - */ - function retrieveRewards( - address voterAddress, - uint256 roundId, - PendingRequestAncillary[] memory toRetrieve - ) public override returns (FixedPoint.Unsigned memory totalRewardToIssue) { - if (migratedAddress != address(0)) { - require(msg.sender == migratedAddress, "Can only call from migrated"); - } - require(roundId < voteTiming.computeCurrentRoundId(getCurrentTime()), "Invalid roundId"); - - Round storage round = rounds[roundId]; - bool isExpired = getCurrentTime() > round.rewardsExpirationTime; - FixedPoint.Unsigned memory snapshotBalance = FixedPoint.Unsigned( - votingToken.balanceOfAt(voterAddress, round.snapshotId) - ); - - // Compute the total amount of reward that will be issued for each of the votes in the round. - FixedPoint.Unsigned memory snapshotTotalSupply = FixedPoint.Unsigned( - votingToken.totalSupplyAt(round.snapshotId) - ); - FixedPoint.Unsigned memory totalRewardPerVote = round.inflationRate.mul(snapshotTotalSupply); - - // Keep track of the voter's accumulated token reward. - totalRewardToIssue = FixedPoint.Unsigned(0); - - for (uint256 i = 0; i < toRetrieve.length; i++) { - PriceRequest storage priceRequest = _getPriceRequest( - toRetrieve[i].identifier, - toRetrieve[i].time, - toRetrieve[i].ancillaryData - ); - VoteInstance storage voteInstance = priceRequest.voteInstances[priceRequest.lastVotingRound]; - // Only retrieve rewards for votes resolved in same round - require(priceRequest.lastVotingRound == roundId, "Retrieve for votes same round"); - - _resolvePriceRequest(priceRequest, voteInstance); - - if (voteInstance.voteSubmissions[voterAddress].revealHash == 0) { - continue; - } else if (isExpired) { - // Emit a 0 token retrieval on expired rewards. - emit RewardsRetrieved( - voterAddress, - roundId, - toRetrieve[i].identifier, - toRetrieve[i].time, - toRetrieve[i].ancillaryData, - 0 - ); - } else if ( - voteInstance.resultComputation.wasVoteCorrect(voteInstance.voteSubmissions[voterAddress].revealHash) - ) { - // The price was successfully resolved during the voter's last voting round, the voter revealed - // and was correct, so they are eligible for a reward. - // Compute the reward and add to the cumulative reward. - - FixedPoint.Unsigned memory reward = snapshotBalance.mul(totalRewardPerVote).div( - voteInstance.resultComputation.getTotalCorrectlyVotedTokens() - ); - totalRewardToIssue = totalRewardToIssue.add(reward); - - // Emit reward retrieval for this vote. - emit RewardsRetrieved( - voterAddress, - roundId, - toRetrieve[i].identifier, - toRetrieve[i].time, - toRetrieve[i].ancillaryData, - reward.rawValue - ); - } else { - // Emit a 0 token retrieval on incorrect votes. - emit RewardsRetrieved( - voterAddress, - roundId, - toRetrieve[i].identifier, - toRetrieve[i].time, - toRetrieve[i].ancillaryData, - 0 - ); - } - - // Delete the submission to capture any refund and clean up storage. - delete voteInstance.voteSubmissions[voterAddress].revealHash; - } - - // Issue any accumulated rewards. - if (totalRewardToIssue.isGreaterThan(0)) { - require(votingToken.mint(voterAddress, totalRewardToIssue.rawValue), "Voting token issuance failed"); - } - } - - // Overloaded method to enable short term backwards compatibility. Will be deprecated in the next DVM version. - function retrieveRewards( - address voterAddress, - uint256 roundId, - PendingRequest[] memory toRetrieve - ) public override returns (FixedPoint.Unsigned memory) { - PendingRequestAncillary[] memory toRetrieveAncillary = new PendingRequestAncillary[](toRetrieve.length); - - for (uint256 i = 0; i < toRetrieve.length; i++) { - toRetrieveAncillary[i].identifier = toRetrieve[i].identifier; - toRetrieveAncillary[i].time = toRetrieve[i].time; - toRetrieveAncillary[i].ancillaryData = ""; - } - - return retrieveRewards(voterAddress, roundId, toRetrieveAncillary); - } - - /**************************************** - * VOTING GETTER FUNCTIONS * - ****************************************/ - - /** - * @notice Gets the queries that are being voted on this round. - * @return pendingRequests array containing identifiers of type `PendingRequest`. - * and timestamps for all pending requests. - */ - function getPendingRequests() - external - view - override(VotingInterface, VotingAncillaryInterface) - returns (PendingRequestAncillary[] memory) - { - uint256 blockTime = getCurrentTime(); - uint256 currentRoundId = voteTiming.computeCurrentRoundId(blockTime); - - // Solidity memory arrays aren't resizable (and reading storage is expensive). Hence this hackery to filter - // `pendingPriceRequests` only to those requests that have an Active RequestStatus. - PendingRequestAncillary[] memory unresolved = new PendingRequestAncillary[](pendingPriceRequests.length); - uint256 numUnresolved = 0; - - for (uint256 i = 0; i < pendingPriceRequests.length; i++) { - PriceRequest storage priceRequest = priceRequests[pendingPriceRequests[i]]; - if (_getRequestStatus(priceRequest, currentRoundId) == RequestStatus.Active) { - unresolved[numUnresolved] = PendingRequestAncillary({ - identifier: priceRequest.identifier, - time: priceRequest.time, - ancillaryData: priceRequest.ancillaryData - }); - numUnresolved++; - } - } - - PendingRequestAncillary[] memory pendingRequests = new PendingRequestAncillary[](numUnresolved); - for (uint256 i = 0; i < numUnresolved; i++) { - pendingRequests[i] = unresolved[i]; - } - return pendingRequests; - } - - /** - * @notice Returns the current voting phase, as a function of the current time. - * @return Phase to indicate the current phase. Either { Commit, Reveal, NUM_PHASES_PLACEHOLDER }. - */ - function getVotePhase() external view override(VotingInterface, VotingAncillaryInterface) returns (Phase) { - return voteTiming.computeCurrentPhase(getCurrentTime()); - } - - /** - * @notice Returns the current round ID, as a function of the current time. - * @return uint256 representing the unique round ID. - */ - function getCurrentRoundId() external view override(VotingInterface, VotingAncillaryInterface) returns (uint256) { - return voteTiming.computeCurrentRoundId(getCurrentTime()); - } - - /**************************************** - * OWNER ADMIN FUNCTIONS * - ****************************************/ - - /** - * @notice Disables this Voting contract in favor of the migrated one. - * @dev Can only be called by the contract owner. - * @param newVotingAddress the newly migrated contract address. - */ - function setMigrated( - address newVotingAddress - ) external override(VotingInterface, VotingAncillaryInterface) onlyOwner { - migratedAddress = newVotingAddress; - } - - /** - * @notice Resets the inflation rate. Note: this change only applies to rounds that have not yet begun. - * @dev This method is public because calldata structs are not currently supported by solidity. - * @param newInflationRate sets the next round's inflation rate. - */ - function setInflationRate( - FixedPoint.Unsigned memory newInflationRate - ) public override(VotingInterface, VotingAncillaryInterface) onlyOwner { - inflationRate = newInflationRate; - } - - /** - * @notice Resets the Gat percentage. Note: this change only applies to rounds that have not yet begun. - * @dev This method is public because calldata structs are not currently supported by solidity. - * @param newGatPercentage sets the next round's Gat percentage. - */ - function setGatPercentage( - FixedPoint.Unsigned memory newGatPercentage - ) public override(VotingInterface, VotingAncillaryInterface) onlyOwner { - require(newGatPercentage.isLessThan(1), "GAT percentage must be < 100%"); - gatPercentage = newGatPercentage; - } - - /** - * @notice Resets the rewards expiration timeout. - * @dev This change only applies to rounds that have not yet begun. - * @param NewRewardsExpirationTimeout how long a caller can wait before choosing to withdraw their rewards. - */ - function setRewardsExpirationTimeout( - uint256 NewRewardsExpirationTimeout - ) public override(VotingInterface, VotingAncillaryInterface) onlyOwner { - rewardsExpirationTimeout = NewRewardsExpirationTimeout; - } - - /**************************************** - * PRIVATE AND INTERNAL FUNCTIONS * - ****************************************/ - - // Returns the price for a given identifer. Three params are returns: bool if there was an error, int to represent - // the resolved price and a string which is filled with an error message, if there was an error or "". - function _getPriceOrError( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) private view returns (bool, int256, string memory) { - PriceRequest storage priceRequest = _getPriceRequest(identifier, time, ancillaryData); - uint256 currentRoundId = voteTiming.computeCurrentRoundId(getCurrentTime()); - - RequestStatus requestStatus = _getRequestStatus(priceRequest, currentRoundId); - if (requestStatus == RequestStatus.Active) { - return (false, 0, "Current voting round not ended"); - } else if (requestStatus == RequestStatus.Resolved) { - VoteInstance storage voteInstance = priceRequest.voteInstances[priceRequest.lastVotingRound]; - (, int256 resolvedPrice) = voteInstance.resultComputation.getResolvedPrice( - _computeGat(priceRequest.lastVotingRound) - ); - return (true, resolvedPrice, ""); - } else if (requestStatus == RequestStatus.Future) { - return (false, 0, "Price is still to be voted on"); - } else { - return (false, 0, "Price was never requested"); - } - } - - function _getPriceRequest( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) private view returns (PriceRequest storage) { - return priceRequests[_encodePriceRequest(identifier, time, ancillaryData)]; - } - - function _encodePriceRequest( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) private pure returns (bytes32) { - return keccak256(abi.encode(identifier, time, ancillaryData)); - } - - function _freezeRoundVariables(uint256 roundId) private { - Round storage round = rounds[roundId]; - // Only on the first reveal should the snapshot be captured for that round. - if (round.snapshotId == 0) { - // There is no snapshot ID set, so create one. - round.snapshotId = votingToken.snapshot(); - - // Set the round inflation rate to the current global inflation rate. - rounds[roundId].inflationRate = inflationRate; - - // Set the round gat percentage to the current global gat rate. - rounds[roundId].gatPercentage = gatPercentage; - - // Set the rewards expiration time based on end of time of this round and the current global timeout. - rounds[roundId].rewardsExpirationTime = voteTiming.computeRoundEndTime(roundId).add( - rewardsExpirationTimeout - ); - } - } - - function _resolvePriceRequest(PriceRequest storage priceRequest, VoteInstance storage voteInstance) private { - if (priceRequest.index == UINT_MAX) { - return; - } - (bool isResolved, int256 resolvedPrice) = voteInstance.resultComputation.getResolvedPrice( - _computeGat(priceRequest.lastVotingRound) - ); - require(isResolved, "Can't resolve unresolved request"); - - // Delete the resolved price request from pendingPriceRequests. - uint256 lastIndex = pendingPriceRequests.length - 1; - PriceRequest storage lastPriceRequest = priceRequests[pendingPriceRequests[lastIndex]]; - lastPriceRequest.index = priceRequest.index; - pendingPriceRequests[priceRequest.index] = pendingPriceRequests[lastIndex]; - pendingPriceRequests.pop(); - - priceRequest.index = UINT_MAX; - emit PriceResolved( - priceRequest.lastVotingRound, - priceRequest.identifier, - priceRequest.time, - resolvedPrice, - priceRequest.ancillaryData - ); - } - - function _computeGat(uint256 roundId) private view returns (FixedPoint.Unsigned memory) { - uint256 snapshotId = rounds[roundId].snapshotId; - if (snapshotId == 0) { - // No snapshot - return max value to err on the side of caution. - return FixedPoint.Unsigned(UINT_MAX); - } - - // Grab the snapshotted supply from the voting token. It's already scaled by 10**18, so we can directly - // initialize the Unsigned value with the returned uint. - FixedPoint.Unsigned memory snapshottedSupply = FixedPoint.Unsigned(votingToken.totalSupplyAt(snapshotId)); - - // Multiply the total supply at the snapshot by the gatPercentage to get the GAT in number of tokens. - return snapshottedSupply.mul(rounds[roundId].gatPercentage); - } - - function _getRequestStatus( - PriceRequest storage priceRequest, - uint256 currentRoundId - ) private view returns (RequestStatus) { - if (priceRequest.lastVotingRound == 0) { - return RequestStatus.NotRequested; - } else if (priceRequest.lastVotingRound < currentRoundId) { - VoteInstance storage voteInstance = priceRequest.voteInstances[priceRequest.lastVotingRound]; - (bool isResolved, ) = voteInstance.resultComputation.getResolvedPrice( - _computeGat(priceRequest.lastVotingRound) - ); - return isResolved ? RequestStatus.Resolved : RequestStatus.Active; - } else if (priceRequest.lastVotingRound == currentRoundId) { - return RequestStatus.Active; - } else { - // Means than priceRequest.lastVotingRound > currentRoundId - return RequestStatus.Future; - } - } - - function _getIdentifierWhitelist() private view returns (IdentifierWhitelistInterface supportedIdentifiers) { - return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VotingToken.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VotingToken.sol deleted file mode 100644 index 428fbf58c..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VotingToken.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/ExpandedERC20.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/extensions/ERC20Snapshot.sol"; - -/** - * @title Ownership of this token allows a voter to respond to price requests. - * @dev Supports snapshotting and allows the Oracle to mint new tokens as rewards. - */ -contract VotingToken is ExpandedERC20, ERC20Snapshot { - /** - * @notice Constructs the VotingToken. - */ - constructor() ExpandedERC20("UMA Voting Token v1", "UMA", 18) ERC20Snapshot() {} - - function decimals() public view virtual override(ERC20, ExpandedERC20) returns (uint8) { - return super.decimals(); - } - - /** - * @notice Creates a new snapshot ID. - * @return uint256 Thew new snapshot ID. - */ - function snapshot() external returns (uint256) { - return _snapshot(); - } - - // _transfer, _mint and _burn are ERC20 internal methods that are overridden by ERC20Snapshot, - // therefore the compiler will complain that VotingToken must override these methods - // because the two base classes (ERC20 and ERC20Snapshot) both define the same functions - - function _transfer(address from, address to, uint256 value) internal override(ERC20) { - super._transfer(from, to, value); - } - - function _mint(address account, uint256 value) internal virtual override(ERC20) { - super._mint(account, value); - } - - function _burn(address account, uint256 value) internal virtual override(ERC20) { - super._burn(account, value); - } - - function _beforeTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual override(ERC20, ERC20Snapshot) { - super._beforeTokenTransfer(from, to, amount); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VotingV2.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VotingV2.sol deleted file mode 100644 index 89d882fa1..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/VotingV2.sol +++ /dev/null @@ -1,1156 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "@openzeppelin/contracts-v4/utils/math/Math.sol"; - -import "./ResultComputationV2.sol"; -import "./Staker.sol"; -import "./VoteTiming.sol"; -import "./Constants.sol"; - -import "../interfaces/MinimumVotingAncillaryInterface.sol"; -import "../interfaces/FinderInterface.sol"; -import "../interfaces/IdentifierWhitelistInterface.sol"; -import "../interfaces/OracleAncillaryInterface.sol"; -import "../interfaces/OracleGovernanceInterface.sol"; -import "../interfaces/OracleInterface.sol"; -import "../interfaces/VotingV2Interface.sol"; -import "../interfaces/RegistryInterface.sol"; -import "../interfaces/SlashingLibraryInterface.sol"; - -/** - * @title VotingV2 contract for the UMA DVM. - * @dev Handles receiving and resolving price requests via a commit-reveal voting schelling scheme. - */ - -contract VotingV2 is Staker, OracleInterface, OracleAncillaryInterface, OracleGovernanceInterface, VotingV2Interface { - using VoteTiming for VoteTiming.Data; - using ResultComputationV2 for ResultComputationV2.Data; - - /**************************************** - * VOTING DATA STRUCTURES * - ****************************************/ - - // Identifies a unique price request. Tracks ongoing votes as well as the result of the vote. - struct PriceRequest { - uint32 lastVotingRound; // Last round that this price request was voted on. Updated when a request is rolled. - bool isGovernance; // Denotes whether this is a governance request or not. - uint64 time; // Timestamp used when evaluating the request. - uint32 rollCount; // The number of rounds that a price request has rolled. Informs if a request can be deleted. - bytes32 identifier; // Identifier that defines how the voters should resolve the request. - mapping(uint32 => VoteInstance) voteInstances; // A map containing all votes for this price in various rounds. - bytes ancillaryData; // Additional data used to resolve the request. - } - - struct VoteInstance { - mapping(address => VoteSubmission) voteSubmissions; // Maps (voter) to their submission. - ResultComputationV2.Data results; // The data structure containing the computed voting results. - } - - struct VoteSubmission { - bytes32 commit; // A bytes32 of 0 indicates no commit or a commit that was already revealed. - bytes32 revealHash; // The hash of the value that was revealed. This is only used for computation of rewards. - } - - struct Round { - SlashingLibraryInterface slashingLibrary; // Slashing library used to compute voter participation slash at this round. - uint128 minParticipationRequirement; // Minimum staked tokens that must vote to resolve a request. - uint128 minAgreementRequirement; // Minimum staked tokens that must agree on an outcome to resolve a request. - uint128 cumulativeStakeAtRound; // Total staked tokens at the start of the round. - uint32 numberOfRequestsToVote; // The number of requests to vote in this round. - } - - struct SlashingTracker { - uint256 wrongVoteSlashPerToken; // The amount of tokens slashed per token staked for a wrong vote. - uint256 noVoteSlashPerToken; // The amount of tokens slashed per token staked for a no vote. - uint256 totalSlashed; // The total amount of tokens slashed for a given request. - uint256 totalCorrectVotes; // The total number of correct votes for a given request. - uint32 lastVotingRound; // The last round that this request was voted on (when it resolved). - } - - enum VoteParticipation { - DidNotVote, // Voter did not vote. - WrongVote, // Voter voted against the resolved price. - CorrectVote // Voter voted with the resolved price. - } - - // Represents the status a price request has. - enum RequestStatus { - NotRequested, // Was never requested. - Active, // Is being voted on in the current round. - Resolved, // Was resolved in a previous round. - Future, // Is scheduled to be voted on in a future round. - ToDelete // Is scheduled to be deleted. - } - - // Only used as a return value in view methods -- never stored in the contract. - struct RequestState { - RequestStatus status; - uint32 lastVotingRound; - } - - /**************************************** - * VOTING STATE * - ****************************************/ - - uint32 public lastRoundIdProcessed; // The last round pendingPriceRequestsIds were traversed in. - - uint64 public nextPendingIndexToProcess; // Next pendingPriceRequestsIds index to process in lastRoundIdProcessed. - - FinderInterface public immutable finder; // Reference to the UMA Finder contract, used to find other UMA contracts. - - SlashingLibraryInterface public slashingLibrary; // Reference to Slashing Library, used to compute slashing amounts. - - VoteTiming.Data public voteTiming; // Vote timing library used to compute round timing related logic. - - OracleAncillaryInterface public immutable previousVotingContract; // Previous voting contract, if migrated. - - mapping(uint256 => Round) public rounds; // Maps round numbers to the rounds. - - mapping(bytes32 => PriceRequest) public priceRequests; // Maps price request IDs to the PriceRequest struct. - - bytes32[] public resolvedPriceRequestIds; // Array of resolved price requestIds. Used to track resolved requests. - - bytes32[] public pendingPriceRequestsIds; // Array of pending price requestIds. Can be resolved in the future. - - uint32 public maxRolls; // The maximum number of times a request can roll before it is deleted automatically. - - uint32 public maxRequestsPerRound; // The maximum number of requests that can be enqueued in a single round. - - address public migratedAddress; // If non-zero, this contract has been migrated to this address. - - uint128 public gat; // GAT: A minimum number of tokens that must participate to resolve a vote. - - uint64 public spat; // SPAT: Minimum percentage of staked tokens that must agree on the answer to resolve a vote. - - uint64 public constant UINT64_MAX = type(uint64).max; // Max value of an unsigned integer. - - uint256 public constant ANCILLARY_BYTES_LIMIT = 8192; // Max length in bytes of ancillary data. - - /**************************************** - * EVENTS * - ****************************************/ - - event VoteCommitted( - address indexed voter, - address indexed caller, - uint32 roundId, - bytes32 indexed identifier, - uint256 time, - bytes ancillaryData - ); - - event EncryptedVote( - address indexed caller, - uint32 indexed roundId, - bytes32 indexed identifier, - uint256 time, - bytes ancillaryData, - bytes encryptedVote - ); - - event VoteRevealed( - address indexed voter, - address indexed caller, - uint32 roundId, - bytes32 indexed identifier, - uint256 time, - bytes ancillaryData, - int256 price, - uint128 numTokens - ); - - event RequestAdded( - address indexed requester, - uint32 indexed roundId, - bytes32 indexed identifier, - uint256 time, - bytes ancillaryData, - bool isGovernance - ); - - event RequestResolved( - uint32 indexed roundId, - uint256 indexed resolvedPriceRequestIndex, - bytes32 indexed identifier, - uint256 time, - bytes ancillaryData, - int256 price - ); - - event VotingContractMigrated(address newAddress); - - event RequestDeleted(bytes32 indexed identifier, uint256 indexed time, bytes ancillaryData, uint32 rollCount); - - event RequestRolled(bytes32 indexed identifier, uint256 indexed time, bytes ancillaryData, uint32 rollCount); - - event GatAndSpatChanged(uint128 newGat, uint64 newSpat); - - event SlashingLibraryChanged(address newAddress); - - event MaxRollsChanged(uint32 newMaxRolls); - - event MaxRequestsPerRoundChanged(uint32 newMaxRequestsPerRound); - - event VoterSlashApplied(address indexed voter, int128 slashedTokens, uint128 postStake); - - event VoterSlashed(address indexed voter, uint256 indexed requestIndex, int128 slashedTokens); - - /** - * @notice Construct the VotingV2 contract. - * @param _emissionRate amount of voting tokens that are emitted per second, split prorate between stakers. - * @param _unstakeCoolDown time that a voter must wait to unstake after requesting to unstake. - * @param _phaseLength length of the voting phases in seconds. - * @param _maxRolls number of times a vote must roll to be auto deleted by the DVM. - * @param _maxRequestsPerRound maximum number of requests that can be enqueued in a single round. - * @param _gat number of tokens that must participate to resolve a vote. - * @param _spat percentage of staked tokens that must agree on the result to resolve a vote. - * @param _votingToken address of the UMA token contract used to commit votes. - * @param _finder keeps track of all contracts within the system based on their interfaceName. - * @param _slashingLibrary contract used to calculate voting slashing penalties based on voter participation. - * @param _previousVotingContract previous voting contract address. - */ - constructor( - uint128 _emissionRate, - uint64 _unstakeCoolDown, - uint64 _phaseLength, - uint32 _maxRolls, - uint32 _maxRequestsPerRound, - uint128 _gat, - uint64 _spat, - address _votingToken, - address _finder, - address _slashingLibrary, - address _previousVotingContract - ) Staker(_emissionRate, _unstakeCoolDown, _votingToken) { - voteTiming.init(_phaseLength); - finder = FinderInterface(_finder); - previousVotingContract = OracleAncillaryInterface(_previousVotingContract); - setGatAndSpat(_gat, _spat); - setSlashingLibrary(_slashingLibrary); - setMaxRequestPerRound(_maxRequestsPerRound); - setMaxRolls(_maxRolls); - } - - /*************************************** - MODIFIERS - ****************************************/ - - modifier onlyRegisteredContract() { - _requireRegisteredContract(); - _; - } - - modifier onlyIfNotMigrated() { - _requireNotMigrated(); - _; - } - - /**************************************** - * PRICE REQUEST AND ACCESS FUNCTIONS * - ****************************************/ - - /** - * @notice Enqueues a request (if a request isn't already present) for the identifier, time and ancillary data. - * @dev Time must be in the past and the identifier must be supported. The length of the ancillary data is limited. - * @param identifier uniquely identifies the price requested. E.g. BTC/USD (encoded as bytes32) could be requested. - * @param time unix timestamp for the price request. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - */ - function requestPrice( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) public override nonReentrant onlyIfNotMigrated onlyRegisteredContract { - _requestPrice(identifier, time, ancillaryData, false); - } - - /** - * @notice Enqueues a governance action request (if not already present) for identifier, time and ancillary data. - * @dev Only the owner of the Voting contract can call this. In normal operation this is the Governor contract. - * @param identifier uniquely identifies the price requested. E.g. Admin 0 (encoded as bytes32) could be requested. - * @param time unix timestamp for the price request. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - */ - function requestGovernanceAction( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) external override onlyOwner onlyIfNotMigrated { - _requestPrice(identifier, time, ancillaryData, true); - } - - /** - * @notice Enqueues a request (if a request isn't already present) for the identifier, time pair. - * @dev Overloaded method to enable short term backwards compatibility when ancillary data is not included. - * @param identifier uniquely identifies the price requested. E.g. BTC/USD (encoded as bytes32) could be requested. - * @param time unix timestamp for the price request. - */ - function requestPrice(bytes32 identifier, uint256 time) external override { - requestPrice(identifier, time, ""); - } - - // Enqueues a request (if a request isn't already present) for the given identifier, time and ancillary data. - function _requestPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData, bool isGovernance) internal { - require(time <= getCurrentTime(), "Can only request in past"); - require(isGovernance || _getIdentifierWhitelist().isIdentifierSupported(identifier), "Unsupported identifier"); - require(ancillaryData.length <= ANCILLARY_BYTES_LIMIT, "Invalid ancillary data"); - - bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); - PriceRequest storage priceRequest = priceRequests[priceRequestId]; - - // Price has never been requested. - uint32 currentRoundId = getCurrentRoundId(); - if (_getRequestStatus(priceRequest, currentRoundId) == RequestStatus.NotRequested) { - uint32 roundIdToVoteOn = getRoundIdToVoteOnRequest(currentRoundId + 1); - ++rounds[roundIdToVoteOn].numberOfRequestsToVote; - priceRequest.identifier = identifier; - priceRequest.time = uint64(time); - priceRequest.ancillaryData = ancillaryData; - priceRequest.lastVotingRound = roundIdToVoteOn; - if (isGovernance) priceRequest.isGovernance = isGovernance; - - pendingPriceRequestsIds.push(priceRequestId); - emit RequestAdded(msg.sender, roundIdToVoteOn, identifier, time, ancillaryData, isGovernance); - } - } - - /** - * @notice Gets the round ID that a request should be voted on. - * @param targetRoundId round ID to start searching for a round to vote on. - * @return uint32 round ID that a request should be voted on. - */ - function getRoundIdToVoteOnRequest(uint32 targetRoundId) public view returns (uint32) { - while (rounds[targetRoundId].numberOfRequestsToVote >= maxRequestsPerRound) ++targetRoundId; - return targetRoundId; - } - - /** - * @notice Returns whether the price for identifier, time and ancillary data is available. - * @param identifier uniquely identifies the price requested. E.g. BTC/USD (encoded as bytes32) could be requested. - * @param time unix timestamp of the price request. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - * @return bool if the DVM has resolved to a price for the given identifier, timestamp and ancillary data. - */ - function hasPrice( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) public view override onlyRegisteredContract returns (bool) { - (bool _hasPrice, , ) = _getPriceOrError(identifier, time, ancillaryData); - return _hasPrice; - } - - /** - * @notice Whether the price for identifier and time is available. - * @dev Overloaded method to enable short term backwards compatibility when ancillary data is not included. - * @param identifier uniquely identifies the price requested. E.g. BTC/USD (encoded as bytes32) could be requested. - * @param time unix timestamp of the price request. - * @return bool if the DVM has resolved to a price for the given identifier and timestamp. - */ - function hasPrice(bytes32 identifier, uint256 time) external view override returns (bool) { - return hasPrice(identifier, time, ""); - } - - /** - * @notice Gets the price for identifier, time and ancillary data if it has already been requested and resolved. - * @dev If the price is not available, the method reverts. - * @param identifier uniquely identifies the price requested. E.g. BTC/USD (encoded as bytes32) could be requested. - * @param time unix timestamp of the price request. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - * @return int256 representing the resolved price for the given identifier, timestamp and ancillary data. - */ - function getPrice( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) public view override onlyRegisteredContract returns (int256) { - (bool _hasPrice, int256 price, string memory message) = _getPriceOrError(identifier, time, ancillaryData); - - // If the price wasn't available, revert with the provided message. - require(_hasPrice, message); - return price; - } - - /** - * @notice Gets the price for identifier and time if it has already been requested and resolved. - * @dev Overloaded method to enable short term backwards compatibility when ancillary data is not included. - * @dev If the price is not available, the method reverts. - * @param identifier uniquely identifies the price requested. E.g. BTC/USD (encoded as bytes32) could be requested. - * @param time unix timestamp of the price request. - * @return int256 representing the resolved price for the given identifier and timestamp. - */ - function getPrice(bytes32 identifier, uint256 time) external view override returns (int256) { - return getPrice(identifier, time, ""); - } - - /** - * @notice Gets the status of a list of price requests, identified by their identifier, time and ancillary data. - * @dev If the status for a particular request is NotRequested, the lastVotingRound will always be 0. - * @param requests array of pending requests which includes identifier, timestamp & ancillary data for the requests. - * @return requestStates a list, in the same order as the input list, giving the status of the specified requests. - */ - function getPriceRequestStatuses( - PendingRequestAncillary[] memory requests - ) public view returns (RequestState[] memory) { - RequestState[] memory requestStates = new RequestState[](requests.length); - uint32 currentRoundId = getCurrentRoundId(); - for (uint256 i = 0; i < requests.length; i = unsafe_inc(i)) { - PriceRequest storage priceRequest = _getPriceRequest( - requests[i].identifier, - requests[i].time, - requests[i].ancillaryData - ); - - RequestStatus status = _getRequestStatus(priceRequest, currentRoundId); - - // If it's an active request, its true lastVotingRound is the current one, even if it hasn't been updated. - if (status == RequestStatus.Active) requestStates[i].lastVotingRound = currentRoundId; - else requestStates[i].lastVotingRound = priceRequest.lastVotingRound; - requestStates[i].status = status; - } - return requestStates; - } - - /**************************************** - * VOTING FUNCTIONS * - ****************************************/ - - /** - * @notice Commit a vote for a price request for identifier at time. - * @dev identifier, time must correspond to a price request that's currently in the commit phase. - * Commits can be changed. - * @dev Since transaction data is public, the salt will be revealed with the vote. While this is the system’s - * expected behavior, voters should never reuse salts. If someone else is able to guess the voted price and knows - * that a salt will be reused, then they can determine the vote pre-reveal. - * @param identifier uniquely identifies the committed vote. E.g. BTC/USD price pair. - * @param time unix timestamp of the price being voted on. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - * @param hash keccak256 hash of the price, salt, voter address, time, ancillaryData, current roundId, identifier. - */ - function commitVote( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData, - bytes32 hash - ) public override nonReentrant { - uint32 currentRoundId = getCurrentRoundId(); - address voter = getVoterFromDelegate(msg.sender); - _updateTrackers(voter); - - require(hash != bytes32(0), "Invalid commit hash"); - require(getVotePhase() == Phase.Commit, "Cannot commit in reveal phase"); - PriceRequest storage priceRequest = _getPriceRequest(identifier, time, ancillaryData); - require(_getRequestStatus(priceRequest, currentRoundId) == RequestStatus.Active, "Request must be active"); - - priceRequest.voteInstances[currentRoundId].voteSubmissions[voter].commit = hash; - - emit VoteCommitted(voter, msg.sender, currentRoundId, identifier, time, ancillaryData); - } - - /** - * @notice Reveal a previously committed vote for identifier at time. - * @dev The revealed price, salt, voter address, time, ancillaryData, current roundId, identifier must hash to the - * latest hash that commitVote() was called with. Only the committer can reveal their vote. - * @param identifier voted on in the commit phase. E.g. BTC/USD price pair. - * @param time specifies the unix timestamp of the price being voted on. - * @param price voted on during the commit phase. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - * @param salt value used to hide the commitment price during the commit phase. - */ - function revealVote( - bytes32 identifier, - uint256 time, - int256 price, - bytes memory ancillaryData, - int256 salt - ) public override nonReentrant { - uint32 currentRoundId = getCurrentRoundId(); - _freezeRoundVariables(currentRoundId); - VoteInstance storage voteInstance = _getPriceRequest(identifier, time, ancillaryData).voteInstances[ - currentRoundId - ]; - address voter = getVoterFromDelegate(msg.sender); - VoteSubmission storage voteSubmission = voteInstance.voteSubmissions[voter]; - - require(getVotePhase() == Phase.Reveal, "Reveal phase has not started yet"); // Can only reveal in reveal phase. - - // Zero hashes are blocked in commit; they indicate a different error: voter did not commit or already revealed. - require(voteSubmission.commit != bytes32(0), "Invalid hash reveal"); - - // Check that the hash that was committed matches to the one that was revealed. Note that if the voter had - // then they must reveal with the same account they had committed with. - require( - keccak256(abi.encodePacked(price, salt, voter, time, ancillaryData, uint256(currentRoundId), identifier)) == - voteSubmission.commit, - "Revealed data != commit hash" - ); - - delete voteSubmission.commit; // Small gas refund for clearing up storage. - voteSubmission.revealHash = keccak256(abi.encode(price)); // Set the voter's submission. - - // Calculate the voters effective stake for this round as the difference between their stake and pending stake. - // This allows for the voter to have staked during this reveal phase and not consider their pending stake. - uint128 effectiveStake = voterStakes[voter].stake - voterStakes[voter].pendingStakes[currentRoundId]; - voteInstance.results.addVote(price, effectiveStake); // Add vote to the results. - emit VoteRevealed(voter, msg.sender, currentRoundId, identifier, time, ancillaryData, price, effectiveStake); - } - - /** - * @notice Commits a vote and logs an event with a data blob, typically an encrypted version of the vote - * @dev An encrypted version of the vote is emitted in an event EncryptedVote to allow off-chain infrastructure to - * retrieve the commit. The contents of encryptedVote are never used on chain: it is purely for convenience. - * @param identifier unique price pair identifier. E.g. BTC/USD price pair. - * @param time unix timestamp of the price request. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - * @param hash keccak256 hash of the price you want to vote for and a int256 salt. - * @param encryptedVote offchain encrypted blob containing the voter's amount, time and salt. - */ - function commitAndEmitEncryptedVote( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData, - bytes32 hash, - bytes memory encryptedVote - ) public override { - commitVote(identifier, time, ancillaryData, hash); - emit EncryptedVote(msg.sender, getCurrentRoundId(), identifier, time, ancillaryData, encryptedVote); - } - - /**************************************** - * VOTING GETTER FUNCTIONS * - ****************************************/ - - /** - * @notice Gets the requests that are being voted on this round. - * @dev This view method returns requests with Active status that may be ahead of the stored contract state as this - * also filters out requests that would be resolvable or deleted if the resolvable requests were processed with the - * processResolvablePriceRequests() method. - * @return pendingRequests array containing identifiers of type PendingRequestAncillaryAugmented. - */ - function getPendingRequests() public view override returns (PendingRequestAncillaryAugmented[] memory) { - // Solidity memory arrays aren't resizable (and reading storage is expensive). Hence this hackery to filter - // pendingPriceRequestsIds only to those requests that have an Active RequestStatus. - PendingRequestAncillaryAugmented[] memory unresolved = new PendingRequestAncillaryAugmented[]( - pendingPriceRequestsIds.length - ); - uint256 numUnresolved = 0; - uint32 currentRoundId = getCurrentRoundId(); - - for (uint256 i = 0; i < pendingPriceRequestsIds.length; i = unsafe_inc(i)) { - PriceRequest storage priceRequest = priceRequests[pendingPriceRequestsIds[i]]; - if (_getRequestStatus(priceRequest, currentRoundId) == RequestStatus.Active) { - unresolved[numUnresolved] = PendingRequestAncillaryAugmented({ - lastVotingRound: priceRequest.lastVotingRound, - isGovernance: priceRequest.isGovernance, - time: priceRequest.time, - rollCount: _getActualRollCount(priceRequest, currentRoundId), - identifier: priceRequest.identifier, - ancillaryData: priceRequest.ancillaryData - }); - numUnresolved++; - } - } - - PendingRequestAncillaryAugmented[] memory pendingRequests = new PendingRequestAncillaryAugmented[]( - numUnresolved - ); - for (uint256 i = 0; i < numUnresolved; i = unsafe_inc(i)) pendingRequests[i] = unresolved[i]; - - return pendingRequests; - } - - /** - * @notice Checks if there are current active requests. - * @return bool true if there are active requests, false otherwise. - */ - function currentActiveRequests() public view returns (bool) { - uint32 currentRoundId = getCurrentRoundId(); - for (uint256 i = 0; i < pendingPriceRequestsIds.length; i = unsafe_inc(i)) - if (_getRequestStatus(priceRequests[pendingPriceRequestsIds[i]], currentRoundId) == RequestStatus.Active) - return true; - - return false; - } - - /** - * @notice Returns the current voting phase, as a function of the current time. - * @return Phase to indicate the current phase. Either { Commit, Reveal, NUM_PHASES }. - */ - function getVotePhase() public view override returns (Phase) { - return Phase(uint256(voteTiming.computeCurrentPhase(getCurrentTime()))); - } - - /** - * @notice Returns the current round ID, as a function of the current time. - * @return uint32 the unique round ID. - */ - function getCurrentRoundId() public view override returns (uint32) { - return uint32(voteTiming.computeCurrentRoundId(getCurrentTime())); - } - - /** - * @notice Returns the round end time, as a function of the round number. - * @param roundId representing the unique round ID. - * @return uint256 representing the round end time. - */ - function getRoundEndTime(uint256 roundId) external view returns (uint256) { - return voteTiming.computeRoundEndTime(roundId); - } - - /** - * @notice Returns the number of current pending price requests to be voted and the number of resolved price - requests over all time. - * @dev This method might return stale values if the state of the contract has changed since the last time - `processResolvablePriceRequests()` was called. To get the most up-to-date values, call - `getNumberOfPriceRequestsPostUpdate()` instead. - * @return numberPendingPriceRequests the total number of pending prices requests. - * @return numberResolvedPriceRequests the total number of prices resolved over all time. - */ - function getNumberOfPriceRequests() - public - view - returns (uint256 numberPendingPriceRequests, uint256 numberResolvedPriceRequests) - { - return (pendingPriceRequestsIds.length, resolvedPriceRequestIds.length); - } - - /** - * @notice Returns the number of current pending price requests to be voted and the number of resolved price - requests over all time after processing any resolvable price requests. - * @return numberPendingPriceRequests the total number of pending prices requests. - * @return numberResolvedPriceRequests the total number of prices resolved over all time. - */ - function getNumberOfPriceRequestsPostUpdate() - external - returns (uint256 numberPendingPriceRequests, uint256 numberResolvedPriceRequests) - { - processResolvablePriceRequests(); - return getNumberOfPriceRequests(); - } - - /** - * @notice Returns aggregate slashing trackers for a given request index. - * @param requestIndex requestIndex the index of the request to fetch slashing trackers for. - * @return SlashingTracker Tracker object contains the slashed UMA per staked UMA per wrong vote and no vote, the - * total UMA slashed in the round and the total number of correct votes in the round. - */ - function requestSlashingTrackers(uint256 requestIndex) public view returns (SlashingTracker memory) { - PriceRequest storage priceRequest = priceRequests[resolvedPriceRequestIds[requestIndex]]; - uint32 lastVotingRound = priceRequest.lastVotingRound; - VoteInstance storage voteInstance = priceRequest.voteInstances[lastVotingRound]; - - uint256 totalVotes = voteInstance.results.totalVotes; - uint256 totalCorrectVotes = voteInstance.results.getTotalCorrectlyVotedTokens(); - uint256 totalStaked = rounds[lastVotingRound].cumulativeStakeAtRound; - - (uint256 wrongVoteSlash, uint256 noVoteSlash) = rounds[lastVotingRound].slashingLibrary.calcSlashing( - totalStaked, - totalVotes, - totalCorrectVotes, - requestIndex, - priceRequest.isGovernance - ); - - uint256 totalSlashed = ((noVoteSlash * (totalStaked - totalVotes)) + - (wrongVoteSlash * (totalVotes - totalCorrectVotes))) / 1e18; - - return SlashingTracker(wrongVoteSlash, noVoteSlash, totalSlashed, totalCorrectVotes, lastVotingRound); - } - - /** - * @notice Returns the voter's participation in the vote for a given request index. - * @param requestIndex requestIndex the index of the request to fetch slashing trackers for. - * @param lastVotingRound the round to get voter participation for. - * @param voter the voter to get participation for. - * @return VoteParticipation enum representing the voter's participation in the vote. - */ - function getVoterParticipation( - uint256 requestIndex, - uint32 lastVotingRound, - address voter - ) public view returns (VoteParticipation) { - VoteInstance storage voteInstance = priceRequests[resolvedPriceRequestIds[requestIndex]].voteInstances[ - lastVotingRound - ]; - bytes32 revealHash = voteInstance.voteSubmissions[voter].revealHash; - if (revealHash == bytes32(0)) return VoteParticipation.DidNotVote; - if (voteInstance.results.wasVoteCorrect(revealHash)) return VoteParticipation.CorrectVote; - return VoteParticipation.WrongVote; - } - - /**************************************** - * OWNER ADMIN FUNCTIONS * - ****************************************/ - - /** - * @notice Disables this Voting contract in favor of the migrated one. - * @dev Can only be called by the contract owner. - * @param newVotingAddress the newly migrated contract address. - */ - function setMigrated(address newVotingAddress) external override onlyOwner { - migratedAddress = newVotingAddress; - emit VotingContractMigrated(newVotingAddress); - } - - /** - * @notice Sets the maximum number of rounds to roll a request can have before the DVM auto deletes it. - * @dev Can only be called by the contract owner. - * @param newMaxRolls the new number of rounds to roll a request before the DVM auto deletes it. - */ - function setMaxRolls(uint32 newMaxRolls) public override onlyOwner { - // Changes to max rolls can impact unresolved requests. To protect against this process requests first. - processResolvablePriceRequests(); - maxRolls = newMaxRolls; - emit MaxRollsChanged(newMaxRolls); - } - - /** - * @notice Sets the maximum number of requests that can be made in a single round. Used to bound the maximum - * sequential slashing that can be applied within a single round. - * @dev Can only be called by the contract owner. - * @param newMaxRequestsPerRound the new maximum number of requests that can be made in a single round. - */ - function setMaxRequestPerRound(uint32 newMaxRequestsPerRound) public override onlyOwner { - require(newMaxRequestsPerRound > 0); - maxRequestsPerRound = newMaxRequestsPerRound; - emit MaxRequestsPerRoundChanged(newMaxRequestsPerRound); - } - - /** - * @notice Resets the GAT number and SPAT percentage. GAT is the minimum number of tokens that must participate in a - * vote for it to resolve (quorum number). SPAT is the minimum percentage of tokens that must agree on a result - * for it to resolve (percentage of staked tokens) This change only applies to subsequent rounds. - * @param newGat sets the next round's GAT and going forward. - * @param newSpat sets the next round's SPAT and going forward. - */ - function setGatAndSpat(uint128 newGat, uint64 newSpat) public override onlyOwner { - require(newGat < votingToken.totalSupply() && newGat > 0); - require(newSpat > 0 && newSpat < 1e18); - gat = newGat; - spat = newSpat; - - emit GatAndSpatChanged(newGat, newSpat); - } - - /** - * @notice Changes the slashing library used by this contract. - * @param _newSlashingLibrary new slashing library address. - */ - function setSlashingLibrary(address _newSlashingLibrary) public override onlyOwner { - slashingLibrary = SlashingLibraryInterface(_newSlashingLibrary); - emit SlashingLibraryChanged(_newSlashingLibrary); - } - - /**************************************** - * STAKING FUNCTIONS * - ****************************************/ - - /** - * @notice Updates the voter's trackers for staking and slashing. Applies all unapplied slashing to given staker. - * @dev Can be called by anyone, but it is not necessary for the contract to function is run the other functions. - * @param voter address of the voter to update the trackers for. - */ - function updateTrackers(address voter) external { - _updateTrackers(voter); - } - - /** - * @notice Updates the voter's trackers for staking and voting, specifying a maximum number of resolved requests to - * traverse. This function can be used in place of updateTrackers to process the trackers in batches, hence avoiding - * potential issues if the number of elements to be processed is large and the associated gas cost is too high. - * @param voter address of the voter to update the trackers for. - * @param maxTraversals maximum number of resolved requests to traverse in this call. - */ - function updateTrackersRange(address voter, uint64 maxTraversals) external { - processResolvablePriceRequests(); - _updateAccountSlashingTrackers(voter, maxTraversals); - } - - // Updates the global and selected wallet's trackers for staking and voting. Note that the order of these calls is - // very important due to the interplay between slashing and inactive/active liquidity. - function _updateTrackers(address voter) internal override { - processResolvablePriceRequests(); - _updateAccountSlashingTrackers(voter, UINT64_MAX); - super._updateTrackers(voter); - } - - /** - * @notice Process and resolve all resolvable price requests. This function traverses all pending price requests and - * resolves them if they are resolvable. It also rolls and deletes requests, if required. - */ - function processResolvablePriceRequests() public { - _processResolvablePriceRequests(UINT64_MAX); - } - - /** - * @notice Process and resolve all resolvable price requests. This function traverses all pending price requests and - * resolves them if they are resolvable. It also rolls and deletes requests, if required. This function can be used - * in place of processResolvablePriceRequests to process the requests in batches, hence avoiding potential issues if - * the number of elements to be processed is large and the associated gas cost is too high. - * @param maxTraversals maximum number of resolved requests to traverse in this call. - */ - function processResolvablePriceRequestsRange(uint64 maxTraversals) external { - _processResolvablePriceRequests(maxTraversals); - } - - // Starting index for a staker is the first value that nextIndexToProcess is set to and defines the first index that - // a staker is suspectable to receiving slashing on. This is set to current length of the resolvedPriceRequestIds. - // Note first call processResolvablePriceRequests to ensure that the resolvedPriceRequestIds array is up to date. - function _getStartingIndexForStaker() internal override returns (uint64) { - processResolvablePriceRequests(); - return SafeCast.toUint64(resolvedPriceRequestIds.length); - } - - // Checks if we are in an active voting reveal phase (currently revealing votes). This impacts if a new staker's - // stake should be activated immediately or if it should be frozen until the end of the reveal phase. - function _inActiveReveal() internal view override returns (bool) { - return (currentActiveRequests() && getVotePhase() == Phase.Reveal); - } - - // This function must be called before any tokens are staked. It updates the voter's pending stakes to reflect the - // new amount to stake. These updates are only made if we are in an active reveal. This is required to appropriately - // calculate a voter's trackers and avoid slashing them for amounts staked during an active reveal phase. - function _computePendingStakes(address voter, uint128 amount) internal override { - if (_inActiveReveal()) { - uint32 currentRoundId = getCurrentRoundId(); - // Freeze round variables to prevent cumulativeActiveStakeAtRound from changing based on the stakes during - // the active reveal phase. This will happen if the first action within the reveal is someone staking. - _freezeRoundVariables(currentRoundId); - // Increment pending stake for voter by amount. With the omission of stake from cumulativeActiveStakeAtRound - // for this round, ensure that the pending stakes is not included in the slashing calculation for this round. - _incrementPendingStake(voter, currentRoundId, amount); - } - } - - // Updates the slashing trackers of a given account based on previous voting activity. This traverses all resolved - // requests for each voter and for each request checks if the voter voted correctly or not. Based on the voters - // voting activity the voters balance is updated accordingly. The caller can provide a maxTraversals parameter to - // limit the number of resolved requests to traverse in this call to bound the gas used. Note each iteration of - // this function re-uses a fresh slash variable to produce useful logs on the amount a voter is slashed. - function _updateAccountSlashingTrackers(address voter, uint64 maxTraversals) internal { - VoterStake storage voterStake = voterStakes[voter]; - uint64 requestIndex = voterStake.nextIndexToProcess; // Traverse all requests from the last considered request. - - // Traverse all elements within the resolvedPriceRequestIds array and update the voter's trackers according to - // their voting activity. Bound the number of iterations to the maxTraversals parameter to cap the gas used. - while (requestIndex < resolvedPriceRequestIds.length && maxTraversals > 0) { - maxTraversals = unsafe_dec_64(maxTraversals); // reduce the number of traversals left & re-use the prop. - - // Get the slashing for this request. This comes from the slashing library and informs to the voter slash. - SlashingTracker memory trackers = requestSlashingTrackers(requestIndex); - - // Use the effective stake as the difference between the current stake and pending stake. The staker will - //have a pending stake if they staked during an active reveal for the voting round in question. - uint256 effectiveStake = voterStake.stake - voterStake.pendingStakes[trackers.lastVotingRound]; - int256 slash; // The amount to slash the voter by for this request. Reset on each entry to emit useful logs. - - // Get the voter participation for this request. This informs if the voter voted correctly or not. - VoteParticipation participation = getVoterParticipation(requestIndex, trackers.lastVotingRound, voter); - - // The voter did not reveal or did not commit. Slash at noVote rate. - if (participation == VoteParticipation.DidNotVote) - slash = -int256(Math.ceilDiv(effectiveStake * trackers.noVoteSlashPerToken, 1e18)); - - // The voter did not vote with the majority. Slash at wrongVote rate. - else if (participation == VoteParticipation.WrongVote) - slash = -int256(Math.ceilDiv(effectiveStake * trackers.wrongVoteSlashPerToken, 1e18)); - - // Else, the voter voted correctly. Receive a pro-rate share of the other voters slash. - else slash = int256((effectiveStake * trackers.totalSlashed) / trackers.totalCorrectVotes); - - emit VoterSlashed(voter, requestIndex, int128(slash)); - voterStake.unappliedSlash += int128(slash); - - // If the next round is different to the current considered round, apply the slash to the voter. - if (isNextRequestRoundDifferent(requestIndex)) _applySlashToVoter(voterStake, voter); - - requestIndex = unsafe_inc_64(requestIndex); // Increment the request index. - } - - // Set the account's nextIndexToProcess to the requestIndex so the next entry starts where we left off. - voterStake.nextIndexToProcess = requestIndex; - } - - // Applies a given slash to a given voter's stake. In the event the sum of the slash and the voter's stake is less - // than 0, the voter's stake is set to 0 to prevent the voter's stake from going negative. unappliedSlash tracked - // all slashing the staker has received but not yet applied to their stake. Apply it then set it to zero. - function _applySlashToVoter(VoterStake storage voterStake, address voter) internal { - if (voterStake.unappliedSlash + int128(voterStake.stake) > 0) - voterStake.stake = uint128(int128(voterStake.stake) + voterStake.unappliedSlash); - else voterStake.stake = 0; - emit VoterSlashApplied(voter, voterStake.unappliedSlash, voterStake.stake); - voterStake.unappliedSlash = 0; - } - - // Checks if the next round (index+1) is different to the current round (index). - function isNextRequestRoundDifferent(uint64 index) internal view returns (bool) { - if (index + 1 >= resolvedPriceRequestIds.length) return true; - - return - priceRequests[resolvedPriceRequestIds[index]].lastVotingRound != - priceRequests[resolvedPriceRequestIds[index + 1]].lastVotingRound; - } - - /**************************************** - * MIGRATION SUPPORT FUNCTIONS * - ****************************************/ - - /** - * @notice Enable retrieval of rewards on a previously migrated away from voting contract. This function is intended - * on being removed from future versions of the Voting contract and aims to solve a short term migration pain point. - * @param voter voter for which rewards will be retrieved. Does not have to be the caller. - * @param roundId the round from which voting rewards will be retrieved from. - * @param toRetrieve array of PendingRequests which rewards are retrieved from. - * @return uint256 the amount of rewards. - */ - function retrieveRewardsOnMigratedVotingContract( - address voter, - uint256 roundId, - MinimumVotingAncillaryInterface.PendingRequestAncillary[] memory toRetrieve - ) external returns (uint256) { - uint256 rewards = MinimumVotingAncillaryInterface(address(previousVotingContract)) - .retrieveRewards(voter, roundId, toRetrieve) - .rawValue; - return rewards; - } - - /**************************************** - * PRIVATE AND INTERNAL FUNCTIONS * - ****************************************/ - - // Deletes a request from the pending requests array, based on index. Swap and pop. - function _removeRequestFromPendingPriceRequestsIds(uint64 pendingRequestIndex) internal { - pendingPriceRequestsIds[pendingRequestIndex] = pendingPriceRequestsIds[pendingPriceRequestsIds.length - 1]; - pendingPriceRequestsIds.pop(); - } - - // Returns the price for a given identifier. Three params are returns: bool if there was an error, int to represent - // the resolved price and a string which is filled with an error message, if there was an error or "". - // This method considers actual request status that might be ahead of the stored contract state that gets updated - // only after processResolvablePriceRequests() is called. - function _getPriceOrError( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) internal view returns (bool, int256, string memory) { - PriceRequest storage priceRequest = _getPriceRequest(identifier, time, ancillaryData); - uint32 currentRoundId = getCurrentRoundId(); - RequestStatus requestStatus = _getRequestStatus(priceRequest, currentRoundId); - - if (requestStatus == RequestStatus.Active) return (false, 0, "Current voting round not ended"); - if (requestStatus == RequestStatus.Resolved) { - VoteInstance storage voteInstance = priceRequest.voteInstances[priceRequest.lastVotingRound]; - (, int256 resolvedPrice) = _getResolvedPrice(voteInstance, priceRequest.lastVotingRound); - return (true, resolvedPrice, ""); - } - - if (requestStatus == RequestStatus.Future) return (false, 0, "Price is still to be voted on"); - if (requestStatus == RequestStatus.ToDelete) return (false, 0, "Price will be deleted"); - (bool previouslyResolved, int256 previousPrice) = _getPriceFromPreviousVotingContract( - identifier, - time, - ancillaryData - ); - if (previouslyResolved) return (true, previousPrice, ""); - return (false, 0, "Price was never requested"); - } - - // Check the previousVotingContract to see if a given price request was resolved. - // Returns true or false, and the resolved price or zero, depending on whether it was found or not. - function _getPriceFromPreviousVotingContract( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) private view returns (bool, int256) { - if (address(previousVotingContract) == address(0)) return (false, 0); - if (previousVotingContract.hasPrice(identifier, time, ancillaryData)) - return (true, previousVotingContract.getPrice(identifier, time, ancillaryData)); - return (false, 0); - } - - // Returns a price request object for a given identifier, time and ancillary data. - function _getPriceRequest( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) private view returns (PriceRequest storage) { - return priceRequests[_encodePriceRequest(identifier, time, ancillaryData)]; - } - - // Returns an encoded bytes32 representing a price request. Used when storing/referencing price requests. - function _encodePriceRequest( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) private pure returns (bytes32) { - return keccak256(abi.encode(identifier, time, ancillaryData)); - } - - // Stores ("freezes") variables that should not shift within an active voting round. Called on reveal but only makes - // a state change if and only if the this is the first reveal. - function _freezeRoundVariables(uint256 roundId) private { - // Only freeze the round if this is the first request in the round. - if (rounds[roundId].minParticipationRequirement == 0) { - rounds[roundId].slashingLibrary = slashingLibrary; - - // The minimum required participation for a vote to settle within this round is the GAT (fixed number). - rounds[roundId].minParticipationRequirement = gat; - - // The minimum votes on the modal outcome for the vote to settle within this round is the SPAT (percentage). - rounds[roundId].minAgreementRequirement = uint128((spat * uint256(cumulativeStake)) / 1e18); - rounds[roundId].cumulativeStakeAtRound = cumulativeStake; // Store the cumulativeStake to work slashing. - } - } - - // Traverse pending price requests and resolve any that are resolvable. If requests are rollable (they did not - // resolve in the previous round and are to be voted in a subsequent round) then roll them. If requests can be - // deleted (they have been rolled up to the maxRolls counter) then delete them. The caller can pass in maxTraversals - // to limit the number of requests that are resolved in a single call to bound the total gas used by this function. - // Note that the resolved index is stores for each round. This means that only the first caller of this function - // per round needs to traverse the pending requests. After that subsequent calls to this are a no-op for that round. - function _processResolvablePriceRequests(uint64 maxTraversals) private { - uint32 currentRoundId = getCurrentRoundId(); - - // Load in the last resolved index for this round to continue off from where the last caller left. - uint64 requestIndex = lastRoundIdProcessed == currentRoundId ? nextPendingIndexToProcess : 0; - // Traverse pendingPriceRequestsIds array and update the requests status according to the state of the request - //(i.e settle, roll or delete request). Bound iterations to the maxTraversals parameter to cap the gas used. - while (requestIndex < pendingPriceRequestsIds.length && maxTraversals > 0) { - maxTraversals = unsafe_dec_64(maxTraversals); - PriceRequest storage request = priceRequests[pendingPriceRequestsIds[requestIndex]]; - - // If the last voting round is greater than or equal to the current round then this request is currently - // being voted on or is enqueued for the next round. In this case, skip it and increment the request index. - if (request.lastVotingRound >= currentRoundId) { - requestIndex = unsafe_inc_64(requestIndex); - continue; // Continue to the next request. - } - - // Else, we are dealing with a request that can either be: a) deleted, b) rolled or c) resolved. - VoteInstance storage voteInstance = request.voteInstances[request.lastVotingRound]; - (bool isResolvable, int256 resolvedPrice) = _getResolvedPrice(voteInstance, request.lastVotingRound); - - if (isResolvable) { - // If resolvable, resolve. This involves a) moving the requestId from pendingPriceRequestsIds array to - // resolvedPriceRequestIds array and b) removing requestId from pendingPriceRequestsIds. Don't need to - // increment requestIndex as from pendingPriceRequestsIds amounts to decreasing the while loop bound. - resolvedPriceRequestIds.push(pendingPriceRequestsIds[requestIndex]); - _removeRequestFromPendingPriceRequestsIds(requestIndex); - emit RequestResolved( - request.lastVotingRound, - resolvedPriceRequestIds.length - 1, - request.identifier, - request.time, - request.ancillaryData, - resolvedPrice - ); - continue; // Continue to the next request. - } - // If not resolvable, but the round has passed its voting round, then it must be deleted or rolled. First, - // increment the rollCount. Use the difference between the current round and the last voting round to - // accommodate the contract not being touched for any number of rounds during the roll. - request.rollCount += currentRoundId - request.lastVotingRound; - - // If the roll count exceeds the threshold and the request is not governance then it is deletable. - if (_shouldDeleteRequest(request.rollCount, request.isGovernance)) { - emit RequestDeleted(request.identifier, request.time, request.ancillaryData, request.rollCount); - delete priceRequests[pendingPriceRequestsIds[requestIndex]]; - _removeRequestFromPendingPriceRequestsIds(requestIndex); - continue; - } - // Else, the request should be rolled. This involves only moving forward the lastVotingRound. - request.lastVotingRound = getRoundIdToVoteOnRequest(currentRoundId); - ++rounds[request.lastVotingRound].numberOfRequestsToVote; - emit RequestRolled(request.identifier, request.time, request.ancillaryData, request.rollCount); - requestIndex = unsafe_inc_64(requestIndex); - } - - lastRoundIdProcessed = currentRoundId; // Store the roundId that was processed. - nextPendingIndexToProcess = requestIndex; // Store the index traversed up to for this round. - } - - // Returns a price request status. A request is either: NotRequested, Active, Resolved, Future or ToDelete. - function _getRequestStatus( - PriceRequest storage priceRequest, - uint32 currentRoundId - ) private view returns (RequestStatus) { - if (priceRequest.lastVotingRound == 0) return RequestStatus.NotRequested; - if (priceRequest.lastVotingRound < currentRoundId) { - // Check if the request has already been resolved - VoteInstance storage voteInstance = priceRequest.voteInstances[priceRequest.lastVotingRound]; - (bool isResolved, ) = _getResolvedPrice(voteInstance, priceRequest.lastVotingRound); - if (isResolved) return RequestStatus.Resolved; - if (_shouldDeleteRequest(_getActualRollCount(priceRequest, currentRoundId), priceRequest.isGovernance)) - return RequestStatus.ToDelete; - return RequestStatus.Active; - } - if (priceRequest.lastVotingRound == currentRoundId) return RequestStatus.Active; - - return RequestStatus.Future; // Means than priceRequest.lastVotingRound > currentRoundId - } - - function _getResolvedPrice( - VoteInstance storage voteInstance, - uint256 lastVotingRound - ) internal view returns (bool isResolved, int256 price) { - return - voteInstance.results.getResolvedPrice( - rounds[lastVotingRound].minParticipationRequirement, - rounds[lastVotingRound].minAgreementRequirement - ); - } - - // Gas optimized uint256 increment. - function unsafe_inc(uint256 x) internal pure returns (uint256) { - unchecked { - return x + 1; - } - } - - // Gas optimized uint64 increment. - function unsafe_inc_64(uint64 x) internal pure returns (uint64) { - unchecked { - return x + 1; - } - } - - // Gas optimized uint64 decrement. - function unsafe_dec_64(uint64 x) internal pure returns (uint64) { - unchecked { - return x - 1; - } - } - - // Returns the registered identifier whitelist, stored in the finder. - function _getIdentifierWhitelist() private view returns (IdentifierWhitelistInterface) { - return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); - } - - // Reverts if the contract has been migrated. Used in a modifier, defined as a private function for gas savings. - function _requireNotMigrated() private view { - require(migratedAddress == address(0), "Contract migrated"); - } - - // Enforces that a calling contract is registered. - function _requireRegisteredContract() private view { - RegistryInterface registry = RegistryInterface(finder.getImplementationAddress(OracleInterfaces.Registry)); - require(registry.isContractRegistered(msg.sender) || msg.sender == migratedAddress, "Caller not registered"); - } - - // Checks if a request should be deleted. A non-gevernance request should be deleted if it has been rolled more than - // the maxRolls. - function _shouldDeleteRequest(uint256 rollCount, bool isGovernance) private view returns (bool) { - return rollCount > maxRolls && !isGovernance; - } - - // Returns the actual roll count of a request. This is the roll count plus the number of rounds that have passed - // since the last voting round. - function _getActualRollCount( - PriceRequest storage priceRequest, - uint32 currentRoundId - ) private view returns (uint32) { - if (currentRoundId <= priceRequest.lastVotingRound) return priceRequest.rollCount; - return priceRequest.rollCount + currentRoundId - priceRequest.lastVotingRound; - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/EmergencyProposerTest.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/EmergencyProposerTest.sol deleted file mode 100644 index 218793404..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/EmergencyProposerTest.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "../EmergencyProposer.sol"; -import "../../../common/implementation/Testable.sol"; - -contract EmergencyProposerTest is EmergencyProposer, Testable { - constructor( - IERC20 _token, - uint256 _quorum, - GovernorV2 _governor, - address _executor, - address _timerAddress, - uint64 _minimumWaitTime - ) EmergencyProposer(_token, _quorum, _governor, _executor, _minimumWaitTime) Testable(_timerAddress) {} - - function getCurrentTime() public view override(EmergencyProposer, Testable) returns (uint256) { - return Testable.getCurrentTime(); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/GovernorTest.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/GovernorTest.sol deleted file mode 100644 index 429e38b0a..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/GovernorTest.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../Governor.sol"; -import "../AdminIdentifierLib.sol"; - -// GovernorTest exposes internal methods in the Governor for testing. -contract GovernorTest is Governor { - constructor(address _timerAddress) Governor(address(0), 0, _timerAddress) {} - - function addPrefix(bytes32 input, bytes32 prefix, uint256 prefixLength) external pure returns (bytes32) { - return AdminIdentifierLib._addPrefix(input, prefix, prefixLength); - } - - function uintToUtf8(uint256 v) external pure returns (bytes32 ret) { - return AdminIdentifierLib._uintToUtf8(v); - } - - function constructIdentifier(uint256 id) external pure returns (bytes32 identifier) { - return AdminIdentifierLib._constructIdentifier(id); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/GovernorV2Test.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/GovernorV2Test.sol deleted file mode 100644 index 4ca10abda..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/GovernorV2Test.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../GovernorV2.sol"; -import "../../../common/implementation/Testable.sol"; - -contract GovernorV2Test is GovernorV2, Testable { - constructor( - address _finderAddress, - uint256 _startingId, - address _timerAddress - ) GovernorV2(_finderAddress, _startingId) Testable(_timerAddress) {} - - function getCurrentTime() public view override(GovernorV2, Testable) returns (uint256) { - return Testable.getCurrentTime(); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/MockAdministratee.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/MockAdministratee.sol deleted file mode 100644 index dd9745df9..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/MockAdministratee.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../interfaces/AdministrateeInterface.sol"; - -// A mock implementation of AdministrateeInterface, taking the place of a financial contract. -contract MockAdministratee is AdministrateeInterface { - uint256 public timesRemargined; - uint256 public timesEmergencyShutdown; - - function remargin() external override { - timesRemargined++; - } - - function emergencyShutdown() external override { - timesEmergencyShutdown++; - } - - function pfc() external pure override returns (FixedPoint.Unsigned memory) { - return FixedPoint.fromUnscaledUint(0); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/PriceIdentifierSlashingLibaryTest.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/PriceIdentifierSlashingLibaryTest.sol deleted file mode 100644 index ecd74c7ba..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/PriceIdentifierSlashingLibaryTest.sol +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "../../interfaces/SlashingLibraryInterface.sol"; -import "../VotingV2.sol"; - -/** - * @title Slashing Library contract that uses the price identifier to calculate the amount of tokens to slash. - */ - -contract PriceIdentifierSlashingLibaryTest is SlashingLibraryInterface { - VotingV2 public voting; - - bytes32 public constant whiteListedIdentifier = "SAFE_NO_VOTE"; - - uint256 public constant slashPerToken = 0.0016e18; - - constructor(address _votingV2Address) { - voting = VotingV2(_votingV2Address); - } - - function calcWrongVoteSlashPerToken( - uint256 totalStaked, - uint256 totalVotes, - uint256 totalCorrectVotes, - uint256 priceRequestIndex - ) public pure returns (uint256) {} - - function calcWrongVoteSlashPerTokenGovernance( - uint256 totalStaked, - uint256 totalVotes, - uint256 totalCorrectVotes, - uint256 priceRequestIndex - ) public pure returns (uint256) {} - - function calcNoVoteSlashPerToken( - uint256 totalStaked, - uint256 totalVotes, - uint256 totalCorrectVotes, - uint256 priceRequestIndex - ) public pure returns (uint256) {} - - function calcSlashing( - uint256 totalStaked, - uint256 totalVotes, - uint256 totalCorrectVotes, - uint256 priceRequestIndex, - bool isGovernance - ) external view returns (uint256 wrongVoteSlashPerToken, uint256 noVoteSlashPerToken) { - bytes32 priceRequestIdentifier = voting.resolvedPriceRequestIds(priceRequestIndex); - (, , , , bytes32 identifier, ) = voting.priceRequests(priceRequestIdentifier); - - // If the identifier is whiteListedIdentifier, then no tokens are slashed for no vote. - uint256 noVoteSlashPerToken = identifier == whiteListedIdentifier ? 0 : slashPerToken; - - // If it's a governance price request, then no tokens are slashed for wrong vote. - uint256 wrongVoteSlashPerToken = isGovernance ? 0 : slashPerToken; - - return (isGovernance ? 0 : slashPerToken, noVoteSlashPerToken); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ProposerV2Test.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ProposerV2Test.sol deleted file mode 100644 index aeb27cae2..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ProposerV2Test.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../ProposerV2.sol"; -import "../AdminIdentifierLib.sol"; -import "../../../common/implementation/Testable.sol"; - -contract ProposerV2Test is ProposerV2, Testable { - constructor( - IERC20 _token, - uint256 _bond, - GovernorV2 _governor, - Finder _finder, - address _timerAddress - ) ProposerV2(_token, _bond, _governor, _finder) Testable(_timerAddress) {} - - function getCurrentTime() public view override(ProposerV2, Testable) returns (uint256) { - return Testable.getCurrentTime(); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/PunitiveSlashingLibraryTest.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/PunitiveSlashingLibraryTest.sol deleted file mode 100644 index e0166b0f0..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/PunitiveSlashingLibraryTest.sol +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "../../interfaces/SlashingLibraryInterface.sol"; - -contract PunitiveSlashingLibraryTest is SlashingLibraryInterface { - function calcWrongVoteSlashPerToken( - uint256 totalStaked, - uint256 totalVotes, - uint256 totalCorrectVotes, - uint256 priceRequestIndex - ) public pure returns (uint256) { - return 0.99e18; - } - - function calcWrongVoteSlashPerTokenGovernance( - uint256 totalStaked, - uint256 totalVotes, - uint256 totalCorrectVotes, - uint256 priceRequestIndex - ) public pure returns (uint256) { - return 0.99e18; - } - - function calcNoVoteSlashPerToken( - uint256 totalStaked, - uint256 totalVotes, - uint256 totalCorrectVotes, - uint256 priceRequestIndex - ) public pure returns (uint256) { - return 0.99e18; - } - - function calcSlashing( - uint256 totalStaked, - uint256 totalVotes, - uint256 totalCorrectVotes, - uint256 priceRequestIndex, - bool isGovernance - ) external pure returns (uint256 wrongVoteSlashPerToken, uint256 noVoteSlashPerToken) { - return ( - isGovernance - ? calcWrongVoteSlashPerTokenGovernance(totalStaked, totalVotes, totalCorrectVotes, priceRequestIndex) - : calcWrongVoteSlashPerToken(totalStaked, totalVotes, totalCorrectVotes, priceRequestIndex), - calcNoVoteSlashPerToken(totalStaked, totalVotes, totalCorrectVotes, priceRequestIndex) - ); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ResultComputationTest.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ResultComputationTest.sol deleted file mode 100644 index ff780591d..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ResultComputationTest.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../ResultComputation.sol"; -import "../../../common/implementation/FixedPoint.sol"; - -// Wraps the library ResultComputation for testing purposes. -contract ResultComputationTest { - using ResultComputation for ResultComputation.Data; - - ResultComputation.Data public data; - - function wrapAddVote(int256 votePrice, uint256 numberTokens) external { - data.addVote(votePrice, FixedPoint.Unsigned(numberTokens)); - } - - function wrapGetResolvedPrice(uint256 minVoteThreshold) external view returns (bool isResolved, int256 price) { - return data.getResolvedPrice(FixedPoint.Unsigned(minVoteThreshold)); - } - - function wrapWasVoteCorrect(bytes32 revealHash) external view returns (bool) { - return data.wasVoteCorrect(revealHash); - } - - function wrapGetTotalCorrectlyVotedTokens() external view returns (uint256) { - return data.getTotalCorrectlyVotedTokens().rawValue; - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/StakerTest.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/StakerTest.sol deleted file mode 100644 index 66eb1b816..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/StakerTest.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../Staker.sol"; -import "../../../common/implementation/Testable.sol"; - -// Version of the Staker contract used in tests so time can be controlled. -abstract contract StakerControlledTiming is Staker, Testable { - constructor( - uint128 _emissionRate, - uint64 _unstakeCoolDown, - address _votingToken, - address _timerAddress - ) Staker(_emissionRate, _unstakeCoolDown, _votingToken) Testable(_timerAddress) {} - - function getCurrentTime() public view virtual override(Staker, Testable) returns (uint256) { - return Testable.getCurrentTime(); - } -} - -contract StakerTest is StakerControlledTiming { - constructor( - uint128 _emissionRate, - uint64 _unstakeCoolDown, - address _votingToken, - address _timer - ) StakerControlledTiming(_emissionRate, _unstakeCoolDown, _votingToken, _timer) {} - - function applySlashingToCumulativeStaked(address voter, int128 amount) public { - _updateTrackers(voter); // apply any unaccumulated rewards before modifying the staked balances. - require(int128(cumulativeStake) + amount >= 0, "Cumulative staked cannot be negative"); - voterStakes[voter].stake = uint128(int128(voterStakes[voter].stake) + amount); - } - - function _inActiveReveal() internal view override returns (bool) { - return false; - } - - function _getStartingIndexForStaker() internal view override returns (uint64) { - return 0; - } - - function _computePendingStakes(address wallet, uint128 amount) internal override {} -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VoteTimingTest.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VoteTimingTest.sol deleted file mode 100644 index afa0d0ca5..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VoteTimingTest.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../interfaces/VotingInterface.sol"; -import "../VoteTiming.sol"; - -// Wraps the library VoteTiming for testing purposes. -contract VoteTimingTest { - using VoteTiming for VoteTiming.Data; - - VoteTiming.Data public voteTiming; - - constructor(uint256 phaseLength) { - wrapInit(phaseLength); - } - - function wrapComputeCurrentRoundId(uint256 currentTime) external view returns (uint256) { - return voteTiming.computeCurrentRoundId(currentTime); - } - - function wrapComputeCurrentPhase(uint256 currentTime) external view returns (VotingAncillaryInterface.Phase) { - return voteTiming.computeCurrentPhase(currentTime); - } - - function wrapInit(uint256 phaseLength) public { - voteTiming.init(phaseLength); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VotingTest.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VotingTest.sol deleted file mode 100644 index 9b68a7a00..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VotingTest.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../Voting.sol"; -import "../../../common/implementation/FixedPoint.sol"; - -// Test contract used to access internal variables in the Voting contract. -contract VotingTest is Voting { - constructor( - uint256 _phaseLength, - FixedPoint.Unsigned memory _gatPercentage, - FixedPoint.Unsigned memory _inflationRate, - uint256 _rewardsExpirationTimeout, - address _votingToken, - address _finder, - address _timerAddress - ) - Voting( - _phaseLength, - _gatPercentage, - _inflationRate, - _rewardsExpirationTimeout, - _votingToken, - _finder, - _timerAddress - ) - {} - - function getPendingPriceRequestsArray() external view returns (bytes32[] memory) { - return pendingPriceRequests; - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VotingV2Test.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VotingV2Test.sol deleted file mode 100644 index 0a014c3f4..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/VotingV2Test.sol +++ /dev/null @@ -1,107 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "../VotingV2.sol"; -import "../../../common/implementation/Testable.sol"; - -// Test contract used to manage the time for the contract in tests. -contract VotingV2ControllableTiming is VotingV2, Testable { - constructor( - uint128 _emissionRate, - uint64 _unstakeCoolDown, - uint64 _phaseLength, - uint32 _maxRolls, - uint32 _maxRequestsPerRound, - uint128 _gat, - uint64 _spat, - address _votingToken, - address _finder, - address _slashingLibrary, - address _previousVotingContract, - address _timerAddress - ) - VotingV2( - _emissionRate, - _unstakeCoolDown, - _phaseLength, - _maxRolls, - _maxRequestsPerRound, - _gat, - _spat, - _votingToken, - _finder, - _slashingLibrary, - _previousVotingContract - ) - Testable(_timerAddress) - {} - - function getCurrentTime() public view override(Staker, Testable) returns (uint256) { - return Testable.getCurrentTime(); - } - - function commitVote(bytes32 identifier, uint256 time, bytes32 hash) external virtual { - commitVote(identifier, time, "", hash); - } - - function revealVote(bytes32 identifier, uint256 time, int256 price, int256 salt) external virtual { - revealVote(identifier, time, price, "", salt); - } - - function commitAndEmitEncryptedVote( - bytes32 identifier, - uint256 time, - bytes32 hash, - bytes memory encryptedVote - ) public { - commitAndEmitEncryptedVote(identifier, time, "", hash, encryptedVote); - } - - function getPendingPriceRequestsArray() external view returns (bytes32[] memory) { - return pendingPriceRequestsIds; - } - - function getPriceRequestStatuses(PendingRequest[] memory requests) external view returns (RequestState[] memory) { - PendingRequestAncillary[] memory requestsAncillary = new PendingRequestAncillary[](requests.length); - - for (uint256 i = 0; i < requests.length; i = unsafe_inc(i)) { - requestsAncillary[i].identifier = requests[i].identifier; - requestsAncillary[i].time = requests[i].time; - requestsAncillary[i].ancillaryData = ""; - } - return getPriceRequestStatuses(requestsAncillary); - } -} - -// Test contract used to access internal variables in the Voting contract. -contract VotingV2Test is VotingV2ControllableTiming { - constructor( - uint128 _emissionRate, - uint64 _unstakeCoolDown, - uint64 _phaseLength, - uint32 _maxRolls, - uint32 _maxRequestsPerRound, - uint128 _gat, - uint64 _spat, - address _votingToken, - address _finder, - address _slashingLibrary, - address _previousVotingContract, - address _timerAddress - ) - VotingV2ControllableTiming( - _emissionRate, - _unstakeCoolDown, - _phaseLength, - _maxRolls, - _maxRequestsPerRound, - _gat, - _spat, - _votingToken, - _finder, - _slashingLibrary, - _previousVotingContract, - _timerAddress - ) - {} -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ZeroedSlashingLibaryTest.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ZeroedSlashingLibaryTest.sol deleted file mode 100644 index d44b659fc..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/implementation/test/ZeroedSlashingLibaryTest.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "../../interfaces/SlashingLibraryInterface.sol"; - -/** - * @title Slashing Library contract that executes no slashing for any actions. Used in tests. - */ - -contract ZeroedSlashingSlashingLibraryTest is SlashingLibraryInterface { - function calcWrongVoteSlashPerToken( - uint256 totalStaked, - uint256 totalVotes, - uint256 totalCorrectVotes, - uint256 priceRequestIndex - ) public pure returns (uint256) { - return 0; - } - - function calcWrongVoteSlashPerTokenGovernance( - uint256 totalStaked, - uint256 totalVotes, - uint256 totalCorrectVotes, - uint256 priceRequestIndex - ) public pure returns (uint256) { - return 0; - } - - function calcNoVoteSlashPerToken( - uint256 totalStaked, - uint256 totalVotes, - uint256 totalCorrectVotes, - uint256 priceRequestIndex - ) public pure returns (uint256) { - return 0; - } - - function calcSlashing( - uint256 totalStaked, - uint256 totalVotes, - uint256 totalCorrectVotes, - uint256 priceRequestIndex, - bool isGovernance - ) external pure returns (uint256 wrongVoteSlashPerToken, uint256 noVoteSlashPerToken) { - return ( - isGovernance - ? calcWrongVoteSlashPerTokenGovernance(totalStaked, totalVotes, totalCorrectVotes, priceRequestIndex) - : calcWrongVoteSlashPerToken(totalStaked, totalVotes, totalCorrectVotes, priceRequestIndex), - calcNoVoteSlashPerToken(totalStaked, totalVotes, totalCorrectVotes, priceRequestIndex) - ); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/AdministrateeInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/AdministrateeInterface.sol deleted file mode 100644 index 96e70bb4d..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/AdministrateeInterface.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/FixedPoint.sol"; - -/** - * @title Interface that all financial contracts expose to the admin. - */ -interface AdministrateeInterface { - /** - * @notice Initiates the shutdown process, in case of an emergency. - */ - function emergencyShutdown() external; - - /** - * @notice A core contract method called independently or as a part of other financial contract transactions. - * @dev It pays fees and moves money between margin accounts to make sure they reflect the NAV of the contract. - */ - function remargin() external; - - /** - * @notice Gets the current profit from corruption for this contract in terms of the collateral currency. - * @dev This is equivalent to the collateral pool available from which to pay fees. Therefore, derived contracts are - * expected to implement this so that pay-fee methods can correctly compute the owed fees as a % of PfC. - * @return pfc value for equal to the current profit from corruption denominated in collateral currency. - */ - function pfc() external view returns (FixedPoint.Unsigned memory); -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/MinimumVotingAncillaryInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/MinimumVotingAncillaryInterface.sol deleted file mode 100644 index 1b662f35b..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/MinimumVotingAncillaryInterface.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -interface MinimumVotingAncillaryInterface { - struct Unsigned { - uint256 rawValue; - } - - struct PendingRequestAncillary { - bytes32 identifier; - uint256 time; - bytes ancillaryData; - } - - function retrieveRewards( - address voterAddress, - uint256 roundId, - PendingRequestAncillary[] memory toRetrieve - ) external returns (Unsigned memory); -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleAncillaryInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleAncillaryInterface.sol deleted file mode 100644 index e5055955f..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleAncillaryInterface.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -/** - * @title Financial contract facing Oracle interface. - * @dev Interface used by financial contracts to interact with the Oracle. Voters will use a different interface. - */ -abstract contract OracleAncillaryInterface { - /** - * @notice Enqueues a request (if a request isn't already present) for the given `identifier`, `time` pair. - * @dev Time must be in the past and the identifier must be supported. - * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - * @param time unix timestamp for the price request. - */ - - function requestPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData) public virtual; - - /** - * @notice Whether the price for `identifier` and `time` is available. - * @dev Time must be in the past and the identifier must be supported. - * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. - * @param time unix timestamp for the price request. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - * @return bool if the DVM has resolved to a price for the given identifier and timestamp. - */ - function hasPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData) public view virtual returns (bool); - - /** - * @notice Gets the price for `identifier` and `time` if it has already been requested and resolved. - * @dev If the price is not available, the method reverts. - * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. - * @param time unix timestamp for the price request. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - * @return int256 representing the resolved price for the given identifier and timestamp. - */ - - function getPrice( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) public view virtual returns (int256); -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleGovernanceInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleGovernanceInterface.sol deleted file mode 100644 index 0bd7dd965..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleGovernanceInterface.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "./OracleInterface.sol"; -import "./OracleAncillaryInterface.sol"; - -/** - * @title Financial contract facing extending the Oracle interface with governance actions. - * @dev Interface used by financial contracts to interact with the Oracle extending governance actions. Voters will use a different interface. - */ -abstract contract OracleGovernanceInterface is OracleInterface, OracleAncillaryInterface { - /** - * @notice Enqueues a request (if a request isn't already present) for the given `identifier`, `time` pair. - * @dev Time must be in the past and the identifier must be supported. - * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - * @param time unix timestamp for the price request. - */ - function requestGovernanceAction(bytes32 identifier, uint256 time, bytes memory ancillaryData) external virtual; -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleInterface.sol deleted file mode 100644 index 610c27c5d..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/OracleInterface.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -/** - * @title Financial contract facing Oracle interface. - * @dev Interface used by financial contracts to interact with the Oracle. Voters will use a different interface. - */ -abstract contract OracleInterface { - /** - * @notice Enqueues a request (if a request isn't already present) for the given `identifier`, `time` pair. - * @dev Time must be in the past and the identifier must be supported. - * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. - * @param time unix timestamp for the price request. - */ - function requestPrice(bytes32 identifier, uint256 time) external virtual; - - /** - * @notice Whether the price for `identifier` and `time` is available. - * @dev Time must be in the past and the identifier must be supported. - * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. - * @param time unix timestamp for the price request. - * @return bool if the DVM has resolved to a price for the given identifier and timestamp. - */ - function hasPrice(bytes32 identifier, uint256 time) external view virtual returns (bool); - - /** - * @notice Gets the price for `identifier` and `time` if it has already been requested and resolved. - * @dev If the price is not available, the method reverts. - * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. - * @param time unix timestamp for the price request. - * @return int256 representing the resolved price for the given identifier and timestamp. - */ - function getPrice(bytes32 identifier, uint256 time) external view virtual returns (int256); -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/RegistryInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/RegistryInterface.sol deleted file mode 100644 index 4bf923a4f..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/RegistryInterface.sol +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -/** - * @title Interface for a registry of contracts and contract creators. - */ -interface RegistryInterface { - /** - * @notice Registers a new contract. - * @dev Only authorized contract creators can call this method. - * @param parties an array of addresses who become parties in the contract. - * @param contractAddress defines the address of the deployed contract. - */ - function registerContract(address[] calldata parties, address contractAddress) external; - - /** - * @notice Returns whether the contract has been registered with the registry. - * @dev If it is registered, it is an authorized participant in the UMA system. - * @param contractAddress address of the contract. - * @return bool indicates whether the contract is registered. - */ - function isContractRegistered(address contractAddress) external view returns (bool); - - /** - * @notice Returns a list of all contracts that are associated with a particular party. - * @param party address of the party. - * @return an array of the contracts the party is registered to. - */ - function getRegisteredContracts(address party) external view returns (address[] memory); - - /** - * @notice Returns all registered contracts. - * @return all registered contract addresses within the system. - */ - function getAllRegisteredContracts() external view returns (address[] memory); - - /** - * @notice Adds a party to the calling contract. - * @dev msg.sender must be the contract to which the party member is added. - * @param party address to be added to the contract. - */ - function addPartyToContract(address party) external; - - /** - * @notice Removes a party member to the calling contract. - * @dev msg.sender must be the contract to which the party member is added. - * @param party address to be removed from the contract. - */ - function removePartyFromContract(address party) external; - - /** - * @notice checks if an address is a party in a contract. - * @param party party to check. - * @param contractAddress address to check against the party. - * @return bool indicating if the address is a party of the contract. - */ - function isPartyMemberOfContract(address party, address contractAddress) external view returns (bool); -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/SlashingLibraryInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/SlashingLibraryInterface.sol deleted file mode 100644 index 3f898abc4..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/SlashingLibraryInterface.sol +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -interface SlashingLibraryInterface { - /** - * @notice Calculates the wrong vote slash per token. - * @param totalStaked The total amount of tokens staked. - * @param totalVotes The total amount of votes. - * @param totalCorrectVotes The total amount of correct votes. - * @param priceRequestIndex The price request index within the resolvedPriceRequestIds array. - * @return uint256 The amount of tokens to slash per token staked. - */ - function calcWrongVoteSlashPerToken( - uint256 totalStaked, - uint256 totalVotes, - uint256 totalCorrectVotes, - uint256 priceRequestIndex - ) external view returns (uint256); - - /** - * @notice Calculates the wrong vote slash per token for governance requests. - * @param totalStaked The total amount of tokens staked. - * @param totalVotes The total amount of votes. - * @param totalCorrectVotes The total amount of correct votes. - * @param priceRequestIndex The price request index within the resolvedPriceRequestIds array. - * @return uint256 The amount of tokens to slash per token staked. - */ - function calcWrongVoteSlashPerTokenGovernance( - uint256 totalStaked, - uint256 totalVotes, - uint256 totalCorrectVotes, - uint256 priceRequestIndex - ) external view returns (uint256); - - /** - * @notice Calculates the no vote slash per token. - * @param totalStaked The total amount of tokens staked. - * @param totalVotes The total amount of votes. - * @param totalCorrectVotes The total amount of correct votes. - * @param priceRequestIndex The price request index within the resolvedPriceRequestIds array. - * @return uint256 The amount of tokens to slash per token staked. - */ - function calcNoVoteSlashPerToken( - uint256 totalStaked, - uint256 totalVotes, - uint256 totalCorrectVotes, - uint256 priceRequestIndex - ) external view returns (uint256); - - /** - * @notice Calculates all slashing trackers in one go to decrease cross-contract calls needed. - * @param totalStaked The total amount of tokens staked. - * @param totalVotes The total amount of votes. - * @param totalCorrectVotes The total amount of correct votes. - * @param priceRequestIndex The price request index within the resolvedPriceRequestIds array. - * @param isGovernance Whether the request is a governance request. - * @return wrongVoteSlashPerToken The amount of tokens to slash for voting wrong. - * @return noVoteSlashPerToken The amount of tokens to slash for not voting. - */ - function calcSlashing( - uint256 totalStaked, - uint256 totalVotes, - uint256 totalCorrectVotes, - uint256 priceRequestIndex, - bool isGovernance - ) external view returns (uint256 wrongVoteSlashPerToken, uint256 noVoteSlashPerToken); -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/StakerInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/StakerInterface.sol deleted file mode 100644 index f7d348a71..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/StakerInterface.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "../implementation/VotingToken.sol"; -import "../../common/interfaces/ExpandedIERC20.sol"; - -interface StakerInterface { - function votingToken() external returns (ExpandedIERC20); - - function stake(uint128 amount) external; - - function requestUnstake(uint128 amount) external; - - function executeUnstake() external; - - function withdrawRewards() external returns (uint128); - - function withdrawAndRestake() external returns (uint128); - - function setEmissionRate(uint128 newEmissionRate) external; - - function setUnstakeCoolDown(uint64 newUnstakeCoolDown) external; - - /** - * @notice Sets the delegate of a voter. This delegate can vote on behalf of the staker. The staker will still own - * all staked balances, receive rewards and be slashed based on the actions of the delegate. Intended use is using a - * low-security available wallet for voting while keeping access to staked amounts secure by a more secure wallet. - * @param delegate the address of the delegate. - */ - function setDelegate(address delegate) external virtual; - - /** - * @notice Sets the delegator of a voter. Acts to accept a delegation. The delegate can only vote for the delegator - * if the delegator also selected the delegate to do so (two-way relationship needed). - * @param delegator the address of the delegator. - */ - function setDelegator(address delegator) external virtual; -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingAncillaryInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingAncillaryInterface.sol deleted file mode 100644 index 5b0487b05..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingAncillaryInterface.sol +++ /dev/null @@ -1,183 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/FixedPoint.sol"; - -/** - * @title Interface that voters must use to Vote on price request resolutions. - */ -abstract contract VotingAncillaryInterface { - struct PendingRequestAncillary { - bytes32 identifier; - uint256 time; - bytes ancillaryData; - } - - // Captures the necessary data for making a commitment. - // Used as a parameter when making batch commitments. - // Not used as a data structure for storage. - struct CommitmentAncillary { - bytes32 identifier; - uint256 time; - bytes ancillaryData; - bytes32 hash; - bytes encryptedVote; - } - - // Captures the necessary data for revealing a vote. - // Used as a parameter when making batch reveals. - // Not used as a data structure for storage. - struct RevealAncillary { - bytes32 identifier; - uint256 time; - int256 price; - bytes ancillaryData; - int256 salt; - } - - // Note: the phases must be in order. Meaning the first enum value must be the first phase, etc. - // `NUM_PHASES` is to get the number of phases. It isn't an actual phase, and it should always be last. - enum Phase { - Commit, - Reveal, - NUM_PHASES - } - - /** - * @notice Commit a vote for a price request for `identifier` at `time`. - * @dev `identifier`, `time` must correspond to a price request that's currently in the commit phase. - * Commits can be changed. - * @dev Since transaction data is public, the salt will be revealed with the vote. While this is the system’s expected behavior, - * voters should never reuse salts. If someone else is able to guess the voted price and knows that a salt will be reused, then - * they can determine the vote pre-reveal. - * @param identifier uniquely identifies the committed vote. E.G. BTC/USD price pair. - * @param time unix timestamp of the price being voted on. - * @param hash keccak256 hash of the `price`, `salt`, voter `address`, `time`, current `roundId`, and `identifier`. - */ - function commitVote(bytes32 identifier, uint256 time, bytes memory ancillaryData, bytes32 hash) public virtual; - - /** - * @notice Submit a batch of commits in a single transaction. - * @dev Using `encryptedVote` is optional. If included then commitment is stored on chain. - * Look at `project-root/common/Constants.js` for the tested maximum number of - * commitments that can fit in one transaction. - * @param commits array of structs that encapsulate an `identifier`, `time`, `hash` and optional `encryptedVote`. - */ - function batchCommit(CommitmentAncillary[] memory commits) public virtual; - - /** - * @notice commits a vote and logs an event with a data blob, typically an encrypted version of the vote - * @dev An encrypted version of the vote is emitted in an event `EncryptedVote` to allow off-chain infrastructure to - * retrieve the commit. The contents of `encryptedVote` are never used on chain: it is purely for convenience. - * @param identifier unique price pair identifier. E.g. BTC/USD price pair. - * @param time unix timestamp of for the price request. - * @param hash keccak256 hash of the price you want to vote for and a `int256 salt`. - * @param encryptedVote offchain encrypted blob containing the voters amount, time and salt. - */ - function commitAndEmitEncryptedVote( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData, - bytes32 hash, - bytes memory encryptedVote - ) public virtual; - - /** - * @notice snapshot the current round's token balances and lock in the inflation rate and GAT. - * @dev This function can be called multiple times but each round will only every have one snapshot at the - * time of calling `_freezeRoundVariables`. - * @param signature signature required to prove caller is an EOA to prevent flash loans from being included in the - * snapshot. - */ - function snapshotCurrentRound(bytes calldata signature) external virtual; - - /** - * @notice Reveal a previously committed vote for `identifier` at `time`. - * @dev The revealed `price`, `salt`, `address`, `time`, `roundId`, and `identifier`, must hash to the latest `hash` - * that `commitVote()` was called with. Only the committer can reveal their vote. - * @param identifier voted on in the commit phase. EG BTC/USD price pair. - * @param time specifies the unix timestamp of the price is being voted on. - * @param price voted on during the commit phase. - * @param salt value used to hide the commitment price during the commit phase. - */ - function revealVote( - bytes32 identifier, - uint256 time, - int256 price, - bytes memory ancillaryData, - int256 salt - ) public virtual; - - /** - * @notice Reveal multiple votes in a single transaction. - * Look at `project-root/common/Constants.js` for the tested maximum number of reveals. - * that can fit in one transaction. - * @dev For more information on reveals, review the comment for `revealVote`. - * @param reveals array of the Reveal struct which contains an identifier, time, price and salt. - */ - function batchReveal(RevealAncillary[] memory reveals) public virtual; - - /** - * @notice Gets the queries that are being voted on this round. - * @return pendingRequests `PendingRequest` array containing identifiers - * and timestamps for all pending requests. - */ - function getPendingRequests() external view virtual returns (PendingRequestAncillary[] memory); - - /** - * @notice Returns the current voting phase, as a function of the current time. - * @return Phase to indicate the current phase. Either { Commit, Reveal, NUM_PHASES }. - */ - function getVotePhase() external view virtual returns (Phase); - - /** - * @notice Returns the current round ID, as a function of the current time. - * @return uint256 representing the unique round ID. - */ - function getCurrentRoundId() external view virtual returns (uint256); - - /** - * @notice Retrieves rewards owed for a set of resolved price requests. - * @dev Can only retrieve rewards if calling for a valid round and if the - * call is done within the timeout threshold (not expired). - * @param voterAddress voter for which rewards will be retrieved. Does not have to be the caller. - * @param roundId the round from which voting rewards will be retrieved from. - * @param toRetrieve array of PendingRequests which rewards are retrieved from. - * @return total amount of rewards returned to the voter. - */ - function retrieveRewards( - address voterAddress, - uint256 roundId, - PendingRequestAncillary[] memory toRetrieve - ) public virtual returns (FixedPoint.Unsigned memory); - - // Voting Owner functions. - - /** - * @notice Disables this Voting contract in favor of the migrated one. - * @dev Can only be called by the contract owner. - * @param newVotingAddress the newly migrated contract address. - */ - function setMigrated(address newVotingAddress) external virtual; - - /** - * @notice Resets the inflation rate. Note: this change only applies to rounds that have not yet begun. - * @dev This method is public because calldata structs are not currently supported by solidity. - * @param newInflationRate sets the next round's inflation rate. - */ - function setInflationRate(FixedPoint.Unsigned memory newInflationRate) public virtual; - - /** - * @notice Resets the Gat percentage. Note: this change only applies to rounds that have not yet begun. - * @dev This method is public because calldata structs are not currently supported by solidity. - * @param newGatPercentage sets the next round's Gat percentage. - */ - function setGatPercentage(FixedPoint.Unsigned memory newGatPercentage) public virtual; - - /** - * @notice Resets the rewards expiration timeout. - * @dev This change only applies to rounds that have not yet begun. - * @param NewRewardsExpirationTimeout how long a caller can wait before choosing to withdraw their rewards. - */ - function setRewardsExpirationTimeout(uint256 NewRewardsExpirationTimeout) public virtual; -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingInterface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingInterface.sol deleted file mode 100644 index 93837ed42..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingInterface.sol +++ /dev/null @@ -1,170 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/FixedPoint.sol"; -import "./VotingAncillaryInterface.sol"; - -/** - * @title Interface that voters must use to Vote on price request resolutions. - */ -abstract contract VotingInterface { - struct PendingRequest { - bytes32 identifier; - uint256 time; - } - - // Captures the necessary data for making a commitment. - // Used as a parameter when making batch commitments. - // Not used as a data structure for storage. - struct Commitment { - bytes32 identifier; - uint256 time; - bytes32 hash; - bytes encryptedVote; - } - - // Captures the necessary data for revealing a vote. - // Used as a parameter when making batch reveals. - // Not used as a data structure for storage. - struct Reveal { - bytes32 identifier; - uint256 time; - int256 price; - int256 salt; - } - - /** - * @notice Commit a vote for a price request for `identifier` at `time`. - * @dev `identifier`, `time` must correspond to a price request that's currently in the commit phase. - * Commits can be changed. - * @dev Since transaction data is public, the salt will be revealed with the vote. While this is the system’s expected behavior, - * voters should never reuse salts. If someone else is able to guess the voted price and knows that a salt will be reused, then - * they can determine the vote pre-reveal. - * @param identifier uniquely identifies the committed vote. EG BTC/USD price pair. - * @param time unix timestamp of the price being voted on. - * @param hash keccak256 hash of the `price`, `salt`, voter `address`, `time`, current `roundId`, and `identifier`. - */ - function commitVote(bytes32 identifier, uint256 time, bytes32 hash) external virtual; - - /** - * @notice Submit a batch of commits in a single transaction. - * @dev Using `encryptedVote` is optional. If included then commitment is stored on chain. - * Look at `project-root/common/Constants.js` for the tested maximum number of - * commitments that can fit in one transaction. - * @param commits array of structs that encapsulate an `identifier`, `time`, `hash` and optional `encryptedVote`. - */ - function batchCommit(Commitment[] memory commits) public virtual; - - /** - * @notice commits a vote and logs an event with a data blob, typically an encrypted version of the vote - * @dev An encrypted version of the vote is emitted in an event `EncryptedVote` to allow off-chain infrastructure to - * retrieve the commit. The contents of `encryptedVote` are never used on chain: it is purely for convenience. - * @param identifier unique price pair identifier. Eg: BTC/USD price pair. - * @param time unix timestamp of for the price request. - * @param hash keccak256 hash of the price you want to vote for and a `int256 salt`. - * @param encryptedVote offchain encrypted blob containing the voters amount, time and salt. - */ - function commitAndEmitEncryptedVote( - bytes32 identifier, - uint256 time, - bytes32 hash, - bytes memory encryptedVote - ) public virtual; - - /** - * @notice snapshot the current round's token balances and lock in the inflation rate and GAT. - * @dev This function can be called multiple times but each round will only every have one snapshot at the - * time of calling `_freezeRoundVariables`. - * @param signature signature required to prove caller is an EOA to prevent flash loans from being included in the - * snapshot. - */ - function snapshotCurrentRound(bytes calldata signature) external virtual; - - /** - * @notice Reveal a previously committed vote for `identifier` at `time`. - * @dev The revealed `price`, `salt`, `address`, `time`, `roundId`, and `identifier`, must hash to the latest `hash` - * that `commitVote()` was called with. Only the committer can reveal their vote. - * @param identifier voted on in the commit phase. EG BTC/USD price pair. - * @param time specifies the unix timestamp of the price is being voted on. - * @param price voted on during the commit phase. - * @param salt value used to hide the commitment price during the commit phase. - */ - function revealVote(bytes32 identifier, uint256 time, int256 price, int256 salt) public virtual; - - /** - * @notice Reveal multiple votes in a single transaction. - * Look at `project-root/common/Constants.js` for the tested maximum number of reveals. - * that can fit in one transaction. - * @dev For more information on reveals, review the comment for `revealVote`. - * @param reveals array of the Reveal struct which contains an identifier, time, price and salt. - */ - function batchReveal(Reveal[] memory reveals) public virtual; - - /** - * @notice Gets the queries that are being voted on this round. - * @return pendingRequests `PendingRequest` array containing identifiers - * and timestamps for all pending requests. - */ - function getPendingRequests() - external - view - virtual - returns (VotingAncillaryInterface.PendingRequestAncillary[] memory); - - /** - * @notice Returns the current voting phase, as a function of the current time. - * @return Phase to indicate the current phase. Either { Commit, Reveal, NUM_PHASES }. - */ - function getVotePhase() external view virtual returns (VotingAncillaryInterface.Phase); - - /** - * @notice Returns the current round ID, as a function of the current time. - * @return uint256 representing the unique round ID. - */ - function getCurrentRoundId() external view virtual returns (uint256); - - /** - * @notice Retrieves rewards owed for a set of resolved price requests. - * @dev Can only retrieve rewards if calling for a valid round and if the - * call is done within the timeout threshold (not expired). - * @param voterAddress voter for which rewards will be retrieved. Does not have to be the caller. - * @param roundId the round from which voting rewards will be retrieved from. - * @param toRetrieve array of PendingRequests which rewards are retrieved from. - * @return total amount of rewards returned to the voter. - */ - function retrieveRewards( - address voterAddress, - uint256 roundId, - PendingRequest[] memory toRetrieve - ) public virtual returns (FixedPoint.Unsigned memory); - - // Voting Owner functions. - - /** - * @notice Disables this Voting contract in favor of the migrated one. - * @dev Can only be called by the contract owner. - * @param newVotingAddress the newly migrated contract address. - */ - function setMigrated(address newVotingAddress) external virtual; - - /** - * @notice Resets the inflation rate. Note: this change only applies to rounds that have not yet begun. - * @dev This method is public because calldata structs are not currently supported by solidity. - * @param newInflationRate sets the next round's inflation rate. - */ - function setInflationRate(FixedPoint.Unsigned memory newInflationRate) public virtual; - - /** - * @notice Resets the Gat percentage. Note: this change only applies to rounds that have not yet begun. - * @dev This method is public because calldata structs are not currently supported by solidity. - * @param newGatPercentage sets the next round's Gat percentage. - */ - function setGatPercentage(FixedPoint.Unsigned memory newGatPercentage) public virtual; - - /** - * @notice Resets the rewards expiration timeout. - * @dev This change only applies to rounds that have not yet begun. - * @param NewRewardsExpirationTimeout how long a caller can wait before choosing to withdraw their rewards. - */ - function setRewardsExpirationTimeout(uint256 NewRewardsExpirationTimeout) public virtual; -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingV2Interface.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingV2Interface.sol deleted file mode 100644 index 8c127058d..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/interfaces/VotingV2Interface.sol +++ /dev/null @@ -1,187 +0,0 @@ -// TODO: add staking/snapshot interfaces to this interface file. - -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -/** - * @title Interface that voters must use to Vote on price request resolutions. - */ -abstract contract VotingV2Interface { - struct PendingRequest { - bytes32 identifier; - uint256 time; - } - - struct PendingRequestAncillary { - bytes32 identifier; - uint256 time; - bytes ancillaryData; - } - - struct PendingRequestAncillaryAugmented { - uint32 lastVotingRound; - bool isGovernance; - uint64 time; - uint32 rollCount; - bytes32 identifier; - bytes ancillaryData; - } - - // Captures the necessary data for making a commitment. - // Used as a parameter when making batch commitments. - // Not used as a data structure for storage. - struct Commitment { - bytes32 identifier; - uint256 time; - bytes32 hash; - bytes encryptedVote; - } - - // Captures the necessary data for revealing a vote. - // Used as a parameter when making batch reveals. - // Not used as a data structure for storage. - struct Reveal { - bytes32 identifier; - uint256 time; - int256 price; - int256 salt; - } - - // Captures the necessary data for making a commitment. - // Used as a parameter when making batch commitments. - // Not used as a data structure for storage. - struct CommitmentAncillary { - bytes32 identifier; - uint256 time; - bytes ancillaryData; - bytes32 hash; - bytes encryptedVote; - } - - // Captures the necessary data for revealing a vote. - // Used as a parameter when making batch reveals. - // Not used as a data structure for storage. - struct RevealAncillary { - bytes32 identifier; - uint256 time; - int256 price; - bytes ancillaryData; - int256 salt; - } - - // Note: the phases must be in order. Meaning the first enum value must be the first phase, etc. - // `NUM_PHASES` is to get the number of phases. It isn't an actual phase, and it should always be last. - enum Phase { - Commit, - Reveal, - NUM_PHASES - } - - /** - * @notice Commit a vote for a price request for `identifier` at `time`. - * @dev `identifier`, `time` must correspond to a price request that's currently in the commit phase. - * Commits can be changed. - * @dev Since transaction data is public, the salt will be revealed with the vote. While this is the system’s expected behavior, - * voters should never reuse salts. If someone else is able to guess the voted price and knows that a salt will be reused, then - * they can determine the vote pre-reveal. - * @param identifier uniquely identifies the committed vote. EG BTC/USD price pair. - * @param time unix timestamp of the price being voted on. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - * @param hash keccak256 hash of the `price`, `salt`, voter `address`, `time`, current `roundId`, and `identifier`. - */ - function commitVote(bytes32 identifier, uint256 time, bytes memory ancillaryData, bytes32 hash) public virtual; - - /** - * @notice commits a vote and logs an event with a data blob, typically an encrypted version of the vote - * @dev An encrypted version of the vote is emitted in an event `EncryptedVote` to allow off-chain infrastructure to - * retrieve the commit. The contents of `encryptedVote` are never used on chain: it is purely for convenience. - * @param identifier unique price pair identifier. Eg: BTC/USD price pair. - * @param time unix timestamp of for the price request. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - * @param hash keccak256 hash of the price you want to vote for and a `int256 salt`. - * @param encryptedVote offchain encrypted blob containing the voters amount, time and salt. - */ - function commitAndEmitEncryptedVote( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData, - bytes32 hash, - bytes memory encryptedVote - ) external virtual; - - /** - * @notice Reveal a previously committed vote for `identifier` at `time`. - * @dev The revealed `price`, `salt`, `address`, `time`, `roundId`, and `identifier`, must hash to the latest `hash` - * that `commitVote()` was called with. Only the committer can reveal their vote. - * @param identifier voted on in the commit phase. EG BTC/USD price pair. - * @param time specifies the unix timestamp of the price is being voted on. - * @param price voted on during the commit phase. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - * @param salt value used to hide the commitment price during the commit phase. - */ - function revealVote( - bytes32 identifier, - uint256 time, - int256 price, - bytes memory ancillaryData, - int256 salt - ) public virtual; - - /** - * @notice Gets the requests that are being voted on this round. - * @return pendingRequests array containing identifiers of type PendingRequestAncillaryAugmented. - */ - function getPendingRequests() external virtual returns (PendingRequestAncillaryAugmented[] memory); - - /** - * @notice Returns the current voting phase, as a function of the current time. - * @return Phase to indicate the current phase. Either { Commit, Reveal, NUM_PHASES }. - */ - function getVotePhase() external view virtual returns (Phase); - - /** - * @notice Returns the current round ID, as a function of the current time. - * @return uint256 representing the unique round ID. - */ - function getCurrentRoundId() external view virtual returns (uint32); - - // Voting Owner functions. - - /** - * @notice Disables this Voting contract in favor of the migrated one. - * @dev Can only be called by the contract owner. - * @param newVotingAddress the newly migrated contract address. - */ - function setMigrated(address newVotingAddress) external virtual; - - /** - * @notice Sets the maximum number of rounds to roll a request can have before the DVM auto deletes it. - * @dev Can only be called by the contract owner. - * @param newMaxRolls the new number of rounds to roll a request before the DVM auto deletes it. - */ - function setMaxRolls(uint32 newMaxRolls) external virtual; - - /** - * @notice Sets the maximum number of requests that can be made in a single round. Used to bound the maximum - * sequential slashing that can be applied within a single round. - * @dev Can only be called by the contract owner. - * @param newMaxRequestsPerRound the new maximum number of requests that can be made in a single round. - */ - function setMaxRequestPerRound(uint32 newMaxRequestsPerRound) external virtual; - - /** - * @notice Resets the GAT number and SPAT percentage. The GAT is the minimum number of tokens that must participate - * in a vote for it to resolve (quorum number). The SPAT is is the minimum percentage of tokens that must agree - * in a vote for it to resolve (percentage of staked tokens) Note: this change only applies to rounds that - * have not yet begun. - * @param newGat sets the next round's GAT and going forward. - * @param newSpat sets the next round's SPAT and going forward. - */ - function setGatAndSpat(uint128 newGat, uint64 newSpat) external virtual; - - /** - * @notice Changes the slashing library used by this contract. - * @param _newSlashingLibrary new slashing library address. - */ - function setSlashingLibrary(address _newSlashingLibrary) external virtual; -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracle.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracle.sol deleted file mode 100644 index 33fc2882f..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracle.sol +++ /dev/null @@ -1,138 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/Testable.sol"; -import "../interfaces/OracleInterface.sol"; -import "../interfaces/IdentifierWhitelistInterface.sol"; -import "../interfaces/FinderInterface.sol"; -import "../implementation/Constants.sol"; - -// A mock oracle used for testing. -contract MockOracle is OracleInterface, Testable { - // Represents an available price. Have to keep a separate bool to allow for price=0. - struct Price { - bool isAvailable; - int256 price; - // Time the verified price became available. - uint256 verifiedTime; - } - - // The two structs below are used in an array and mapping to keep track of prices that have been requested but are - // not yet available. - struct QueryIndex { - bool isValid; - uint256 index; - } - - // Represents a (identifier, time) point that has been queried. - struct QueryPoint { - bytes32 identifier; - uint256 time; - } - - // Reference to the Finder. - FinderInterface private finder; - - // Maps request IDs to their resolved Price structs. - mapping(bytes32 => Price) private verifiedPrices; - - // Maps request IDs to their pending QueryIndex structs. - mapping(bytes32 => QueryIndex) private queryIndices; - - // Array of pending QueryPoint structs. - QueryPoint[] private requestedPrices; - - event PriceRequestAdded( - address indexed requester, - bytes32 indexed identifier, - uint256 time, - bytes32 indexed requestId - ); - event PushedPrice( - address indexed pusher, - bytes32 indexed identifier, - uint256 time, - int256 price, - bytes32 indexed requestId - ); - - constructor(address _finderAddress, address _timerAddress) Testable(_timerAddress) { - finder = FinderInterface(_finderAddress); - } - - // Enqueues a request (if a request isn't already present) for the given (identifier, time) pair. - - function requestPrice(bytes32 identifier, uint256 time) public override { - require(_getIdentifierWhitelist().isIdentifierSupported(identifier)); - bytes32 requestId = _encodePriceRequest(identifier, time); - Price storage lookup = verifiedPrices[requestId]; - if (!lookup.isAvailable && !queryIndices[requestId].isValid) { - // New query, enqueue it for review. - queryIndices[requestId] = QueryIndex(true, requestedPrices.length); - requestedPrices.push(QueryPoint(identifier, time)); - emit PriceRequestAdded(msg.sender, identifier, time, requestId); - } - } - - // Pushes the verified price for a requested query. - function pushPrice(bytes32 identifier, uint256 time, int256 price) public { - bytes32 requestId = _encodePriceRequest(identifier, time); - verifiedPrices[requestId] = Price(true, price, getCurrentTime()); - - QueryIndex storage queryIndex = queryIndices[requestId]; - require(queryIndex.isValid, "Can't push prices that haven't been requested"); - // Delete from the array. Instead of shifting the queries over, replace the contents of `indexToReplace` with - // the contents of the last index (unless it is the last index). - uint256 indexToReplace = queryIndex.index; - delete queryIndices[requestId]; - uint256 lastIndex = requestedPrices.length - 1; - if (lastIndex != indexToReplace) { - QueryPoint storage queryToCopy = requestedPrices[lastIndex]; - queryIndices[_encodePriceRequest(queryToCopy.identifier, queryToCopy.time)].index = indexToReplace; - requestedPrices[indexToReplace] = queryToCopy; - } - requestedPrices.pop(); - - emit PushedPrice(msg.sender, identifier, time, price, requestId); - } - - // Wrapper function to push the verified price by request ID. - function pushPriceByRequestId(bytes32 requestId, int256 price) external { - QueryPoint memory queryPoint = getRequestParameters(requestId); - pushPrice(queryPoint.identifier, queryPoint.time, price); - } - - // Checks whether a price has been resolved. - function hasPrice(bytes32 identifier, uint256 time) public view override returns (bool) { - Price storage lookup = verifiedPrices[_encodePriceRequest(identifier, time)]; - return lookup.isAvailable; - } - - // Gets a price that has already been resolved. - function getPrice(bytes32 identifier, uint256 time) public view override returns (int256) { - Price storage lookup = verifiedPrices[_encodePriceRequest(identifier, time)]; - require(lookup.isAvailable); - return lookup.price; - } - - // Gets the queries that still need verified prices. - function getPendingQueries() external view returns (QueryPoint[] memory) { - return requestedPrices; - } - - // Gets the request parameters by request ID. - function getRequestParameters(bytes32 requestId) public view returns (QueryPoint memory) { - QueryIndex storage queryIndex = queryIndices[requestId]; - require(queryIndex.isValid, "Request ID not found"); - return requestedPrices[queryIndex.index]; - } - - function _getIdentifierWhitelist() private view returns (IdentifierWhitelistInterface supportedIdentifiers) { - return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); - } - - // Returns an encoded bytes32 representing a price request ID. Used when storing/referencing price requests. - function _encodePriceRequest(bytes32 identifier, uint256 time) internal pure returns (bytes32) { - return keccak256(abi.encode(identifier, time)); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleAncillary.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleAncillary.sol deleted file mode 100644 index 0b8ee5b3e..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleAncillary.sol +++ /dev/null @@ -1,154 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/Testable.sol"; -import "../interfaces/OracleAncillaryInterface.sol"; -import "../interfaces/IdentifierWhitelistInterface.sol"; -import "../interfaces/FinderInterface.sol"; -import "../implementation/Constants.sol"; - -// A mock oracle used for testing. -contract MockOracleAncillary is OracleAncillaryInterface, Testable { - // Represents an available price. Have to keep a separate bool to allow for price=0. - struct Price { - bool isAvailable; - int256 price; - // Time the verified price became available. - uint256 verifiedTime; - } - - // The two structs below are used in an array and mapping to keep track of prices that have been requested but are - // not yet available. - struct QueryIndex { - bool isValid; - uint256 index; - } - - // Represents a (identifier, time, ancillary data) point that has been queried. - struct QueryPoint { - bytes32 identifier; - uint256 time; - bytes ancillaryData; - } - - // Reference to the Finder. - FinderInterface private finder; - - // Maps request IDs to their resolved Price structs. - mapping(bytes32 => Price) internal verifiedPrices; - - // Maps request IDs to their pending QueryIndex structs. - mapping(bytes32 => QueryIndex) internal queryIndices; - - // Array of pending QueryPoint structs. - QueryPoint[] internal requestedPrices; - - event PriceRequestAdded( - address indexed requester, - bytes32 indexed identifier, - uint256 time, - bytes ancillaryData, - bytes32 indexed requestId - ); - event PushedPrice( - address indexed pusher, - bytes32 indexed identifier, - uint256 time, - bytes ancillaryData, - int256 price, - bytes32 indexed requestId - ); - - constructor(address _finderAddress, address _timerAddress) Testable(_timerAddress) { - finder = FinderInterface(_finderAddress); - } - - // Enqueues a request (if a request isn't already present) for the given identifier, time and ancillary data. - - function requestPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData) public override { - require(_getIdentifierWhitelist().isIdentifierSupported(identifier)); - bytes32 requestId = _encodePriceRequest(identifier, time, ancillaryData); - Price storage lookup = verifiedPrices[requestId]; - if (!lookup.isAvailable && !queryIndices[requestId].isValid) { - // New query, enqueue it for review. - queryIndices[requestId] = QueryIndex(true, requestedPrices.length); - requestedPrices.push(QueryPoint(identifier, time, ancillaryData)); - emit PriceRequestAdded(msg.sender, identifier, time, ancillaryData, requestId); - } - } - - // Pushes the verified price for a requested query. - function pushPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData, int256 price) public { - bytes32 requestId = _encodePriceRequest(identifier, time, ancillaryData); - verifiedPrices[requestId] = Price(true, price, getCurrentTime()); - - QueryIndex storage queryIndex = queryIndices[requestId]; - require(queryIndex.isValid, "Can't push prices that haven't been requested"); - // Delete from the array. Instead of shifting the queries over, replace the contents of `indexToReplace` with - // the contents of the last index (unless it is the last index). - uint256 indexToReplace = queryIndex.index; - delete queryIndices[requestId]; - uint256 lastIndex = requestedPrices.length - 1; - if (lastIndex != indexToReplace) { - QueryPoint storage queryToCopy = requestedPrices[lastIndex]; - queryIndices[_encodePriceRequest(queryToCopy.identifier, queryToCopy.time, queryToCopy.ancillaryData)] - .index = indexToReplace; - requestedPrices[indexToReplace] = queryToCopy; - } - requestedPrices.pop(); - - emit PushedPrice(msg.sender, identifier, time, ancillaryData, price, requestId); - } - - // Wrapper function to push the verified price by request ID. - function pushPriceByRequestId(bytes32 requestId, int256 price) external { - QueryPoint memory queryPoint = getRequestParameters(requestId); - pushPrice(queryPoint.identifier, queryPoint.time, queryPoint.ancillaryData, price); - } - - // Checks whether a price has been resolved. - function hasPrice( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) public view override returns (bool) { - Price storage lookup = verifiedPrices[_encodePriceRequest(identifier, time, ancillaryData)]; - return lookup.isAvailable; - } - - // Gets a price that has already been resolved. - function getPrice( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) public view override returns (int256) { - Price storage lookup = verifiedPrices[_encodePriceRequest(identifier, time, ancillaryData)]; - require(lookup.isAvailable); - return lookup.price; - } - - // Gets the queries that still need verified prices. - function getPendingQueries() external view returns (QueryPoint[] memory) { - return requestedPrices; - } - - // Gets the request parameters by request ID. - function getRequestParameters(bytes32 requestId) public view returns (QueryPoint memory) { - QueryIndex storage queryIndex = queryIndices[requestId]; - require(queryIndex.isValid, "Request ID not found"); - return requestedPrices[queryIndex.index]; - } - - function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface supportedIdentifiers) { - return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); - } - - // Returns an encoded bytes32 representing a price request ID. Used when storing/referencing price requests. - function _encodePriceRequest( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) internal pure returns (bytes32) { - return keccak256(abi.encode(identifier, time, ancillaryData)); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleCombined.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleCombined.sol deleted file mode 100644 index 0b313816a..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleCombined.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "./MockOracleAncillary.sol"; - -// A mock oracle used for testing. Allows both ancillary and non-ancillary methods to be called. -contract MockOracleCombined is MockOracleAncillary { - constructor(address _finderAddress, address _timerAddress) MockOracleAncillary(_finderAddress, _timerAddress) {} - - // Enqueues a request (if a request isn't already present) for the given (identifier, time) pair. - function requestPrice(bytes32 identifier, uint256 time) public { - requestPrice(identifier, time, ""); - } - - // Pushes the verified price for a requested query. - function pushPrice(bytes32 identifier, uint256 time, int256 price) external { - pushPrice(identifier, time, "", price); - } - - // Checks whether a price has been resolved. - function hasPrice(bytes32 identifier, uint256 time) public view returns (bool) { - return hasPrice(identifier, time, ""); - } - - // Gets a price that has already been resolved. - function getPrice(bytes32 identifier, uint256 time) public view returns (int256) { - return getPrice(identifier, time, ""); - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleGovernance.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleGovernance.sol deleted file mode 100644 index 8d49a1744..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/test/MockOracleGovernance.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../interfaces/OracleGovernanceInterface.sol"; -import "./MockOracleAncillary.sol"; - -// A mock oracle used for testing. -contract MockOracleGovernance is MockOracleAncillary { - constructor(address _finderAddress, address _timerAddress) MockOracleAncillary(_finderAddress, _timerAddress) {} - - // Enqueues a governance request (if a request isn't already present) for the given (identifier, time) pair. - function requestGovernanceAction(bytes32 identifier, uint256 time, bytes memory ancillaryData) public { - _requestPrice(identifier, time, ancillaryData, true); - } - - function _requestPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData, bool isGovernance) internal { - require(isGovernance || _getIdentifierWhitelist().isIdentifierSupported(identifier)); - bytes32 requestId = _encodePriceRequest(identifier, time, ancillaryData); - Price storage lookup = verifiedPrices[requestId]; - if (!lookup.isAvailable && !queryIndices[requestId].isValid) { - // New query, enqueue it for review. - queryIndices[requestId] = QueryIndex(true, requestedPrices.length); - QueryPoint memory queryPoint = QueryPoint(identifier, time, ancillaryData); - requestedPrices.push(queryPoint); - emit PriceRequestAdded(msg.sender, identifier, time, ancillaryData, requestId); - } - } -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/test/VotingAncillaryInterfaceTest.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/test/VotingAncillaryInterfaceTest.sol deleted file mode 100644 index b5fdbb18e..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/test/VotingAncillaryInterfaceTest.sol +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/FixedPoint.sol"; -import "../../common/implementation/Testable.sol"; -import "../interfaces/OracleAncillaryInterface.sol"; -import "../interfaces/VotingAncillaryInterface.sol"; - -// A mock oracle used for testing. Exports the voting & oracle interfaces and events that contain ancillary data. -abstract contract VotingAncillaryInterfaceTesting is OracleAncillaryInterface, VotingAncillaryInterface, Testable { - using FixedPoint for FixedPoint.Unsigned; - - // Events, data structures and functions not exported in the base interfaces, used for testing. - event VoteCommitted( - address indexed voter, - uint256 indexed roundId, - bytes32 indexed identifier, - uint256 time, - bytes ancillaryData - ); - - event EncryptedVote( - address indexed voter, - uint256 indexed roundId, - bytes32 indexed identifier, - uint256 time, - bytes ancillaryData, - bytes encryptedVote - ); - - event VoteRevealed( - address indexed voter, - uint256 indexed roundId, - bytes32 indexed identifier, - uint256 time, - int256 price, - bytes ancillaryData, - uint256 numTokens - ); - - event RewardsRetrieved( - address indexed voter, - uint256 indexed roundId, - bytes32 indexed identifier, - uint256 time, - bytes ancillaryData, - uint256 numTokens - ); - - event PriceRequestAdded(uint256 indexed roundId, bytes32 indexed identifier, uint256 time); - - event PriceResolved( - uint256 indexed roundId, - bytes32 indexed identifier, - uint256 time, - int256 price, - bytes ancillaryData - ); - - struct Round { - uint256 snapshotId; // Voting token snapshot ID for this round. 0 if no snapshot has been taken. - FixedPoint.Unsigned inflationRate; // Inflation rate set for this round. - FixedPoint.Unsigned gatPercentage; // Gat rate set for this round. - uint256 rewardsExpirationTime; // Time that rewards for this round can be claimed until. - } - - // Represents the status a price request has. - enum RequestStatus { - NotRequested, // Was never requested. - Active, // Is being voted on in the current round. - Resolved, // Was resolved in a previous round. - Future // Is scheduled to be voted on in a future round. - } - - // Only used as a return value in view methods -- never stored in the contract. - struct RequestState { - RequestStatus status; - uint256 lastVotingRound; - } - - function rounds(uint256 roundId) public view virtual returns (Round memory); - - function getPriceRequestStatuses( - VotingAncillaryInterface.PendingRequestAncillary[] memory requests - ) public view virtual returns (RequestState[] memory); - - function getPendingPriceRequestsArray() external view virtual returns (bytes32[] memory); -} diff --git a/contracts/external/uma/core/contracts/data-verification-mechanism/test/VotingInterfaceTest.sol b/contracts/external/uma/core/contracts/data-verification-mechanism/test/VotingInterfaceTest.sol deleted file mode 100644 index a9b0b92e8..000000000 --- a/contracts/external/uma/core/contracts/data-verification-mechanism/test/VotingInterfaceTest.sol +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/FixedPoint.sol"; -import "../../common/implementation/Testable.sol"; -import "../interfaces/OracleInterface.sol"; -import "../interfaces/VotingInterface.sol"; - -// A mock oracle used for testing. Exports the voting & oracle interfaces and events that contain no ancillary data. -abstract contract VotingInterfaceTesting is OracleInterface, VotingInterface, Testable { - using FixedPoint for FixedPoint.Unsigned; - - // Events, data structures and functions not exported in the base interfaces, used for testing. - event VoteCommitted( - address indexed voter, - uint256 indexed roundId, - bytes32 indexed identifier, - uint256 time, - bytes ancillaryData - ); - - event EncryptedVote( - address indexed voter, - uint256 indexed roundId, - bytes32 indexed identifier, - uint256 time, - bytes ancillaryData, - bytes encryptedVote - ); - - event VoteRevealed( - address indexed voter, - uint256 indexed roundId, - bytes32 indexed identifier, - uint256 time, - int256 price, - bytes ancillaryData, - uint256 numTokens - ); - - event RewardsRetrieved( - address indexed voter, - uint256 indexed roundId, - bytes32 indexed identifier, - uint256 time, - bytes ancillaryData, - uint256 numTokens - ); - - event PriceRequestAdded(uint256 indexed roundId, bytes32 indexed identifier, uint256 time); - - event PriceResolved( - uint256 indexed roundId, - bytes32 indexed identifier, - uint256 time, - int256 price, - bytes ancillaryData - ); - - struct Round { - uint256 snapshotId; // Voting token snapshot ID for this round. 0 if no snapshot has been taken. - FixedPoint.Unsigned inflationRate; // Inflation rate set for this round. - FixedPoint.Unsigned gatPercentage; // Gat rate set for this round. - uint256 rewardsExpirationTime; // Time that rewards for this round can be claimed until. - } - - // Represents the status a price request has. - enum RequestStatus { - NotRequested, // Was never requested. - Active, // Is being voted on in the current round. - Resolved, // Was resolved in a previous round. - Future // Is scheduled to be voted on in a future round. - } - - // Only used as a return value in view methods -- never stored in the contract. - struct RequestState { - RequestStatus status; - uint256 lastVotingRound; - } - - function rounds(uint256 roundId) public view virtual returns (Round memory); - - function getPriceRequestStatuses( - VotingInterface.PendingRequest[] memory requests - ) public view virtual returns (RequestState[] memory); - - function getPendingPriceRequestsArray() external view virtual returns (bytes32[] memory); -} diff --git a/contracts/external/uma/core/contracts/external/README.md b/contracts/external/uma/core/contracts/external/README.md deleted file mode 100644 index 8c35978d2..000000000 --- a/contracts/external/uma/core/contracts/external/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# External contracts - -All contracts in this folder were originally copied from an external source and any changes are documented in the `CHANGELOG.md` for each respective sub folder. diff --git a/contracts/external/uma/core/contracts/external/avm/AVM_CrossDomainEnabled.sol b/contracts/external/uma/core/contracts/external/avm/AVM_CrossDomainEnabled.sol deleted file mode 100644 index 9d26452d1..000000000 --- a/contracts/external/uma/core/contracts/external/avm/AVM_CrossDomainEnabled.sol +++ /dev/null @@ -1,39 +0,0 @@ -// Copied logic from https://github.com/makerdao/arbitrum-dai-bridge/blob/34acc39bc6f3a2da0a837ea3c5dbc634ec61c7de/contracts/l2/L2CrossDomainEnabled.sol -// with a change to the solidity version. - -pragma solidity ^0.8.0; - -import "./interfaces/ArbSys.sol"; - -abstract contract AVM_CrossDomainEnabled { - event SentCrossDomainMessage(address indexed from, address indexed to, uint256 indexed id, bytes data); - - modifier onlyFromCrossDomainAccount(address l1Counterpart) { - require(msg.sender == applyL1ToL2Alias(l1Counterpart), "ONLY_COUNTERPART_GATEWAY"); - _; - } - - uint160 constant offset = uint160(0x1111000000000000000000000000000000001111); - - // l1 addresses are transformed during l1->l2 calls. See https://developer.offchainlabs.com/docs/l1_l2_messages#address-aliasing for more information. - function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) { - l2Address = address(uint160(l1Address) + offset); - } - - // Sends a message to L1 via the ArbSys contract. See https://developer.offchainlabs.com/docs/arbsys. - // After the Arbitrum chain advances some set amount of time, ArbOS gathers all outgoing messages, Merklizes them, - // and publishes the root as an OutboxEntry in the chain's outbox. Note that this happens "automatically"; - // i.e., it requires no additional action from the user. After the Outbox entry is published on the L1 chain, - // the user (or anybody) can compute the Merkle proof of inclusion of their outgoing message. Anytime after the - // dispute window passes (~7 days), any user can execute the L1 message by calling Outbox.executeTransaction; - // if it reverts, it can be re-executed any number of times and with no upper time-bound. - // To read more about the L2 --> L1 lifecycle, see: https://developer.offchainlabs.com/docs/l1_l2_messages#explanation. - function sendCrossDomainMessage(address user, address to, bytes memory data) internal returns (uint256) { - // note: this method doesn't support sending ether to L1 together with a call - uint256 id = ArbSys(address(100)).sendTxToL1(to, data); - - emit SentCrossDomainMessage(user, to, id, data); - - return id; - } -} diff --git a/contracts/external/uma/core/contracts/external/avm/Arbitrum_CrossDomainEnabled.sol b/contracts/external/uma/core/contracts/external/avm/Arbitrum_CrossDomainEnabled.sol deleted file mode 100644 index fd1d7020e..000000000 --- a/contracts/external/uma/core/contracts/external/avm/Arbitrum_CrossDomainEnabled.sol +++ /dev/null @@ -1,66 +0,0 @@ -// Copied logic from https://github.com/makerdao/arbitrum-dai-bridge/blob/34acc39bc6f3a2da0a837ea3c5dbc634ec61c7de/contracts/l1/L1CrossDomainEnabled.sol -// with a change to the solidity version. -pragma solidity ^0.8.0; - -import "../../external/avm/interfaces/iArbitrum_Inbox.sol"; -import "../../external/avm/interfaces/iArbitrum_Outbox.sol"; - -abstract contract Arbitrum_CrossDomainEnabled { - iArbitrum_Inbox public immutable inbox; - - /** - * @param _inbox Contract that sends generalized messages to the Arbitrum chain. - */ - constructor(address _inbox) { - inbox = iArbitrum_Inbox(_inbox); - } - - // More details about retryable ticket parameters here: https://developer.offchainlabs.com/docs/l1_l2_messages#parameters - // This function will not apply aliassing to the `user` address on L2. - // Note: If `l1CallValue > 0`, then this contract must contain at least that much ETH to send as msg.value to the - // inbox. - function sendTxToL2NoAliassing( - address target, // Address where transaction will initiate on L2. - address user, // Address where excess gas is credited on L2. - uint256 l1CallValue, // msg.value deposited to `user` on L2. - uint256 maxSubmissionCost, // Amount of ETH allocated to pay for base submission fee. The user is charged this - // fee to cover the storage costs of keeping their retryable ticket's calldata in the retry buffer. This should - // also cover the `l2CallValue`, but we set that to 0. This amount is proportional to the size of `data`. - uint256 maxGas, // Gas limit for immediate L2 execution attempt. - uint256 gasPriceBid, // L2 gas price bid for immediate L2 execution attempt. - bytes memory data // ABI encoded data to send to target. - ) internal returns (uint256) { - // createRetryableTicket API: https://developer.offchainlabs.com/docs/sol_contract_docs/md_docs/arb-bridge-eth/bridge/inbox#createretryableticketaddress-destaddr-uint256-l2callvalue-uint256-maxsubmissioncost-address-excessfeerefundaddress-address-callvaluerefundaddress-uint256-maxgas-uint256-gaspricebid-bytes-data-%E2%86%92-uint256-external - // - address destAddr: destination L2 contract address - // - uint256 l2CallValue: call value for retryable L2 message - // - uint256 maxSubmissionCost: Max gas deducted from user's L2 balance to cover base submission fee - // - address excessFeeRefundAddress: maxgas x gasprice - execution cost gets credited here on L2 - // - address callValueRefundAddress: l2CallValue gets credited here on L2 if retryable txn times out or gets cancelled - // - uint256 maxGas: Max gas deducted from user's L2 balance to cover L2 execution - // - uint256 gasPriceBid: price bid for L2 execution - // - bytes data: ABI encoded data of L2 message - uint256 seqNum = inbox.createRetryableTicketNoRefundAliasRewrite{ value: l1CallValue }( - target, - 0, // we always assume that l2CallValue = 0 - maxSubmissionCost, - user, - user, - maxGas, - gasPriceBid, - data - ); - return seqNum; - } - - // Copied mostly from: https://github.com/makerdao/arbitrum-dai-bridge/blob/34acc39bc6f3a2da0a837ea3c5dbc634ec61c7de/contracts/l1/L1CrossDomainEnabled.sol#L31 - modifier onlyFromCrossDomainAccount(address l2Counterpart) { - // a message coming from the counterpart gateway was executed by the bridge - IBridge bridge = IBridge(inbox.bridge()); - require(msg.sender == address(bridge), "NOT_FROM_BRIDGE"); - - // and the outbox reports that the L2 address of the sender is the counterpart gateway - address l2ToL1Sender = iArbitrum_Outbox(bridge.activeOutbox()).l2ToL1Sender(); - require(l2ToL1Sender == l2Counterpart, "ONLY_COUNTERPART_GATEWAY"); - _; - } -} diff --git a/contracts/external/uma/core/contracts/external/avm/Arbitrum_Messenger.sol b/contracts/external/uma/core/contracts/external/avm/Arbitrum_Messenger.sol deleted file mode 100644 index 5fe2d350a..000000000 --- a/contracts/external/uma/core/contracts/external/avm/Arbitrum_Messenger.sol +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "./Arbitrum_CrossDomainEnabled.sol"; -import "@openzeppelin/contracts-v4/access/Ownable.sol"; - -/** - * @notice Sends cross chain messages Arbitrum L2 network. - * @dev This contract's owner should be set to the BridgeAdmin deployed on the same L1 network so that only the - * BridgeAdmin can call cross-chain administrative functions on the L2 DepositBox via this messenger. - * @dev This address will be the sender of any L1 --> L2 retryable tickets, so it should be set as the cross domain - * owner for L2 contracts that expect to receive cross domain messages. - */ -contract Arbitrum_Messenger is Ownable, Arbitrum_CrossDomainEnabled { - event RelayedMessage( - address indexed from, - address indexed to, - uint256 indexed seqNum, - address userToRefund, - uint256 l1CallValue, - uint256 gasLimit, - uint256 gasPrice, - uint256 maxSubmissionCost, - bytes data - ); - - /** - * @param _inbox Contract that sends generalized messages to the Arbitrum chain. - */ - constructor(address _inbox) Arbitrum_CrossDomainEnabled(_inbox) {} - - /** - * @notice Sends a message to an account on L2. If this message reverts on l2 for any reason it can either be - * resent on L1, or redeemed on L2 manually. To learn more see how "retryable tickets" work on Arbitrum - * https://developer.offchainlabs.com/docs/l1_l2_messages#parameters - * @param target The intended recipient on L2. - * @param userToRefund User on L2 to refund extra fees to. - * @param l1CallValue Amount of ETH deposited to `target` contract on L2. Used to pay for L2 submission fee and - * l2CallValue. This will usually be > 0. - * @param gasLimit The gasLimit for the receipt of the message on L2. - * @param gasPrice Gas price bid for L2 execution. - * @param maxSubmissionCost: Max gas deducted from user's L2 balance to cover base submission fee. - * This amount is proportional to the size of `data`. - * @param message The data to send to the target (usually calldata to a function with - * `onlyFromCrossDomainAccount()`) - */ - function relayMessage( - address target, - address userToRefund, - uint256 l1CallValue, - uint256 gasLimit, - uint256 gasPrice, - uint256 maxSubmissionCost, - bytes memory message - ) external payable onlyOwner { - // Since we know the L2 target's address in advance, we don't need to alias an L1 address. - uint256 seqNumber = sendTxToL2NoAliassing( - target, - userToRefund, - l1CallValue, - maxSubmissionCost, - gasLimit, - gasPrice, - message - ); - emit RelayedMessage( - msg.sender, - target, - seqNumber, - userToRefund, - l1CallValue, - gasLimit, - gasPrice, - maxSubmissionCost, - message - ); - } -} diff --git a/contracts/external/uma/core/contracts/external/avm/CHANGELOG.md b/contracts/external/uma/core/contracts/external/avm/CHANGELOG.md deleted file mode 100644 index 41a7b5f74..000000000 --- a/contracts/external/uma/core/contracts/external/avm/CHANGELOG.md +++ /dev/null @@ -1,25 +0,0 @@ -# Change Log - -Any modifications to original source code can be found in this document. Original sources are also listed here. - -## interfaces - -- **[iArbitrum_Inbox.sol](https://github.com/makerdao/arbitrum-dai-bridge/blob/7f1b47ef65a43f1696c5f1681109daac127d9c95/contracts/arbitrum/IInbox.sol):** - - - Bumped solidity version to >= 0.8.x - - Removed functions from interface that are not used by `ArbitrumCrossDomainEnabled.sol` or `AVM_CrossDomainEnabled.sol` - - Removed IMessageProvider inheritance. - -- **[iArbitrum_Outbox.sol](https://github.com/OffchainLabs/arbitrum-tutorials/blob/4761fa1ba1f1eca95e8c03f24f1442ed5aecd8bd/packages/arb-shared-dependencies/contracts/Outbox.sol):** - - - Bumped solidity version to >= 0.8.x - - Removed functions from interface that are not used by `ArbitrumCrossDomainEnabled.sol` or `AVM_CrossDomainEnabled.sol` - -- **[ArbSys.sol](https://github.com/makerdao/arbitrum-dai-bridge/blob/54a2109a97c5b1504824c6317d358e2d2733b5a3/contracts/arbitrum/ArbSys.sol):** - - - Bumped solidity version to >= 0.8.x - -## abstract contracts - -- **[AVM_CrossDomainEnabled.sol](https://github.com/makerdao/arbitrum-dai-bridge/blob/34acc39bc6f3a2da0a837ea3c5dbc634ec61c7de/contracts/l2/L2CrossDomainEnabled.sol):** - - Bumped solidity version to >= 0.8.x diff --git a/contracts/external/uma/core/contracts/external/avm/interfaces/ArbSys.sol b/contracts/external/uma/core/contracts/external/avm/interfaces/ArbSys.sol deleted file mode 100644 index 3c0a67a6e..000000000 --- a/contracts/external/uma/core/contracts/external/avm/interfaces/ArbSys.sol +++ /dev/null @@ -1,79 +0,0 @@ -// Copied logic from https://github.com/makerdao/arbitrum-dai-bridge/blob/54a2109a97c5b1504824c6317d358e2d2733b5a3/contracts/arbitrum/ArbSys.sol -// with changes only to the solidity version and comments. - -pragma solidity ^0.8.0; - -/** - * @notice Precompiled contract that exists in every Arbitrum chain at address(100), - * 0x0000000000000000000000000000000000000064. Exposes a variety of system-level functionality. ArbSys provides - * systems functionality useful to some Arbitrum contracts. Any contract running on an Arbitrum Chain can call the - * chain's ArbSys. - */ -interface ArbSys { - /** - * @notice Get internal version number identifying an ArbOS build - * @return version number as int - */ - function arbOSVersion() external pure returns (uint256); - - function arbChainID() external view returns (uint256); - - /** - * @notice Get Arbitrum block number (distinct from L1 block number; Arbitrum genesis block has block number 0) - * @return block number as int - */ - function arbBlockNumber() external view returns (uint256); - - /** - * @notice Send given amount of Eth to dest from sender. - * This is a convenience function, which is equivalent to calling sendTxToL1 with empty calldataForL1. - * @param destination recipient address on L1 - * @return unique identifier for this L2-to-L1 transaction. - */ - function withdrawEth(address destination) external payable returns (uint256); - - /** - * @notice Send a transaction to L1 - * @param destination recipient address on L1 - * @param calldataForL1 (optional) calldata for L1 contract call - * @return a unique identifier for this L2-to-L1 transaction. - */ - function sendTxToL1(address destination, bytes calldata calldataForL1) external payable returns (uint256); - - /** - * @notice get the number of transactions issued by the given external account or the account sequence number of the given contract - * @param account target account - * @return the number of transactions issued by the given external account or the account sequence number of the given contract - */ - function getTransactionCount(address account) external view returns (uint256); - - /** - * @notice get the value of target L2 storage slot - * This function is only callable from address 0 to prevent contracts from being able to call it - * @param account target account - * @param index target index of storage slot - * @return stotage value for the given account at the given index - */ - function getStorageAt(address account, uint256 index) external view returns (uint256); - - /** - * @notice check if current call is coming from l1 - * @return true if the caller of this was called directly from L1 - */ - function isTopLevelCall() external view returns (bool); - - event EthWithdrawal(address indexed destAddr, uint256 amount); - - event L2ToL1Transaction( - address caller, - address indexed destination, - uint256 indexed uniqueId, - uint256 indexed batchNumber, - uint256 indexInBatch, - uint256 arbBlockNum, - uint256 ethBlockNum, - uint256 timestamp, - uint256 callvalue, - bytes data - ); -} diff --git a/contracts/external/uma/core/contracts/external/avm/interfaces/iArbitrum_Inbox.sol b/contracts/external/uma/core/contracts/external/avm/interfaces/iArbitrum_Inbox.sol deleted file mode 100644 index ad4d4b505..000000000 --- a/contracts/external/uma/core/contracts/external/avm/interfaces/iArbitrum_Inbox.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -pragma solidity ^0.8.0; - -interface IBridge { - function activeOutbox() external view returns (address); -} - -interface iArbitrum_Inbox { - // Retryable tickets are the Arbitrum protocol’s canonical method for passing generalized messages from Ethereum to - // Arbitrum. A retryable ticket is an L2 message encoded and delivered by L1; if gas is provided, it will be executed - // immediately. If no gas is provided or the execution reverts, it will be placed in the L2 retry buffer, - // where any user can re-execute for some fixed period (roughly one week). - // Retryable tickets are created by calling Inbox.createRetryableTicket. - // More details here: https://developer.offchainlabs.com/docs/l1_l2_messages#ethereum-to-arbitrum-retryable-tickets - function createRetryableTicketNoRefundAliasRewrite( - address destAddr, - uint256 l2CallValue, - uint256 maxSubmissionCost, - address excessFeeRefundAddress, - address callValueRefundAddress, - uint256 maxGas, - uint256 gasPriceBid, - bytes calldata data - ) external payable returns (uint256); - - function bridge() external view returns (address); -} diff --git a/contracts/external/uma/core/contracts/external/avm/interfaces/iArbitrum_Outbox.sol b/contracts/external/uma/core/contracts/external/avm/interfaces/iArbitrum_Outbox.sol deleted file mode 100644 index 1e0eb4cdb..000000000 --- a/contracts/external/uma/core/contracts/external/avm/interfaces/iArbitrum_Outbox.sol +++ /dev/null @@ -1,26 +0,0 @@ -// Copied logic from https://github.com/OffchainLabs/arbitrum-tutorials/blob/4761fa1ba1f1eca95e8c03f24f1442ed5aecd8bd/packages/arb-shared-dependencies/contracts/Outbox.sol -// with changes only to the solidity version. - -// SPDX-License-Identifier: Apache-2.0 - -/* - * Copyright 2021, Offchain Labs, Inc. - * - * 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 - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * 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. - */ - -pragma solidity ^0.8.0; - -interface iArbitrum_Outbox { - function l2ToL1Sender() external view returns (address); -} diff --git a/contracts/external/uma/core/contracts/external/boba/BobaAddressManager.sol b/contracts/external/uma/core/contracts/external/boba/BobaAddressManager.sol deleted file mode 100644 index e754081a5..000000000 --- a/contracts/external/uma/core/contracts/external/boba/BobaAddressManager.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.9; - -abstract contract BobaAddressManager { - /** - * Retrieves the address associated with a given name. - * @param _name Name to retrieve an address for. - * @return Address associated with the given name. - */ - function getAddress(string memory _name) external view virtual returns (address); -} diff --git a/contracts/external/uma/core/contracts/external/chainbridge/Bridge.sol b/contracts/external/uma/core/contracts/external/chainbridge/Bridge.sol deleted file mode 100644 index 115ffed9a..000000000 --- a/contracts/external/uma/core/contracts/external/chainbridge/Bridge.sol +++ /dev/null @@ -1,451 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/access/AccessControl.sol"; -import "@openzeppelin/contracts-v4/security/Pausable.sol"; -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; -import "./interfaces/IDepositExecute.sol"; -import "./interfaces/IBridge.sol"; -import "./interfaces/IERCHandler.sol"; -import "./interfaces/IGenericHandler.sol"; - -/** - @title Facilitates deposits, creation and votiing of deposit proposals, and deposit executions. - @author ChainSafe Systems. - */ -contract Bridge is Pausable, AccessControl { - using SafeMath for uint256; - - uint8 public _chainID; - uint256 public _relayerThreshold; - uint256 public _totalRelayers; - uint256 public _totalProposals; - uint256 public _fee; - uint256 public _expiry; - - enum Vote { - No, - Yes - } - - enum ProposalStatus { - Inactive, - Active, - Passed, - Executed, - Cancelled - } - - struct Proposal { - bytes32 _resourceID; - bytes32 _dataHash; - address[] _yesVotes; - address[] _noVotes; - ProposalStatus _status; - uint256 _proposedBlock; - } - - // destinationChainID => number of deposits - mapping(uint8 => uint64) public _depositCounts; - // resourceID => handler address - mapping(bytes32 => address) public _resourceIDToHandlerAddress; - // depositNonce => destinationChainID => bytes - mapping(uint64 => mapping(uint8 => bytes)) public _depositRecords; - // destinationChainID + depositNonce => dataHash => Proposal - mapping(uint72 => mapping(bytes32 => Proposal)) public _proposals; - // destinationChainID + depositNonce => dataHash => relayerAddress => bool - mapping(uint72 => mapping(bytes32 => mapping(address => bool))) public _hasVotedOnProposal; - - event RelayerThresholdChanged(uint256 indexed newThreshold); - event RelayerAdded(address indexed relayer); - event RelayerRemoved(address indexed relayer); - event Deposit(uint8 indexed destinationChainID, bytes32 indexed resourceID, uint64 indexed depositNonce); - event ProposalEvent( - uint8 indexed originChainID, - uint64 indexed depositNonce, - ProposalStatus indexed status, - bytes32 resourceID, - bytes32 dataHash - ); - - event ProposalVote( - uint8 indexed originChainID, - uint64 indexed depositNonce, - ProposalStatus indexed status, - bytes32 resourceID - ); - - bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE"); - - modifier onlyAdmin() { - _onlyAdmin(); - _; - } - - modifier onlyAdminOrRelayer() { - _onlyAdminOrRelayer(); - _; - } - - modifier onlyRelayers() { - _onlyRelayers(); - _; - } - - function _onlyAdminOrRelayer() private view { - require( - hasRole(DEFAULT_ADMIN_ROLE, msg.sender) || hasRole(RELAYER_ROLE, msg.sender), - "sender is not relayer or admin" - ); - } - - function _onlyAdmin() private view { - require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "sender doesn't have admin role"); - } - - function _onlyRelayers() private view { - require(hasRole(RELAYER_ROLE, msg.sender), "sender doesn't have relayer role"); - } - - /** - @notice Initializes Bridge, creates and grants {msg.sender} the admin role, - creates and grants {initialRelayers} the relayer role. - @param chainID ID of chain the Bridge contract exists on. - @param initialRelayers Addresses that should be initially granted the relayer role. - @param initialRelayerThreshold Number of votes needed for a deposit proposal to be considered passed. - */ - constructor( - uint8 chainID, - address[] memory initialRelayers, - uint256 initialRelayerThreshold, - uint256 fee, - uint256 expiry - ) { - _chainID = chainID; - _relayerThreshold = initialRelayerThreshold; - _fee = fee; - _expiry = expiry; - - _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); - _setRoleAdmin(RELAYER_ROLE, DEFAULT_ADMIN_ROLE); - - for (uint256 i; i < initialRelayers.length; i++) { - grantRole(RELAYER_ROLE, initialRelayers[i]); - _totalRelayers++; - } - } - - /** - @notice Returns true if {relayer} has the relayer role. - @param relayer Address to check. - */ - function isRelayer(address relayer) external view returns (bool) { - return hasRole(RELAYER_ROLE, relayer); - } - - /** - @notice Removes admin role from {msg.sender} and grants it to {newAdmin}. - @notice Only callable by an address that currently has the admin role. - @param newAdmin Address that admin role will be granted to. - */ - function renounceAdmin(address newAdmin) external onlyAdmin { - grantRole(DEFAULT_ADMIN_ROLE, newAdmin); - renounceRole(DEFAULT_ADMIN_ROLE, msg.sender); - } - - /** - @notice Pauses deposits, proposal creation and voting, and deposit executions. - @notice Only callable by an address that currently has the admin role. - */ - function adminPauseTransfers() external onlyAdmin { - _pause(); - } - - /** - @notice Unpauses deposits, proposal creation and voting, and deposit executions. - @notice Only callable by an address that currently has the admin role. - */ - function adminUnpauseTransfers() external onlyAdmin { - _unpause(); - } - - /** - @notice Modifies the number of votes required for a proposal to be considered passed. - @notice Only callable by an address that currently has the admin role. - @param newThreshold Value {_relayerThreshold} will be changed to. - @notice Emits {RelayerThresholdChanged} event. - */ - function adminChangeRelayerThreshold(uint256 newThreshold) external onlyAdmin { - _relayerThreshold = newThreshold; - emit RelayerThresholdChanged(newThreshold); - } - - /** - @notice Grants {relayerAddress} the relayer role and increases {_totalRelayer} count. - @notice Only callable by an address that currently has the admin role. - @param relayerAddress Address of relayer to be added. - @notice Emits {RelayerAdded} event. - */ - function adminAddRelayer(address relayerAddress) external onlyAdmin { - require(!hasRole(RELAYER_ROLE, relayerAddress), "addr already has relayer role!"); - grantRole(RELAYER_ROLE, relayerAddress); - emit RelayerAdded(relayerAddress); - _totalRelayers++; - } - - /** - @notice Removes relayer role for {relayerAddress} and decreases {_totalRelayer} count. - @notice Only callable by an address that currently has the admin role. - @param relayerAddress Address of relayer to be removed. - @notice Emits {RelayerRemoved} event. - */ - function adminRemoveRelayer(address relayerAddress) external onlyAdmin { - require(hasRole(RELAYER_ROLE, relayerAddress), "addr doesn't have relayer role!"); - revokeRole(RELAYER_ROLE, relayerAddress); - emit RelayerRemoved(relayerAddress); - _totalRelayers--; - } - - /** - @notice Sets a new resource for handler contracts that use the IERCHandler interface, - and maps the {handlerAddress} to {resourceID} in {_resourceIDToHandlerAddress}. - @notice Only callable by an address that currently has the admin role. - @param handlerAddress Address of handler resource will be set for. - @param resourceID ResourceID to be used when making deposits. - @param tokenAddress Address of contract to be called when a deposit is made and a deposited is executed. - */ - function adminSetResource(address handlerAddress, bytes32 resourceID, address tokenAddress) external onlyAdmin { - _resourceIDToHandlerAddress[resourceID] = handlerAddress; - IERCHandler handler = IERCHandler(handlerAddress); - handler.setResource(resourceID, tokenAddress); - } - - /** - @notice Sets a new resource for handler contracts that use the IGenericHandler interface, - and maps the {handlerAddress} to {resourceID} in {_resourceIDToHandlerAddress}. - @notice Only callable by an address that currently has the admin role. - @param handlerAddress Address of handler resource will be set for. - @param resourceID ResourceID to be used when making deposits. - @param contractAddress Address of contract to be called when a deposit is made and a deposited is executed. - */ - function adminSetGenericResource( - address handlerAddress, - bytes32 resourceID, - address contractAddress, - bytes4 depositFunctionSig, - bytes4 executeFunctionSig - ) external onlyAdmin { - _resourceIDToHandlerAddress[resourceID] = handlerAddress; - IGenericHandler handler = IGenericHandler(handlerAddress); - handler.setResource(resourceID, contractAddress, depositFunctionSig, executeFunctionSig); - } - - /** - @notice Sets a resource as burnable for handler contracts that use the IERCHandler interface. - @notice Only callable by an address that currently has the admin role. - @param handlerAddress Address of handler resource will be set for. - @param tokenAddress Address of contract to be called when a deposit is made and a deposited is executed. - */ - function adminSetBurnable(address handlerAddress, address tokenAddress) external onlyAdmin { - IERCHandler handler = IERCHandler(handlerAddress); - handler.setBurnable(tokenAddress); - } - - /** - @notice Returns a proposal. - @param originChainID Chain ID deposit originated from. - @param depositNonce ID of proposal generated by proposal's origin Bridge contract. - @param dataHash Hash of data to be provided when deposit proposal is executed. - @return Proposal which consists of: - - _dataHash Hash of data to be provided when deposit proposal is executed. - - _yesVotes Number of votes in favor of proposal. - - _noVotes Number of votes against proposal. - - _status Current status of proposal. - */ - function getProposal( - uint8 originChainID, - uint64 depositNonce, - bytes32 dataHash - ) external view returns (Proposal memory) { - uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(originChainID); - return _proposals[nonceAndID][dataHash]; - } - - /** - @notice Changes deposit fee. - @notice Only callable by admin. - @param newFee Value {_fee} will be updated to. - */ - function adminChangeFee(uint256 newFee) external onlyAdmin { - require(_fee != newFee, "Current fee is equal to new fee"); - _fee = newFee; - } - - /** - @notice Used to manually withdraw funds from ERC safes. - @param handlerAddress Address of handler to withdraw from. - @param tokenAddress Address of token to withdraw. - @param recipient Address to withdraw tokens to. - @param amountOrTokenID Either the amount of ERC20 tokens or the ERC721 token ID to withdraw. - */ - function adminWithdraw( - address handlerAddress, - address tokenAddress, - address recipient, - uint256 amountOrTokenID - ) external onlyAdmin { - IERCHandler handler = IERCHandler(handlerAddress); - handler.withdraw(tokenAddress, recipient, amountOrTokenID); - } - - /** - @notice Initiates a transfer using a specified handler contract. - @notice Only callable when Bridge is not paused. - @param destinationChainID ID of chain deposit will be bridged to. - @param resourceID ResourceID used to find address of handler to be used for deposit. - @param data Additional data to be passed to specified handler. - @notice Emits {Deposit} event. - */ - function deposit(uint8 destinationChainID, bytes32 resourceID, bytes calldata data) external payable whenNotPaused { - require(msg.value == _fee, "Incorrect fee supplied"); - - address handler = _resourceIDToHandlerAddress[resourceID]; - require(handler != address(0), "resourceID not mapped to handler"); - - uint64 depositNonce = ++_depositCounts[destinationChainID]; - _depositRecords[depositNonce][destinationChainID] = data; - - IDepositExecute depositHandler = IDepositExecute(handler); - depositHandler.deposit(resourceID, destinationChainID, depositNonce, msg.sender, data); - - emit Deposit(destinationChainID, resourceID, depositNonce); - } - - /** - @notice When called, {msg.sender} will be marked as voting in favor of proposal. - @notice Only callable by relayers when Bridge is not paused. - @param chainID ID of chain deposit originated from. - @param depositNonce ID of deposited generated by origin Bridge contract. - @param dataHash Hash of data provided when deposit was made. - @notice Proposal must not have already been passed or executed. - @notice {msg.sender} must not have already voted on proposal. - @notice Emits {ProposalEvent} event with status indicating the proposal status. - @notice Emits {ProposalVote} event. - */ - function voteProposal( - uint8 chainID, - uint64 depositNonce, - bytes32 resourceID, - bytes32 dataHash - ) external onlyRelayers whenNotPaused { - uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(chainID); - Proposal storage proposal = _proposals[nonceAndID][dataHash]; - - require(_resourceIDToHandlerAddress[resourceID] != address(0), "no handler for resourceID"); - require(uint256(proposal._status) <= 1, "proposal already passed/executed/cancelled"); - require(!_hasVotedOnProposal[nonceAndID][dataHash][msg.sender], "relayer already voted"); - - if (uint256(proposal._status) == 0) { - ++_totalProposals; - _proposals[nonceAndID][dataHash] = Proposal({ - _resourceID: resourceID, - _dataHash: dataHash, - _yesVotes: new address[](1), - _noVotes: new address[](0), - _status: ProposalStatus.Active, - _proposedBlock: block.number - }); - - proposal._yesVotes[0] = msg.sender; - emit ProposalEvent(chainID, depositNonce, ProposalStatus.Active, resourceID, dataHash); - } else { - if (block.number.sub(proposal._proposedBlock) > _expiry) { - // if the number of blocks that has passed since this proposal was - // submitted exceeds the expiry threshold set, cancel the proposal - proposal._status = ProposalStatus.Cancelled; - emit ProposalEvent(chainID, depositNonce, ProposalStatus.Cancelled, resourceID, dataHash); - } else { - require(dataHash == proposal._dataHash, "datahash mismatch"); - proposal._yesVotes.push(msg.sender); - } - } - if (proposal._status != ProposalStatus.Cancelled) { - _hasVotedOnProposal[nonceAndID][dataHash][msg.sender] = true; - emit ProposalVote(chainID, depositNonce, proposal._status, resourceID); - - // If _depositThreshold is set to 1, then auto finalize - // or if _relayerThreshold has been exceeded - if (_relayerThreshold <= 1 || proposal._yesVotes.length >= _relayerThreshold) { - proposal._status = ProposalStatus.Passed; - - emit ProposalEvent(chainID, depositNonce, ProposalStatus.Passed, resourceID, dataHash); - } - } - } - - /** - @notice Executes a deposit proposal that is considered passed using a specified handler contract. - @notice Only callable by relayers when Bridge is not paused. - @param chainID ID of chain deposit originated from. - @param depositNonce ID of deposited generated by origin Bridge contract. - @param dataHash Hash of data originally provided when deposit was made. - @notice Proposal must be past expiry threshold. - @notice Emits {ProposalEvent} event with status {Cancelled}. - */ - function cancelProposal(uint8 chainID, uint64 depositNonce, bytes32 dataHash) public onlyAdminOrRelayer { - uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(chainID); - Proposal storage proposal = _proposals[nonceAndID][dataHash]; - - require(proposal._status != ProposalStatus.Cancelled, "Proposal already cancelled"); - require(block.number.sub(proposal._proposedBlock) > _expiry, "Proposal not at expiry threshold"); - - proposal._status = ProposalStatus.Cancelled; - emit ProposalEvent(chainID, depositNonce, ProposalStatus.Cancelled, proposal._resourceID, proposal._dataHash); - } - - /** - @notice Executes a deposit proposal that is considered passed using a specified handler contract. - @notice Only callable by relayers when Bridge is not paused. - @param chainID ID of chain deposit originated from. - @param resourceID ResourceID to be used when making deposits. - @param depositNonce ID of deposited generated by origin Bridge contract. - @param data Data originally provided when deposit was made. - @notice Proposal must have Passed status. - @notice Hash of {data} must equal proposal's {dataHash}. - @notice Emits {ProposalEvent} event with status {Executed}. - */ - function executeProposal( - uint8 chainID, - uint64 depositNonce, - bytes calldata data, - bytes32 resourceID - ) external onlyRelayers whenNotPaused { - address handler = _resourceIDToHandlerAddress[resourceID]; - uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(chainID); - bytes32 dataHash = keccak256(abi.encodePacked(handler, data)); - Proposal storage proposal = _proposals[nonceAndID][dataHash]; - - require(proposal._status != ProposalStatus.Inactive, "proposal is not active"); - require(proposal._status == ProposalStatus.Passed, "proposal already transferred"); - require(dataHash == proposal._dataHash, "data doesn't match datahash"); - - proposal._status = ProposalStatus.Executed; - - IDepositExecute depositHandler = IDepositExecute(_resourceIDToHandlerAddress[proposal._resourceID]); - depositHandler.executeProposal(proposal._resourceID, data); - - emit ProposalEvent(chainID, depositNonce, proposal._status, proposal._resourceID, proposal._dataHash); - } - - /** - @notice Transfers eth in the contract to the specified addresses. The parameters addrs and amounts are mapped 1-1. - This means that the address at index 0 for addrs will receive the amount (in WEI) from amounts at index 0. - @param addrs Array of addresses to transfer {amounts} to. - @param amounts Array of amonuts to transfer to {addrs}. - */ - function transferFunds(address payable[] calldata addrs, uint256[] calldata amounts) external onlyAdmin { - for (uint256 i = 0; i < addrs.length; i++) { - addrs[i].transfer(amounts[i]); - } - } -} diff --git a/contracts/external/uma/core/contracts/external/chainbridge/CHANGELOG.md b/contracts/external/uma/core/contracts/external/chainbridge/CHANGELOG.md deleted file mode 100644 index edfdcb588..000000000 --- a/contracts/external/uma/core/contracts/external/chainbridge/CHANGELOG.md +++ /dev/null @@ -1,26 +0,0 @@ -# Change Log - -Any modifications to original source code can be found in this document. Original sources are also listed here. - -## chainbridge - -- **[Bridge.sol](https://github.com/ChainSafe/chainbridge-solidity/blob/849db5657b8ce7c340a8847078de87d3a9e421f1/contracts/Bridge.sol):** - - Changed version from from `0.6.4` --> `0.8` - - Changed imported `Pausable` and `SafeMath` contracts to `@openzeppelin/contracts` implementations. - -## handlers - -- **[GenericHandler.sol](https://github.com/ChainSafe/chainbridge-solidity/blob/849db5657b8ce7c340a8847078de87d3a9e421f1/contracts/handlers/GenericHandler.sol):** - - Changed version from from `0.6.4` --> `0.8` - -## interfaces - -- **[IBridge.sol](https://github.com/ChainSafe/chainbridge-solidity/blob/849db5657b8ce7c340a8847078de87d3a9e421f1/contracts/interfaces/IBridge.sol):** - - Changed version from from `0.6.4` --> `0.8` - - Added `deposit(uint8 destinationChainID, bytes32 resourceID, bytes data)` to interface so that `SinkOracle` and `SourceOracle` contracts can access it. -- **[IDepositExecute.sol](https://github.com/ChainSafe/chainbridge-solidity/blob/849db5657b8ce7c340a8847078de87d3a9e421f1/contracts/interfaces/IDepositExecute.sol):** - - Changed version from from `0.6.4` --> `0.8` -- **[IERCHandler.sol](https://github.com/ChainSafe/chainbridge-solidity/blob/849db5657b8ce7c340a8847078de87d3a9e421f1/contracts/interfaces/IERCHandler.sol):** - - Changed version from from `0.6.4` --> `0.8` -- **[IGenericHandler.sol](https://github.com/ChainSafe/chainbridge-solidity/blob/849db5657b8ce7c340a8847078de87d3a9e421f1/contracts/interfaces/IGenericHandler.sol):** - - Changed version from from `0.6.4` --> `0.8` diff --git a/contracts/external/uma/core/contracts/external/chainbridge/handlers/GenericHandler.sol b/contracts/external/uma/core/contracts/external/chainbridge/handlers/GenericHandler.sol deleted file mode 100644 index f15c820a3..000000000 --- a/contracts/external/uma/core/contracts/external/chainbridge/handlers/GenericHandler.sol +++ /dev/null @@ -1,237 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../interfaces/IGenericHandler.sol"; - -/** - @title Handles generic deposits and deposit executions. - @author ChainSafe Systems. - @notice This contract is intended to be used with the Bridge contract. - */ -contract GenericHandler is IGenericHandler { - address public _bridgeAddress; - - struct DepositRecord { - uint8 _destinationChainID; - address _depositer; - bytes32 _resourceID; - bytes _metaData; - } - - // depositNonce => Deposit Record - mapping(uint8 => mapping(uint64 => DepositRecord)) public _depositRecords; - - // resourceID => contract address - mapping(bytes32 => address) public _resourceIDToContractAddress; - - // contract address => resourceID - mapping(address => bytes32) public _contractAddressToResourceID; - - // contract address => deposit function signature - mapping(address => bytes4) public _contractAddressToDepositFunctionSignature; - - // contract address => execute proposal function signature - mapping(address => bytes4) public _contractAddressToExecuteFunctionSignature; - - // token contract address => is whitelisted - mapping(address => bool) public _contractWhitelist; - - modifier onlyBridge() { - _onlyBridge(); - _; - } - - function _onlyBridge() private view { - require(msg.sender == _bridgeAddress, "sender must be bridge contract"); - } - - /** - @param bridgeAddress Contract address of previously deployed Bridge. - @param initialResourceIDs Resource IDs used to identify a specific contract address. - These are the Resource IDs this contract will initially support. - @param initialContractAddresses These are the addresses the {initialResourceIDs} will point to, and are the contracts that will be - called to perform deposit and execution calls. - @param initialDepositFunctionSignatures These are the function signatures {initialContractAddresses} will point to, - and are the function that will be called when executing {deposit} - @param initialExecuteFunctionSignatures These are the function signatures {initialContractAddresses} will point to, - and are the function that will be called when executing {executeProposal} - - @dev {initialResourceIDs}, {initialContractAddresses}, {initialDepositFunctionSignatures}, - and {initialExecuteFunctionSignatures} must all have the same length. Also, - values must be ordered in the way that that index x of any mentioned array - must be intended for value x of any other array, e.g. {initialContractAddresses}[0] - is the intended address for {initialDepositFunctionSignatures}[0]. - */ - constructor( - address bridgeAddress, - bytes32[] memory initialResourceIDs, - address[] memory initialContractAddresses, - bytes4[] memory initialDepositFunctionSignatures, - bytes4[] memory initialExecuteFunctionSignatures - ) { - require( - initialResourceIDs.length == initialContractAddresses.length, - "initialResourceIDs and initialContractAddresses len mismatch" - ); - - require( - initialContractAddresses.length == initialDepositFunctionSignatures.length, - "provided contract addresses and function signatures len mismatch" - ); - - require( - initialDepositFunctionSignatures.length == initialExecuteFunctionSignatures.length, - "provided deposit and execute function signatures len mismatch" - ); - - _bridgeAddress = bridgeAddress; - - for (uint256 i = 0; i < initialResourceIDs.length; i++) { - _setResource( - initialResourceIDs[i], - initialContractAddresses[i], - initialDepositFunctionSignatures[i], - initialExecuteFunctionSignatures[i] - ); - } - } - - /** - @param depositNonce This ID will have been generated by the Bridge contract. - @param destId ID of chain deposit will be bridged to. - @return DepositRecord which consists of: - - _destinationChainID ChainID deposited tokens are intended to end up on. - - _resourceID ResourceID used when {deposit} was executed. - - _depositer Address that initially called {deposit} in the Bridge contract. - - _metaData Data to be passed to method executed in corresponding {resourceID} contract. - */ - function getDepositRecord(uint64 depositNonce, uint8 destId) external view returns (DepositRecord memory) { - return _depositRecords[destId][depositNonce]; - } - - /** - @notice First verifies {_resourceIDToContractAddress}[{resourceID}] and - {_contractAddressToResourceID}[{contractAddress}] are not already set, - then sets {_resourceIDToContractAddress} with {contractAddress}, - {_contractAddressToResourceID} with {resourceID}, - {_contractAddressToDepositFunctionSignature} with {depositFunctionSig}, - {_contractAddressToExecuteFunctionSignature} with {executeFunctionSig}, - and {_contractWhitelist} to true for {contractAddress}. - @param resourceID ResourceID to be used when making deposits. - @param contractAddress Address of contract to be called when a deposit is made and a deposited is executed. - @param depositFunctionSig Function signature of method to be called in {contractAddress} when a deposit is made. - @param executeFunctionSig Function signature of method to be called in {contractAddress} when a deposit is executed. - */ - function setResource( - bytes32 resourceID, - address contractAddress, - bytes4 depositFunctionSig, - bytes4 executeFunctionSig - ) external override onlyBridge { - _setResource(resourceID, contractAddress, depositFunctionSig, executeFunctionSig); - } - - /** - @notice A deposit is initiatied by making a deposit in the Bridge contract. - @param destinationChainID Chain ID deposit is expected to be bridged to. - @param depositNonce This value is generated as an ID by the Bridge contract. - @param depositer Address of account making the deposit in the Bridge contract. - @notice {contractAddress} is required to be whitelisted - @notice If {_contractAddressToDepositFunctionSignature}[{contractAddress}] is set, - {metaData} is expected to consist of needed function arguments. - */ - function deposit( - bytes32 resourceID, - uint8 destinationChainID, - uint64 depositNonce, - address depositer, - bytes calldata - ) external onlyBridge { - bytes32 lenMetadata; - bytes memory metadata; - - assembly { - // Load length of metadata from data + 64 - lenMetadata := calldataload(0xC4) - // Load free memory pointer - metadata := mload(0x40) - - mstore(0x40, add(0x20, add(metadata, lenMetadata))) - - // func sig (4) + destinationChainId (padded to 32) + depositNonce (32) + depositor (32) + - // bytes length (32) + resourceId (32) + length (32) = 0xC4 - - calldatacopy( - metadata, // copy to metadata - 0xC4, // copy from calldata after metadata length declaration @0xC4 - sub(calldatasize(), 0xC4) // copy size (calldatasize - (0xC4 + the space metaData takes up)) - ) - } - - address contractAddress = _resourceIDToContractAddress[resourceID]; - require(_contractWhitelist[contractAddress], "provided contractAddress is not whitelisted"); - - bytes4 sig = _contractAddressToDepositFunctionSignature[contractAddress]; - if (sig != bytes4(0)) { - bytes memory callData = abi.encodePacked(sig, metadata); - (bool success, ) = contractAddress.call(callData); - require(success, "delegatecall to contractAddress failed"); - } - - _depositRecords[destinationChainID][depositNonce] = DepositRecord( - destinationChainID, - depositer, - resourceID, - metadata - ); - } - - /** - @notice Proposal execution should be initiated when a proposal is finalized in the Bridge contract. - @notice {contractAddress} is required to be whitelisted - @notice If {_contractAddressToExecuteFunctionSignature}[{contractAddress}] is set, - {metaData} is expected to consist of needed function arguments. - */ - function executeProposal(bytes32 resourceID, bytes calldata) external onlyBridge { - bytes memory metaData; - assembly { - // metadata has variable length - // load free memory pointer to store metadata - metaData := mload(0x40) - // first 32 bytes of variable length in storage refer to length - let lenMeta := calldataload(0x64) - mstore(0x40, add(0x60, add(metaData, lenMeta))) - - // in the calldata, metadata is stored @0x64 after accounting for function signature, and 2 previous params - calldatacopy( - metaData, // copy to metaData - 0x64, // copy from calldata after data length declaration at 0x64 - sub(calldatasize(), 0x64) // copy size (calldatasize - 0x64) - ) - } - - address contractAddress = _resourceIDToContractAddress[resourceID]; - require(_contractWhitelist[contractAddress], "provided contractAddress is not whitelisted"); - - bytes4 sig = _contractAddressToExecuteFunctionSignature[contractAddress]; - if (sig != bytes4(0)) { - bytes memory callData = abi.encodePacked(sig, metaData); - (bool success, ) = contractAddress.call(callData); - require(success, "delegatecall to contractAddress failed"); - } - } - - function _setResource( - bytes32 resourceID, - address contractAddress, - bytes4 depositFunctionSig, - bytes4 executeFunctionSig - ) internal { - _resourceIDToContractAddress[resourceID] = contractAddress; - _contractAddressToResourceID[contractAddress] = resourceID; - _contractAddressToDepositFunctionSignature[contractAddress] = depositFunctionSig; - _contractAddressToExecuteFunctionSignature[contractAddress] = executeFunctionSig; - - _contractWhitelist[contractAddress] = true; - } -} diff --git a/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IBridge.sol b/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IBridge.sol deleted file mode 100644 index 1106eeaa1..000000000 --- a/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IBridge.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -/** - @title Interface for Bridge contract. - @author ChainSafe Systems. - */ -interface IBridge { - /** - @notice Exposing getter for {_chainID} instead of forcing the use of call. - @return uint8 The {_chainID} that is currently set for the Bridge contract. - */ - function _chainID() external returns (uint8); - - function deposit(uint8 destinationChainID, bytes32 resourceID, bytes calldata data) external; -} diff --git a/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IDepositExecute.sol b/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IDepositExecute.sol deleted file mode 100644 index bc8df3b9c..000000000 --- a/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IDepositExecute.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -/** - @title Interface for handler contracts that support deposits and deposit executions. - @author ChainSafe Systems. - */ -interface IDepositExecute { - /** - @notice It is intended that deposit are made using the Bridge contract. - @param destinationChainID Chain ID deposit is expected to be bridged to. - @param depositNonce This value is generated as an ID by the Bridge contract. - @param depositer Address of account making the deposit in the Bridge contract. - @param data Consists of additional data needed for a specific deposit. - */ - function deposit( - bytes32 resourceID, - uint8 destinationChainID, - uint64 depositNonce, - address depositer, - bytes calldata data - ) external; - - /** - @notice It is intended that proposals are executed by the Bridge contract. - @param data Consists of additional data needed for a specific deposit execution. - */ - function executeProposal(bytes32 resourceID, bytes calldata data) external; -} diff --git a/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IERCHandler.sol b/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IERCHandler.sol deleted file mode 100644 index 045af10a7..000000000 --- a/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IERCHandler.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -/** - @title Interface to be used with handlers that support ERC20s and ERC721s. - @author ChainSafe Systems. - */ -interface IERCHandler { - /** - @notice Correlates {resourceID} with {contractAddress}. - @param resourceID ResourceID to be used when making deposits. - @param contractAddress Address of contract to be called when a deposit is made and a deposited is executed. - */ - function setResource(bytes32 resourceID, address contractAddress) external; - - /** - @notice Marks {contractAddress} as mintable/burnable. - @param contractAddress Address of contract to be used when making or executing deposits. - */ - function setBurnable(address contractAddress) external; - - /** - @notice Used to manually release funds from ERC safes. - @param tokenAddress Address of token contract to release. - @param recipient Address to release tokens to. - @param amountOrTokenID Either the amount of ERC20 tokens or the ERC721 token ID to release. - */ - function withdraw(address tokenAddress, address recipient, uint256 amountOrTokenID) external; -} diff --git a/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IGenericHandler.sol b/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IGenericHandler.sol deleted file mode 100644 index 9c4f59fcd..000000000 --- a/contracts/external/uma/core/contracts/external/chainbridge/interfaces/IGenericHandler.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -/** - @title Interface for handler that handles generic deposits and deposit executions. - @dev Copied directly from here: https://github.com/ChainSafe/chainbridge-solidity/releases/tag/v1.0.0. - @author ChainSafe Systems. - */ -interface IGenericHandler { - /** - @notice Correlates {resourceID} with {contractAddress}, {depositFunctionSig}, and {executeFunctionSig}. - @param resourceID ResourceID to be used when making deposits. - @param contractAddress Address of contract to be called when a deposit is made and a deposited is executed. - @param depositFunctionSig Function signature of method to be called in {contractAddress} when a deposit is made. - @param executeFunctionSig Function signature of method to be called in {contractAddress} when a deposit is executed. - */ - function setResource( - bytes32 resourceID, - address contractAddress, - bytes4 depositFunctionSig, - bytes4 executeFunctionSig - ) external; -} diff --git a/contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL1StandardBridge.sol b/contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL1StandardBridge.sol deleted file mode 100644 index 0483c28ae..000000000 --- a/contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL1StandardBridge.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import { Address } from "@openzeppelin/contracts-v4/utils/Address.sol"; -import { SafeERC20 } from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; - -/** - * @title L1StandardBridge - * @dev The L1 ETH and ERC20 Bridge is a contract which stores deposited L1 funds and standard - * tokens that are in use on L2. It synchronizes a corresponding L2 Bridge, informing it of deposits - * and listening to it for newly finalized withdrawals. - * - */ -interface OptimismL1StandardBridge { - function depositETH(uint32 _l2Gas, bytes calldata _data) external payable; - - function depositERC20( - address _l1Token, - address _l2Token, - uint256 _amount, - uint32 _l2Gas, - bytes calldata _data - ) external; -} diff --git a/contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL2StandardBridge.sol b/contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL2StandardBridge.sol deleted file mode 100644 index 0a453170f..000000000 --- a/contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL2StandardBridge.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -/** - * @title L2StandardBridge - * @dev The L2 Standard bridge is a contract which works together with the L1 Standard bridge to - * enable ETH and ERC20 transitions between L1 and L2. - * This contract acts as a minter for new tokens when it hears about deposits into the L1 Standard - * bridge. - * This contract also acts as a burner of the tokens intended for withdrawal, informing the L1 - * bridge to release L1 funds. - */ -contract OptimismL2StandardBridge { - /******************************** - * External Contract References * - ********************************/ - - address public l1TokenBridge; -} diff --git a/contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL2StandardERC20.sol b/contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL2StandardERC20.sol deleted file mode 100644 index a13dfccd9..000000000 --- a/contracts/external/uma/core/contracts/external/optimism-bridge/interfaces/OptimismL2StandardERC20.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { ERC20 } from "@openzeppelin/contracts-v4/token/ERC20/ERC20.sol"; - -abstract contract OptimismL2StandardERC20 is ERC20 { - address public l1Token; - address public l2Bridge; -} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/EmergencyShutdownable.sol b/contracts/external/uma/core/contracts/financial-templates/common/EmergencyShutdownable.sol deleted file mode 100644 index 0304614cc..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/common/EmergencyShutdownable.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; - -/** - * @title EmergencyShutdownable contract. - * @notice Any contract that inherits this contract will have an emergency shutdown timestamp state variable. - * This contract provides modifiers that can be used by children contracts to determine if the contract is - * in the shutdown state. The child contract is expected to implement the logic that happens - * once a shutdown occurs. - */ - -abstract contract EmergencyShutdownable { - using SafeMath for uint256; - - /**************************************** - * EMERGENCY SHUTDOWN DATA STRUCTURES * - ****************************************/ - - // Timestamp used in case of emergency shutdown. 0 if no shutdown has been triggered. - uint256 public emergencyShutdownTimestamp; - - /**************************************** - * MODIFIERS * - ****************************************/ - - modifier notEmergencyShutdown() { - _notEmergencyShutdown(); - _; - } - - modifier isEmergencyShutdown() { - _isEmergencyShutdown(); - _; - } - - /**************************************** - * EXTERNAL FUNCTIONS * - ****************************************/ - - constructor() { - emergencyShutdownTimestamp = 0; - } - - /**************************************** - * INTERNAL FUNCTIONS * - ****************************************/ - - function _notEmergencyShutdown() internal view { - // Note: removed require string to save bytecode. - require(emergencyShutdownTimestamp == 0); - } - - function _isEmergencyShutdown() internal view { - // Note: removed require string to save bytecode. - require(emergencyShutdownTimestamp != 0); - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/FeePayer.sol b/contracts/external/uma/core/contracts/financial-templates/common/FeePayer.sol deleted file mode 100644 index 3adaa935f..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/common/FeePayer.sol +++ /dev/null @@ -1,322 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; - -import "../../common/implementation/Lockable.sol"; -import "../../common/implementation/FixedPoint.sol"; -import "../../common/implementation/Testable.sol"; - -import "../../data-verification-mechanism/interfaces/StoreInterface.sol"; -import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; -import "../../data-verification-mechanism/interfaces/AdministrateeInterface.sol"; -import "../../data-verification-mechanism/implementation/Constants.sol"; - -/** - * @title FeePayer contract. - * @notice Provides fee payment functionality for the ExpiringMultiParty contract. - * contract is abstract as each derived contract that inherits `FeePayer` must implement `pfc()`. - */ - -abstract contract FeePayer is AdministrateeInterface, Testable, Lockable { - using SafeMath for uint256; - using FixedPoint for FixedPoint.Unsigned; - using SafeERC20 for IERC20; - - /**************************************** - * FEE PAYER DATA STRUCTURES * - ****************************************/ - - // The collateral currency used to back the positions in this contract. - IERC20 public collateralCurrency; - - // Finder contract used to look up addresses for UMA system contracts. - FinderInterface public finder; - - // Tracks the last block time when the fees were paid. - uint256 private lastPaymentTime; - - // Tracks the cumulative fees that have been paid by the contract for use by derived contracts. - // The multiplier starts at 1, and is updated by computing cumulativeFeeMultiplier * (1 - effectiveFee). - // Put another way, the cumulativeFeeMultiplier is (1 - effectiveFee1) * (1 - effectiveFee2) ... - // For example: - // The cumulativeFeeMultiplier should start at 1. - // If a 1% fee is charged, the multiplier should update to .99. - // If another 1% fee is charged, the multiplier should be 0.99^2 (0.9801). - FixedPoint.Unsigned public cumulativeFeeMultiplier; - - /**************************************** - * EVENTS * - ****************************************/ - - event RegularFeesPaid(uint256 indexed regularFee, uint256 indexed lateFee); - event FinalFeesPaid(uint256 indexed amount); - - /**************************************** - * MODIFIERS * - ****************************************/ - - // modifier that calls payRegularFees(). - modifier fees() virtual { - // Note: the regular fee is applied on every fee-accruing transaction, where the total change is simply the - // regular fee applied linearly since the last update. This implies that the compounding rate depends on the - // frequency of update transactions that have this modifier, and it never reaches the ideal of continuous - // compounding. This approximate-compounding pattern is common in the Ethereum ecosystem because of the - // complexity of compounding data on-chain. - payRegularFees(); - _; - } - - /** - * @notice Constructs the FeePayer contract. Called by child contracts. - * @param _collateralAddress ERC20 token that is used as the underlying collateral for the synthetic. - * @param _finderAddress UMA protocol Finder used to discover other protocol contracts. - * @param _timerAddress Contract that stores the current time in a testing environment. - * Must be set to 0x0 for production environments that use live time. - */ - constructor(address _collateralAddress, address _finderAddress, address _timerAddress) Testable(_timerAddress) { - collateralCurrency = IERC20(_collateralAddress); - finder = FinderInterface(_finderAddress); - lastPaymentTime = getCurrentTime(); - cumulativeFeeMultiplier = FixedPoint.fromUnscaledUint(1); - } - - /**************************************** - * FEE PAYMENT FUNCTIONS * - ****************************************/ - - /** - * @notice Pays UMA DVM regular fees (as a % of the collateral pool) to the Store contract. - * @dev These must be paid periodically for the life of the contract. If the contract has not paid its regular fee - * in a week or more then a late penalty is applied which is sent to the caller. If the amount of - * fees owed are greater than the pfc, then this will pay as much as possible from the available collateral. - * An event is only fired if the fees charged are greater than 0. - * @return totalPaid Amount of collateral that the contract paid (sum of the amount paid to the Store and caller). - * This returns 0 and exit early if there is no pfc, fees were already paid during the current block, or the fee rate is 0. - */ - function payRegularFees() public nonReentrant returns (FixedPoint.Unsigned memory) { - uint256 time = getCurrentTime(); - FixedPoint.Unsigned memory collateralPool = _pfc(); - - // Fetch the regular fees, late penalty and the max possible to pay given the current collateral within the contract. - ( - FixedPoint.Unsigned memory regularFee, - FixedPoint.Unsigned memory latePenalty, - FixedPoint.Unsigned memory totalPaid - ) = getOutstandingRegularFees(time); - lastPaymentTime = time; - - // If there are no fees to pay then exit early. - if (totalPaid.isEqual(0)) { - return totalPaid; - } - - emit RegularFeesPaid(regularFee.rawValue, latePenalty.rawValue); - - _adjustCumulativeFeeMultiplier(totalPaid, collateralPool); - - if (regularFee.isGreaterThan(0)) { - StoreInterface store = _getStore(); - collateralCurrency.safeIncreaseAllowance(address(store), regularFee.rawValue); - store.payOracleFeesErc20(address(collateralCurrency), regularFee); - } - - if (latePenalty.isGreaterThan(0)) { - collateralCurrency.safeTransfer(msg.sender, latePenalty.rawValue); - } - return totalPaid; - } - - /** - * @notice Fetch any regular fees that the contract has pending but has not yet paid. If the fees to be paid are more - * than the total collateral within the contract then the totalPaid returned is full contract collateral amount. - * @dev This returns 0 and exit early if there is no pfc, fees were already paid during the current block, or the fee rate is 0. - * @return regularFee outstanding unpaid regular fee. - * @return latePenalty outstanding unpaid late fee for being late in previous fee payments. - * @return totalPaid Amount of collateral that the contract paid (sum of the amount paid to the Store and caller). - */ - function getOutstandingRegularFees( - uint256 time - ) - public - view - returns ( - FixedPoint.Unsigned memory regularFee, - FixedPoint.Unsigned memory latePenalty, - FixedPoint.Unsigned memory totalPaid - ) - { - StoreInterface store = _getStore(); - FixedPoint.Unsigned memory collateralPool = _pfc(); - - // Exit early if there is no collateral or if fees were already paid during this block. - if (collateralPool.isEqual(0) || lastPaymentTime == time) { - return (regularFee, latePenalty, totalPaid); - } - - (regularFee, latePenalty) = store.computeRegularFee(lastPaymentTime, time, collateralPool); - - totalPaid = regularFee.add(latePenalty); - if (totalPaid.isEqual(0)) { - return (regularFee, latePenalty, totalPaid); - } - // If the effective fees paid as a % of the pfc is > 100%, then we need to reduce it and make the contract pay - // as much of the fee that it can (up to 100% of its pfc). We'll reduce the late penalty first and then the - // regular fee, which has the effect of paying the store first, followed by the caller if there is any fee remaining. - if (totalPaid.isGreaterThan(collateralPool)) { - FixedPoint.Unsigned memory deficit = totalPaid.sub(collateralPool); - FixedPoint.Unsigned memory latePenaltyReduction = FixedPoint.min(latePenalty, deficit); - latePenalty = latePenalty.sub(latePenaltyReduction); - deficit = deficit.sub(latePenaltyReduction); - regularFee = regularFee.sub(FixedPoint.min(regularFee, deficit)); - totalPaid = collateralPool; - } - } - - /** - * @notice Gets the current profit from corruption for this contract in terms of the collateral currency. - * @dev This is equivalent to the collateral pool available from which to pay fees. Therefore, derived contracts are - * expected to implement this so that pay-fee methods can correctly compute the owed fees as a % of PfC. - * @return pfc value for equal to the current profit from corruption denominated in collateral currency. - */ - function pfc() external view override nonReentrantView returns (FixedPoint.Unsigned memory) { - return _pfc(); - } - - /** - * @notice Removes excess collateral balance not counted in the PfC by distributing it out pro-rata to all sponsors. - * @dev Multiplying the `cumulativeFeeMultiplier` by the ratio of non-PfC-collateral :: PfC-collateral effectively - * pays all sponsors a pro-rata portion of the excess collateral. - * @dev This will revert if PfC is 0 and this contract's collateral balance > 0. - */ - function gulp() external nonReentrant { - _gulp(); - } - - /**************************************** - * INTERNAL FUNCTIONS * - ****************************************/ - - // Pays UMA Oracle final fees of `amount` in `collateralCurrency` to the Store contract. Final fee is a flat fee - // charged for each price request. If payer is the contract, adjusts internal bookkeeping variables. If payer is not - // the contract, pulls in `amount` of collateral currency. - function _payFinalFees(address payer, FixedPoint.Unsigned memory amount) internal { - if (amount.isEqual(0)) { - return; - } - - if (payer != address(this)) { - // If the payer is not the contract pull the collateral from the payer. - collateralCurrency.safeTransferFrom(payer, address(this), amount.rawValue); - } else { - // If the payer is the contract, adjust the cumulativeFeeMultiplier to compensate. - FixedPoint.Unsigned memory collateralPool = _pfc(); - - // The final fee must be < available collateral or the fee will be larger than 100%. - // Note: revert reason removed to save bytecode. - require(collateralPool.isGreaterThan(amount)); - - _adjustCumulativeFeeMultiplier(amount, collateralPool); - } - - emit FinalFeesPaid(amount.rawValue); - - StoreInterface store = _getStore(); - collateralCurrency.safeIncreaseAllowance(address(store), amount.rawValue); - store.payOracleFeesErc20(address(collateralCurrency), amount); - } - - function _gulp() internal { - FixedPoint.Unsigned memory currentPfc = _pfc(); - FixedPoint.Unsigned memory currentBalance = FixedPoint.Unsigned(collateralCurrency.balanceOf(address(this))); - if (currentPfc.isLessThan(currentBalance)) { - cumulativeFeeMultiplier = cumulativeFeeMultiplier.mul(currentBalance.div(currentPfc)); - } - } - - function _pfc() internal view virtual returns (FixedPoint.Unsigned memory); - - function _getStore() internal view returns (StoreInterface) { - return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store)); - } - - function _computeFinalFees() internal view returns (FixedPoint.Unsigned memory finalFees) { - StoreInterface store = _getStore(); - return store.computeFinalFee(address(collateralCurrency)); - } - - // Returns the user's collateral minus any fees that have been subtracted since it was originally - // deposited into the contract. Note: if the contract has paid fees since it was deployed, the raw - // value should be larger than the returned value. - function _getFeeAdjustedCollateral( - FixedPoint.Unsigned memory rawCollateral - ) internal view returns (FixedPoint.Unsigned memory collateral) { - return rawCollateral.mul(cumulativeFeeMultiplier); - } - - // Returns the user's collateral minus any pending fees that have yet to be subtracted. - function _getPendingRegularFeeAdjustedCollateral( - FixedPoint.Unsigned memory rawCollateral - ) internal view returns (FixedPoint.Unsigned memory) { - (, , FixedPoint.Unsigned memory currentTotalOutstandingRegularFees) = getOutstandingRegularFees( - getCurrentTime() - ); - if (currentTotalOutstandingRegularFees.isEqual(FixedPoint.fromUnscaledUint(0))) return rawCollateral; - - // Calculate the total outstanding regular fee as a fraction of the total contract PFC. - FixedPoint.Unsigned memory effectiveOutstandingFee = currentTotalOutstandingRegularFees.divCeil(_pfc()); - - // Scale as rawCollateral* (1 - effectiveOutstandingFee) to apply the pro-rata amount to the regular fee. - return rawCollateral.mul(FixedPoint.fromUnscaledUint(1).sub(effectiveOutstandingFee)); - } - - // Converts a user-readable collateral value into a raw value that accounts for already-assessed fees. If any fees - // have been taken from this contract in the past, then the raw value will be larger than the user-readable value. - function _convertToRawCollateral( - FixedPoint.Unsigned memory collateral - ) internal view returns (FixedPoint.Unsigned memory rawCollateral) { - return collateral.div(cumulativeFeeMultiplier); - } - - // Decrease rawCollateral by a fee-adjusted collateralToRemove amount. Fee adjustment scales up collateralToRemove - // by dividing it by cumulativeFeeMultiplier. There is potential for this quotient to be floored, therefore - // rawCollateral is decreased by less than expected. Because this method is usually called in conjunction with an - // actual removal of collateral from this contract, return the fee-adjusted amount that the rawCollateral is - // decreased by so that the caller can minimize error between collateral removed and rawCollateral debited. - function _removeCollateral( - FixedPoint.Unsigned storage rawCollateral, - FixedPoint.Unsigned memory collateralToRemove - ) internal returns (FixedPoint.Unsigned memory removedCollateral) { - FixedPoint.Unsigned memory initialBalance = _getFeeAdjustedCollateral(rawCollateral); - FixedPoint.Unsigned memory adjustedCollateral = _convertToRawCollateral(collateralToRemove); - rawCollateral.rawValue = rawCollateral.sub(adjustedCollateral).rawValue; - removedCollateral = initialBalance.sub(_getFeeAdjustedCollateral(rawCollateral)); - } - - // Increase rawCollateral by a fee-adjusted collateralToAdd amount. Fee adjustment scales up collateralToAdd - // by dividing it by cumulativeFeeMultiplier. There is potential for this quotient to be floored, therefore - // rawCollateral is increased by less than expected. Because this method is usually called in conjunction with an - // actual addition of collateral to this contract, return the fee-adjusted amount that the rawCollateral is - // increased by so that the caller can minimize error between collateral added and rawCollateral credited. - // NOTE: This return value exists only for the sake of symmetry with _removeCollateral. We don't actually use it - // because we are OK if more collateral is stored in the contract than is represented by rawTotalPositionCollateral. - function _addCollateral( - FixedPoint.Unsigned storage rawCollateral, - FixedPoint.Unsigned memory collateralToAdd - ) internal returns (FixedPoint.Unsigned memory addedCollateral) { - FixedPoint.Unsigned memory initialBalance = _getFeeAdjustedCollateral(rawCollateral); - FixedPoint.Unsigned memory adjustedCollateral = _convertToRawCollateral(collateralToAdd); - rawCollateral.rawValue = rawCollateral.add(adjustedCollateral).rawValue; - addedCollateral = _getFeeAdjustedCollateral(rawCollateral).sub(initialBalance); - } - - // Scale the cumulativeFeeMultiplier by the ratio of fees paid to the current available collateral. - function _adjustCumulativeFeeMultiplier( - FixedPoint.Unsigned memory amount, - FixedPoint.Unsigned memory currentPfc - ) internal { - FixedPoint.Unsigned memory effectiveFee = amount.divCeil(currentPfc); - cumulativeFeeMultiplier = cumulativeFeeMultiplier.mul(FixedPoint.fromUnscaledUint(1).sub(effectiveFee)); - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/FundingRateApplier.sol b/contracts/external/uma/core/contracts/financial-templates/common/FundingRateApplier.sol deleted file mode 100644 index f3ebc8d8c..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/common/FundingRateApplier.sol +++ /dev/null @@ -1,338 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/utils/math/SafeCast.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; - -import "../../common/implementation/Lockable.sol"; -import "../../common/implementation/FixedPoint.sol"; -import "../../common/implementation/Testable.sol"; -import "../../common/implementation/AncillaryData.sol"; - -import "../../data-verification-mechanism/implementation/Constants.sol"; -import "../../optimistic-oracle-v2/interfaces/OptimisticOracleInterface.sol"; -import "../perpetual-multiparty/ConfigStoreInterface.sol"; - -import "./EmergencyShutdownable.sol"; -import "./FeePayer.sol"; - -/** - * @title FundingRateApplier contract. - * @notice Provides funding rate payment functionality for the Perpetual contract. - */ - -abstract contract FundingRateApplier is EmergencyShutdownable, FeePayer { - using FixedPoint for FixedPoint.Unsigned; - using FixedPoint for FixedPoint.Signed; - using SafeERC20 for IERC20; - using SafeMath for uint256; - - /**************************************** - * FUNDING RATE APPLIER DATA STRUCTURES * - ****************************************/ - - struct FundingRate { - // Current funding rate value. - FixedPoint.Signed rate; - // Identifier to retrieve the funding rate. - bytes32 identifier; - // Tracks the cumulative funding payments that have been paid to the sponsors. - // The multiplier starts at 1, and is updated by computing cumulativeFundingRateMultiplier * (1 + effectivePayment). - // Put another way, the cumulativeFeeMultiplier is (1 + effectivePayment1) * (1 + effectivePayment2) ... - // For example: - // The cumulativeFundingRateMultiplier should start at 1. - // If a 1% funding payment is paid to sponsors, the multiplier should update to 1.01. - // If another 1% fee is charged, the multiplier should be 1.01^2 (1.0201). - FixedPoint.Unsigned cumulativeMultiplier; - // Most recent time that the funding rate was updated. - uint256 updateTime; - // Most recent time that the funding rate was applied and changed the cumulative multiplier. - uint256 applicationTime; - // The time for the active (if it exists) funding rate proposal. 0 otherwise. - uint256 proposalTime; - } - - FundingRate public fundingRate; - - // Remote config store managed an owner. - ConfigStoreInterface public configStore; - - /**************************************** - * EVENTS * - ****************************************/ - - event FundingRateUpdated(int256 newFundingRate, uint256 indexed updateTime, uint256 reward); - - /**************************************** - * MODIFIERS * - ****************************************/ - - // This is overridden to both pay fees (which is done by applyFundingRate()) and apply the funding rate. - modifier fees() override { - // Note: the funding rate is applied on every fee-accruing transaction, where the total change is simply the - // rate applied linearly since the last update. This implies that the compounding rate depends on the frequency - // of update transactions that have this modifier, and it never reaches the ideal of continuous compounding. - // This approximate-compounding pattern is common in the Ethereum ecosystem because of the complexity of - // compounding data on-chain. - applyFundingRate(); - _; - } - - // Note: this modifier is intended to be used if the caller intends to _only_ pay regular fees. - modifier paysRegularFees() { - payRegularFees(); - _; - } - - /** - * @notice Constructs the FundingRateApplier contract. Called by child contracts. - * @param _fundingRateIdentifier identifier that tracks the funding rate of this contract. - * @param _collateralAddress address of the collateral token. - * @param _finderAddress Finder used to discover financial-product-related contracts. - * @param _configStoreAddress address of the remote configuration store managed by an external owner. - * @param _tokenScaling initial scaling to apply to the token value (i.e. scales the tracking index). - * @param _timerAddress address of the timer contract in test envs, otherwise 0x0. - */ - constructor( - bytes32 _fundingRateIdentifier, - address _collateralAddress, - address _finderAddress, - address _configStoreAddress, - FixedPoint.Unsigned memory _tokenScaling, - address _timerAddress - ) FeePayer(_collateralAddress, _finderAddress, _timerAddress) EmergencyShutdownable() { - uint256 currentTime = getCurrentTime(); - fundingRate.updateTime = currentTime; - fundingRate.applicationTime = currentTime; - - // Seed the cumulative multiplier with the token scaling, from which it will be scaled as funding rates are - // applied over time. - fundingRate.cumulativeMultiplier = _tokenScaling; - - fundingRate.identifier = _fundingRateIdentifier; - configStore = ConfigStoreInterface(_configStoreAddress); - } - - /** - * @notice This method takes 3 distinct actions: - * 1. Pays out regular fees. - * 2. If possible, resolves the outstanding funding rate proposal, pulling the result in and paying out the rewards. - * 3. Applies the prevailing funding rate over the most recent period. - */ - function applyFundingRate() public paysRegularFees nonReentrant { - _applyEffectiveFundingRate(); - } - - /** - * @notice Proposes a new funding rate. Proposer receives a reward if correct. - * @param rate funding rate being proposed. - * @param timestamp time at which the funding rate was computed. - */ - function proposeFundingRate( - FixedPoint.Signed memory rate, - uint256 timestamp - ) external fees nonReentrant returns (FixedPoint.Unsigned memory totalBond) { - require(fundingRate.proposalTime == 0); - _validateFundingRate(rate); - - // Timestamp must be after the last funding rate update time, within the last 30 minutes. - uint256 currentTime = getCurrentTime(); - uint256 updateTime = fundingRate.updateTime; - require(timestamp > updateTime && timestamp >= currentTime.sub(_getConfig().proposalTimePastLimit)); - - // Set the proposal time in order to allow this contract to track this request. - fundingRate.proposalTime = timestamp; - - OptimisticOracleInterface optimisticOracle = _getOptimisticOracle(); - - // Set up optimistic oracle. - bytes32 identifier = fundingRate.identifier; - bytes memory ancillaryData = _getAncillaryData(); - // Note: requestPrice will revert if `timestamp` is less than the current block timestamp. - optimisticOracle.requestPrice(identifier, timestamp, ancillaryData, collateralCurrency, 0); - totalBond = FixedPoint.Unsigned( - optimisticOracle.setBond( - identifier, - timestamp, - ancillaryData, - _pfc().mul(_getConfig().proposerBondPercentage).rawValue - ) - ); - - // Pull bond from caller and send to optimistic oracle. - if (totalBond.isGreaterThan(0)) { - collateralCurrency.safeTransferFrom(msg.sender, address(this), totalBond.rawValue); - collateralCurrency.safeIncreaseAllowance(address(optimisticOracle), totalBond.rawValue); - } - - optimisticOracle.proposePriceFor( - msg.sender, - address(this), - identifier, - timestamp, - ancillaryData, - rate.rawValue - ); - } - - // Returns a token amount scaled by the current funding rate multiplier. - // Note: if the contract has paid fees since it was deployed, the raw value should be larger than the returned value. - function _getFundingRateAppliedTokenDebt( - FixedPoint.Unsigned memory rawTokenDebt - ) internal view returns (FixedPoint.Unsigned memory tokenDebt) { - return rawTokenDebt.mul(fundingRate.cumulativeMultiplier); - } - - function _getOptimisticOracle() internal view returns (OptimisticOracleInterface) { - return OptimisticOracleInterface(finder.getImplementationAddress(OracleInterfaces.OptimisticOracle)); - } - - function _getConfig() internal returns (ConfigStoreInterface.ConfigSettings memory) { - return configStore.updateAndGetCurrentConfig(); - } - - function _updateFundingRate() internal { - uint256 proposalTime = fundingRate.proposalTime; - // If there is no pending proposal then do nothing. Otherwise check to see if we can update the funding rate. - if (proposalTime != 0) { - // Attempt to update the funding rate. - OptimisticOracleInterface optimisticOracle = _getOptimisticOracle(); - bytes32 identifier = fundingRate.identifier; - bytes memory ancillaryData = _getAncillaryData(); - - // Try to get the price from the optimistic oracle. This call will revert if the request has not resolved - // yet. If the request has not resolved yet, then we need to do additional checks to see if we should - // "forget" the pending proposal and allow new proposals to update the funding rate. - try optimisticOracle.settleAndGetPrice(identifier, proposalTime, ancillaryData) returns (int256 price) { - // If successful, determine if the funding rate state needs to be updated. - // If the request is more recent than the last update then we should update it. - uint256 lastUpdateTime = fundingRate.updateTime; - if (proposalTime >= lastUpdateTime) { - // Update funding rates - fundingRate.rate = FixedPoint.Signed(price); - fundingRate.updateTime = proposalTime; - - // If there was no dispute, send a reward. - FixedPoint.Unsigned memory reward = FixedPoint.fromUnscaledUint(0); - OptimisticOracleInterface.Request memory request = optimisticOracle.getRequest( - address(this), - identifier, - proposalTime, - ancillaryData - ); - if (request.disputer == address(0)) { - reward = _pfc().mul(_getConfig().rewardRatePerSecond).mul(proposalTime.sub(lastUpdateTime)); - if (reward.isGreaterThan(0)) { - _adjustCumulativeFeeMultiplier(reward, _pfc()); - collateralCurrency.safeTransfer(request.proposer, reward.rawValue); - } - } - - // This event will only be emitted after the fundingRate struct's "updateTime" has been set - // to the latest proposal's proposalTime, indicating that the proposal has been published. - // So, it suffices to just emit fundingRate.updateTime here. - emit FundingRateUpdated(fundingRate.rate.rawValue, fundingRate.updateTime, reward.rawValue); - } - - // Set proposal time to 0 since this proposal has now been resolved. - fundingRate.proposalTime = 0; - } catch { - // Stop tracking and allow other proposals to come in if: - // - The requester address is empty, indicating that the Oracle does not know about this funding rate - // request. This is possible if the Oracle is replaced while the price request is still pending. - // - The request has been disputed. - OptimisticOracleInterface.Request memory request = optimisticOracle.getRequest( - address(this), - identifier, - proposalTime, - ancillaryData - ); - if (request.disputer != address(0) || request.proposer == address(0)) { - fundingRate.proposalTime = 0; - } - } - } - } - - // Constraining the range of funding rates limits the PfC for any dishonest proposer and enhances the - // perpetual's security. For example, let's examine the case where the max and min funding rates - // are equivalent to +/- 500%/year. This 1000% funding rate range allows a 8.6% profit from corruption for a - // proposer who can deter honest proposers for 74 hours: - // 1000%/year / 360 days / 24 hours * 74 hours max attack time = ~ 8.6%. - // How would attack work? Imagine that the market is very volatile currently and that the "true" funding - // rate for the next 74 hours is -500%, but a dishonest proposer successfully proposes a rate of +500% - // (after a two hour liveness) and disputes honest proposers for the next 72 hours. This results in a funding - // rate error of 1000% for 74 hours, until the DVM can set the funding rate back to its correct value. - function _validateFundingRate(FixedPoint.Signed memory rate) internal { - require( - rate.isLessThanOrEqual(_getConfig().maxFundingRate) && - rate.isGreaterThanOrEqual(_getConfig().minFundingRate) - ); - } - - // Fetches a funding rate from the Store, determines the period over which to compute an effective fee, - // and multiplies the current multiplier by the effective fee. - // A funding rate < 1 will reduce the multiplier, and a funding rate of > 1 will increase the multiplier. - // Note: 1 is set as the neutral rate because there are no negative numbers in FixedPoint, so we decide to treat - // values < 1 as "negative". - function _applyEffectiveFundingRate() internal { - // If contract is emergency shutdown, then the funding rate multiplier should no longer change. - if (emergencyShutdownTimestamp != 0) { - return; - } - - uint256 currentTime = getCurrentTime(); - uint256 paymentPeriod = currentTime.sub(fundingRate.applicationTime); - - _updateFundingRate(); // Update the funding rate if there is a resolved proposal. - fundingRate.cumulativeMultiplier = _calculateEffectiveFundingRate( - paymentPeriod, - fundingRate.rate, - fundingRate.cumulativeMultiplier - ); - - fundingRate.applicationTime = currentTime; - } - - function _calculateEffectiveFundingRate( - uint256 paymentPeriodSeconds, - FixedPoint.Signed memory fundingRatePerSecond, - FixedPoint.Unsigned memory currentCumulativeFundingRateMultiplier - ) internal pure returns (FixedPoint.Unsigned memory newCumulativeFundingRateMultiplier) { - // Note: this method uses named return variables to save a little bytecode. - - // The overall formula that this function is performing: - // newCumulativeFundingRateMultiplier = - // (1 + (fundingRatePerSecond * paymentPeriodSeconds)) * currentCumulativeFundingRateMultiplier. - FixedPoint.Signed memory ONE = FixedPoint.fromUnscaledInt(1); - - // Multiply the per-second rate over the number of seconds that have elapsed to get the period rate. - FixedPoint.Signed memory periodRate = fundingRatePerSecond.mul(SafeCast.toInt256(paymentPeriodSeconds)); - - // Add one to create the multiplier to scale the existing fee multiplier. - FixedPoint.Signed memory signedPeriodMultiplier = ONE.add(periodRate); - - // Max with 0 to ensure the multiplier isn't negative, then cast to an Unsigned. - FixedPoint.Unsigned memory unsignedPeriodMultiplier = FixedPoint.fromSigned( - FixedPoint.max(signedPeriodMultiplier, FixedPoint.fromUnscaledInt(0)) - ); - - // Multiply the existing cumulative funding rate multiplier by the computed period multiplier to get the new - // cumulative funding rate multiplier. - newCumulativeFundingRateMultiplier = currentCumulativeFundingRateMultiplier.mul(unsignedPeriodMultiplier); - } - - /** - * @dev We do not need to check that the ancillary data length is less than the hardcoded max length in the - * OptimisticOracle because the length of the ancillary data is fixed in this function. - */ - function _getAncillaryData() internal view returns (bytes memory) { - // When ancillary data is passed to the optimistic oracle, it should be tagged with the token address - // whose funding rate it's trying to get so that financial contracts can re-use the same identifier for - // multiple funding rate products. - return AncillaryData.appendKeyValueAddress("", "tokenAddress", _getTokenAddress()); - } - - function _getTokenAddress() internal view virtual returns (address); -} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/SyntheticToken.sol b/contracts/external/uma/core/contracts/financial-templates/common/SyntheticToken.sol deleted file mode 100644 index 20dac683e..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/common/SyntheticToken.sol +++ /dev/null @@ -1,86 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; -import "../../common/implementation/ExpandedERC20.sol"; -import "../../common/implementation/Lockable.sol"; - -/** - * @title Burnable and mintable ERC20. - * @dev The contract deployer will initially be the only minter, burner and owner capable of adding new roles. - */ - -contract SyntheticToken is ExpandedERC20, Lockable { - /** - * @notice Constructs the SyntheticToken. - * @param tokenName The name which describes the new token. - * @param tokenSymbol The ticker abbreviation of the name. Ideally < 5 chars. - * @param tokenDecimals The number of decimals to define token precision. - */ - constructor( - string memory tokenName, - string memory tokenSymbol, - uint8 tokenDecimals - ) ExpandedERC20(tokenName, tokenSymbol, tokenDecimals) nonReentrant() {} - - /** - * @notice Add Minter role to account. - * @dev The caller must have the Owner role. - * @param account The address to which the Minter role is added. - */ - function addMinter(address account) external override nonReentrant { - addMember(uint256(Roles.Minter), account); - } - - /** - * @notice Remove Minter role from account. - * @dev The caller must have the Owner role. - * @param account The address from which the Minter role is removed. - */ - function removeMinter(address account) external nonReentrant { - removeMember(uint256(Roles.Minter), account); - } - - /** - * @notice Add Burner role to account. - * @dev The caller must have the Owner role. - * @param account The address to which the Burner role is added. - */ - function addBurner(address account) external override nonReentrant { - addMember(uint256(Roles.Burner), account); - } - - /** - * @notice Removes Burner role from account. - * @dev The caller must have the Owner role. - * @param account The address from which the Burner role is removed. - */ - function removeBurner(address account) external nonReentrant { - removeMember(uint256(Roles.Burner), account); - } - - /** - * @notice Reset Owner role to account. - * @dev The caller must have the Owner role. - * @param account The new holder of the Owner role. - */ - function resetOwner(address account) external override nonReentrant { - resetMember(uint256(Roles.Owner), account); - } - - /** - * @notice Checks if a given account holds the Minter role. - * @param account The address which is checked for the Minter role. - * @return bool True if the provided account is a Minter. - */ - function isMinter(address account) public view nonReentrantView returns (bool) { - return holdsRole(uint256(Roles.Minter), account); - } - - /** - * @notice Checks if a given account holds the Burner role. - * @param account The address which is checked for the Burner role. - * @return bool True if the provided account is a Burner. - */ - function isBurner(address account) public view nonReentrantView returns (bool) { - return holdsRole(uint256(Roles.Burner), account); - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/TokenFactory.sol b/contracts/external/uma/core/contracts/financial-templates/common/TokenFactory.sol deleted file mode 100644 index b7909e660..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/common/TokenFactory.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "./SyntheticToken.sol"; -import "../../common/interfaces/ExpandedIERC20.sol"; -import "../../common/implementation/Lockable.sol"; - -/** - * @title Factory for creating new mintable and burnable tokens. - */ - -contract TokenFactory is Lockable { - /** - * @notice Create a new token and return it to the caller. - * @dev The caller will become the only minter and burner and the new owner capable of assigning the roles. - * @param tokenName used to describe the new token. - * @param tokenSymbol short ticker abbreviation of the name. Ideally < 5 chars. - * @param tokenDecimals used to define the precision used in the token's numerical representation. - * @return newToken an instance of the newly created token interface. - */ - function createToken( - string calldata tokenName, - string calldata tokenSymbol, - uint8 tokenDecimals - ) external nonReentrant returns (ExpandedIERC20 newToken) { - SyntheticToken mintableToken = new SyntheticToken(tokenName, tokenSymbol, tokenDecimals); - mintableToken.resetOwner(msg.sender); - newToken = ExpandedIERC20(address(mintableToken)); - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/WETH9.sol b/contracts/external/uma/core/contracts/financial-templates/common/WETH9.sol deleted file mode 100644 index 02dc9fb8c..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/common/WETH9.sol +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -// Copied from the verified code from Etherscan: -// https://etherscan.io/address/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2#code -// And then updated for Solidity version 0.6. Specific changes: -// * Change `function() public` into `receive() external` and `fallback() external` -// * Change `this.balance` to `address(this).balance` -// * Ran prettier -contract WETH9 { - string public name = "Wrapped Ether"; - string public symbol = "WETH"; - uint8 public decimals = 18; - - event Approval(address indexed src, address indexed guy, uint256 wad); - event Transfer(address indexed src, address indexed dst, uint256 wad); - event Deposit(address indexed dst, uint256 wad); - event Withdrawal(address indexed src, uint256 wad); - - mapping(address => uint256) public balanceOf; - mapping(address => mapping(address => uint256)) public allowance; - - receive() external payable { - deposit(); - } - - fallback() external payable { - deposit(); - } - - function deposit() public payable { - balanceOf[msg.sender] += msg.value; - emit Deposit(msg.sender, msg.value); - } - - function withdraw(uint256 wad) public { - require(balanceOf[msg.sender] >= wad); - balanceOf[msg.sender] -= wad; - payable(msg.sender).transfer(wad); - emit Withdrawal(msg.sender, wad); - } - - function totalSupply() public view returns (uint256) { - return address(this).balance; - } - - function approve(address guy, uint256 wad) public returns (bool) { - allowance[msg.sender][guy] = wad; - emit Approval(msg.sender, guy, wad); - return true; - } - - function transfer(address dst, uint256 wad) public returns (bool) { - return transferFrom(msg.sender, dst, wad); - } - - function transferFrom(address src, address dst, uint256 wad) public returns (bool) { - require(balanceOf[src] >= wad); - - if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) { - require(allowance[src][msg.sender] >= wad); - allowance[src][msg.sender] -= wad; - } - - balanceOf[src] -= wad; - balanceOf[dst] += wad; - - emit Transfer(src, dst, wad); - - return true; - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/CoveredCallFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/CoveredCallFinancialProductLibrary.sol deleted file mode 100644 index 103989865..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/CoveredCallFinancialProductLibrary.sol +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; -import "./FinancialProductLibrary.sol"; -import "../../../../common/implementation/Lockable.sol"; - -/** - * @title Covered Call Financial Product Library. - * @notice Adds custom price transformation logic to modify the behavior of the expiring multi party contract. The - * contract holds say 1 WETH in collateral and pays out a portion of that, at expiry, if ETHUSD is above a set strike. If - * ETHUSD is below that strike, the contract pays out 0. The fraction paid out if above the strike is defined by - * (oraclePrice - strikePrice) / oraclePrice; - * Example: expiry is DEC 31. Strike is $400. Each token is backed by 1 WETH. - * If ETHUSD = $600 at expiry, the call is $200 in the money, and the contract pays out 0.333 WETH (worth $200). - * If ETHUSD = $800 at expiry, the call is $400 in the money, and the contract pays out 0.5 WETH (worth $400). - * If ETHUSD =< $400 at expiry, the call is out of the money, and the contract pays out 0 WETH. - */ -contract CoveredCallFinancialProductLibrary is FinancialProductLibrary, Lockable { - using FixedPoint for FixedPoint.Unsigned; - - mapping(address => FixedPoint.Unsigned) private financialProductStrikes; - - /** - * @notice Enables any address to set the strike price for an associated financial product. - * @param financialProduct address of the financial product. - * @param strikePrice the strike price for the covered call to be applied to the financial product. - * @dev Note: a) Any address can set the initial strike price b) A strike price cannot be 0. - * c) A strike price can only be set once to prevent the deployer from changing the strike after the fact. - * d) For safety, a strike price should be set before depositing any synthetic tokens in a liquidity pool. - * e) financialProduct must expose an expirationTimestamp method. - */ - function setFinancialProductStrike( - address financialProduct, - FixedPoint.Unsigned memory strikePrice - ) public nonReentrant { - require(strikePrice.isGreaterThan(0), "Cant set 0 strike"); - require(financialProductStrikes[financialProduct].isEqual(0), "Strike already set"); - require(ExpiringContractInterface(financialProduct).expirationTimestamp() != 0, "Invalid EMP contract"); - financialProductStrikes[financialProduct] = strikePrice; - } - - /** - * @notice Returns the strike price associated with a given financial product address. - * @param financialProduct address of the financial product. - * @return strikePrice for the associated financial product. - */ - function getStrikeForFinancialProduct( - address financialProduct - ) public view nonReentrantView returns (FixedPoint.Unsigned memory) { - return financialProductStrikes[financialProduct]; - } - - /** - * @notice Returns a transformed price by applying the call option payout structure. - * @param oraclePrice price from the oracle to be transformed. - * @param requestTime timestamp the oraclePrice was requested at. - * @return transformedPrice the input oracle price with the price transformation logic applied to it. - */ - function transformPrice( - FixedPoint.Unsigned memory oraclePrice, - uint256 requestTime - ) public view override nonReentrantView returns (FixedPoint.Unsigned memory) { - FixedPoint.Unsigned memory strike = financialProductStrikes[msg.sender]; - require(strike.isGreaterThan(0), "Caller has no strike"); - // If price request is made before expiry, return 1. Thus we can keep the contract 100% collateralized with - // each token backed 1:1 by collateral currency. - if (requestTime < ExpiringContractInterface(msg.sender).expirationTimestamp()) { - return FixedPoint.fromUnscaledUint(1); - } - if (oraclePrice.isLessThanOrEqual(strike)) { - return FixedPoint.fromUnscaledUint(0); - } else { - // Token expires to be worth the fraction of a collateral token that's in the money. - // eg if ETHUSD is $500 and strike is $400, token is redeemable for 100/500 = 0.2 WETH (worth $100). - // Note: oraclePrice cannot be 0 here because it would always satisfy the if above because 0 <= x is always - // true. - return (oraclePrice.sub(strike)).div(oraclePrice); - } - } - - /** - * @notice Returns a transformed collateral requirement by applying the covered call payout structure. - * @return transformedCollateralRequirement the input collateral requirement with the transformation logic applied to it. - */ - function transformCollateralRequirement( - FixedPoint.Unsigned memory, - FixedPoint.Unsigned memory - ) public view override nonReentrantView returns (FixedPoint.Unsigned memory) { - FixedPoint.Unsigned memory strike = financialProductStrikes[msg.sender]; - require(strike.isGreaterThan(0), "Caller has no strike"); - - // Always return 1 because option must be collateralized by 1 token. - return FixedPoint.fromUnscaledUint(1); - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/FinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/FinancialProductLibrary.sol deleted file mode 100644 index ac7de39b0..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/FinancialProductLibrary.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; -import "../../../../common/implementation/FixedPoint.sol"; - -interface ExpiringContractInterface { - function expirationTimestamp() external view returns (uint256); -} - -/** - * @title Financial product library contract - * @notice Provides price and collateral requirement transformation interfaces that can be overridden by custom - * Financial product library implementations. - */ -abstract contract FinancialProductLibrary { - using FixedPoint for FixedPoint.Unsigned; - - /** - * @notice Transforms a given oracle price using the financial product libraries transformation logic. - * @param oraclePrice input price returned by the DVM to be transformed. - * @return transformedOraclePrice input oraclePrice with the transformation function applied. - */ - function transformPrice( - FixedPoint.Unsigned memory oraclePrice, - uint256 - ) public view virtual returns (FixedPoint.Unsigned memory) { - return oraclePrice; - } - - /** - * @notice Transforms a given collateral requirement using the financial product libraries transformation logic. - * @param collateralRequirement input collateral requirement to be transformed. - * @return transformedCollateralRequirement input collateral requirement with the transformation function applied. - */ - function transformCollateralRequirement( - FixedPoint.Unsigned memory, - FixedPoint.Unsigned memory collateralRequirement - ) public view virtual returns (FixedPoint.Unsigned memory) { - return collateralRequirement; - } - - /** - * @notice Transforms a given price identifier using the financial product libraries transformation logic. - * @param priceIdentifier input price identifier defined for the financial contract. - * @return transformedPriceIdentifier input price identifier with the transformation function applied. - */ - function transformPriceIdentifier(bytes32 priceIdentifier, uint256) public view virtual returns (bytes32) { - return priceIdentifier; - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/KpiOptionsFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/KpiOptionsFinancialProductLibrary.sol deleted file mode 100644 index 66550eacf..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/KpiOptionsFinancialProductLibrary.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; -import "./FinancialProductLibrary.sol"; -import "../../../../common/implementation/Lockable.sol"; - -/** - * @title KPI Options Financial Product Library - * @notice Adds custom tranformation logic to modify the price and collateral requirement behavior of the expiring multi party contract. - * If a price request is made pre-expiry, the price should always be set to 2 and the collateral requirement should be set to 1. - * Post-expiry, the collateral requirement is left as 1 and the price is left unchanged. - */ -contract KpiOptionsFinancialProductLibrary is FinancialProductLibrary, Lockable { - /** - * @notice Returns a transformed price for pre-expiry price requests. - * @param oraclePrice price from the oracle to be transformed. - * @param requestTime timestamp the oraclePrice was requested at. - * @return transformedPrice the input oracle price with the price transformation logic applied to it. - */ - function transformPrice( - FixedPoint.Unsigned memory oraclePrice, - uint256 requestTime - ) public view override nonReentrantView returns (FixedPoint.Unsigned memory) { - // If price request is made before expiry, return 2. Thus we can keep the contract 100% collateralized with - // each token backed 1:2 by collateral currency. Post-expiry, leave unchanged. - if (requestTime < ExpiringContractInterface(msg.sender).expirationTimestamp()) { - return FixedPoint.fromUnscaledUint(2); - } else { - return oraclePrice; - } - } - - /** - * @notice Returns a transformed collateral requirement that is set to be equivalent to 2 tokens pre-expiry. - * @return transformedCollateralRequirement the input collateral requirement with the transformation logic applied to it. - */ - function transformCollateralRequirement( - FixedPoint.Unsigned memory, - FixedPoint.Unsigned memory - ) public view override nonReentrantView returns (FixedPoint.Unsigned memory) { - // Always return 1. - return FixedPoint.fromUnscaledUint(1); - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/PostExpirationIdentifierTransformationFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/PostExpirationIdentifierTransformationFinancialProductLibrary.sol deleted file mode 100644 index a8359d9f1..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/PostExpirationIdentifierTransformationFinancialProductLibrary.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; -import "./FinancialProductLibrary.sol"; -import "../../../../common/implementation/Lockable.sol"; - -/** - * @title Post-Expiration Identifier Transformation Financial Product Library - * @notice Adds custom identifier transformation to enable a financial contract to use two different identifiers, depending - * on when a price request is made. If the request is made at or after expiration then a transformation is made to the identifier - * & if it is before expiration then the original identifier is returned. This library enables self referential - * TWAP identifier to be used on synthetics pre-expiration, in conjunction with a separate identifier at expiration. - */ -contract PostExpirationIdentifierTransformationFinancialProductLibrary is FinancialProductLibrary, Lockable { - mapping(address => bytes32) financialProductTransformedIdentifiers; - - /** - * @notice Enables the deployer of the library to set the transformed identifier for an associated financial product. - * @param financialProduct address of the financial product. - * @param transformedIdentifier the identifier for the financial product to be used if the contract is post expiration. - * @dev Note: a) Any address can set identifier transformations b) The identifier can't be set to blank. c) A - * transformed price can only be set once to prevent the deployer from changing it after the fact. d) financialProduct - * must expose an expirationTimestamp method. - */ - function setFinancialProductTransformedIdentifier( - address financialProduct, - bytes32 transformedIdentifier - ) public nonReentrant { - require(transformedIdentifier != "", "Cant set to empty transformation"); - require(financialProductTransformedIdentifiers[financialProduct] == "", "Transformation already set"); - require(ExpiringContractInterface(financialProduct).expirationTimestamp() != 0, "Invalid EMP contract"); - financialProductTransformedIdentifiers[financialProduct] = transformedIdentifier; - } - - /** - * @notice Returns the transformed identifier associated with a given financial product address. - * @param financialProduct address of the financial product. - * @return transformed identifier for the associated financial product. - */ - function getTransformedIdentifierForFinancialProduct( - address financialProduct - ) public view nonReentrantView returns (bytes32) { - return financialProductTransformedIdentifiers[financialProduct]; - } - - /** - * @notice Returns a transformed price identifier if the contract is post-expiration and no transformation if pre. - * @param identifier input price identifier to be transformed. - * @param requestTime timestamp the identifier is to be used at. - * @return transformedPriceIdentifier the input price identifier with the transformation logic applied to it. - */ - function transformPriceIdentifier( - bytes32 identifier, - uint256 requestTime - ) public view override nonReentrantView returns (bytes32) { - require(financialProductTransformedIdentifiers[msg.sender] != "", "Caller has no transformation"); - // If the request time is after contract expiration then return the transformed identifier. Else, return the - // original price identifier. - if (requestTime < ExpiringContractInterface(msg.sender).expirationTimestamp()) { - return identifier; - } else { - return financialProductTransformedIdentifiers[msg.sender]; - } - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/PreExpirationIdentifierTransformationFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/PreExpirationIdentifierTransformationFinancialProductLibrary.sol deleted file mode 100644 index 81f28a4b9..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/PreExpirationIdentifierTransformationFinancialProductLibrary.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; -import "./FinancialProductLibrary.sol"; -import "../../../../common/implementation/Lockable.sol"; - -/** - * @title Pre-Expiration Identifier Transformation Financial Product Library - * @notice Adds custom identifier transformation to enable a financial contract to use two different identifiers, depending - * on when a price request is made. If the request is made before expiration then a transformation is made to the identifier - * & if it is at or after expiration then the original identifier is returned. This library enables self referential - * TWAP identifier to be used on synthetics pre-expiration, in conjunction with a separate identifier at expiration. - */ -contract PreExpirationIdentifierTransformationFinancialProductLibrary is FinancialProductLibrary, Lockable { - mapping(address => bytes32) financialProductTransformedIdentifiers; - - /** - * @notice Enables the deployer of the library to set the transformed identifier for an associated financial product. - * @param financialProduct address of the financial product. - * @param transformedIdentifier the identifier for the financial product to be used if the contract is pre expiration. - * @dev Note: a) Any address can set identifier transformations b) The identifier can't be set to blank. c) A - * transformed price can only be set once to prevent the deployer from changing it after the fact. d) financialProduct - * must expose an expirationTimestamp method. - */ - function setFinancialProductTransformedIdentifier( - address financialProduct, - bytes32 transformedIdentifier - ) public nonReentrant { - require(transformedIdentifier != "", "Cant set to empty transformation"); - require(financialProductTransformedIdentifiers[financialProduct] == "", "Transformation already set"); - require(ExpiringContractInterface(financialProduct).expirationTimestamp() != 0, "Invalid EMP contract"); - financialProductTransformedIdentifiers[financialProduct] = transformedIdentifier; - } - - /** - * @notice Returns the transformed identifier associated with a given financial product address. - * @param financialProduct address of the financial product. - * @return transformed identifier for the associated financial product. - */ - function getTransformedIdentifierForFinancialProduct( - address financialProduct - ) public view nonReentrantView returns (bytes32) { - return financialProductTransformedIdentifiers[financialProduct]; - } - - /** - * @notice Returns a transformed price identifier if the contract is pre-expiration and no transformation if post. - * @param identifier input price identifier to be transformed. - * @param requestTime timestamp the identifier is to be used at. - * @return transformedPriceIdentifier the input price identifier with the transformation logic applied to it. - */ - function transformPriceIdentifier( - bytes32 identifier, - uint256 requestTime - ) public view override nonReentrantView returns (bytes32) { - require(financialProductTransformedIdentifiers[msg.sender] != "", "Caller has no transformation"); - // If the request time is before contract expiration then return the transformed identifier. Else, return the - // original price identifier. - if (requestTime < ExpiringContractInterface(msg.sender).expirationTimestamp()) { - return financialProductTransformedIdentifiers[msg.sender]; - } else { - return identifier; - } - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/StructuredNoteFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/StructuredNoteFinancialProductLibrary.sol deleted file mode 100644 index 3afa17855..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/expiring-multiparty-libraries/StructuredNoteFinancialProductLibrary.sol +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; -import "./FinancialProductLibrary.sol"; -import "@openzeppelin/contracts-v4/access/Ownable.sol"; -import "../../../../common/implementation/Lockable.sol"; - -/** - * @title Structured Note Financial Product Library - * @notice Adds custom price transformation logic to modify the behavior of the expiring multi party contract. The - * contract holds say 1 WETH in collateral and pays out that 1 WETH if, at expiry, ETHUSD is below a set strike. If - * ETHUSD is above that strike, the contract pays out a given dollar amount of ETH. - * Example: expiry is DEC 31. Strike is $400. Each token is backed by 1 WETH - * If ETHUSD < $400 at expiry, token is redeemed for 1 ETH. - * If ETHUSD >= $400 at expiry, token is redeemed for $400 worth of ETH, as determined by the DVM. - */ -contract StructuredNoteFinancialProductLibrary is FinancialProductLibrary, Ownable, Lockable { - using FixedPoint for FixedPoint.Unsigned; - - mapping(address => FixedPoint.Unsigned) financialProductStrikes; - - /** - * @notice Enables the deployer of the library to set the strike price for an associated financial product. - * @param financialProduct address of the financial product. - * @param strikePrice the strike price for the structured note to be applied to the financial product. - * @dev Note: a) Only the owner (deployer) of this library can set new strike prices b) A strike price cannot be 0. - * c) A strike price can only be set once to prevent the deployer from changing the strike after the fact. - * d) financialProduct must exposes an expirationTimestamp method. - */ - function setFinancialProductStrike( - address financialProduct, - FixedPoint.Unsigned memory strikePrice - ) public onlyOwner nonReentrant { - require(strikePrice.isGreaterThan(0), "Cant set 0 strike"); - require(financialProductStrikes[financialProduct].isEqual(0), "Strike already set"); - require(ExpiringContractInterface(financialProduct).expirationTimestamp() != 0, "Invalid EMP contract"); - financialProductStrikes[financialProduct] = strikePrice; - } - - /** - * @notice Returns the strike price associated with a given financial product address. - * @param financialProduct address of the financial product. - * @return strikePrice for the associated financial product. - */ - function getStrikeForFinancialProduct( - address financialProduct - ) public view nonReentrantView returns (FixedPoint.Unsigned memory) { - return financialProductStrikes[financialProduct]; - } - - /** - * @notice Returns a transformed price by applying the structured note payout structure. - * @param oraclePrice price from the oracle to be transformed. - * @param requestTime timestamp the oraclePrice was requested at. - * @return transformedPrice the input oracle price with the price transformation logic applied to it. - */ - function transformPrice( - FixedPoint.Unsigned memory oraclePrice, - uint256 requestTime - ) public view override nonReentrantView returns (FixedPoint.Unsigned memory) { - FixedPoint.Unsigned memory strike = financialProductStrikes[msg.sender]; - require(strike.isGreaterThan(0), "Caller has no strike"); - // If price request is made before expiry, return 1. Thus we can keep the contract 100% collateralized with - // each token backed 1:1 by collateral currency. - if (requestTime < ExpiringContractInterface(msg.sender).expirationTimestamp()) { - return FixedPoint.fromUnscaledUint(1); - } - if (oraclePrice.isLessThan(strike)) { - return FixedPoint.fromUnscaledUint(1); - } else { - // Token expires to be worth strike $ worth of collateral. - // eg if ETHUSD is $500 and strike is $400, token is redeemable for 400/500 = 0.8 WETH. - return strike.div(oraclePrice); - } - } - - /** - * @notice Returns a transformed collateral requirement by applying the structured note payout structure. If the price - * of the structured note is greater than the strike then the collateral requirement scales down accordingly. - * @param oraclePrice price from the oracle to transform the collateral requirement. - * @param collateralRequirement financial products collateral requirement to be scaled according to price and strike. - * @return transformedCollateralRequirement the input collateral requirement with the transformation logic applied to it. - */ - function transformCollateralRequirement( - FixedPoint.Unsigned memory oraclePrice, - FixedPoint.Unsigned memory collateralRequirement - ) public view override nonReentrantView returns (FixedPoint.Unsigned memory) { - FixedPoint.Unsigned memory strike = financialProductStrikes[msg.sender]; - require(strike.isGreaterThan(0), "Caller has no strike"); - // If the price is less than the strike than the original collateral requirement is used. - if (oraclePrice.isLessThan(strike)) { - return collateralRequirement; - } else { - // If the price is more than the strike then the collateral requirement is scaled by the strike. For example - // a strike of $400 and a CR of 1.2 would yield: - // ETHUSD = $350, payout is 1 WETH. CR is multiplied by 1. resulting CR = 1.2 - // ETHUSD = $400, payout is 1 WETH. CR is multiplied by 1. resulting CR = 1.2 - // ETHUSD = $425, payout is 0.941 WETH (worth $400). CR is multiplied by 0.941. resulting CR = 1.1292 - // ETHUSD = $500, payout is 0.8 WETH (worth $400). CR multiplied by 0.8. resulting CR = 0.96 - return collateralRequirement.mul(strike.div(oraclePrice)); - } - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/BinaryOptionLongShortPairFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/BinaryOptionLongShortPairFinancialProductLibrary.sol deleted file mode 100644 index 341dbca63..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/BinaryOptionLongShortPairFinancialProductLibrary.sol +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; - -import "./LongShortPairFinancialProductLibrary.sol"; -import "../../../../common/implementation/Lockable.sol"; - -/** - * @title Binary Option Long Short Pair Financial Product Library. - * @notice Adds settlement logic to binary option LSPs. Binary options settle with all collateral allocated to - * either the long or short side, depending on the settlement price. They can be used to make prediction markets or any - * kind of binary bet. Settlement is defined using a strike price which informs which side of the bet was correct. If - * settlement price is greater or equal to the strike then all value is sent to the long side. Otherwise, all value - * is sent to the short side. The settlement price could be a scalar (like the price of ETH) or a binary bet with - * settlement being 0 or 1 depending on the outcome. - */ -contract BinaryOptionLongShortPairFinancialProductLibrary is LongShortPairFinancialProductLibrary, Lockable { - using FixedPoint for FixedPoint.Unsigned; - using SafeMath for uint256; - - struct BinaryLongShortPairParameters { - bool isSet; - int256 strikePrice; - } - - mapping(address => BinaryLongShortPairParameters) public longShortPairParameters; - - /** - * @notice Enables any address to set the strike price for an associated binary option. - * @param longShortPair address of the LSP. - * @param strikePrice the strike price for the binary option. - * @dev Note: a) Any address can set the initial strike price b) A strike can be 0. - * c) A strike price can only be set once to prevent the deployer from changing the strike after the fact. - * d) For safety, a strike price should be set before depositing any synthetic tokens in a liquidity pool. - * e) longShortPair must expose an expirationTimestamp method to validate it is correctly deployed. - */ - function setLongShortPairParameters(address longShortPair, int256 strikePrice) public nonReentrant { - require(ExpiringContractInterface(longShortPair).expirationTimestamp() != 0, "Invalid LSP address"); - require(!longShortPairParameters[longShortPair].isSet, "Parameters already set"); - - longShortPairParameters[longShortPair] = BinaryLongShortPairParameters({ - isSet: true, - strikePrice: strikePrice - }); - } - - /** - * @notice Returns a number between 0 and 1e18 to indicate how much collateral each long and short token are entitled - * to per collateralPerPair. - * @param expiryPrice price from the optimistic oracle for the LSP price identifier. - * @return expiryPercentLong to indicate how much collateral should be sent between long and short tokens. - */ - function percentageLongCollateralAtExpiry( - int256 expiryPrice - ) public view override nonReentrantView returns (uint256) { - BinaryLongShortPairParameters memory params = longShortPairParameters[msg.sender]; - require(params.isSet, "Params not set for calling LSP"); - - if (expiryPrice >= params.strikePrice) return FixedPoint.fromUnscaledUint(1).rawValue; - else return FixedPoint.fromUnscaledUint(0).rawValue; - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/CappedYieldDollarLongShortPairFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/CappedYieldDollarLongShortPairFinancialProductLibrary.sol deleted file mode 100644 index d5227baf2..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/CappedYieldDollarLongShortPairFinancialProductLibrary.sol +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/utils/math/Math.sol"; - -import "./LongShortPairFinancialProductLibrary.sol"; -import "../../../../common/implementation/Lockable.sol"; - -/** - * @title Capped Yield Dollar Long Short Pair Financial Product Library - * @notice Adds settlement logic to create Yield Dollar LSPs. A range bond is the combination of a Yield dollar and short - * put option enabling the token sponsor to issue structured products to unlock DeFi treasuries. This library is like - * a Range Bond, but with no embedded call option. - * A Capped Yield Dollar is defined as = Yield Dollar - Put Option. In order for the Capped Yield Dollar to be fully - * collateralized and non-liquidatable, there is a low price for the collateral token below which the Capped Yield Dollar - * will be worth < $1. - * Numerically this is found using: - * N = Notional of bond - * P = price of token - * T = number of tokens - * R1 = low price range - * C = collateral per pair, should be N/R1 - * T = min(1,(R1/P)*C) - * If you want a yield dollar denominated as N = $1, you should set C to 1/R1. In that case, T = min(1,1/P). - * - At any price below the low price range (R1) the long side effectively holds a fixed number of collateral equal to - * collateralPerPair from the LSP with the value of expiryPercentLong = 1. This is the max payout in collateral. - * - Any price equal to or above R1 gives a payout equivalent to a yield dollar (bond) of notional N. In this range the - * expiryPercentLong shifts to keep the payout in dollar terms equal to the bond notional. - * With this equation, the contract deployer does not need to specify the bond notional N. The notional can be calculated - * by taking R1*collateralPerPair from the LSP. - */ -contract CappedYieldDollarLongShortPairFinancialProductLibrary is LongShortPairFinancialProductLibrary, Lockable { - using FixedPoint for FixedPoint.Unsigned; - - mapping(address => uint256) public lowPriceRanges; - - /** - * @notice Enables any address to set the low price range for an associated financial product. - * @param longShortPair address of the LSP contract. - * @param lowPriceRange low price range below which the payout transforms from a yield dollar to a short put option. - * @dev above the lowPriceRange the contract will payout a fixed amount of - * lowPriceRange*collateralPerPair (i.e the "notional" of the yield dollar). - * @dev Note: a) Any address can set these parameters b) existing LSP parameters for address not set. - * c) low price range can only be set once to prevent the deployer from changing the parameters after the fact. - * d) For safety, a low price range should be set before depositing any synthetic tokens in a liquidity pool. - * e) longShortPair must expose an expirationTimestamp method to validate it is correctly deployed. - */ - function setLongShortPairParameters(address longShortPair, uint256 lowPriceRange) public nonReentrant { - require(ExpiringContractInterface(longShortPair).expirationTimestamp() != 0, "Invalid LSP address"); - require(lowPriceRanges[longShortPair] == 0, "Parameters already set"); - - lowPriceRanges[longShortPair] = lowPriceRange; - } - - /** - * @notice Returns a number between 0 and 1e18 to indicate how much collateral each long and short token are - * entitled to per collateralPerPair. - * @param expiryPrice price from the optimistic oracle for the LSP price identifier. - * @return expiryPercentLong to indicate how much collateral should be sent between long and short tokens. - */ - function percentageLongCollateralAtExpiry( - int256 expiryPrice - ) public view override nonReentrantView returns (uint256) { - uint256 contractLowPriceRange = lowPriceRanges[msg.sender]; - require(contractLowPriceRange != 0, "Params not set for calling LSP"); - - // This function returns a value between 0 and 1e18 to be used in conjunction with the LSP collateralPerPair - // that allocates collateral between the short and long tokens on expiry. This can be simplified by considering - // the price in two discrete ranges: 1) below the low price range, 2) above the low price range. - uint256 positiveExpiryPrice = expiryPrice > 0 ? uint256(expiryPrice) : 0; - - // For expiry prices below lower bound, return expiryPercentLong = 1 (full position) - if (positiveExpiryPrice <= contractLowPriceRange) return FixedPoint.fromUnscaledUint(1).rawValue; - // For expiry prices above lower bound. For example, if the lower bound of Sushi is $4, collateral per pair - // is 0.25, and the expiry price is $12, the payout will be (4/12)*0.25, or .08333 Sushi. With Sushi at $12, - // .08333 Sushi is equal to $1. - return FixedPoint.Unsigned(contractLowPriceRange).div(FixedPoint.Unsigned(positiveExpiryPrice)).rawValue; - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/CoveredCallLongShortPairFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/CoveredCallLongShortPairFinancialProductLibrary.sol deleted file mode 100644 index 85ba17856..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/CoveredCallLongShortPairFinancialProductLibrary.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; - -import "./LongShortPairFinancialProductLibrary.sol"; -import "../../../../common/implementation/Lockable.sol"; - -/** - * @title Covered call Long Short Pair Financial Product Library. - * @notice Adds settlement logic to create covered call LSPs. The contract will payout a scaled amount of collateral - * depending on where the settlement price lands relative to the call's strike price. If the settlement is below the - * strike price then longs expire worthless. If the settlement is above the strike then the payout is the fraction above - * the strike defined by (expiryPrice - strikePrice) / expiryPrice. For example, consider a covered call option - * collateralized in ETH, with a strike a price of 3000. - * - If the price is less than 3000 then the each long is worth 0 and each short is worth collateralPerPair. - * - If the price is more than 3000 then each long is worth the fraction of collateralPerPair that was in the money and - * each short is worth the remaining collateralPerPair. - * - Say settlement price is 3500. Then expiryPercentLong = (3500 - 3000) / 3500 = 0.143. The value of this 0.143 ETH - * is worth 0.143*3500=500 which is the percentage of the collateralPerPair that was above the strike price. - */ -contract CoveredCallLongShortPairFinancialProductLibrary is LongShortPairFinancialProductLibrary, Lockable { - using FixedPoint for FixedPoint.Unsigned; - using SafeMath for uint256; - - mapping(address => uint256) public longShortPairStrikePrices; - - /** - * @notice Enables any address to set the strike price for an associated LSP. - * @param longShortPair address of the LSP. - * @param strikePrice the strike price for the covered call for the associated LSP. - * @dev Note: a) Any address can set the initial strike price b) A strike price cannot be 0. - * c) A strike price can only be set once to prevent the deployer from changing the strike after the fact. - * d) For safety, a strike price should be set before depositing any synthetic tokens in a liquidity pool. - * e) longShortPair must expose an expirationTimestamp method to validate it is correctly deployed. - */ - function setLongShortPairParameters(address longShortPair, uint256 strikePrice) public nonReentrant { - require(ExpiringContractInterface(longShortPair).expirationTimestamp() != 0, "Invalid LSP address"); - require(longShortPairStrikePrices[longShortPair] == 0, "Parameters already set"); - - longShortPairStrikePrices[longShortPair] = strikePrice; - } - - /** - * @notice Returns a number between 0 and 1e18 to indicate how much collateral each long and short token are entitled - * to per collateralPerPair. - * @param expiryPrice price from the optimistic oracle for the LSP price identifier. - * @return expiryPercentLong to indicate how much collateral should be sent between long and short tokens. - */ - function percentageLongCollateralAtExpiry( - int256 expiryPrice - ) public view override nonReentrantView returns (uint256) { - uint256 contractStrikePrice = longShortPairStrikePrices[msg.sender]; - require(contractStrikePrice != 0, "Params not set for calling LSP"); - - // If the expiry price is less than the strike price then the long options expire worthless (out of the money). - // Note we do not consider negative expiry prices in this call option implementation. - if (expiryPrice < 0 || uint256(expiryPrice) < contractStrikePrice) - return FixedPoint.fromUnscaledUint(0).rawValue; - - // Else, token expires to be worth the fraction of a collateral token that's in the money. eg if ETH is $3500 - // and strike is $3000, long token is redeemable for (3500-3000)/3500 = 0.143 WETH which is worth $500 and the - // short token is worth the remaining 0.8. This is strictly < 1, tending to 1 as the expiry tends to infinity. - return - (FixedPoint.Unsigned(uint256(expiryPrice)).sub(FixedPoint.Unsigned(contractStrikePrice))) - .div(FixedPoint.Unsigned(uint256(expiryPrice))) - .rawValue; - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/FlooredLinearLongShortPairFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/FlooredLinearLongShortPairFinancialProductLibrary.sol deleted file mode 100644 index b81e6b2e7..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/FlooredLinearLongShortPairFinancialProductLibrary.sol +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/utils/math/SignedSafeMath.sol"; - -import "./LongShortPairFinancialProductLibrary.sol"; -import "../../../../common/implementation/Lockable.sol"; - -/** - * @title Floored Linear Long Short Pair Financial Product Library. - * @notice Adds settlement logic to create floored linear LSPs. The contract will payout a scaled amount of collateral - * depending on where the settlement price lands within a price range between a lowerBound and an upperBound with - * configured minimum floorPercentage payout for all prices below lowerBound. If settlement price is within the price - * range then the expiryPercentLong is defined by (expiryPrice - lowerBound) / (upperBound - lowerBound) * - * (1 - floorPercentage) + floorPercentage. This number represents the amount of collateral from the collateralPerPair - * that will be sent to the long side. If the price is at or higher than the upperBound then expiryPercentLong = 1. - * If the price is at or lower than the lowerBound then expiryPercentLong = floorPercentage. For example, consider a - * floored linear LSP on the price of ETH collateralized in USDC with an upperBound = 4000, lowerBound = 2000 and - * floorPercentage = 20% with a collateralPerPair of 1000 (i.e each pair of long and shorts is worth 1000 USDC). At - * settlement the expiryPercentLong would equal 1 (each long worth 1000 and short worth 0) if ETH price was >= 4000 and - * it would equal 0.2 if <= 2000 (each long is worth minimum 200 and each short is worth maximum 800). If between the - * two (say 3500) then expiryPercentLong = (3500 - 2000) / (4000 - 2000) * (1 - 0.2) + 0.2 = 0.8. Therefore each long - * is worth 800 and each short is worth 200. - */ -contract FlooredLinearLongShortPairFinancialProductLibrary is LongShortPairFinancialProductLibrary, Lockable { - using FixedPoint for FixedPoint.Unsigned; - using SignedSafeMath for int256; - - struct LinearLongShortPairParameters { - int256 upperBound; - int256 lowerBound; - uint256 floorPercentage; - } - - mapping(address => LinearLongShortPairParameters) public longShortPairParameters; - - /** - * @notice Enables any address to set the parameters for an associated financial product. - * @param longShortPair address of the LSP contract. - * @param upperBound the upper price that the linear LSP will operate within. - * @param lowerBound the lower price that the linear LSP will operate within. - * @param floorPercentage the lowest possible payout percentage from collateralPerPair to each long token expressed - * with 1e18 decimals. E.g., a 20% floor percentage should be expressed as 200000000000000000. - * @dev Note: a) Any address can set these parameters b) existing LSP parameters for address not set. - * c) upperBound > lowerBound. - * d) floorPercentage <= 1e18 (no need to check >= 0 as floorPercentage is unsigned). - * e) parameters can only be set once to prevent the deployer from changing the parameters after the fact. - * f) For safety, parameters should be set before depositing any synthetic tokens in a liquidity pool. - * g) longShortPair must expose an expirationTimestamp method to validate it is correctly deployed. - */ - function setLongShortPairParameters( - address longShortPair, - int256 upperBound, - int256 lowerBound, - uint256 floorPercentage - ) public nonReentrant { - require(ExpiringContractInterface(longShortPair).expirationTimestamp() != 0, "Invalid LSP address"); - require(upperBound > lowerBound, "Invalid bounds"); - require(floorPercentage <= 1e18, "Invalid floor percentage"); - - LinearLongShortPairParameters memory params = longShortPairParameters[longShortPair]; - require(params.upperBound == 0 && params.lowerBound == 0, "Parameters already set"); - - longShortPairParameters[longShortPair] = LinearLongShortPairParameters({ - upperBound: upperBound, - lowerBound: lowerBound, - floorPercentage: floorPercentage - }); - } - - /** - * @notice Returns a number between floorPercentage and 1e18 to indicate how much collateral each long and short - * token is entitled to per collateralPerPair. - * @param expiryPrice price from the optimistic oracle for the LSP price identifier. - * @return expiryPercentLong to indicate how much collateral should be sent between long and short tokens. - */ - function percentageLongCollateralAtExpiry( - int256 expiryPrice - ) public view override nonReentrantView returns (uint256) { - LinearLongShortPairParameters memory params = longShortPairParameters[msg.sender]; - require(params.upperBound != 0 || params.lowerBound != 0, "Params not set for calling LSP"); - - if (expiryPrice >= params.upperBound) return 1e18; - - if (expiryPrice <= params.lowerBound) return params.floorPercentage; - - // if not exceeding bounds, expiryPercentLong = (expiryPrice - lowerBound) / (upperBound - lowerBound) * - // (1 - floorPercentage) + floorPercentage - return - FixedPoint - .Unsigned(uint256(expiryPrice - params.lowerBound)) - .div(FixedPoint.Unsigned(uint256(params.upperBound - params.lowerBound))) - .mul(FixedPoint.Unsigned(1e18 - params.floorPercentage)) - .add(FixedPoint.Unsigned(params.floorPercentage)) - .rawValue; - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/LinearLongShortPairFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/LinearLongShortPairFinancialProductLibrary.sol deleted file mode 100644 index ff7e5cf5e..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/LinearLongShortPairFinancialProductLibrary.sol +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/utils/math/SignedSafeMath.sol"; - -import "./LongShortPairFinancialProductLibrary.sol"; -import "../../../../common/implementation/Lockable.sol"; - -/** - * @title Linear Long Short Pair Financial Product Library. - * @notice Adds settlement logic to create linear LSPs. The contract will payout a scaled amount of collateral - * depending on where the settlement price lands within a price range between an upperBound and a lowerBound. If - * settlement price is within the price range then the expiryPercentLong is defined by - * (expiryPrice - lowerBound) / (upperBound - lowerBound). This number represent the amount of collateral from the - * collateralPerPair that will be sent to the long and short side. If the price is higher than the upperBound then - * expiryPercentLong = 1. if the price is lower than the lower bound then expiryPercentLong = 0. For example, consider - * a linear LSP on the price of ETH collateralized in USDC with an upperBound = 4000 and lowerBound = 2000 with a - * collateralPerPair of 1000 (i.e each pair of long and shorts is worth 1000 USDC). At settlement the expiryPercentLong - * would equal 1 (each long worth 1000 and short worth 0) if ETH price was > 4000 and it would equal 0 if < 2000 - * (each long is worthless and each short is worth 1000). If between the two (say 3500) then expiryPercentLong - * = (3500 - 2000) / (4000 - 2000) = 0.75. Therefore each long is worth 750 and each short is worth 250. - */ -contract LinearLongShortPairFinancialProductLibrary is LongShortPairFinancialProductLibrary, Lockable { - using FixedPoint for FixedPoint.Unsigned; - using SignedSafeMath for int256; - - struct LinearLongShortPairParameters { - int256 upperBound; - int256 lowerBound; - } - - mapping(address => LinearLongShortPairParameters) public longShortPairParameters; - - /** - * @notice Enables any address to set the parameters for an associated financial product. - * @param longShortPair address of the LSP contract. - * @param upperBound the upper price that the linear LSP will operate within. - * @param lowerBound the lower price that the linear LSP will operate within. - * @dev Note: a) Any address can set these parameters b) existing LSP parameters for address not set. - * c) upperBound > lowerBound. - * d) parameters can only be set once to prevent the deployer from changing the parameters after the fact. - * e) For safety, parameters should be set before depositing any synthetic tokens in a liquidity pool. - * f) longShortPair must expose an expirationTimestamp method to validate it is correctly deployed. - */ - function setLongShortPairParameters( - address longShortPair, - int256 upperBound, - int256 lowerBound - ) public nonReentrant { - require(ExpiringContractInterface(longShortPair).expirationTimestamp() != 0, "Invalid LSP address"); - require(upperBound > lowerBound, "Invalid bounds"); - - LinearLongShortPairParameters memory params = longShortPairParameters[longShortPair]; - require(params.upperBound == 0 && params.lowerBound == 0, "Parameters already set"); - - longShortPairParameters[longShortPair] = LinearLongShortPairParameters({ - upperBound: upperBound, - lowerBound: lowerBound - }); - } - - /** - * @notice Returns a number between 0 and 1e18 to indicate how much collateral each long and short token is entitled - * to per collateralPerPair. - * @param expiryPrice price from the optimistic oracle for the LSP price identifier. - * @return expiryPercentLong to indicate how much collateral should be sent between long and short tokens. - */ - function percentageLongCollateralAtExpiry( - int256 expiryPrice - ) public view override nonReentrantView returns (uint256) { - LinearLongShortPairParameters memory params = longShortPairParameters[msg.sender]; - require(params.upperBound != 0 || params.lowerBound != 0, "Params not set for calling LSP"); - - if (expiryPrice >= params.upperBound) return FixedPoint.fromUnscaledUint(1).rawValue; - - if (expiryPrice <= params.lowerBound) return FixedPoint.fromUnscaledUint(0).rawValue; - - // if not exceeding bounds, expiryPercentLong = (expiryPrice - lowerBound) / (upperBound - lowerBound) - return - FixedPoint - .Unsigned(uint256(expiryPrice - params.lowerBound)) - .div(FixedPoint.Unsigned(uint256(params.upperBound - params.lowerBound))) - .rawValue; - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/LongShortPairFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/LongShortPairFinancialProductLibrary.sol deleted file mode 100644 index c9b705382..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/LongShortPairFinancialProductLibrary.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; -import "../../../../common/implementation/FixedPoint.sol"; -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; - -interface ExpiringContractInterface { - function expirationTimestamp() external view returns (uint256); -} - -abstract contract LongShortPairFinancialProductLibrary { - function percentageLongCollateralAtExpiry(int256 expiryPrice) public view virtual returns (uint256); -} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/RangeBondLongShortPairFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/RangeBondLongShortPairFinancialProductLibrary.sol deleted file mode 100644 index 9b183e2da..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/RangeBondLongShortPairFinancialProductLibrary.sol +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/utils/math/SignedSafeMath.sol"; -import "@openzeppelin/contracts-v4/utils/math/Math.sol"; - -import "./LongShortPairFinancialProductLibrary.sol"; -import "../../../../common/implementation/Lockable.sol"; - -/** - * @title Range Bond Long Short Pair Financial Product Library - * @notice Adds settlement logic to create range bond LSPs. A range bond is the combination of a Yield dollar, short put - * option and long call option enabling the token sponsor to issue structured products to unlock DeFi treasuries. - * A range bond is defined as = Yield Dollar - Put Option + Call option. Numerically this is found using: - * N = Notional of bond - * P = price of token - * T = number of tokens - * R1 = low price range - * R2 = high price range - * T = min(N/P,N/R1) + max((N/R2*(P-R2))/P,0) - * - At any price below the low price range (R1) the long side effectively holds a fixed number of collateral equal to - * collateralPerPair from the LSP with the value of expiryPercentLong = 1. This is the max payout in collateral. - * - Any price between R1 and R2 gives a payout equivalent to a yield dollar (bond) of notional N. In this range the - * expiryPercentLong shifts to keep the payout in dollar terms equal to the bond notional. - * - At any price above R2 the long holders are entitled to a fixed, minimum number of collateral equal to N/R2 with a - * expiryPercentLong=(N/R2)/collateralPerPair. - * The expression for the number of tokens paid out to the long side (T above) can be algebraically simplified, - * transformed to remove the notional and reframed to express the expiryPercentLong as [min(max(1/R2,1/P),1/R1)]/(1/R1) - * With this equation, the contract deployer does not need to specify the bond notional N. The notional can be calculated - * by taking R1*collateralPerPair from the LSP. - */ -contract RangeBondLongShortPairFinancialProductLibrary is LongShortPairFinancialProductLibrary, Lockable { - using FixedPoint for FixedPoint.Unsigned; - using SignedSafeMath for int256; - - struct RangeBondLongShortPairParameters { - uint256 highPriceRange; - uint256 lowPriceRange; - } - - mapping(address => RangeBondLongShortPairParameters) public longShortPairParameters; - - /** - * @notice Enables any address to set the parameters price for an associated financial product. - * @param longShortPair address of the LSP contract. - * @param highPriceRange high price range after which the payout transforms from a yield dollar to a call option. - * @param lowPriceRange low price range below which the payout transforms from a yield dollar to a short put option. - * @dev between highPriceRange and lowPriceRange the contract will payout a fixed amount of - * lowPriceRange*collateralPerPair (i.e the "notional" of the yield dollar). - * @dev Note: a) Any address can set these parameters b) existing LSP parameters for address not set. - * c) highPriceRange > lowPriceRange. - * d) parameters price can only be set once to prevent the deployer from changing the parameters after the fact. - * e) For safety, a parameters should be set before depositing any synthetic tokens in a liquidity pool. - * f) longShortPair must expose an expirationTimestamp method to validate it is correctly deployed. - */ - function setLongShortPairParameters( - address longShortPair, - uint256 highPriceRange, - uint256 lowPriceRange - ) public nonReentrant { - require(ExpiringContractInterface(longShortPair).expirationTimestamp() != 0, "Invalid LSP address"); - - require(highPriceRange > lowPriceRange, "Invalid bounds"); - - RangeBondLongShortPairParameters memory params = longShortPairParameters[longShortPair]; - require(params.highPriceRange == 0 && params.lowPriceRange == 0, "Parameters already set"); - - longShortPairParameters[longShortPair] = RangeBondLongShortPairParameters({ - highPriceRange: highPriceRange, - lowPriceRange: lowPriceRange - }); - } - - /** - * @notice Returns a number between 0 and 1e18 to indicate how much collateral each long and short token are - * entitled to per collateralPerPair. - * @param expiryPrice price from the optimistic oracle for the LSP price identifier. - * @return expiryPercentLong to indicate how much collateral should be sent between long and short tokens. - */ - function percentageLongCollateralAtExpiry( - int256 expiryPrice - ) public view override nonReentrantView returns (uint256) { - RangeBondLongShortPairParameters memory params = longShortPairParameters[msg.sender]; - require(params.highPriceRange != 0 || params.lowPriceRange != 0, "Params not set for calling LSP"); - - // This function returns a value between 0 and 1e18 to be used in conjunction with the LSP collateralPerPair - // that allocates collateral between the short and long tokens on expiry. This can be simplified by considering - // the price in three discrete ranges: 1) below the low price range, 2) between low and high range and 3) above - // the high price range. - uint256 positiveExpiryPrice = expiryPrice > 0 ? uint256(expiryPrice) : 0; - - // 1) The long position is entitled to the full position in this range. The short token is worth 0. - if (positiveExpiryPrice <= params.lowPriceRange) return FixedPoint.fromUnscaledUint(1).rawValue; - // 2) Within the range, the long position is entitled to the bond notional value which is equal to - // (collateral tokens * lowPriceRange). - if (positiveExpiryPrice <= params.highPriceRange) - return FixedPoint.Unsigned(params.lowPriceRange).div(FixedPoint.Unsigned(positiveExpiryPrice)).rawValue; - // 3) Above the range, the long position is entitled to a fixed number of tokens. - return FixedPoint.Unsigned(params.lowPriceRange).div(FixedPoint.Unsigned(params.highPriceRange)).rawValue; - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/SimpleSuccessTokenLongShortPairFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/SimpleSuccessTokenLongShortPairFinancialProductLibrary.sol deleted file mode 100644 index 97bf4a515..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/SimpleSuccessTokenLongShortPairFinancialProductLibrary.sol +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "./LongShortPairFinancialProductLibrary.sol"; -import "../../../../common/implementation/Lockable.sol"; - -/** - * @title Simple Success Token Long Short Pair Financial Product Library. - * @notice Adds settlement logic to create success token LSPs. A success token pays out 50% of collateral as a - * floor, with the remaining 50% functioning like an embedded covered call. - * If the settlement is below the strike price then longs are worth 50% of collateral. - * If the settlement is above the strike then the payout is equal to: - * 0.5 + (0.5 * (expiryPrice - strikePrice) / expiryPrice) - * For example, consider a covered call option collateralized in SUSHI, with a strike a price of $20, - * and collateralPerPair of 2. - * - If the price is less than $20 then the each long is worth 0.5 collateralPerPair and each short is worth 0.5 - * collateralPerPair. i.e., each long is worth 1 SUSHI (calls expire worthless). - * - If the price is more than $20 then each long is worth 0.5 collateralPerPair plus 0.5 times the fraction of - * collateralPerPair that was in the money, and each short is worth the remaining collateralPerPair. - * - Say settlement price is $30. Then expiryPercentLong = 0.5 + (0.5 * (30 - 20) / 30) = 0.6667. - * If the collateralPerPair is 2, that means the long payout is 0.6667*2 = 1.3333 $SUSHI, which at a settlement - * price of $30 is worth $40. This is equivalent to the value of 1 $SUSHI plus the value of the $20 strike - * embedded call. - */ -contract SimpleSuccessTokenLongShortPairFinancialProductLibrary is LongShortPairFinancialProductLibrary, Lockable { - using FixedPoint for FixedPoint.Unsigned; - - mapping(address => uint256) public longShortPairStrikePrices; - uint256 basePercentage = 500000000000000000; // 0.5 with 18 decimals - uint256 variablePercentage = uint256(1000000000000000000) - basePercentage; - - /** - * @notice Enables any address to set the strike price for an associated LSP. - * @param longShortPair address of the LSP. - * @param strikePrice the strike price for the covered call for the associated LSP. - * @dev Note: a) Any address can set the initial strike price b) A strike price cannot be 0. - * c) A strike price can only be set once to prevent the deployer from changing the strike after the fact. - * d) For safety, a strike price should be set before depositing any synthetic tokens in a liquidity pool. - * e) longShortPair must expose an expirationTimestamp method to validate it is correctly deployed. - */ - function setLongShortPairParameters(address longShortPair, uint256 strikePrice) public nonReentrant { - require(ExpiringContractInterface(longShortPair).expirationTimestamp() != 0, "Invalid LSP address"); - require(longShortPairStrikePrices[longShortPair] == 0, "Parameters already set"); - - longShortPairStrikePrices[longShortPair] = strikePrice; - } - - /** - * @notice Returns a number between 0 and 1e18 to indicate how much collateral each long and short token are entitled - * to per collateralPerPair. - * @param expiryPrice price from the optimistic oracle for the LSP price identifier. - * @return expiryPercentLong to indicate how much collateral should be sent between long and short tokens. - */ - function percentageLongCollateralAtExpiry( - int256 expiryPrice - ) public view override nonReentrantView returns (uint256) { - uint256 contractStrikePrice = longShortPairStrikePrices[msg.sender]; - require(contractStrikePrice != 0, "Params not set for calling LSP"); - - // If the expiry price is less than the strike price then the long options expire worthless (out of the money). - // In this case, return of value of 50% (half of collateral goes to long) - // Note we do not consider negative expiry prices in this call option implementation. - uint256 positiveExpiryPrice = expiryPrice > 0 ? uint256(expiryPrice) : 0; - if (positiveExpiryPrice == 0 || uint256(positiveExpiryPrice) <= contractStrikePrice) return basePercentage; - - // Else, token expires to be worth the 0.5 of the collateral plus 0.5 * the fraction of a collateral token - // that's in the money. - // eg if SUSHI is $30 and strike is $20, long token is redeemable for 0.5 + 0.5*(30-20)/30 = 0.6667% which if the - // collateralPerPair is 2, is worth 1.3333 $SUSHI, which is worth $40 if 1 $SUSHI is worth $30. - // This return value is strictly < 1, tending to 1 as the expiryPrice tends to infinity. - return - ( - FixedPoint.Unsigned(basePercentage).add( - FixedPoint - .Unsigned(variablePercentage) - .mul( - FixedPoint.Unsigned(uint256(positiveExpiryPrice)).sub( - FixedPoint.Unsigned(contractStrikePrice) - ) - ) - .div(FixedPoint.Unsigned(uint256(positiveExpiryPrice))) - ) - ).rawValue; - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/SuccessTokenLongShortPairFinancialProductLibrary.sol b/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/SuccessTokenLongShortPairFinancialProductLibrary.sol deleted file mode 100644 index f65da1306..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/common/financial-product-libraries/long-short-pair-libraries/SuccessTokenLongShortPairFinancialProductLibrary.sol +++ /dev/null @@ -1,86 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "./LongShortPairFinancialProductLibrary.sol"; -import "../../../../common/implementation/Lockable.sol"; - -/** - * @title Success Token Long Short Pair Financial Product Library. - * @notice Adds settlement logic to create success token LSPs. A success token pays out a fixed amount of - * collateral as a floor, with the remaining amount functioning like an embedded option. The embedded - * option in this case uses payout logic that resembles a covered call. I.e., the token expires to be worth - * basePercentage + (1 - basePercentage) * (expiryPrice - strikePrice). - */ -contract SuccessTokenLongShortPairFinancialProductLibrary is LongShortPairFinancialProductLibrary, Lockable { - using FixedPoint for FixedPoint.Unsigned; - - struct SuccessTokenLongShortPairParameters { - uint256 strikePrice; - uint256 basePercentage; - } - - mapping(address => SuccessTokenLongShortPairParameters) public longShortPairParameters; - - /** - * @notice Enables any address to set the strike price for an associated LSP. - * @param longShortPair address of the LSP. - * @param strikePrice the strike price for the covered call for the associated LSP. - * @param basePercentage the base percentage of collateral per pair paid out to long tokens, expressed - * with 1e18 decimals. E.g., a 50% base percentage should be expressed 500000000000000000, or 0.5 with - * 1e18 decimals. The base percentage cannot be set to 0. - * @dev Note: a) Any address can set the initial strike price b) A strike price cannot be 0. - * c) A strike price can only be set once to prevent the deployer from changing the strike after the fact. - * d) For safety, a strike price should be set before depositing any synthetic tokens in a liquidity pool. - * e) longShortPair must expose an expirationTimestamp method to validate it is correctly deployed. - */ - function setLongShortPairParameters( - address longShortPair, - uint256 strikePrice, - uint256 basePercentage - ) public nonReentrant { - require(ExpiringContractInterface(longShortPair).expirationTimestamp() != 0, "Invalid LSP address"); - SuccessTokenLongShortPairParameters memory params = longShortPairParameters[longShortPair]; - require(params.strikePrice == 0 && params.basePercentage == 0, "Parameters already set"); - require(strikePrice != 0 && basePercentage != 0, "Base percentage and strike price cannot be set to 0"); - - longShortPairParameters[longShortPair] = SuccessTokenLongShortPairParameters({ - strikePrice: strikePrice, - basePercentage: basePercentage - }); - } - - /** - * @notice Returns a number between 0 and 1e18 to indicate how much collateral each long and short token are entitled - * to per collateralPerPair. - * @param expiryPrice price from the optimistic oracle for the LSP price identifier. - * @return expiryPercentLong to indicate how much collateral should be sent between long and short tokens. - */ - function percentageLongCollateralAtExpiry( - int256 expiryPrice - ) public view override nonReentrantView returns (uint256) { - SuccessTokenLongShortPairParameters memory params = longShortPairParameters[msg.sender]; - require(params.strikePrice != 0 || params.basePercentage != 0, "Params not set for calling LSP"); - - // If the expiry price is less than the strike price then the long options expire worthless (out of the money). - // In this case, return of value of the base percentage to the long tokenholders. - // Note we do not consider negative expiry prices in this implementation. - uint256 positiveExpiryPrice = expiryPrice > 0 ? uint256(expiryPrice) : 0; - if (positiveExpiryPrice == 0 || positiveExpiryPrice <= params.strikePrice) return params.basePercentage; - - // Else, token expires to be worth basePercentage + (1 - basePercentage) * (expiryPrice - strikePrice). - // E.g., if base percentage is 50%, $TOKEN is $30, and strike is $20, long token is redeemable for - // 0.5 + 0.5*(30-20)/30 = 0.6667%, which if the collateralPerPair is 2, is worth 1.3333 $TOKEN, which is - // worth $40 if 1 $TOKEN is worth $30. This return value is strictly < 1. The return value tends to 1 as - // the expiryPrice tends to infinity. Due to rounding down and precision errors, this may return a very - // slightly smaller value than expected. - return - ( - FixedPoint.Unsigned(params.basePercentage).add( - FixedPoint - .Unsigned(1e18 - params.basePercentage) - .mul(FixedPoint.Unsigned(positiveExpiryPrice).sub(FixedPoint.Unsigned(params.strikePrice))) - .div(FixedPoint.Unsigned(positiveExpiryPrice)) - ) - ).rawValue; - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiParty.sol b/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiParty.sol deleted file mode 100644 index 3d8a6a9ef..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiParty.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "./Liquidatable.sol"; - -/** - * @title Expiring Multi Party. - * @notice Convenient wrapper for Liquidatable. - */ -contract ExpiringMultiParty is Liquidatable { - /** - * @notice Constructs the ExpiringMultiParty contract. - * @param params struct to define input parameters for construction of Liquidatable. Some params - * are fed directly into the PricelessPositionManager's constructor within the inheritance tree. - */ - constructor( - ConstructorParams memory params - ) Liquidatable(params) // Note: since there is no logic here, there is no need to add a re-entrancy guard. - { - - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiPartyCreator.sol b/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiPartyCreator.sol deleted file mode 100644 index 8bd26a90e..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiPartyCreator.sol +++ /dev/null @@ -1,146 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/interfaces/ExpandedIERC20.sol"; -import "../../common/interfaces/IERC20Standard.sol"; -import "../../data-verification-mechanism/implementation/ContractCreator.sol"; -import "../../common/implementation/Testable.sol"; -import "../../common/implementation/AddressWhitelist.sol"; -import "../../common/implementation/Lockable.sol"; -import "../common/TokenFactory.sol"; -import "../common/SyntheticToken.sol"; -import "./ExpiringMultiPartyLib.sol"; - -/** - * @title Expiring Multi Party Contract creator. - * @notice Factory contract to create and register new instances of expiring multiparty contracts. - * Responsible for constraining the parameters used to construct a new EMP. This creator contains a number of constraints - * that are applied to newly created expiring multi party contract. These constraints can evolve over time and are - * initially constrained to conservative values in this first iteration. Technically there is nothing in the - * ExpiringMultiParty contract requiring these constraints. However, because `createExpiringMultiParty()` is intended - * to be the only way to create valid financial contracts that are registered with the DVM (via _registerContract), - we can enforce deployment configurations here. - */ -contract ExpiringMultiPartyCreator is ContractCreator, Testable, Lockable { - using FixedPoint for FixedPoint.Unsigned; - - /**************************************** - * EMP CREATOR DATA STRUCTURES * - ****************************************/ - - struct Params { - uint256 expirationTimestamp; - address collateralAddress; - bytes32 priceFeedIdentifier; - string syntheticName; - string syntheticSymbol; - FixedPoint.Unsigned collateralRequirement; - FixedPoint.Unsigned disputeBondPercentage; - FixedPoint.Unsigned sponsorDisputeRewardPercentage; - FixedPoint.Unsigned disputerDisputeRewardPercentage; - FixedPoint.Unsigned minSponsorTokens; - uint256 withdrawalLiveness; - uint256 liquidationLiveness; - address financialProductLibraryAddress; - } - // Address of TokenFactory used to create a new synthetic token. - address public tokenFactoryAddress; - - event CreatedExpiringMultiParty(address indexed expiringMultiPartyAddress, address indexed deployerAddress); - - /** - * @notice Constructs the ExpiringMultiPartyCreator contract. - * @param _finderAddress UMA protocol Finder used to discover other protocol contracts. - * @param _tokenFactoryAddress ERC20 token factory used to deploy synthetic token instances. - * @param _timerAddress Contract that stores the current time in a testing environment. - */ - constructor( - address _finderAddress, - address _tokenFactoryAddress, - address _timerAddress - ) ContractCreator(_finderAddress) Testable(_timerAddress) nonReentrant() { - tokenFactoryAddress = _tokenFactoryAddress; - } - - /** - * @notice Creates an instance of expiring multi party and registers it within the registry. - * @param params is a `ConstructorParams` object from ExpiringMultiParty. - * @return address of the deployed ExpiringMultiParty contract. - */ - function createExpiringMultiParty(Params memory params) public nonReentrant returns (address) { - // Create a new synthetic token using the params. - require(bytes(params.syntheticName).length != 0, "Missing synthetic name"); - require(bytes(params.syntheticSymbol).length != 0, "Missing synthetic symbol"); - TokenFactory tf = TokenFactory(tokenFactoryAddress); - - // If the collateral token does not have a `decimals()` method, then a default precision of 18 will be - // applied to the newly created synthetic token. - uint8 syntheticDecimals = _getSyntheticDecimals(params.collateralAddress); - ExpandedIERC20 tokenCurrency = tf.createToken(params.syntheticName, params.syntheticSymbol, syntheticDecimals); - address derivative = ExpiringMultiPartyLib.deploy(_convertParams(params, tokenCurrency)); - - // Give permissions to new derivative contract and then hand over ownership. - tokenCurrency.addMinter(derivative); - tokenCurrency.addBurner(derivative); - tokenCurrency.resetOwner(derivative); - - _registerContract(new address[](0), derivative); - - emit CreatedExpiringMultiParty(derivative, msg.sender); - - return derivative; - } - - /**************************************** - * PRIVATE FUNCTIONS * - ****************************************/ - - // Converts createExpiringMultiParty params to ExpiringMultiParty constructor params. - function _convertParams( - Params memory params, - ExpandedIERC20 newTokenCurrency - ) private view returns (ExpiringMultiParty.ConstructorParams memory constructorParams) { - // Known from creator deployment. - constructorParams.finderAddress = finderAddress; - constructorParams.timerAddress = timerAddress; - - // Enforce configuration constraints. - require(params.withdrawalLiveness != 0, "Withdrawal liveness cannot be 0"); - require(params.liquidationLiveness != 0, "Liquidation liveness cannot be 0"); - require(params.expirationTimestamp > block.timestamp, "Invalid expiration time"); - _requireWhitelistedCollateral(params.collateralAddress); - - // We don't want EMP deployers to be able to intentionally or unintentionally set - // liveness periods that could induce arithmetic overflow, but we also don't want - // to be opinionated about what livenesses are "correct", so we will somewhat - // arbitrarily set the liveness upper bound to 100 years (5200 weeks). In practice, liveness - // periods even greater than a few days would make the EMP unusable for most users. - require(params.withdrawalLiveness < 5200 weeks, "Withdrawal liveness too large"); - require(params.liquidationLiveness < 5200 weeks, "Liquidation liveness too large"); - - // Input from function call. - constructorParams.tokenAddress = address(newTokenCurrency); - constructorParams.expirationTimestamp = params.expirationTimestamp; - constructorParams.collateralAddress = params.collateralAddress; - constructorParams.priceFeedIdentifier = params.priceFeedIdentifier; - constructorParams.collateralRequirement = params.collateralRequirement; - constructorParams.disputeBondPercentage = params.disputeBondPercentage; - constructorParams.sponsorDisputeRewardPercentage = params.sponsorDisputeRewardPercentage; - constructorParams.disputerDisputeRewardPercentage = params.disputerDisputeRewardPercentage; - constructorParams.minSponsorTokens = params.minSponsorTokens; - constructorParams.withdrawalLiveness = params.withdrawalLiveness; - constructorParams.liquidationLiveness = params.liquidationLiveness; - constructorParams.financialProductLibraryAddress = params.financialProductLibraryAddress; - } - - // IERC20Standard.decimals() will revert if the collateral contract has not implemented the decimals() method, - // which is possible since the method is only an OPTIONAL method in the ERC20 standard: - // https://eips.ethereum.org/EIPS/eip-20#methods. - function _getSyntheticDecimals(address _collateralAddress) public view returns (uint8 decimals) { - try IERC20Standard(_collateralAddress).decimals() returns (uint8 _decimals) { - return _decimals; - } catch { - return 18; - } - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiPartyLib.sol b/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiPartyLib.sol deleted file mode 100644 index 9ad551fd1..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/ExpiringMultiPartyLib.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "./ExpiringMultiParty.sol"; - -/** - * @title Provides convenient Expiring Multi Party contract utilities. - * @dev Using this library to deploy EMP's allows calling contracts to avoid importing the full EMP bytecode. - */ -library ExpiringMultiPartyLib { - /** - * @notice Returns address of new EMP deployed with given `params` configuration. - * @dev Caller will need to register new EMP with the Registry to begin requesting prices. Caller is also - * responsible for enforcing constraints on `params`. - * @param params is a `ConstructorParams` object from ExpiringMultiParty. - * @return address of the deployed ExpiringMultiParty contract - */ - function deploy(ExpiringMultiParty.ConstructorParams memory params) public returns (address) { - ExpiringMultiParty derivative = new ExpiringMultiParty(params); - return address(derivative); - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/Liquidatable.sol b/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/Liquidatable.sol deleted file mode 100644 index 29320e9e2..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/Liquidatable.sol +++ /dev/null @@ -1,634 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts-v4/utils/Address.sol"; - -import "./PricelessPositionManager.sol"; - -import "../../common/implementation/FixedPoint.sol"; - -/** - * @title Liquidatable - * @notice Adds logic to a position-managing contract that enables callers to liquidate an undercollateralized position. - * @dev The liquidation has a liveness period before expiring successfully, during which someone can "dispute" the - * liquidation, which sends a price request to the relevant Oracle to settle the final collateralization ratio based on - * a DVM price. The contract enforces dispute rewards in order to incentivize disputers to correctly dispute false - * liquidations and compensate position sponsors who had their position incorrectly liquidated. Importantly, a - * prospective disputer must deposit a dispute bond that they can lose in the case of an unsuccessful dispute. - * NOTE: this contract does _not_ work with ERC777 collateral currencies or any others that call into the receiver on - * transfer(). Using an ERC777 token would allow a user to maliciously grief other participants (while also losing - * money themselves). - */ -contract Liquidatable is PricelessPositionManager { - using FixedPoint for FixedPoint.Unsigned; - using SafeMath for uint256; - using SafeERC20 for IERC20; - using SafeERC20 for ExpandedIERC20; - using Address for address; - - /**************************************** - * LIQUIDATION DATA STRUCTURES * - ****************************************/ - - // Because of the check in withdrawable(), the order of these enum values should not change. - enum Status { - Uninitialized, - NotDisputed, - Disputed, - DisputeSucceeded, - DisputeFailed - } - - struct LiquidationData { - // Following variables set upon creation of liquidation: - address sponsor; // Address of the liquidated position's sponsor - address liquidator; // Address who created this liquidation - Status state; // Liquidated (and expired or not), Pending a Dispute, or Dispute has resolved - uint256 liquidationTime; // Time when liquidation is initiated, needed to get price from Oracle - // Following variables determined by the position that is being liquidated: - FixedPoint.Unsigned tokensOutstanding; // Synthetic tokens required to be burned by liquidator to initiate dispute - FixedPoint.Unsigned lockedCollateral; // Collateral locked by contract and released upon expiry or post-dispute - // Amount of collateral being liquidated, which could be different from - // lockedCollateral if there were pending withdrawals at the time of liquidation - FixedPoint.Unsigned liquidatedCollateral; - // Unit value (starts at 1) that is used to track the fees per unit of collateral over the course of the liquidation. - FixedPoint.Unsigned rawUnitCollateral; - // Following variable set upon initiation of a dispute: - address disputer; // Person who is disputing a liquidation - // Following variable set upon a resolution of a dispute: - FixedPoint.Unsigned settlementPrice; // Final price as determined by an Oracle following a dispute - FixedPoint.Unsigned finalFee; - } - - // Define the contract's constructor parameters as a struct to enable more variables to be specified. - // This is required to enable more params, over and above Solidity's limits. - struct ConstructorParams { - // Params for PricelessPositionManager only. - uint256 expirationTimestamp; - uint256 withdrawalLiveness; - address collateralAddress; - address tokenAddress; - address finderAddress; - address timerAddress; - address financialProductLibraryAddress; - bytes32 priceFeedIdentifier; - FixedPoint.Unsigned minSponsorTokens; - // Params specifically for Liquidatable. - uint256 liquidationLiveness; - FixedPoint.Unsigned collateralRequirement; - FixedPoint.Unsigned disputeBondPercentage; - FixedPoint.Unsigned sponsorDisputeRewardPercentage; - FixedPoint.Unsigned disputerDisputeRewardPercentage; - } - - // This struct is used in the `withdrawLiquidation` method that disperses liquidation and dispute rewards. - // `payToX` stores the total collateral to withdraw from the contract to pay X. This value might differ - // from `paidToX` due to precision loss between accounting for the `rawCollateral` versus the - // fee-adjusted collateral. These variables are stored within a struct to avoid the stack too deep error. - struct RewardsData { - FixedPoint.Unsigned payToSponsor; - FixedPoint.Unsigned payToLiquidator; - FixedPoint.Unsigned payToDisputer; - FixedPoint.Unsigned paidToSponsor; - FixedPoint.Unsigned paidToLiquidator; - FixedPoint.Unsigned paidToDisputer; - } - - // Liquidations are unique by ID per sponsor - mapping(address => LiquidationData[]) public liquidations; - - // Total collateral in liquidation. - FixedPoint.Unsigned public rawLiquidationCollateral; - - // Immutable contract parameters: - // Amount of time for pending liquidation before expiry. - // !!Note: The lower the liquidation liveness value, the more risk incurred by sponsors. - // Extremely low liveness values increase the chance that opportunistic invalid liquidations - // expire without dispute, thereby decreasing the usability for sponsors and increasing the risk - // for the contract as a whole. An insolvent contract is extremely risky for any sponsor or synthetic - // token holder for the contract. - uint256 public liquidationLiveness; - // Required collateral:TRV ratio for a position to be considered sufficiently collateralized. - FixedPoint.Unsigned public collateralRequirement; - // Percent of a Liquidation/Position's lockedCollateral to be deposited by a potential disputer - // Represented as a multiplier, for example 1.5e18 = "150%" and 0.05e18 = "5%" - FixedPoint.Unsigned public disputeBondPercentage; - // Percent of oraclePrice paid to sponsor in the Disputed state (i.e. following a successful dispute) - // Represented as a multiplier, see above. - FixedPoint.Unsigned public sponsorDisputeRewardPercentage; - // Percent of oraclePrice paid to disputer in the Disputed state (i.e. following a successful dispute) - // Represented as a multiplier, see above. - FixedPoint.Unsigned public disputerDisputeRewardPercentage; - - /**************************************** - * EVENTS * - ****************************************/ - - event LiquidationCreated( - address indexed sponsor, - address indexed liquidator, - uint256 indexed liquidationId, - uint256 tokensOutstanding, - uint256 lockedCollateral, - uint256 liquidatedCollateral, - uint256 liquidationTime - ); - event LiquidationDisputed( - address indexed sponsor, - address indexed liquidator, - address indexed disputer, - uint256 liquidationId, - uint256 disputeBondAmount - ); - event DisputeSettled( - address indexed caller, - address indexed sponsor, - address indexed liquidator, - address disputer, - uint256 liquidationId, - bool disputeSucceeded - ); - event LiquidationWithdrawn( - address indexed caller, - uint256 paidToLiquidator, - uint256 paidToDisputer, - uint256 paidToSponsor, - Status indexed liquidationStatus, - uint256 settlementPrice - ); - - /**************************************** - * MODIFIERS * - ****************************************/ - - modifier disputable(uint256 liquidationId, address sponsor) { - _disputable(liquidationId, sponsor); - _; - } - - modifier withdrawable(uint256 liquidationId, address sponsor) { - _withdrawable(liquidationId, sponsor); - _; - } - - /** - * @notice Constructs the liquidatable contract. - * @param params struct to define input parameters for construction of Liquidatable. Some params - * are fed directly into the PricelessPositionManager's constructor within the inheritance tree. - */ - constructor( - ConstructorParams memory params - ) - PricelessPositionManager( - params.expirationTimestamp, - params.withdrawalLiveness, - params.collateralAddress, - params.tokenAddress, - params.finderAddress, - params.priceFeedIdentifier, - params.minSponsorTokens, - params.timerAddress, - params.financialProductLibraryAddress - ) - nonReentrant() - { - require(params.collateralRequirement.isGreaterThan(1)); - require(params.sponsorDisputeRewardPercentage.add(params.disputerDisputeRewardPercentage).isLessThan(1)); - - // Set liquidatable specific variables. - liquidationLiveness = params.liquidationLiveness; - collateralRequirement = params.collateralRequirement; - disputeBondPercentage = params.disputeBondPercentage; - sponsorDisputeRewardPercentage = params.sponsorDisputeRewardPercentage; - disputerDisputeRewardPercentage = params.disputerDisputeRewardPercentage; - } - - /**************************************** - * LIQUIDATION FUNCTIONS * - ****************************************/ - - /** - * @notice Liquidates the sponsor's position if the caller has enough - * synthetic tokens to retire the position's outstanding tokens. Liquidations above - * a minimum size also reset an ongoing "slow withdrawal"'s liveness. - * @dev This method generates an ID that will uniquely identify liquidation for the sponsor. This contract must be - * approved to spend at least `tokensLiquidated` of `tokenCurrency` and at least `finalFeeBond` of `collateralCurrency`. - * @dev This contract must have the Burner role for the `tokenCurrency`. - * @param sponsor address of the sponsor to liquidate. - * @param minCollateralPerToken abort the liquidation if the position's collateral per token is below this value. - * @param maxCollateralPerToken abort the liquidation if the position's collateral per token exceeds this value. - * @param maxTokensToLiquidate max number of tokens to liquidate. - * @param deadline abort the liquidation if the transaction is mined after this timestamp. - * @return liquidationId ID of the newly created liquidation. - * @return tokensLiquidated amount of synthetic tokens removed and liquidated from the `sponsor`'s position. - * @return finalFeeBond amount of collateral to be posted by liquidator and returned if not disputed successfully. - */ - function createLiquidation( - address sponsor, - FixedPoint.Unsigned calldata minCollateralPerToken, - FixedPoint.Unsigned calldata maxCollateralPerToken, - FixedPoint.Unsigned calldata maxTokensToLiquidate, - uint256 deadline - ) - external - fees - onlyPreExpiration - nonReentrant - returns ( - uint256 liquidationId, - FixedPoint.Unsigned memory tokensLiquidated, - FixedPoint.Unsigned memory finalFeeBond - ) - { - // Check that this transaction was mined pre-deadline. - require(getCurrentTime() <= deadline, "Mined after deadline"); - - // Retrieve Position data for sponsor - PositionData storage positionToLiquidate = _getPositionData(sponsor); - - tokensLiquidated = FixedPoint.min(maxTokensToLiquidate, positionToLiquidate.tokensOutstanding); - require(tokensLiquidated.isGreaterThan(0)); - - // Starting values for the Position being liquidated. If withdrawal request amount is > position's collateral, - // then set this to 0, otherwise set it to (startCollateral - withdrawal request amount). - FixedPoint.Unsigned memory startCollateral = _getFeeAdjustedCollateral(positionToLiquidate.rawCollateral); - FixedPoint.Unsigned memory startCollateralNetOfWithdrawal = FixedPoint.fromUnscaledUint(0); - if (positionToLiquidate.withdrawalRequestAmount.isLessThanOrEqual(startCollateral)) { - startCollateralNetOfWithdrawal = startCollateral.sub(positionToLiquidate.withdrawalRequestAmount); - } - - // Scoping to get rid of a stack too deep error. - { - FixedPoint.Unsigned memory startTokens = positionToLiquidate.tokensOutstanding; - - // The Position's collateralization ratio must be between [minCollateralPerToken, maxCollateralPerToken]. - // maxCollateralPerToken >= startCollateralNetOfWithdrawal / startTokens. - require( - maxCollateralPerToken.mul(startTokens).isGreaterThanOrEqual(startCollateralNetOfWithdrawal), - "CR is more than max liq. price" - ); - // minCollateralPerToken >= startCollateralNetOfWithdrawal / startTokens. - require( - minCollateralPerToken.mul(startTokens).isLessThanOrEqual(startCollateralNetOfWithdrawal), - "CR is less than min liq. price" - ); - } - - // Compute final fee at time of liquidation. - finalFeeBond = _computeFinalFees(); - - // These will be populated within the scope below. - FixedPoint.Unsigned memory lockedCollateral; - FixedPoint.Unsigned memory liquidatedCollateral; - - // Scoping to get rid of a stack too deep error. - { - FixedPoint.Unsigned memory ratio = tokensLiquidated.div(positionToLiquidate.tokensOutstanding); - - // The actual amount of collateral that gets moved to the liquidation. - lockedCollateral = startCollateral.mul(ratio); - - // For purposes of disputes, it's actually this liquidatedCollateral value that's used. This value is net of - // withdrawal requests. - liquidatedCollateral = startCollateralNetOfWithdrawal.mul(ratio); - - // Part of the withdrawal request is also removed. Ideally: - // liquidatedCollateral + withdrawalAmountToRemove = lockedCollateral. - FixedPoint.Unsigned memory withdrawalAmountToRemove = positionToLiquidate.withdrawalRequestAmount.mul( - ratio - ); - _reduceSponsorPosition(sponsor, tokensLiquidated, lockedCollateral, withdrawalAmountToRemove); - } - - // Add to the global liquidation collateral count. - _addCollateral(rawLiquidationCollateral, lockedCollateral.add(finalFeeBond)); - - // Construct liquidation object. - // Note: All dispute-related values are zeroed out until a dispute occurs. liquidationId is the index of the new - // LiquidationData that is pushed into the array, which is equal to the current length of the array pre-push. - liquidationId = liquidations[sponsor].length; - liquidations[sponsor].push( - LiquidationData({ - sponsor: sponsor, - liquidator: msg.sender, - state: Status.NotDisputed, - liquidationTime: getCurrentTime(), - tokensOutstanding: tokensLiquidated, - lockedCollateral: lockedCollateral, - liquidatedCollateral: liquidatedCollateral, - rawUnitCollateral: _convertToRawCollateral(FixedPoint.fromUnscaledUint(1)), - disputer: address(0), - settlementPrice: FixedPoint.fromUnscaledUint(0), - finalFee: finalFeeBond - }) - ); - - // If this liquidation is a subsequent liquidation on the position, and the liquidation size is larger than - // some "griefing threshold", then re-set the liveness. This enables a liquidation against a withdraw request to be - // "dragged out" if the position is very large and liquidators need time to gather funds. The griefing threshold - // is enforced so that liquidations for trivially small # of tokens cannot drag out an honest sponsor's slow withdrawal. - - // We arbitrarily set the "griefing threshold" to `minSponsorTokens` because it is the only parameter - // denominated in token currency units and we can avoid adding another parameter. - FixedPoint.Unsigned memory griefingThreshold = minSponsorTokens; - if ( - positionToLiquidate.withdrawalRequestPassTimestamp > 0 && // The position is undergoing a slow withdrawal. - positionToLiquidate.withdrawalRequestPassTimestamp > getCurrentTime() && // The slow withdrawal has not yet expired. - tokensLiquidated.isGreaterThanOrEqual(griefingThreshold) // The liquidated token count is above a "griefing threshold". - ) { - positionToLiquidate.withdrawalRequestPassTimestamp = getCurrentTime().add(withdrawalLiveness); - } - - emit LiquidationCreated( - sponsor, - msg.sender, - liquidationId, - tokensLiquidated.rawValue, - lockedCollateral.rawValue, - liquidatedCollateral.rawValue, - getCurrentTime() - ); - - // Destroy tokens - tokenCurrency.safeTransferFrom(msg.sender, address(this), tokensLiquidated.rawValue); - tokenCurrency.burn(tokensLiquidated.rawValue); - - // Pull final fee from liquidator. - collateralCurrency.safeTransferFrom(msg.sender, address(this), finalFeeBond.rawValue); - } - - /** - * @notice Disputes a liquidation, if the caller has enough collateral to post a dispute bond - * and pay a fixed final fee charged on each price request. - * @dev Can only dispute a liquidation before the liquidation expires and if there are no other pending disputes. - * This contract must be approved to spend at least the dispute bond amount of `collateralCurrency`. This dispute - * bond amount is calculated from `disputeBondPercentage` times the collateral in the liquidation. - * @param liquidationId of the disputed liquidation. - * @param sponsor the address of the sponsor whose liquidation is being disputed. - * @return totalPaid amount of collateral charged to disputer (i.e. final fee bond + dispute bond). - */ - function dispute( - uint256 liquidationId, - address sponsor - ) external disputable(liquidationId, sponsor) fees nonReentrant returns (FixedPoint.Unsigned memory totalPaid) { - LiquidationData storage disputedLiquidation = _getLiquidationData(sponsor, liquidationId); - - // Multiply by the unit collateral so the dispute bond is a percentage of the locked collateral after fees. - FixedPoint.Unsigned memory disputeBondAmount = disputedLiquidation - .lockedCollateral - .mul(disputeBondPercentage) - .mul(_getFeeAdjustedCollateral(disputedLiquidation.rawUnitCollateral)); - _addCollateral(rawLiquidationCollateral, disputeBondAmount); - - // Request a price from DVM. Liquidation is pending dispute until DVM returns a price. - disputedLiquidation.state = Status.Disputed; - disputedLiquidation.disputer = msg.sender; - - // Enqueue a request with the DVM. - _requestOraclePriceLiquidation(disputedLiquidation.liquidationTime); - - emit LiquidationDisputed( - sponsor, - disputedLiquidation.liquidator, - msg.sender, - liquidationId, - disputeBondAmount.rawValue - ); - totalPaid = disputeBondAmount.add(disputedLiquidation.finalFee); - - // Pay the final fee for requesting price from the DVM. - _payFinalFees(msg.sender, disputedLiquidation.finalFee); - - // Transfer the dispute bond amount from the caller to this contract. - collateralCurrency.safeTransferFrom(msg.sender, address(this), disputeBondAmount.rawValue); - } - - /** - * @notice After a dispute has settled or after a non-disputed liquidation has expired, - * anyone can call this method to disperse payments to the sponsor, liquidator, and disdputer. - * @dev If the dispute SUCCEEDED: the sponsor, liquidator, and disputer are eligible for payment. - * If the dispute FAILED: only the liquidator can receive payment. - * This method will revert if rewards have already been dispersed. - * @param liquidationId uniquely identifies the sponsor's liquidation. - * @param sponsor address of the sponsor associated with the liquidation. - * @return data about rewards paid out. - */ - function withdrawLiquidation( - uint256 liquidationId, - address sponsor - ) public withdrawable(liquidationId, sponsor) fees nonReentrant returns (RewardsData memory) { - LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId); - - // Settles the liquidation if necessary. This call will revert if the price has not resolved yet. - _settle(liquidationId, sponsor); - - // Calculate rewards as a function of the TRV. - // Note: all payouts are scaled by the unit collateral value so all payouts are charged the fees pro rata. - FixedPoint.Unsigned memory feeAttenuation = _getFeeAdjustedCollateral(liquidation.rawUnitCollateral); - FixedPoint.Unsigned memory settlementPrice = liquidation.settlementPrice; - FixedPoint.Unsigned memory tokenRedemptionValue = liquidation.tokensOutstanding.mul(settlementPrice).mul( - feeAttenuation - ); - FixedPoint.Unsigned memory collateral = liquidation.lockedCollateral.mul(feeAttenuation); - FixedPoint.Unsigned memory disputerDisputeReward = disputerDisputeRewardPercentage.mul(tokenRedemptionValue); - FixedPoint.Unsigned memory sponsorDisputeReward = sponsorDisputeRewardPercentage.mul(tokenRedemptionValue); - FixedPoint.Unsigned memory disputeBondAmount = collateral.mul(disputeBondPercentage); - FixedPoint.Unsigned memory finalFee = liquidation.finalFee.mul(feeAttenuation); - - // There are three main outcome states: either the dispute succeeded, failed or was not updated. - // Based on the state, different parties of a liquidation receive different amounts. - // After assigning rewards based on the liquidation status, decrease the total collateral held in this contract - // by the amount to pay each party. The actual amounts withdrawn might differ if _removeCollateral causes - // precision loss. - RewardsData memory rewards; - if (liquidation.state == Status.DisputeSucceeded) { - // If the dispute is successful then all three users should receive rewards: - - // Pay DISPUTER: disputer reward + dispute bond + returned final fee - rewards.payToDisputer = disputerDisputeReward.add(disputeBondAmount).add(finalFee); - - // Pay SPONSOR: remaining collateral (collateral - TRV) + sponsor reward - rewards.payToSponsor = sponsorDisputeReward.add(collateral.sub(tokenRedemptionValue)); - - // Pay LIQUIDATOR: TRV - dispute reward - sponsor reward - // If TRV > Collateral, then subtract rewards from collateral - // NOTE: `payToLiquidator` should never be below zero since we enforce that - // (sponsorDisputePct+disputerDisputePct) <= 1 in the constructor when these params are set. - rewards.payToLiquidator = tokenRedemptionValue.sub(sponsorDisputeReward).sub(disputerDisputeReward); - - // Transfer rewards and debit collateral - rewards.paidToLiquidator = _removeCollateral(rawLiquidationCollateral, rewards.payToLiquidator); - rewards.paidToSponsor = _removeCollateral(rawLiquidationCollateral, rewards.payToSponsor); - rewards.paidToDisputer = _removeCollateral(rawLiquidationCollateral, rewards.payToDisputer); - - collateralCurrency.safeTransfer(liquidation.disputer, rewards.paidToDisputer.rawValue); - collateralCurrency.safeTransfer(liquidation.liquidator, rewards.paidToLiquidator.rawValue); - collateralCurrency.safeTransfer(liquidation.sponsor, rewards.paidToSponsor.rawValue); - - // In the case of a failed dispute only the liquidator can withdraw. - } else if (liquidation.state == Status.DisputeFailed) { - // Pay LIQUIDATOR: collateral + dispute bond + returned final fee - rewards.payToLiquidator = collateral.add(disputeBondAmount).add(finalFee); - - // Transfer rewards and debit collateral - rewards.paidToLiquidator = _removeCollateral(rawLiquidationCollateral, rewards.payToLiquidator); - - collateralCurrency.safeTransfer(liquidation.liquidator, rewards.paidToLiquidator.rawValue); - - // If the state is pre-dispute but time has passed liveness then there was no dispute. We represent this - // state as a dispute failed and the liquidator can withdraw. - } else if (liquidation.state == Status.NotDisputed) { - // Pay LIQUIDATOR: collateral + returned final fee - rewards.payToLiquidator = collateral.add(finalFee); - - // Transfer rewards and debit collateral - rewards.paidToLiquidator = _removeCollateral(rawLiquidationCollateral, rewards.payToLiquidator); - - collateralCurrency.safeTransfer(liquidation.liquidator, rewards.paidToLiquidator.rawValue); - } - - emit LiquidationWithdrawn( - msg.sender, - rewards.paidToLiquidator.rawValue, - rewards.paidToDisputer.rawValue, - rewards.paidToSponsor.rawValue, - liquidation.state, - settlementPrice.rawValue - ); - - // Free up space after collateral is withdrawn by removing the liquidation object from the array. - delete liquidations[sponsor][liquidationId]; - - return rewards; - } - - /** - * @notice Gets all liquidation information for a given sponsor address. - * @param sponsor address of the position sponsor. - * @return liquidationData array of all liquidation information for the given sponsor address. - */ - function getLiquidations( - address sponsor - ) external view nonReentrantView returns (LiquidationData[] memory liquidationData) { - return liquidations[sponsor]; - } - - /** - * @notice Accessor method to calculate a transformed collateral requirement using the finanical product library - specified during contract deployment. If no library was provided then no modification to the collateral requirement is done. - * @param price input price used as an input to transform the collateral requirement. - * @return transformedCollateralRequirement collateral requirement with transformation applied to it. - * @dev This method should never revert. - */ - function transformCollateralRequirement( - FixedPoint.Unsigned memory price - ) public view nonReentrantView returns (FixedPoint.Unsigned memory) { - return _transformCollateralRequirement(price); - } - - /**************************************** - * INTERNAL FUNCTIONS * - ****************************************/ - - // This settles a liquidation if it is in the Disputed state. If not, it will immediately return. - // If the liquidation is in the Disputed state, but a price is not available, this will revert. - function _settle(uint256 liquidationId, address sponsor) internal { - LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId); - - // Settlement only happens when state == Disputed and will only happen once per liquidation. - // If this liquidation is not ready to be settled, this method should return immediately. - if (liquidation.state != Status.Disputed) { - return; - } - - // Get the returned price from the oracle. If this has not yet resolved will revert. - liquidation.settlementPrice = _getOraclePriceLiquidation(liquidation.liquidationTime); - - // Find the value of the tokens in the underlying collateral. - FixedPoint.Unsigned memory tokenRedemptionValue = liquidation.tokensOutstanding.mul( - liquidation.settlementPrice - ); - - // The required collateral is the value of the tokens in underlying * required collateral ratio. The Transform - // Collateral requirement method applies a from the financial Product library to change the scaled the collateral - // requirement based on the settlement price. If no library was specified when deploying the emp then this makes no change. - FixedPoint.Unsigned memory requiredCollateral = tokenRedemptionValue.mul( - _transformCollateralRequirement(liquidation.settlementPrice) - ); - - // If the position has more than the required collateral it is solvent and the dispute is valid(liquidation is invalid) - // Note that this check uses the liquidatedCollateral not the lockedCollateral as this considers withdrawals. - bool disputeSucceeded = liquidation.liquidatedCollateral.isGreaterThanOrEqual(requiredCollateral); - liquidation.state = disputeSucceeded ? Status.DisputeSucceeded : Status.DisputeFailed; - - emit DisputeSettled( - msg.sender, - sponsor, - liquidation.liquidator, - liquidation.disputer, - liquidationId, - disputeSucceeded - ); - } - - function _pfc() internal view override returns (FixedPoint.Unsigned memory) { - return super._pfc().add(_getFeeAdjustedCollateral(rawLiquidationCollateral)); - } - - function _getLiquidationData( - address sponsor, - uint256 liquidationId - ) internal view returns (LiquidationData storage liquidation) { - LiquidationData[] storage liquidationArray = liquidations[sponsor]; - - // Revert if the caller is attempting to access an invalid liquidation - // (one that has never been created or one has never been initialized). - require( - liquidationId < liquidationArray.length && liquidationArray[liquidationId].state != Status.Uninitialized, - "Invalid liquidation ID" - ); - return liquidationArray[liquidationId]; - } - - function _getLiquidationExpiry(LiquidationData storage liquidation) internal view returns (uint256) { - return liquidation.liquidationTime.add(liquidationLiveness); - } - - // These internal functions are supposed to act identically to modifiers, but re-used modifiers - // unnecessarily increase contract bytecode size. - // source: https://blog.polymath.network/solidity-tips-and-tricks-to-save-gas-and-reduce-bytecode-size-c44580b218e6 - function _disputable(uint256 liquidationId, address sponsor) internal view { - LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId); - require( - (getCurrentTime() < _getLiquidationExpiry(liquidation)) && (liquidation.state == Status.NotDisputed), - "Liquidation not disputable" - ); - } - - function _withdrawable(uint256 liquidationId, address sponsor) internal view { - LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId); - Status state = liquidation.state; - - // Must be disputed or the liquidation has passed expiry. - require( - (state > Status.NotDisputed) || - ((_getLiquidationExpiry(liquidation) <= getCurrentTime()) && (state == Status.NotDisputed)), - "Liquidation not withdrawable" - ); - } - - function _transformCollateralRequirement( - FixedPoint.Unsigned memory price - ) internal view returns (FixedPoint.Unsigned memory) { - if (!address(financialProductLibrary).isContract()) return collateralRequirement; - try financialProductLibrary.transformCollateralRequirement(price, collateralRequirement) returns ( - FixedPoint.Unsigned memory transformedCollateralRequirement - ) { - return transformedCollateralRequirement; - } catch { - return collateralRequirement; - } - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/PricelessPositionManager.sol b/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/PricelessPositionManager.sol deleted file mode 100644 index cb90445e1..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/expiring-multiparty/PricelessPositionManager.sol +++ /dev/null @@ -1,985 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts-v4/utils/Address.sol"; - -import "../../common/implementation/FixedPoint.sol"; -import "../../common/interfaces/ExpandedIERC20.sol"; -import "../../common/interfaces/IERC20Standard.sol"; - -import "../../data-verification-mechanism/interfaces/OracleInterface.sol"; -import "../../optimistic-oracle-v2/interfaces/OptimisticOracleInterface.sol"; -import "../../data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol"; -import "../../data-verification-mechanism/implementation/Constants.sol"; - -import "../common/FeePayer.sol"; -import "../common/financial-product-libraries/expiring-multiparty-libraries/FinancialProductLibrary.sol"; - -/** - * @title Financial contract with priceless position management. - * @notice Handles positions for multiple sponsors in an optimistic (i.e., priceless) way without relying - * on a price feed. On construction, deploys a new ERC20, managed by this contract, that is the synthetic token. - */ - -contract PricelessPositionManager is FeePayer { - using SafeMath for uint256; - using FixedPoint for FixedPoint.Unsigned; - using SafeERC20 for IERC20; - using SafeERC20 for ExpandedIERC20; - using Address for address; - - /**************************************** - * PRICELESS POSITION DATA STRUCTURES * - ****************************************/ - - // Stores the state of the PricelessPositionManager. Set on expiration, emergency shutdown, or settlement. - enum ContractState { - Open, - ExpiredPriceRequested, - ExpiredPriceReceived - } - ContractState public contractState; - - // Represents a single sponsor's position. All collateral is held by this contract. - // This struct acts as bookkeeping for how much of that collateral is allocated to each sponsor. - struct PositionData { - FixedPoint.Unsigned tokensOutstanding; - // Tracks pending withdrawal requests. A withdrawal request is pending if `withdrawalRequestPassTimestamp != 0`. - uint256 withdrawalRequestPassTimestamp; - FixedPoint.Unsigned withdrawalRequestAmount; - // Raw collateral value. This value should never be accessed directly -- always use _getFeeAdjustedCollateral(). - // To add or remove collateral, use _addCollateral() and _removeCollateral(). - FixedPoint.Unsigned rawCollateral; - // Tracks pending transfer position requests. A transfer position request is pending if `transferPositionRequestPassTimestamp != 0`. - uint256 transferPositionRequestPassTimestamp; - } - - // Maps sponsor addresses to their positions. Each sponsor can have only one position. - mapping(address => PositionData) public positions; - - // Keep track of the total collateral and tokens across all positions to enable calculating the - // global collateralization ratio without iterating over all positions. - FixedPoint.Unsigned public totalTokensOutstanding; - - // Similar to the rawCollateral in PositionData, this value should not be used directly. - // _getFeeAdjustedCollateral(), _addCollateral() and _removeCollateral() must be used to access and adjust. - FixedPoint.Unsigned public rawTotalPositionCollateral; - - // Synthetic token created by this contract. - ExpandedIERC20 public tokenCurrency; - - // Unique identifier for DVM price feed ticker. - bytes32 public priceIdentifier; - // Time that this contract expires. Should not change post-construction unless an emergency shutdown occurs. - uint256 public expirationTimestamp; - // Time that has to elapse for a withdrawal request to be considered passed, if no liquidations occur. - // !!Note: The lower the withdrawal liveness value, the more risk incurred by the contract. - // Extremely low liveness values increase the chance that opportunistic invalid withdrawal requests - // expire without liquidation, thereby increasing the insolvency risk for the contract as a whole. An insolvent - // contract is extremely risky for any sponsor or synthetic token holder for the contract. - uint256 public withdrawalLiveness; - - // Minimum number of tokens in a sponsor's position. - FixedPoint.Unsigned public minSponsorTokens; - - // The expiry price pulled from the DVM. - FixedPoint.Unsigned public expiryPrice; - - // Instance of FinancialProductLibrary to provide custom price and collateral requirement transformations to extend - // the functionality of the EMP to support a wider range of financial products. - FinancialProductLibrary public financialProductLibrary; - - /**************************************** - * EVENTS * - ****************************************/ - - event RequestTransferPosition(address indexed oldSponsor); - event RequestTransferPositionExecuted(address indexed oldSponsor, address indexed newSponsor); - event RequestTransferPositionCanceled(address indexed oldSponsor); - event Deposit(address indexed sponsor, uint256 indexed collateralAmount); - event Withdrawal(address indexed sponsor, uint256 indexed collateralAmount); - event RequestWithdrawal(address indexed sponsor, uint256 indexed collateralAmount); - event RequestWithdrawalExecuted(address indexed sponsor, uint256 indexed collateralAmount); - event RequestWithdrawalCanceled(address indexed sponsor, uint256 indexed collateralAmount); - event PositionCreated(address indexed sponsor, uint256 indexed collateralAmount, uint256 indexed tokenAmount); - event NewSponsor(address indexed sponsor); - event EndedSponsorPosition(address indexed sponsor); - event Repay(address indexed sponsor, uint256 indexed numTokensRepaid, uint256 indexed newTokenCount); - event Redeem(address indexed sponsor, uint256 indexed collateralAmount, uint256 indexed tokenAmount); - event ContractExpired(address indexed caller); - event SettleExpiredPosition( - address indexed caller, - uint256 indexed collateralReturned, - uint256 indexed tokensBurned - ); - event EmergencyShutdown(address indexed caller, uint256 originalExpirationTimestamp, uint256 shutdownTimestamp); - - /**************************************** - * MODIFIERS * - ****************************************/ - - modifier onlyPreExpiration() { - _onlyPreExpiration(); - _; - } - - modifier onlyPostExpiration() { - _onlyPostExpiration(); - _; - } - - modifier onlyCollateralizedPosition(address sponsor) { - _onlyCollateralizedPosition(sponsor); - _; - } - - // Check that the current state of the pricelessPositionManager is Open. - // This prevents multiple calls to `expire` and `EmergencyShutdown` post expiration. - modifier onlyOpenState() { - _onlyOpenState(); - _; - } - - modifier noPendingWithdrawal(address sponsor) { - _positionHasNoPendingWithdrawal(sponsor); - _; - } - - /** - * @notice Construct the PricelessPositionManager - * @dev Deployer of this contract should consider carefully which parties have ability to mint and burn - * the synthetic tokens referenced by `_tokenAddress`. This contract's security assumes that no external accounts - * can mint new tokens, which could be used to steal all of this contract's locked collateral. - * We recommend to only use synthetic token contracts whose sole Owner role (the role capable of adding & removing roles) - * is assigned to this contract, whose sole Minter role is assigned to this contract, and whose - * total supply is 0 prior to construction of this contract. - * @param _expirationTimestamp unix timestamp of when the contract will expire. - * @param _withdrawalLiveness liveness delay, in seconds, for pending withdrawals. - * @param _collateralAddress ERC20 token used as collateral for all positions. - * @param _tokenAddress ERC20 token used as synthetic token. - * @param _finderAddress UMA protocol Finder used to discover other protocol contracts. - * @param _priceIdentifier registered in the DVM for the synthetic. - * @param _minSponsorTokens minimum number of tokens that must exist at any time in a position. - * @param _timerAddress Contract that stores the current time in a testing environment. - * Must be set to 0x0 for production environments that use live time. - * @param _financialProductLibraryAddress Contract providing contract state transformations. - */ - constructor( - uint256 _expirationTimestamp, - uint256 _withdrawalLiveness, - address _collateralAddress, - address _tokenAddress, - address _finderAddress, - bytes32 _priceIdentifier, - FixedPoint.Unsigned memory _minSponsorTokens, - address _timerAddress, - address _financialProductLibraryAddress - ) FeePayer(_collateralAddress, _finderAddress, _timerAddress) nonReentrant() { - require(_expirationTimestamp > getCurrentTime()); - require(_getIdentifierWhitelist().isIdentifierSupported(_priceIdentifier)); - - expirationTimestamp = _expirationTimestamp; - withdrawalLiveness = _withdrawalLiveness; - tokenCurrency = ExpandedIERC20(_tokenAddress); - minSponsorTokens = _minSponsorTokens; - priceIdentifier = _priceIdentifier; - - // Initialize the financialProductLibrary at the provided address. - financialProductLibrary = FinancialProductLibrary(_financialProductLibraryAddress); - } - - /**************************************** - * POSITION FUNCTIONS * - ****************************************/ - - /** - * @notice Requests to transfer ownership of the caller's current position to a new sponsor address. - * Once the request liveness is passed, the sponsor can execute the transfer and specify the new sponsor. - * @dev The liveness length is the same as the withdrawal liveness. - */ - function requestTransferPosition() public onlyPreExpiration nonReentrant { - PositionData storage positionData = _getPositionData(msg.sender); - require(positionData.transferPositionRequestPassTimestamp == 0); - - // Make sure the proposed expiration of this request is not post-expiry. - uint256 requestPassTime = getCurrentTime().add(withdrawalLiveness); - require(requestPassTime < expirationTimestamp); - - // Update the position object for the user. - positionData.transferPositionRequestPassTimestamp = requestPassTime; - - emit RequestTransferPosition(msg.sender); - } - - /** - * @notice After a passed transfer position request (i.e., by a call to `requestTransferPosition` and waiting - * `withdrawalLiveness`), transfers ownership of the caller's current position to `newSponsorAddress`. - * @dev Transferring positions can only occur if the recipient does not already have a position. - * @param newSponsorAddress is the address to which the position will be transferred. - */ - function transferPositionPassedRequest( - address newSponsorAddress - ) public onlyPreExpiration noPendingWithdrawal(msg.sender) nonReentrant { - require( - _getFeeAdjustedCollateral(positions[newSponsorAddress].rawCollateral).isEqual( - FixedPoint.fromUnscaledUint(0) - ) - ); - PositionData storage positionData = _getPositionData(msg.sender); - require( - positionData.transferPositionRequestPassTimestamp != 0 && - positionData.transferPositionRequestPassTimestamp <= getCurrentTime() - ); - - // Reset transfer request. - positionData.transferPositionRequestPassTimestamp = 0; - - positions[newSponsorAddress] = positionData; - delete positions[msg.sender]; - - emit RequestTransferPositionExecuted(msg.sender, newSponsorAddress); - emit NewSponsor(newSponsorAddress); - emit EndedSponsorPosition(msg.sender); - } - - /** - * @notice Cancels a pending transfer position request. - */ - function cancelTransferPosition() external onlyPreExpiration nonReentrant { - PositionData storage positionData = _getPositionData(msg.sender); - require(positionData.transferPositionRequestPassTimestamp != 0); - - emit RequestTransferPositionCanceled(msg.sender); - - // Reset withdrawal request. - positionData.transferPositionRequestPassTimestamp = 0; - } - - /** - * @notice Transfers `collateralAmount` of `collateralCurrency` into the specified sponsor's position. - * @dev Increases the collateralization level of a position after creation. This contract must be approved to spend - * at least `collateralAmount` of `collateralCurrency`. - * @param sponsor the sponsor to credit the deposit to. - * @param collateralAmount total amount of collateral tokens to be sent to the sponsor's position. - */ - function depositTo( - address sponsor, - FixedPoint.Unsigned memory collateralAmount - ) public onlyPreExpiration noPendingWithdrawal(sponsor) fees nonReentrant { - require(collateralAmount.isGreaterThan(0)); - PositionData storage positionData = _getPositionData(sponsor); - - // Increase the position and global collateral balance by collateral amount. - _incrementCollateralBalances(positionData, collateralAmount); - - emit Deposit(sponsor, collateralAmount.rawValue); - - // Move collateral currency from sender to contract. - collateralCurrency.safeTransferFrom(msg.sender, address(this), collateralAmount.rawValue); - } - - /** - * @notice Transfers `collateralAmount` of `collateralCurrency` into the caller's position. - * @dev Increases the collateralization level of a position after creation. This contract must be approved to spend - * at least `collateralAmount` of `collateralCurrency`. - * @param collateralAmount total amount of collateral tokens to be sent to the sponsor's position. - */ - function deposit(FixedPoint.Unsigned memory collateralAmount) public { - // This is just a thin wrapper over depositTo that specified the sender as the sponsor. - depositTo(msg.sender, collateralAmount); - } - - /** - * @notice Transfers `collateralAmount` of `collateralCurrency` from the sponsor's position to the sponsor. - * @dev Reverts if the withdrawal puts this position's collateralization ratio below the global collateralization - * ratio. In that case, use `requestWithdrawal`. Might not withdraw the full requested amount to account for precision loss. - * @param collateralAmount is the amount of collateral to withdraw. - * @return amountWithdrawn The actual amount of collateral withdrawn. - */ - function withdraw( - FixedPoint.Unsigned memory collateralAmount - ) - public - onlyPreExpiration - noPendingWithdrawal(msg.sender) - fees - nonReentrant - returns (FixedPoint.Unsigned memory amountWithdrawn) - { - require(collateralAmount.isGreaterThan(0)); - PositionData storage positionData = _getPositionData(msg.sender); - - // Decrement the sponsor's collateral and global collateral amounts. Check the GCR between decrement to ensure - // position remains above the GCR within the withdrawal. If this is not the case the caller must submit a request. - amountWithdrawn = _decrementCollateralBalancesCheckGCR(positionData, collateralAmount); - - emit Withdrawal(msg.sender, amountWithdrawn.rawValue); - - // Move collateral currency from contract to sender. - // Note: that we move the amount of collateral that is decreased from rawCollateral (inclusive of fees) - // instead of the user requested amount. This eliminates precision loss that could occur - // where the user withdraws more collateral than rawCollateral is decremented by. - collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue); - } - - /** - * @notice Starts a withdrawal request that, if passed, allows the sponsor to withdraw` from their position. - * @dev The request will be pending for `withdrawalLiveness`, during which the position can be liquidated. - * @param collateralAmount the amount of collateral requested to withdraw - */ - function requestWithdrawal( - FixedPoint.Unsigned memory collateralAmount - ) public onlyPreExpiration noPendingWithdrawal(msg.sender) nonReentrant { - PositionData storage positionData = _getPositionData(msg.sender); - require( - collateralAmount.isGreaterThan(0) && - collateralAmount.isLessThanOrEqual(_getFeeAdjustedCollateral(positionData.rawCollateral)) - ); - - // Make sure the proposed expiration of this request is not post-expiry. - uint256 requestPassTime = getCurrentTime().add(withdrawalLiveness); - require(requestPassTime < expirationTimestamp); - - // Update the position object for the user. - positionData.withdrawalRequestPassTimestamp = requestPassTime; - positionData.withdrawalRequestAmount = collateralAmount; - - emit RequestWithdrawal(msg.sender, collateralAmount.rawValue); - } - - /** - * @notice After a passed withdrawal request (i.e., by a call to `requestWithdrawal` and waiting - * `withdrawalLiveness`), withdraws `positionData.withdrawalRequestAmount` of collateral currency. - * @dev Might not withdraw the full requested amount in order to account for precision loss or if the full requested - * amount exceeds the collateral in the position (due to paying fees). - * @return amountWithdrawn The actual amount of collateral withdrawn. - */ - function withdrawPassedRequest() - external - onlyPreExpiration - fees - nonReentrant - returns (FixedPoint.Unsigned memory amountWithdrawn) - { - PositionData storage positionData = _getPositionData(msg.sender); - require( - positionData.withdrawalRequestPassTimestamp != 0 && - positionData.withdrawalRequestPassTimestamp <= getCurrentTime() - ); - - // If withdrawal request amount is > position collateral, then withdraw the full collateral amount. - // This situation is possible due to fees charged since the withdrawal was originally requested. - FixedPoint.Unsigned memory amountToWithdraw = positionData.withdrawalRequestAmount; - if (positionData.withdrawalRequestAmount.isGreaterThan(_getFeeAdjustedCollateral(positionData.rawCollateral))) { - amountToWithdraw = _getFeeAdjustedCollateral(positionData.rawCollateral); - } - - // Decrement the sponsor's collateral and global collateral amounts. - amountWithdrawn = _decrementCollateralBalances(positionData, amountToWithdraw); - - // Reset withdrawal request by setting withdrawal amount and withdrawal timestamp to 0. - _resetWithdrawalRequest(positionData); - - // Transfer approved withdrawal amount from the contract to the caller. - collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue); - - emit RequestWithdrawalExecuted(msg.sender, amountWithdrawn.rawValue); - } - - /** - * @notice Cancels a pending withdrawal request. - */ - function cancelWithdrawal() external nonReentrant { - PositionData storage positionData = _getPositionData(msg.sender); - require(positionData.withdrawalRequestPassTimestamp != 0); - - emit RequestWithdrawalCanceled(msg.sender, positionData.withdrawalRequestAmount.rawValue); - - // Reset withdrawal request by setting withdrawal amount and withdrawal timestamp to 0. - _resetWithdrawalRequest(positionData); - } - - /** - * @notice Creates tokens by creating a new position or by augmenting an existing position. Pulls `collateralAmount` into the sponsor's position and mints `numTokens` of `tokenCurrency`. - * @dev Reverts if minting these tokens would put the position's collateralization ratio below the - * global collateralization ratio. This contract must be approved to spend at least `collateralAmount` of - * `collateralCurrency`. - * @dev This contract must have the Minter role for the `tokenCurrency`. - * @param collateralAmount is the number of collateral tokens to collateralize the position with - * @param numTokens is the number of tokens to mint from the position. - */ - function create( - FixedPoint.Unsigned memory collateralAmount, - FixedPoint.Unsigned memory numTokens - ) public onlyPreExpiration fees nonReentrant { - PositionData storage positionData = positions[msg.sender]; - - // Either the new create ratio or the resultant position CR must be above the current GCR. - require( - (_checkCollateralization( - _getFeeAdjustedCollateral(positionData.rawCollateral).add(collateralAmount), - positionData.tokensOutstanding.add(numTokens) - ) || _checkCollateralization(collateralAmount, numTokens)), - "Insufficient collateral" - ); - - require(positionData.withdrawalRequestPassTimestamp == 0, "Pending withdrawal"); - if (positionData.tokensOutstanding.isEqual(0)) { - require(numTokens.isGreaterThanOrEqual(minSponsorTokens), "Below minimum sponsor position"); - emit NewSponsor(msg.sender); - } - - // Increase the position and global collateral balance by collateral amount. - _incrementCollateralBalances(positionData, collateralAmount); - - // Add the number of tokens created to the position's outstanding tokens. - positionData.tokensOutstanding = positionData.tokensOutstanding.add(numTokens); - - totalTokensOutstanding = totalTokensOutstanding.add(numTokens); - - emit PositionCreated(msg.sender, collateralAmount.rawValue, numTokens.rawValue); - - // Transfer tokens into the contract from caller and mint corresponding synthetic tokens to the caller's address. - collateralCurrency.safeTransferFrom(msg.sender, address(this), collateralAmount.rawValue); - require(tokenCurrency.mint(msg.sender, numTokens.rawValue)); - } - - /** - * @notice Burns `numTokens` of `tokenCurrency` to decrease sponsors position size, without sending back `collateralCurrency`. - * This is done by a sponsor to increase position CR. Resulting size is bounded by minSponsorTokens. - * @dev Can only be called by token sponsor. This contract must be approved to spend `numTokens` of `tokenCurrency`. - * @dev This contract must have the Burner role for the `tokenCurrency`. - * @param numTokens is the number of tokens to be burnt from the sponsor's debt position. - */ - function repay( - FixedPoint.Unsigned memory numTokens - ) public onlyPreExpiration noPendingWithdrawal(msg.sender) fees nonReentrant { - PositionData storage positionData = _getPositionData(msg.sender); - require(numTokens.isLessThanOrEqual(positionData.tokensOutstanding)); - - // Decrease the sponsors position tokens size. Ensure it is above the min sponsor size. - FixedPoint.Unsigned memory newTokenCount = positionData.tokensOutstanding.sub(numTokens); - require(newTokenCount.isGreaterThanOrEqual(minSponsorTokens)); - positionData.tokensOutstanding = newTokenCount; - - // Update the totalTokensOutstanding after redemption. - totalTokensOutstanding = totalTokensOutstanding.sub(numTokens); - - emit Repay(msg.sender, numTokens.rawValue, newTokenCount.rawValue); - - // Transfer the tokens back from the sponsor and burn them. - tokenCurrency.safeTransferFrom(msg.sender, address(this), numTokens.rawValue); - tokenCurrency.burn(numTokens.rawValue); - } - - /** - * @notice Burns `numTokens` of `tokenCurrency` and sends back the proportional amount of `collateralCurrency`. - * @dev Can only be called by a token sponsor. Might not redeem the full proportional amount of collateral - * in order to account for precision loss. This contract must be approved to spend at least `numTokens` of - * `tokenCurrency`. - * @dev This contract must have the Burner role for the `tokenCurrency`. - * @param numTokens is the number of tokens to be burnt for a commensurate amount of collateral. - * @return amountWithdrawn The actual amount of collateral withdrawn. - */ - function redeem( - FixedPoint.Unsigned memory numTokens - ) public noPendingWithdrawal(msg.sender) fees nonReentrant returns (FixedPoint.Unsigned memory amountWithdrawn) { - PositionData storage positionData = _getPositionData(msg.sender); - require(!numTokens.isGreaterThan(positionData.tokensOutstanding)); - - FixedPoint.Unsigned memory fractionRedeemed = numTokens.div(positionData.tokensOutstanding); - FixedPoint.Unsigned memory collateralRedeemed = fractionRedeemed.mul( - _getFeeAdjustedCollateral(positionData.rawCollateral) - ); - - // If redemption returns all tokens the sponsor has then we can delete their position. Else, downsize. - if (positionData.tokensOutstanding.isEqual(numTokens)) { - amountWithdrawn = _deleteSponsorPosition(msg.sender); - } else { - // Decrement the sponsor's collateral and global collateral amounts. - amountWithdrawn = _decrementCollateralBalances(positionData, collateralRedeemed); - - // Decrease the sponsors position tokens size. Ensure it is above the min sponsor size. - FixedPoint.Unsigned memory newTokenCount = positionData.tokensOutstanding.sub(numTokens); - require(newTokenCount.isGreaterThanOrEqual(minSponsorTokens), "Below minimum sponsor position"); - positionData.tokensOutstanding = newTokenCount; - - // Update the totalTokensOutstanding after redemption. - totalTokensOutstanding = totalTokensOutstanding.sub(numTokens); - } - - emit Redeem(msg.sender, amountWithdrawn.rawValue, numTokens.rawValue); - - // Transfer collateral from contract to caller and burn callers synthetic tokens. - collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue); - tokenCurrency.safeTransferFrom(msg.sender, address(this), numTokens.rawValue); - tokenCurrency.burn(numTokens.rawValue); - } - - /** - * @notice After a contract has passed expiry all token holders can redeem their tokens for underlying at the - * prevailing price defined by the DVM from the `expire` function. - * @dev This burns all tokens from the caller of `tokenCurrency` and sends back the proportional amount of - * `collateralCurrency`. Might not redeem the full proportional amount of collateral in order to account for - * precision loss. This contract must be approved to spend `tokenCurrency` at least up to the caller's full balance. - * @dev This contract must have the Burner role for the `tokenCurrency`. - * @return amountWithdrawn The actual amount of collateral withdrawn. - */ - function settleExpired() - external - onlyPostExpiration - fees - nonReentrant - returns (FixedPoint.Unsigned memory amountWithdrawn) - { - // If the contract state is open and onlyPostExpiration passed then `expire()` has not yet been called. - require(contractState != ContractState.Open, "Unexpired position"); - - // Get the current settlement price and store it. If it is not resolved will revert. - if (contractState != ContractState.ExpiredPriceReceived) { - expiryPrice = _getOraclePriceExpiration(expirationTimestamp); - contractState = ContractState.ExpiredPriceReceived; - } - - // Get caller's tokens balance and calculate amount of underlying entitled to them. - FixedPoint.Unsigned memory tokensToRedeem = FixedPoint.Unsigned(tokenCurrency.balanceOf(msg.sender)); - FixedPoint.Unsigned memory totalRedeemableCollateral = tokensToRedeem.mul(expiryPrice); - - // If the caller is a sponsor with outstanding collateral they are also entitled to their excess collateral after their debt. - PositionData storage positionData = positions[msg.sender]; - if (_getFeeAdjustedCollateral(positionData.rawCollateral).isGreaterThan(0)) { - // Calculate the underlying entitled to a token sponsor. This is collateral - debt in underlying. - FixedPoint.Unsigned memory tokenDebtValueInCollateral = positionData.tokensOutstanding.mul(expiryPrice); - FixedPoint.Unsigned memory positionCollateral = _getFeeAdjustedCollateral(positionData.rawCollateral); - - // If the debt is greater than the remaining collateral, they cannot redeem anything. - FixedPoint.Unsigned memory positionRedeemableCollateral = tokenDebtValueInCollateral.isLessThan( - positionCollateral - ) - ? positionCollateral.sub(tokenDebtValueInCollateral) - : FixedPoint.Unsigned(0); - - // Add the number of redeemable tokens for the sponsor to their total redeemable collateral. - totalRedeemableCollateral = totalRedeemableCollateral.add(positionRedeemableCollateral); - - // Reset the position state as all the value has been removed after settlement. - delete positions[msg.sender]; - emit EndedSponsorPosition(msg.sender); - } - - // Take the min of the remaining collateral and the collateral "owed". If the contract is undercapitalized, - // the caller will get as much collateral as the contract can pay out. - FixedPoint.Unsigned memory payout = FixedPoint.min( - _getFeeAdjustedCollateral(rawTotalPositionCollateral), - totalRedeemableCollateral - ); - - // Decrement total contract collateral and outstanding debt. - amountWithdrawn = _removeCollateral(rawTotalPositionCollateral, payout); - totalTokensOutstanding = totalTokensOutstanding.sub(tokensToRedeem); - - emit SettleExpiredPosition(msg.sender, amountWithdrawn.rawValue, tokensToRedeem.rawValue); - - // Transfer tokens & collateral and burn the redeemed tokens. - collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue); - tokenCurrency.safeTransferFrom(msg.sender, address(this), tokensToRedeem.rawValue); - tokenCurrency.burn(tokensToRedeem.rawValue); - } - - /**************************************** - * GLOBAL STATE FUNCTIONS * - ****************************************/ - - /** - * @notice Locks contract state in expired and requests oracle price. - * @dev this function can only be called once the contract is expired and can't be re-called. - */ - function expire() external onlyPostExpiration onlyOpenState fees nonReentrant { - contractState = ContractState.ExpiredPriceRequested; - - // Final fees do not need to be paid when sending a request to the optimistic oracle. - _requestOraclePriceExpiration(expirationTimestamp); - - emit ContractExpired(msg.sender); - } - - /** - * @notice Premature contract settlement under emergency circumstances. - * @dev Only the governor can call this function as they are permissioned within the `FinancialContractAdmin`. - * Upon emergency shutdown, the contract settlement time is set to the shutdown time. This enables withdrawal - * to occur via the standard `settleExpired` function. Contract state is set to `ExpiredPriceRequested` - * which prevents re-entry into this function or the `expire` function. No fees are paid when calling - * `emergencyShutdown` as the governor who would call the function would also receive the fees. - */ - function emergencyShutdown() external override onlyPreExpiration onlyOpenState nonReentrant { - require(msg.sender == _getFinancialContractsAdminAddress()); - - contractState = ContractState.ExpiredPriceRequested; - // Expiratory time now becomes the current time (emergency shutdown time). - // Price requested at this time stamp. `settleExpired` can now withdraw at this timestamp. - uint256 oldExpirationTimestamp = expirationTimestamp; - expirationTimestamp = getCurrentTime(); - _requestOraclePriceExpiration(expirationTimestamp); - - emit EmergencyShutdown(msg.sender, oldExpirationTimestamp, expirationTimestamp); - } - - /** - * @notice Theoretically supposed to pay fees and move money between margin accounts to make sure they - * reflect the NAV of the contract. However, this functionality doesn't apply to this contract. - * @dev This is supposed to be implemented by any contract that inherits `AdministrateeInterface` and callable - * only by the Governor contract. This method is therefore minimally implemented in this contract and does nothing. - */ - function remargin() external override onlyPreExpiration nonReentrant { - return; - } - - /** - * @notice Accessor method for a sponsor's collateral. - * @dev This is necessary because the struct returned by the positions() method shows - * rawCollateral, which isn't a user-readable value. - * @dev This method accounts for pending regular fees that have not yet been withdrawn from this contract, for - * example if the `lastPaymentTime != currentTime`. - * @param sponsor address whose collateral amount is retrieved. - * @return collateralAmount amount of collateral within a sponsors position. - */ - function getCollateral(address sponsor) external view nonReentrantView returns (FixedPoint.Unsigned memory) { - // Note: do a direct access to avoid the validity check. - return _getPendingRegularFeeAdjustedCollateral(_getFeeAdjustedCollateral(positions[sponsor].rawCollateral)); - } - - /** - * @notice Accessor method for the total collateral stored within the PricelessPositionManager. - * @return totalCollateral amount of all collateral within the Expiring Multi Party Contract. - * @dev This method accounts for pending regular fees that have not yet been withdrawn from this contract, for - * example if the `lastPaymentTime != currentTime`. - */ - function totalPositionCollateral() external view nonReentrantView returns (FixedPoint.Unsigned memory) { - return _getPendingRegularFeeAdjustedCollateral(_getFeeAdjustedCollateral(rawTotalPositionCollateral)); - } - - /** - * @notice Accessor method to compute a transformed price using the finanicalProductLibrary specified at contract - * deployment. If no library was provided then no modification to the price is done. - * @param price input price to be transformed. - * @param requestTime timestamp the oraclePrice was requested at. - * @return transformedPrice price with the transformation function applied to it. - * @dev This method should never revert. - */ - - function transformPrice( - FixedPoint.Unsigned memory price, - uint256 requestTime - ) public view nonReentrantView returns (FixedPoint.Unsigned memory) { - return _transformPrice(price, requestTime); - } - - /** - * @notice Accessor method to compute a transformed price identifier using the finanicalProductLibrary specified - * at contract deployment. If no library was provided then no modification to the identifier is done. - * @param requestTime timestamp the identifier is to be used at. - * @return transformedPrice price with the transformation function applied to it. - * @dev This method should never revert. - */ - function transformPriceIdentifier(uint256 requestTime) public view nonReentrantView returns (bytes32) { - return _transformPriceIdentifier(requestTime); - } - - /**************************************** - * INTERNAL FUNCTIONS * - ****************************************/ - - // Reduces a sponsor's position and global counters by the specified parameters. Handles deleting the entire - // position if the entire position is being removed. Does not make any external transfers. - function _reduceSponsorPosition( - address sponsor, - FixedPoint.Unsigned memory tokensToRemove, - FixedPoint.Unsigned memory collateralToRemove, - FixedPoint.Unsigned memory withdrawalAmountToRemove - ) internal { - PositionData storage positionData = _getPositionData(sponsor); - - // If the entire position is being removed, delete it instead. - if ( - tokensToRemove.isEqual(positionData.tokensOutstanding) && - _getFeeAdjustedCollateral(positionData.rawCollateral).isEqual(collateralToRemove) - ) { - _deleteSponsorPosition(sponsor); - return; - } - - // Decrement the sponsor's collateral and global collateral amounts. - _decrementCollateralBalances(positionData, collateralToRemove); - - // Ensure that the sponsor will meet the min position size after the reduction. - FixedPoint.Unsigned memory newTokenCount = positionData.tokensOutstanding.sub(tokensToRemove); - require(newTokenCount.isGreaterThanOrEqual(minSponsorTokens), "Below minimum sponsor position"); - positionData.tokensOutstanding = newTokenCount; - - // Decrement the position's withdrawal amount. - positionData.withdrawalRequestAmount = positionData.withdrawalRequestAmount.sub(withdrawalAmountToRemove); - - // Decrement the total outstanding tokens in the overall contract. - totalTokensOutstanding = totalTokensOutstanding.sub(tokensToRemove); - } - - // Deletes a sponsor's position and updates global counters. Does not make any external transfers. - function _deleteSponsorPosition(address sponsor) internal returns (FixedPoint.Unsigned memory) { - PositionData storage positionToLiquidate = _getPositionData(sponsor); - - FixedPoint.Unsigned memory startingGlobalCollateral = _getFeeAdjustedCollateral(rawTotalPositionCollateral); - - // Remove the collateral and outstanding from the overall total position. - FixedPoint.Unsigned memory remainingRawCollateral = positionToLiquidate.rawCollateral; - rawTotalPositionCollateral = rawTotalPositionCollateral.sub(remainingRawCollateral); - totalTokensOutstanding = totalTokensOutstanding.sub(positionToLiquidate.tokensOutstanding); - - // Reset the sponsors position to have zero outstanding and collateral. - delete positions[sponsor]; - - emit EndedSponsorPosition(sponsor); - - // Return fee-adjusted amount of collateral deleted from position. - return startingGlobalCollateral.sub(_getFeeAdjustedCollateral(rawTotalPositionCollateral)); - } - - function _pfc() internal view virtual override returns (FixedPoint.Unsigned memory) { - return _getFeeAdjustedCollateral(rawTotalPositionCollateral); - } - - function _getPositionData( - address sponsor - ) internal view onlyCollateralizedPosition(sponsor) returns (PositionData storage) { - return positions[sponsor]; - } - - function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) { - return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); - } - - function _getOracle() internal view returns (OracleInterface) { - return OracleInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); - } - - function _getOptimisticOracle() internal view returns (OptimisticOracleInterface) { - return OptimisticOracleInterface(finder.getImplementationAddress(OracleInterfaces.OptimisticOracle)); - } - - function _getFinancialContractsAdminAddress() internal view returns (address) { - return finder.getImplementationAddress(OracleInterfaces.FinancialContractsAdmin); - } - - // Requests a price for transformed `priceIdentifier` at `requestedTime` from the Oracle. - function _requestOraclePriceExpiration(uint256 requestedTime) internal { - OptimisticOracleInterface optimisticOracle = _getOptimisticOracle(); - - // Increase token allowance to enable the optimistic oracle reward transfer. - FixedPoint.Unsigned memory reward = _computeFinalFees(); - collateralCurrency.safeIncreaseAllowance(address(optimisticOracle), reward.rawValue); - optimisticOracle.requestPrice( - _transformPriceIdentifier(requestedTime), - requestedTime, - _getAncillaryData(), - collateralCurrency, - reward.rawValue // Reward is equal to the final fee - ); - - // Apply haircut to all sponsors by decrementing the cumlativeFeeMultiplier by the amount lost from the final fee. - _adjustCumulativeFeeMultiplier(reward, _pfc()); - } - - // Fetches a resolved Oracle price from the Oracle. Reverts if the Oracle hasn't resolved for this request. - function _getOraclePriceExpiration(uint256 requestedTime) internal returns (FixedPoint.Unsigned memory) { - // Create an instance of the oracle and get the price. If the price is not resolved revert. - OptimisticOracleInterface optimisticOracle = _getOptimisticOracle(); - require( - optimisticOracle.hasPrice( - address(this), - _transformPriceIdentifier(requestedTime), - requestedTime, - _getAncillaryData() - ) - ); - int256 optimisticOraclePrice = optimisticOracle.settleAndGetPrice( - _transformPriceIdentifier(requestedTime), - requestedTime, - _getAncillaryData() - ); - - // For now we don't want to deal with negative prices in positions. - if (optimisticOraclePrice < 0) { - optimisticOraclePrice = 0; - } - return _transformPrice(FixedPoint.Unsigned(uint256(optimisticOraclePrice)), requestedTime); - } - - // Requests a price for transformed `priceIdentifier` at `requestedTime` from the Oracle. - function _requestOraclePriceLiquidation(uint256 requestedTime) internal { - OracleInterface oracle = _getOracle(); - oracle.requestPrice(_transformPriceIdentifier(requestedTime), requestedTime); - } - - // Fetches a resolved Oracle price from the Oracle. Reverts if the Oracle hasn't resolved for this request. - function _getOraclePriceLiquidation(uint256 requestedTime) internal view returns (FixedPoint.Unsigned memory) { - // Create an instance of the oracle and get the price. If the price is not resolved revert. - OracleInterface oracle = _getOracle(); - require(oracle.hasPrice(_transformPriceIdentifier(requestedTime), requestedTime), "Unresolved oracle price"); - int256 oraclePrice = oracle.getPrice(_transformPriceIdentifier(requestedTime), requestedTime); - - // For now we don't want to deal with negative prices in positions. - if (oraclePrice < 0) { - oraclePrice = 0; - } - return _transformPrice(FixedPoint.Unsigned(uint256(oraclePrice)), requestedTime); - } - - // Reset withdrawal request by setting the withdrawal request and withdrawal timestamp to 0. - function _resetWithdrawalRequest(PositionData storage positionData) internal { - positionData.withdrawalRequestAmount = FixedPoint.fromUnscaledUint(0); - positionData.withdrawalRequestPassTimestamp = 0; - } - - // Ensure individual and global consistency when increasing collateral balances. Returns the change to the position. - function _incrementCollateralBalances( - PositionData storage positionData, - FixedPoint.Unsigned memory collateralAmount - ) internal returns (FixedPoint.Unsigned memory) { - _addCollateral(positionData.rawCollateral, collateralAmount); - return _addCollateral(rawTotalPositionCollateral, collateralAmount); - } - - // Ensure individual and global consistency when decrementing collateral balances. Returns the change to the - // position. We elect to return the amount that the global collateral is decreased by, rather than the individual - // position's collateral, because we need to maintain the invariant that the global collateral is always - // <= the collateral owned by the contract to avoid reverts on withdrawals. The amount returned = amount withdrawn. - function _decrementCollateralBalances( - PositionData storage positionData, - FixedPoint.Unsigned memory collateralAmount - ) internal returns (FixedPoint.Unsigned memory) { - _removeCollateral(positionData.rawCollateral, collateralAmount); - return _removeCollateral(rawTotalPositionCollateral, collateralAmount); - } - - // Ensure individual and global consistency when decrementing collateral balances. Returns the change to the position. - // This function is similar to the _decrementCollateralBalances function except this function checks position GCR - // between the decrements. This ensures that collateral removal will not leave the position undercollateralized. - function _decrementCollateralBalancesCheckGCR( - PositionData storage positionData, - FixedPoint.Unsigned memory collateralAmount - ) internal returns (FixedPoint.Unsigned memory) { - _removeCollateral(positionData.rawCollateral, collateralAmount); - require(_checkPositionCollateralization(positionData), "CR below GCR"); - return _removeCollateral(rawTotalPositionCollateral, collateralAmount); - } - - // These internal functions are supposed to act identically to modifiers, but re-used modifiers - // unnecessarily increase contract bytecode size. - // source: https://blog.polymath.network/solidity-tips-and-tricks-to-save-gas-and-reduce-bytecode-size-c44580b218e6 - function _onlyOpenState() internal view { - require(contractState == ContractState.Open, "Contract state is not OPEN"); - } - - function _onlyPreExpiration() internal view { - require(getCurrentTime() < expirationTimestamp, "Only callable pre-expiry"); - } - - function _onlyPostExpiration() internal view { - require(getCurrentTime() >= expirationTimestamp, "Only callable post-expiry"); - } - - function _onlyCollateralizedPosition(address sponsor) internal view { - require( - _getFeeAdjustedCollateral(positions[sponsor].rawCollateral).isGreaterThan(0), - "Position has no collateral" - ); - } - - // Note: This checks whether an already existing position has a pending withdrawal. This cannot be used on the - // `create` method because it is possible that `create` is called on a new position (i.e. one without any collateral - // or tokens outstanding) which would fail the `onlyCollateralizedPosition` modifier on `_getPositionData`. - function _positionHasNoPendingWithdrawal(address sponsor) internal view { - require(_getPositionData(sponsor).withdrawalRequestPassTimestamp == 0, "Pending withdrawal"); - } - - /**************************************** - * PRIVATE FUNCTIONS * - ****************************************/ - - function _checkPositionCollateralization(PositionData storage positionData) private view returns (bool) { - return - _checkCollateralization( - _getFeeAdjustedCollateral(positionData.rawCollateral), - positionData.tokensOutstanding - ); - } - - // Checks whether the provided `collateral` and `numTokens` have a collateralization ratio above the global - // collateralization ratio. - function _checkCollateralization( - FixedPoint.Unsigned memory collateral, - FixedPoint.Unsigned memory numTokens - ) private view returns (bool) { - FixedPoint.Unsigned memory global = _getCollateralizationRatio( - _getFeeAdjustedCollateral(rawTotalPositionCollateral), - totalTokensOutstanding - ); - FixedPoint.Unsigned memory thisChange = _getCollateralizationRatio(collateral, numTokens); - return !global.isGreaterThan(thisChange); - } - - function _getCollateralizationRatio( - FixedPoint.Unsigned memory collateral, - FixedPoint.Unsigned memory numTokens - ) private pure returns (FixedPoint.Unsigned memory ratio) { - if (!numTokens.isGreaterThan(0)) { - return FixedPoint.fromUnscaledUint(0); - } else { - return collateral.div(numTokens); - } - } - - // IERC20Standard.decimals() will revert if the collateral contract has not implemented the decimals() method, - // which is possible since the method is only an OPTIONAL method in the ERC20 standard: - // https://eips.ethereum.org/EIPS/eip-20#methods. - function _getSyntheticDecimals(address _collateralAddress) public view returns (uint8 decimals) { - try IERC20Standard(_collateralAddress).decimals() returns (uint8 _decimals) { - return _decimals; - } catch { - return 18; - } - } - - function _transformPrice( - FixedPoint.Unsigned memory price, - uint256 requestTime - ) internal view returns (FixedPoint.Unsigned memory) { - if (!address(financialProductLibrary).isContract()) return price; - try financialProductLibrary.transformPrice(price, requestTime) returns ( - FixedPoint.Unsigned memory transformedPrice - ) { - return transformedPrice; - } catch { - return price; - } - } - - function _transformPriceIdentifier(uint256 requestTime) internal view returns (bytes32) { - if (!address(financialProductLibrary).isContract()) return priceIdentifier; - try financialProductLibrary.transformPriceIdentifier(priceIdentifier, requestTime) returns ( - bytes32 transformedIdentifier - ) { - return transformedIdentifier; - } catch { - return priceIdentifier; - } - } - - function _getAncillaryData() internal view returns (bytes memory) { - // Note: when ancillary data is passed to the optimistic oracle, it should be tagged with the token address - // whose funding rate it's trying to get. - return abi.encodePacked(address(tokenCurrency)); - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/long-short-pair/LongShortPair.sol b/contracts/external/uma/core/contracts/financial-templates/long-short-pair/LongShortPair.sol deleted file mode 100644 index e55437767..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/long-short-pair/LongShortPair.sol +++ /dev/null @@ -1,431 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/utils/math/Math.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; - -import "../common/financial-product-libraries/long-short-pair-libraries/LongShortPairFinancialProductLibrary.sol"; - -import "../../common/implementation/AncillaryData.sol"; -import "../../common/implementation/Testable.sol"; -import "../../common/implementation/Lockable.sol"; -import "../../common/implementation/FixedPoint.sol"; - -import "../../common/interfaces/ExpandedIERC20.sol"; -import "../../common/interfaces/AddressWhitelistInterface.sol"; - -import "../../data-verification-mechanism/interfaces/OracleInterface.sol"; -import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; -import "../../data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol"; -import "../../data-verification-mechanism/implementation/Constants.sol"; - -import "../../optimistic-oracle-v2/interfaces/OptimisticOracleV2Interface.sol"; - -/** - * @title Long Short Pair. - * @notice Uses a combination of long and short tokens to tokenize the bounded price exposure to a given identifier. - */ -contract LongShortPair is Testable, Lockable { - using FixedPoint for FixedPoint.Unsigned; - using SafeERC20 for IERC20; - - /************************************* - * LONG SHORT PAIR DATA STRUCTURES * - *************************************/ - - // Define the contract's constructor parameters as a struct to enable more variables to be specified. - struct ConstructorParams { - string pairName; // Name of the long short pair contract. - uint64 expirationTimestamp; // Unix timestamp of when the contract will expire. - uint256 collateralPerPair; // How many units of collateral are required to mint one pair of synthetic tokens. - bytes32 priceIdentifier; // Price identifier, registered in the DVM for the long short pair. - bool enableEarlyExpiration; // Enables the LSP contract to be settled early. - ExpandedIERC20 longToken; // Token used as long in the LSP. Mint and burn rights needed by this contract. - ExpandedIERC20 shortToken; // Token used as short in the LSP. Mint and burn rights needed by this contract. - IERC20 collateralToken; // Collateral token used to back LSP synthetics. - LongShortPairFinancialProductLibrary financialProductLibrary; // Contract providing settlement payout logic. - bytes customAncillaryData; // Custom ancillary data to be passed along with the price request to the OO. - uint256 proposerReward; // Optimistic oracle reward amount, pulled from the caller of the expire function. - uint256 optimisticOracleLivenessTime; // OO liveness time for price requests. - uint256 optimisticOracleProposerBond; // OO proposer bond for price requests. - FinderInterface finder; // DVM finder to find other UMA ecosystem contracts. - address timerAddress; // Timer used to synchronize contract time in testing. Set to 0x000... in production. - } - - bool public receivedSettlementPrice; - - bool public enableEarlyExpiration; // If set, the LSP contract can request to be settled early by calling the OO. - uint64 public expirationTimestamp; - uint64 public earlyExpirationTimestamp; // Set in the case the contract is expired early. - string public pairName; - uint256 public collateralPerPair; // Amount of collateral a pair of tokens is always redeemable for. - - // Number between 0 and 1e18 to allocate collateral between long & short tokens at redemption. 0 entitles each short - // to collateralPerPair and each long to 0. 1e18 makes each long worth collateralPerPair and short 0. - uint256 public expiryPercentLong; - bytes32 public priceIdentifier; - - // Price returned from the Optimistic oracle at settlement time. - int256 public expiryPrice; - - // External contract interfaces. - IERC20 public collateralToken; - ExpandedIERC20 public longToken; - ExpandedIERC20 public shortToken; - FinderInterface public finder; - LongShortPairFinancialProductLibrary public financialProductLibrary; - - // Optimistic oracle customization parameters. - bytes public customAncillaryData; - uint256 public proposerReward; - uint256 public optimisticOracleLivenessTime; - uint256 public optimisticOracleProposerBond; - - /**************************************** - * EVENTS * - ****************************************/ - - event TokensCreated(address indexed sponsor, uint256 indexed collateralUsed, uint256 indexed tokensMinted); - event TokensRedeemed(address indexed sponsor, uint256 indexed collateralReturned, uint256 indexed tokensRedeemed); - event ContractExpired(address indexed caller); - event EarlyExpirationRequested(address indexed caller, uint64 earlyExpirationTimeStamp); - event PositionSettled(address indexed sponsor, uint256 collateralReturned, uint256 longTokens, uint256 shortTokens); - - /**************************************** - * MODIFIERS * - ****************************************/ - - modifier preExpiration() { - require(getCurrentTime() < expirationTimestamp, "Only callable pre-expiry"); - _; - } - - modifier postExpiration() { - require(getCurrentTime() >= expirationTimestamp, "Only callable post-expiry"); - _; - } - - modifier notEarlyExpired() { - require(!isContractEarlyExpired(), "Contract already early expired"); - _; - } - - /** - * @notice Construct the LongShortPair - * @param params Constructor params used to initialize the LSP. Key-valued object with the following structure: - * - `pairName`: Name of the long short pair contract. - * - `expirationTimestamp`: Unix timestamp of when the contract will expire. - * - `collateralPerPair`: How many units of collateral are required to mint one pair of synthetic tokens. - * - `priceIdentifier`: Price identifier, registered in the DVM for the long short pair. - * - `longToken`: Token used as long in the LSP. Mint and burn rights needed by this contract. - * - `shortToken`: Token used as short in the LSP. Mint and burn rights needed by this contract. - * - `collateralToken`: Collateral token used to back LSP synthetics. - * - `financialProductLibrary`: Contract providing settlement payout logic. - * - `customAncillaryData`: Custom ancillary data to be passed along with the price request to the OO. - * - `proposerReward`: Preloaded reward to incentivize settlement price proposals. - * - `optimisticOracleLivenessTime`: OO liveness time for price requests. - * - `optimisticOracleProposerBond`: OO proposer bond for price requests. - * - `finder`: DVM finder to find other UMA ecosystem contracts. - * - `timerAddress`: Timer used to synchronize contract time in testing. Set to 0x000... in production. - */ - constructor(ConstructorParams memory params) Testable(params.timerAddress) { - finder = params.finder; - require(bytes(params.pairName).length > 0, "Pair name cant be empty"); - require(params.expirationTimestamp > getCurrentTime(), "Expiration timestamp in past"); - require(params.collateralPerPair > 0, "Collateral per pair cannot be 0"); - require(_getIdentifierWhitelist().isIdentifierSupported(params.priceIdentifier), "Identifier not registered"); - require(address(_getOptimisticOracle()) != address(0), "Invalid finder"); - require(address(params.financialProductLibrary) != address(0), "Invalid FinancialProductLibrary"); - require(_getCollateralWhitelist().isOnWhitelist(address(params.collateralToken)), "Collateral not whitelisted"); - require(params.optimisticOracleLivenessTime > 0, "OO liveness cannot be 0"); - require(params.optimisticOracleLivenessTime < 5200 weeks, "OO liveness too large"); - - pairName = params.pairName; - expirationTimestamp = params.expirationTimestamp; - collateralPerPair = params.collateralPerPair; - priceIdentifier = params.priceIdentifier; - enableEarlyExpiration = params.enableEarlyExpiration; - - longToken = params.longToken; - shortToken = params.shortToken; - collateralToken = params.collateralToken; - - financialProductLibrary = params.financialProductLibrary; - OptimisticOracleV2Interface optimisticOracle = _getOptimisticOracle(); - - // Ancillary data + additional stamped information should be less than ancillary data limit. Consider early - // expiration ancillary data, if enableEarlyExpiration is set. - customAncillaryData = params.customAncillaryData; - require( - optimisticOracle - .stampAncillaryData( - (enableEarlyExpiration ? getEarlyExpirationAncillaryData() : customAncillaryData), - address(this) - ) - .length <= optimisticOracle.ancillaryBytesLimit(), - "Ancillary Data too long" - ); - - proposerReward = params.proposerReward; - optimisticOracleLivenessTime = params.optimisticOracleLivenessTime; - optimisticOracleProposerBond = params.optimisticOracleProposerBond; - } - - /**************************************** - * POSITION FUNCTIONS * - ****************************************/ - - /** - * @notice Creates a pair of long and short tokens equal in number to tokensToCreate. Pulls the required collateral - * amount into this contract, defined by the collateralPerPair value. - * @dev The caller must approve this contract to transfer `tokensToCreate * collateralPerPair` amount of collateral. - * @param tokensToCreate number of long and short synthetic tokens to create. - * @return collateralUsed total collateral used to mint the synthetics. - */ - function create(uint256 tokensToCreate) public preExpiration nonReentrant returns (uint256 collateralUsed) { - // Note the use of mulCeil to prevent small collateralPerPair causing rounding of collateralUsed to 0 enabling - // callers to mint dust LSP tokens without paying any collateral. - collateralUsed = FixedPoint.Unsigned(tokensToCreate).mulCeil(FixedPoint.Unsigned(collateralPerPair)).rawValue; - - collateralToken.safeTransferFrom(msg.sender, address(this), collateralUsed); - - require(longToken.mint(msg.sender, tokensToCreate)); - require(shortToken.mint(msg.sender, tokensToCreate)); - - emit TokensCreated(msg.sender, collateralUsed, tokensToCreate); - } - - /** - * @notice Redeems a pair of long and short tokens equal in number to tokensToRedeem. Returns the commensurate - * amount of collateral to the caller for the pair of tokens, defined by the collateralPerPair value. - * @dev This contract must have the `Burner` role for the `longToken` and `shortToken` in order to call `burnFrom`. - * @dev The caller does not need to approve this contract to transfer any amount of `tokensToRedeem` since long - * and short tokens are burned, rather than transferred, from the caller. - * @dev This method can be called either pre or post expiration. - * @param tokensToRedeem number of long and short synthetic tokens to redeem. - * @return collateralReturned total collateral returned in exchange for the pair of synthetics. - */ - function redeem(uint256 tokensToRedeem) public nonReentrant returns (uint256 collateralReturned) { - require(longToken.burnFrom(msg.sender, tokensToRedeem)); - require(shortToken.burnFrom(msg.sender, tokensToRedeem)); - - collateralReturned = FixedPoint.Unsigned(tokensToRedeem).mul(FixedPoint.Unsigned(collateralPerPair)).rawValue; - - collateralToken.safeTransfer(msg.sender, collateralReturned); - - emit TokensRedeemed(msg.sender, collateralReturned, tokensToRedeem); - } - - /** - * @notice Settle long and/or short tokens in for collateral at a rate informed by the contract settlement. - * @dev Uses financialProductLibrary to compute the redemption rate between long and short tokens. - * @dev This contract must have the `Burner` role for the `longToken` and `shortToken` in order to call `burnFrom`. - * @dev The caller does not need to approve this contract to transfer any amount of `tokensToRedeem` since long - * and short tokens are burned, rather than transferred, from the caller. - * @dev This function can be called before or after expiration to facilitate early expiration. If a price has - * not yet been resolved for either normal or early expiration yet then it will revert. - * @param longTokensToRedeem number of long tokens to settle. - * @param shortTokensToRedeem number of short tokens to settle. - * @return collateralReturned total collateral returned in exchange for the pair of synthetics. - */ - function settle( - uint256 longTokensToRedeem, - uint256 shortTokensToRedeem - ) public nonReentrant returns (uint256 collateralReturned) { - // Either early expiration is enabled and it's before the expiration time or it's after the expiration time. - require( - (enableEarlyExpiration && getCurrentTime() < expirationTimestamp) || - getCurrentTime() >= expirationTimestamp, - "Cannot settle" - ); - - // Get the settlement price and store it. Also sets expiryPercentLong to inform settlement. Reverts if either: - // a) the price request has not resolved (either a normal expiration call or early expiration call) or b) If the - // the contract was attempted to be settled early but the price returned is the ignore oracle price. - // Note that we use the bool receivedSettlementPrice over checking for price != 0 as 0 is a valid price. - if (!receivedSettlementPrice) getExpirationPrice(); - - require(longToken.burnFrom(msg.sender, longTokensToRedeem)); - require(shortToken.burnFrom(msg.sender, shortTokensToRedeem)); - - // expiryPercentLong is a number between 0 and 1e18. 0 means all collateral goes to short tokens and 1e18 means - // all collateral goes to the long token. Total collateral returned is the sum of payouts. - uint256 longCollateralRedeemed = FixedPoint - .Unsigned(longTokensToRedeem) - .mul(FixedPoint.Unsigned(collateralPerPair)) - .mul(FixedPoint.Unsigned(expiryPercentLong)) - .rawValue; - uint256 shortCollateralRedeemed = FixedPoint - .Unsigned(shortTokensToRedeem) - .mul(FixedPoint.Unsigned(collateralPerPair)) - .mul(FixedPoint.fromUnscaledUint(1).sub(FixedPoint.Unsigned(expiryPercentLong))) - .rawValue; - - collateralReturned = longCollateralRedeemed + shortCollateralRedeemed; - collateralToken.safeTransfer(msg.sender, collateralReturned); - - emit PositionSettled(msg.sender, collateralReturned, longTokensToRedeem, shortTokensToRedeem); - } - - /**************************************** - * GLOBAL STATE FUNCTIONS * - ****************************************/ - - /** - * @notice Enables the LSP to request early expiration. This initiates a price request to the optimistic oracle at - * the provided timestamp with a modified version of the ancillary data that includes the key "earlyExpiration:1" - * which signals to the OO that this is an early expiration request, rather than standard settlement. - * @dev The caller must approve this contract to transfer `proposerReward` amount of collateral. - * @dev Will revert if: a) the contract is already early expired, b) it is after the expiration timestamp, c) - * early expiration is disabled for this contract, d) the proposed expiration timestamp is in the future. - * e) an early expiration attempt has already been made (in pending state). - * @param _earlyExpirationTimestamp timestamp at which the early expiration is proposed. - */ - function requestEarlyExpiration( - uint64 _earlyExpirationTimestamp - ) public nonReentrant notEarlyExpired preExpiration { - require(enableEarlyExpiration, "Early expiration disabled"); - require(_earlyExpirationTimestamp <= getCurrentTime(), "Only propose expire in the past"); - require(_earlyExpirationTimestamp > 0, "Early expiration can't be 0"); - - earlyExpirationTimestamp = _earlyExpirationTimestamp; - - _requestOraclePrice(earlyExpirationTimestamp, getEarlyExpirationAncillaryData()); - - emit EarlyExpirationRequested(msg.sender, _earlyExpirationTimestamp); - } - - /** - * @notice Expire the LSP contract. Makes a request to the optimistic oracle to inform the settlement price. - * @dev The caller must approve this contract to transfer `proposerReward` amount of collateral. - * @dev Will revert if: a) the contract is already early expired, b) it is before the expiration timestamp or c) - * an expire call has already been made. - */ - function expire() public nonReentrant notEarlyExpired postExpiration { - _requestOraclePrice(expirationTimestamp, customAncillaryData); - - emit ContractExpired(msg.sender); - } - - /*********************************** - * GLOBAL VIEW FUNCTIONS * - ***********************************/ - - /** - * @notice Returns the number of long and short tokens a sponsor wallet holds. - * @param sponsor address of the sponsor to query. - * @return longTokens the number of long tokens held by the sponsor. - * @return shortTokens the number of short tokens held by the sponsor. - */ - function getPositionTokens( - address sponsor - ) public view nonReentrantView returns (uint256 longTokens, uint256 shortTokens) { - return (longToken.balanceOf(sponsor), shortToken.balanceOf(sponsor)); - } - - /** - * @notice Generates a modified ancillary data that indicates the contract is being expired early. - */ - function getEarlyExpirationAncillaryData() public view returns (bytes memory) { - return AncillaryData.appendKeyValueUint(customAncillaryData, "earlyExpiration", 1); - } - - /** - * @notice Defines a special number that, if returned during an attempted early expiration, will cause the contract - * to do nothing and not expire. This enables the OO (and DVM voters in the case of a dispute) to choose to keep - * the contract running, thereby denying the early settlement request. - */ - function ignoreEarlyExpirationPrice() public pure returns (int256) { - return type(int256).min; - } - - /** - * @notice If the earlyExpirationTimestamp is != 0 then a previous early expiration OO request might still be in the - * pending state. Check if the OO contains the ignore early price. If it does not contain this then the contract - * was early expired correctly. Note that _getOraclePrice call will revert if the price request is still pending, - * thereby reverting all upstream calls pre-settlement of the early expiration price request. - */ - function isContractEarlyExpired() public returns (bool) { - return (earlyExpirationTimestamp != 0 && - _getOraclePrice(earlyExpirationTimestamp, getEarlyExpirationAncillaryData()) != - ignoreEarlyExpirationPrice()); - } - - /**************************************** - * INTERNAL FUNCTIONS * - ****************************************/ - - // Return the oracle price for a given request timestamp and ancillary data combo. - function _getOraclePrice(uint64 requestTimestamp, bytes memory requestAncillaryData) internal returns (int256) { - return _getOptimisticOracle().settleAndGetPrice(priceIdentifier, requestTimestamp, requestAncillaryData); - } - - // Request a price in the optimistic oracle for a given request timestamp and ancillary data combo. Set the bonds - // accordingly to the deployer's parameters. Will revert if re-requesting for a previously requested combo. - function _requestOraclePrice(uint64 requestTimestamp, bytes memory requestAncillaryData) internal { - OptimisticOracleV2Interface optimisticOracle = _getOptimisticOracle(); - - // If the proposer reward was set then pull it from the caller of the function. - if (proposerReward > 0) { - collateralToken.safeTransferFrom(msg.sender, address(this), proposerReward); - collateralToken.safeApprove(address(optimisticOracle), proposerReward); - } - optimisticOracle.requestPrice( - priceIdentifier, - uint256(requestTimestamp), - requestAncillaryData, - collateralToken, - proposerReward - ); - - // Set the Optimistic oracle liveness for the price request. - optimisticOracle.setCustomLiveness( - priceIdentifier, - uint256(requestTimestamp), - requestAncillaryData, - optimisticOracleLivenessTime - ); - - // Set the Optimistic oracle proposer bond for the price request. - optimisticOracle.setBond( - priceIdentifier, - uint256(requestTimestamp), - requestAncillaryData, - optimisticOracleProposerBond - ); - } - - // Fetch the optimistic oracle expiration price. If the oracle has the price for the provided expiration timestamp - // and customData combo then return this. Else, try fetch the price on the early expiration ancillary data. If - // there is no price for either, revert. If the early expiration price is the ignore price will also revert. - function getExpirationPrice() internal { - if (_getOptimisticOracle().hasPrice(address(this), priceIdentifier, expirationTimestamp, customAncillaryData)) - expiryPrice = _getOraclePrice(expirationTimestamp, customAncillaryData); - else { - expiryPrice = _getOraclePrice(earlyExpirationTimestamp, getEarlyExpirationAncillaryData()); - require(expiryPrice != ignoreEarlyExpirationPrice(), "Oracle prevents early expiration"); - } - - // Finally, compute the value of expiryPercentLong based on the expiryPrice. Cap the return value at 1e18 as - // this should, by definition, between 0 and 1e18. - expiryPercentLong = Math.min( - financialProductLibrary.percentageLongCollateralAtExpiry(expiryPrice), - FixedPoint.fromUnscaledUint(1).rawValue - ); - - receivedSettlementPrice = true; - } - - function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) { - return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); - } - - function _getCollateralWhitelist() internal view returns (AddressWhitelistInterface) { - return AddressWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist)); - } - - function _getOptimisticOracle() internal view returns (OptimisticOracleV2Interface) { - return OptimisticOracleV2Interface(finder.getImplementationAddress(OracleInterfaces.OptimisticOracleV2)); - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/long-short-pair/LongShortPairCreator.sol b/contracts/external/uma/core/contracts/financial-templates/long-short-pair/LongShortPairCreator.sol deleted file mode 100644 index 3323decac..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/long-short-pair/LongShortPairCreator.sol +++ /dev/null @@ -1,166 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/interfaces/ExpandedIERC20.sol"; -import "../../common/interfaces/IERC20Standard.sol"; -import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; -import "../../common/implementation/Testable.sol"; -import "../../common/implementation/Lockable.sol"; -import "../common/TokenFactory.sol"; -import "./LongShortPair.sol"; -import "../common/financial-product-libraries/long-short-pair-libraries/LongShortPairFinancialProductLibrary.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; - -/** - * @title Long Short Pair Contract Creator. - * @notice Factory contract to create new instances of long short pair contracts. - * Responsible for constraining the parameters used to construct a new LSP. These constraints can evolve over time and - * are initially constrained to conservative values in this first iteration. - */ -contract LongShortPairCreator is Testable, Lockable { - using FixedPoint for FixedPoint.Unsigned; - using SafeERC20 for IERC20Standard; - - struct CreatorParams { - string pairName; - uint64 expirationTimestamp; - uint256 collateralPerPair; - bytes32 priceIdentifier; - bool enableEarlyExpiration; - string longSynthName; - string longSynthSymbol; - string shortSynthName; - string shortSynthSymbol; - IERC20Standard collateralToken; - LongShortPairFinancialProductLibrary financialProductLibrary; - bytes customAncillaryData; - uint256 proposerReward; - uint256 optimisticOracleLivenessTime; - uint256 optimisticOracleProposerBond; - } - - // Address of TokenFactory used to create a new synthetic token. - TokenFactory public tokenFactory; - - FinderInterface public finder; - - event CreatedLongShortPair( - address indexed longShortPair, - address indexed deployerAddress, - address longToken, - address shortToken - ); - - /** - * @notice Constructs the LongShortPairCreator contract. - * @param _finder UMA protocol Finder used to discover other protocol contracts. - * @param _tokenFactory ERC20 token factory used to deploy synthetic token instances. - * @param _timer Contract that stores the current time in a testing environment. - */ - constructor(FinderInterface _finder, TokenFactory _tokenFactory, address _timer) Testable(_timer) nonReentrant() { - tokenFactory = _tokenFactory; - finder = _finder; - } - - /** - * @notice Creates a longShortPair contract and associated long and short tokens. - * @param params Constructor params used to initialize the LSP. Key-valued object with the following structure: - * - `pairName`: Name of the long short pair contract. - * - `expirationTimestamp`: Unix timestamp of when the contract will expire. - * - `collateralPerPair`: How many units of collateral are required to mint one pair of synthetic tokens. - * - `priceIdentifier`: Registered in the DVM for the synthetic. - * - `enableEarlyExpiration`: Enables the LSP contract to be settled early. - * - `longSynthName`: Name of the long synthetic tokens to be created. - * - `longSynthSymbol`: Symbol of the long synthetic tokens to be created. - * - `shortSynthName`: Name of the short synthetic tokens to be created. - * - `shortSynthSymbol`: Symbol of the short synthetic tokens to be created. - * - `collateralToken`: ERC20 token used as collateral in the LSP. - * - `financialProductLibrary`: Contract providing settlement payout logic. - * - `customAncillaryData`: Custom ancillary data to be passed along with the price request. If not needed, this - * should be left as a 0-length bytes array. - * - `proposerReward`: Optimistic oracle reward amount, pulled from the caller of the expire function. - * - `optimisticOracleLivenessTime`: Optimistic oracle liveness time for price requests. - * - `optimisticOracleProposerBond`: Optimistic oracle proposer bond for price requests. - * @return lspAddress the deployed address of the new long short pair contract. - * @notice Created LSP is not registered within the registry as the LSP uses the Optimistic Oracle for settlement. - * @notice The LSP constructor does a number of validations on input params. These are not repeated here. - */ - function createLongShortPair(CreatorParams memory params) public nonReentrant returns (address) { - // Create a new synthetic token using the params. - require(bytes(params.longSynthName).length != 0, "Missing long synthetic name"); - require(bytes(params.shortSynthName).length != 0, "Missing short synthetic name"); - require(bytes(params.longSynthSymbol).length != 0, "Missing long synthetic symbol"); - require(bytes(params.shortSynthSymbol).length != 0, "Missing short synthetic symbol"); - - // If the collateral token does not have a `decimals()` method, then a default precision of 18 will be - // applied to the newly created synthetic token. - uint8 collateralDecimals = _getSyntheticDecimals(params.collateralToken); - ExpandedIERC20 longToken = tokenFactory.createToken( - params.longSynthName, - params.longSynthSymbol, - collateralDecimals - ); - ExpandedIERC20 shortToken = tokenFactory.createToken( - params.shortSynthName, - params.shortSynthSymbol, - collateralDecimals - ); - - // Deploy the LSP contract. - LongShortPair lsp = new LongShortPair(_convertParams(params, longToken, shortToken)); - - address lspAddress = address(lsp); - - // Give permissions to new lsp contract and then hand over ownership. - longToken.addMinter(lspAddress); - longToken.addBurner(lspAddress); - longToken.resetOwner(lspAddress); - - shortToken.addMinter(lspAddress); - shortToken.addBurner(lspAddress); - shortToken.resetOwner(lspAddress); - - emit CreatedLongShortPair(lspAddress, msg.sender, address(longToken), address(shortToken)); - - return lspAddress; - } - - // Converts createLongShortPair creator params to LongShortPair constructor params. - function _convertParams( - CreatorParams memory creatorParams, - ExpandedIERC20 longToken, - ExpandedIERC20 shortToken - ) private view returns (LongShortPair.ConstructorParams memory constructorParams) { - // Input from function call. - constructorParams.pairName = creatorParams.pairName; - constructorParams.expirationTimestamp = creatorParams.expirationTimestamp; - constructorParams.collateralPerPair = creatorParams.collateralPerPair; - constructorParams.priceIdentifier = creatorParams.priceIdentifier; - constructorParams.enableEarlyExpiration = creatorParams.enableEarlyExpiration; - constructorParams.collateralToken = creatorParams.collateralToken; - constructorParams.financialProductLibrary = creatorParams.financialProductLibrary; - constructorParams.customAncillaryData = creatorParams.customAncillaryData; - constructorParams.proposerReward = creatorParams.proposerReward; - constructorParams.optimisticOracleLivenessTime = creatorParams.optimisticOracleLivenessTime; - constructorParams.optimisticOracleProposerBond = creatorParams.optimisticOracleProposerBond; - - // Constructed long & short synthetic tokens. - constructorParams.longToken = longToken; - constructorParams.shortToken = shortToken; - - // Finder and timer. Should be the same as that used in this factory contract. - constructorParams.finder = finder; - constructorParams.timerAddress = timerAddress; - } - - // IERC20Standard.decimals() will revert if the collateral contract has not implemented the decimals() method, - // which is possible since the method is only an OPTIONAL method in the ERC20 standard: - // https://eips.ethereum.org/EIPS/eip-20#methods. - function _getSyntheticDecimals(IERC20Standard _collateralToken) private view returns (uint8 decimals) { - try _collateralToken.decimals() returns (uint8 _decimals) { - return _decimals; - } catch { - return 18; - } - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/optimistic-distributor/OptimisticDistributor.sol b/contracts/external/uma/core/contracts/financial-templates/optimistic-distributor/OptimisticDistributor.sol deleted file mode 100644 index 04b123535..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/optimistic-distributor/OptimisticDistributor.sol +++ /dev/null @@ -1,438 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/AncillaryData.sol"; -import "../../common/implementation/Lockable.sol"; -import "../../common/implementation/MultiCaller.sol"; -import "../../common/implementation/Testable.sol"; -import "../../common/interfaces/AddressWhitelistInterface.sol"; -import "../../merkle-distributor/implementation/MerkleDistributor.sol"; -import "../../data-verification-mechanism/implementation/Constants.sol"; -import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; -import "../../data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol"; -import "../../optimistic-oracle-v2/interfaces/OptimisticOracleV2Interface.sol"; - -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; - -/** - * @title OptimisticDistributor contract. - * @notice Allows sponsors to distribute rewards through MerkleDistributor contract secured by UMA Optimistic Oracle. - */ -contract OptimisticDistributor is Lockable, MultiCaller, Testable { - using SafeERC20 for IERC20; - - /******************************************** - * OPTIMISTIC DISTRIBUTOR DATA STRUCTURES * - ********************************************/ - - // Represents reward posted by a sponsor. - struct Reward { - bool distributionExecuted; - address sponsor; - IERC20 rewardToken; - uint256 maximumRewardAmount; - uint256 earliestProposalTimestamp; - uint256 optimisticOracleProposerBond; - uint256 optimisticOracleLivenessTime; - uint256 previousProposalTimestamp; - bytes32 priceIdentifier; - bytes customAncillaryData; - } - - // Represents proposed rewards distribution. - struct Proposal { - uint256 rewardIndex; - uint256 timestamp; - bytes32 merkleRoot; - string ipfsHash; - } - - /******************************************** - * STATE VARIABLES AND CONSTANTS * - ********************************************/ - - // Reserve for bytes appended to ancillary data (e.g. OracleSpoke) when resolving price from non-mainnet chains. - // This also covers appending rewardIndex by this contract. - uint256 public constant ANCILLARY_BYTES_RESERVE = 512; - - // Restrict Optimistic Oracle liveness to between 10 minutes and 100 years. - uint256 public constant MINIMUM_LIVENESS = 10 minutes; - uint256 public constant MAXIMUM_LIVENESS = 5200 weeks; - - // Ancillary data length limit can be synced and stored in the contract. - uint256 public ancillaryBytesLimit; - - // Rewards are stored in dynamic array. - Reward[] public rewards; - - // Immutable variables used to validate input parameters when funding new rewards. - uint256 public immutable maximumFundingPeriod; - uint256 public immutable maximumProposerBond; - - // Proposals are mapped to hash of their identifier, timestamp and ancillaryData. - mapping(bytes32 => Proposal) public proposals; - - // Immutable variables provided at deployment. - FinderInterface public immutable finder; - IERC20 public immutable bondToken; - - // Merkle Distributor is automatically deployed on constructor and owned by this contract. - MerkleDistributor public immutable merkleDistributor; - - // Interface parameters that can be synced and stored in the contract. - OptimisticOracleV2Interface public optimisticOracle; - - /******************************************** - * EVENTS * - ********************************************/ - - event RewardCreated( - address indexed sponsor, - IERC20 rewardToken, - uint256 indexed rewardIndex, - uint256 maximumRewardAmount, - uint256 earliestProposalTimestamp, - uint256 optimisticOracleProposerBond, - uint256 optimisticOracleLivenessTime, - bytes32 indexed priceIdentifier, - bytes customAncillaryData - ); - event RewardIncreased(uint256 indexed rewardIndex, uint256 newMaximumRewardAmount); - event ProposalCreated( - address indexed sponsor, - IERC20 rewardToken, - uint256 indexed rewardIndex, - uint256 proposalTimestamp, - uint256 maximumRewardAmount, - bytes32 indexed proposalId, - bytes32 merkleRoot, - string ipfsHash - ); - event RewardDistributed( - address indexed sponsor, - IERC20 rewardToken, - uint256 indexed rewardIndex, - uint256 maximumRewardAmount, - bytes32 indexed proposalId, - bytes32 merkleRoot, - string ipfsHash - ); - event ProposalRejected(uint256 indexed rewardIndex, bytes32 indexed proposalId); - - /** - * @notice Constructor. - * @param _finder Finder to look up UMA contract addresses. - * @param _bondToken ERC20 token that the bond is paid in. - * @param _timer Contract that stores the current time in a testing environment. - * @param _maximumFundingPeriod Maximum period for reward funding (proposals allowed only afterwards). - * @param _maximumProposerBond Maximum allowed Optimistic Oracle proposer bond amount. - */ - constructor( - FinderInterface _finder, - IERC20 _bondToken, - address _timer, - uint256 _maximumFundingPeriod, - uint256 _maximumProposerBond - ) Testable(_timer) { - finder = _finder; - require(_getCollateralWhitelist().isOnWhitelist(address(_bondToken)), "Bond token not supported"); - bondToken = _bondToken; - syncUmaEcosystemParams(); - maximumFundingPeriod = _maximumFundingPeriod; - maximumProposerBond = _maximumProposerBond; - merkleDistributor = new MerkleDistributor(); - } - - /******************************************** - * FUNDING FUNCTIONS * - ********************************************/ - - /** - * @notice Allows any caller to create a Reward struct and deposit tokens that are linked to these rewards. - * @dev The caller must approve this contract to transfer `maximumRewardAmount` amount of `rewardToken`. - * @param rewardToken ERC20 token that the rewards will be paid in. - * @param maximumRewardAmount Maximum reward amount that the sponsor is posting for distribution. - * @param earliestProposalTimestamp Starting timestamp when proposals for distribution can be made. - * @param priceIdentifier Identifier that should be passed to the Optimistic Oracle on proposed distribution. - * @param customAncillaryData Custom ancillary data that should be sent to the Optimistic Oracle on proposed - * distribution. - * @param optimisticOracleProposerBond Amount of bondToken that should be posted in addition to final fee - * to the Optimistic Oracle on proposed distribution. - * @param optimisticOracleLivenessTime Liveness period in seconds during which proposed distribution can be - * disputed through Optimistic Oracle. - */ - function createReward( - uint256 maximumRewardAmount, - uint256 earliestProposalTimestamp, - uint256 optimisticOracleProposerBond, - uint256 optimisticOracleLivenessTime, - bytes32 priceIdentifier, - IERC20 rewardToken, - bytes calldata customAncillaryData - ) external nonReentrant { - require(earliestProposalTimestamp <= getCurrentTime() + maximumFundingPeriod, "Too long till proposal opening"); - require(optimisticOracleProposerBond <= maximumProposerBond, "OO proposer bond too high"); - require(_getIdentifierWhitelist().isIdentifierSupported(priceIdentifier), "Identifier not registered"); - require(_ancillaryDataWithinLimits(customAncillaryData), "Ancillary data too long"); - require(optimisticOracleLivenessTime >= MINIMUM_LIVENESS, "OO liveness too small"); - require(optimisticOracleLivenessTime < MAXIMUM_LIVENESS, "OO liveness too large"); - - // Store funded reward and log created reward. - Reward memory reward = Reward({ - distributionExecuted: false, - sponsor: msg.sender, - rewardToken: rewardToken, - maximumRewardAmount: maximumRewardAmount, - earliestProposalTimestamp: earliestProposalTimestamp, - optimisticOracleProposerBond: optimisticOracleProposerBond, - optimisticOracleLivenessTime: optimisticOracleLivenessTime, - previousProposalTimestamp: 0, - priceIdentifier: priceIdentifier, - customAncillaryData: customAncillaryData - }); - uint256 rewardIndex = rewards.length; - rewards.push() = reward; - emit RewardCreated( - reward.sponsor, - reward.rewardToken, - rewardIndex, - reward.maximumRewardAmount, - reward.earliestProposalTimestamp, - reward.optimisticOracleProposerBond, - reward.optimisticOracleLivenessTime, - reward.priceIdentifier, - reward.customAncillaryData - ); - - // Pull maximum rewards from the sponsor. - rewardToken.safeTransferFrom(msg.sender, address(this), maximumRewardAmount); - } - - /** - * @notice Allows anyone to deposit additional rewards for distribution before `earliestProposalTimestamp`. - * @dev The caller must approve this contract to transfer `additionalRewardAmount` amount of `rewardToken`. - * @param rewardIndex Index for identifying existing Reward struct that should receive additional funding. - * @param additionalRewardAmount Additional reward amount that the sponsor is posting for distribution. - */ - function increaseReward(uint256 rewardIndex, uint256 additionalRewardAmount) external nonReentrant { - require(rewardIndex < rewards.length, "Invalid rewardIndex"); - require(getCurrentTime() < rewards[rewardIndex].earliestProposalTimestamp, "Funding period ended"); - - // Update maximumRewardAmount and log new amount. - rewards[rewardIndex].maximumRewardAmount += additionalRewardAmount; - emit RewardIncreased(rewardIndex, rewards[rewardIndex].maximumRewardAmount); - - // Pull additional rewards from the sponsor. - rewards[rewardIndex].rewardToken.safeTransferFrom(msg.sender, address(this), additionalRewardAmount); - } - - /******************************************** - * DISTRIBUTION FUNCTIONS * - ********************************************/ - - /** - * @notice Allows any caller to propose distribution for funded reward starting from `earliestProposalTimestamp`. - * Only one undisputed proposal at a time is allowed. - * @dev The caller must approve this contract to transfer `optimisticOracleProposerBond` + final fee amount - * of `bondToken`. - * @param rewardIndex Index for identifying existing Reward struct that should be proposed for distribution. - * @param merkleRoot Merkle root describing allocation of proposed rewards distribution. - * @param ipfsHash Hash of IPFS object, conveniently stored for clients to verify proposed distribution. - */ - function proposeDistribution( - uint256 rewardIndex, - bytes32 merkleRoot, - string calldata ipfsHash - ) external nonReentrant { - require(rewardIndex < rewards.length, "Invalid rewardIndex"); - - uint256 timestamp = getCurrentTime(); - Reward memory reward = rewards[rewardIndex]; - require(timestamp >= reward.earliestProposalTimestamp, "Cannot propose in funding period"); - require(!reward.distributionExecuted, "Reward already distributed"); - require(_noBlockingProposal(rewardIndex, reward), "New proposals blocked"); - - // Store current timestamp at reward struct so that any subsequent proposals are blocked till dispute. - rewards[rewardIndex].previousProposalTimestamp = timestamp; - - // Append rewardIndex to ancillary data. - bytes memory ancillaryData = _appendRewardIndex(rewardIndex, reward.customAncillaryData); - - // Generate hash for proposalId. - bytes32 proposalId = _getProposalId(reward.priceIdentifier, timestamp, ancillaryData); - - // Request price from Optimistic Oracle. - optimisticOracle.requestPrice(reward.priceIdentifier, timestamp, ancillaryData, bondToken, 0); - - // Set proposal liveness and bond and calculate total bond amount. - optimisticOracle.setCustomLiveness( - reward.priceIdentifier, - timestamp, - ancillaryData, - reward.optimisticOracleLivenessTime - ); - uint256 totalBond = optimisticOracle.setBond( - reward.priceIdentifier, - timestamp, - ancillaryData, - reward.optimisticOracleProposerBond - ); - - // Pull proposal bond and final fee from the proposer, and approve it for Optimistic Oracle. - bondToken.safeTransferFrom(msg.sender, address(this), totalBond); - bondToken.safeApprove(address(optimisticOracle), totalBond); - - // Propose canonical value representing "True"; i.e. the proposed distribution is valid. - optimisticOracle.proposePriceFor( - msg.sender, - address(this), - reward.priceIdentifier, - timestamp, - ancillaryData, - int256(1e18) - ); - - // Store and log proposed distribution. - proposals[proposalId] = Proposal({ - rewardIndex: rewardIndex, - timestamp: timestamp, - merkleRoot: merkleRoot, - ipfsHash: ipfsHash - }); - emit ProposalCreated( - reward.sponsor, - reward.rewardToken, - rewardIndex, - timestamp, - reward.maximumRewardAmount, - proposalId, - merkleRoot, - ipfsHash - ); - } - - /** - * @notice Allows any caller to execute distribution that has been validated by the Optimistic Oracle. - * @param proposalId Hash for identifying existing rewards distribution proposal. - * @dev Calling this for unresolved proposals will revert. - */ - function executeDistribution(bytes32 proposalId) external nonReentrant { - // All valid proposals should have non-zero proposal timestamp. - Proposal memory proposal = proposals[proposalId]; - require(proposal.timestamp != 0, "Invalid proposalId"); - - // Only one validated proposal per reward can be executed for distribution. - Reward memory reward = rewards[proposal.rewardIndex]; - require(!reward.distributionExecuted, "Reward already distributed"); - - // Append reward index to ancillary data. - bytes memory ancillaryData = _appendRewardIndex(proposal.rewardIndex, reward.customAncillaryData); - - // Get resolved price. Reverts if the request is not settled or settleable. - int256 resolvedPrice = optimisticOracle.settleAndGetPrice( - reward.priceIdentifier, - proposal.timestamp, - ancillaryData - ); - - // Transfer rewards to MerkleDistributor for accepted proposal and flag distributionExecuted. - // This does not revert on rejected proposals so that disputer could receive back its bond and winning - // in the same transaction when settleAndGetPrice is called above. - if (resolvedPrice == 1e18) { - rewards[proposal.rewardIndex].distributionExecuted = true; - - reward.rewardToken.safeApprove(address(merkleDistributor), reward.maximumRewardAmount); - merkleDistributor.setWindow( - reward.maximumRewardAmount, - address(reward.rewardToken), - proposal.merkleRoot, - proposal.ipfsHash - ); - emit RewardDistributed( - reward.sponsor, - reward.rewardToken, - proposal.rewardIndex, - reward.maximumRewardAmount, - proposalId, - proposal.merkleRoot, - proposal.ipfsHash - ); - } - // ProposalRejected can be emitted multiple times whenever someone tries to execute the same rejected proposal. - else emit ProposalRejected(proposal.rewardIndex, proposalId); - } - - /******************************************** - * MAINTENANCE FUNCTIONS * - ********************************************/ - - /** - * @notice Updates the address stored in this contract for the OptimisticOracle to the latest version set - * in the Finder. - * @dev There is no risk of leaving this function public for anyone to call as in all cases we want the address of - * OptimisticOracle in this contract to map to the latest version in the Finder. - */ - function syncUmaEcosystemParams() public nonReentrant { - optimisticOracle = _getOptimisticOracle(); - ancillaryBytesLimit = optimisticOracle.ancillaryBytesLimit(); - } - - /******************************************** - * INTERNAL FUNCTIONS * - ********************************************/ - - function _getOptimisticOracle() internal view returns (OptimisticOracleV2Interface) { - return OptimisticOracleV2Interface(finder.getImplementationAddress(OracleInterfaces.OptimisticOracleV2)); - } - - function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) { - return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); - } - - function _getCollateralWhitelist() internal view returns (AddressWhitelistInterface) { - return AddressWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist)); - } - - function _appendRewardIndex( - uint256 rewardIndex, - bytes memory customAncillaryData - ) internal pure returns (bytes memory) { - return AncillaryData.appendKeyValueUint(customAncillaryData, "rewardIndex", rewardIndex); - } - - function _ancillaryDataWithinLimits(bytes memory customAncillaryData) internal view returns (bool) { - // Since rewardIndex has variable length as string, it is not appended here and is assumed - // to be included in ANCILLARY_BYTES_RESERVE. - return - optimisticOracle.stampAncillaryData(customAncillaryData, address(this)).length + ANCILLARY_BYTES_RESERVE <= - ancillaryBytesLimit; - } - - function _getProposalId( - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) internal pure returns (bytes32) { - return keccak256(abi.encode(identifier, timestamp, ancillaryData)); - } - - // Returns true if there are no blocking proposals (eiter there were no prior proposals or they were disputed). - function _noBlockingProposal(uint256 rewardIndex, Reward memory reward) internal view returns (bool) { - // Valid proposal cannot have zero timestamp. - if (reward.previousProposalTimestamp == 0) return true; - - bytes memory ancillaryData = _appendRewardIndex(rewardIndex, reward.customAncillaryData); - OptimisticOracleV2Interface.Request memory blockingRequest = optimisticOracle.getRequest( - address(this), - reward.priceIdentifier, - reward.previousProposalTimestamp, - ancillaryData - ); - - // Previous proposal is blocking till disputed that can be detected by non-zero disputer address. - // In case Optimistic Oracle was upgraded since the previous proposal it needs to be unblocked for new proposal. - // This can be detected by uninitialized bonding currency for the previous proposal. - return blockingRequest.disputer != address(0) || address(blockingRequest.currency) == address(0); - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarder.sol b/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarder.sol deleted file mode 100644 index f0ca56da1..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarder.sol +++ /dev/null @@ -1,114 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/token/ERC721/ERC721.sol"; - -import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; -import "./OptimisticRewarderBase.sol"; -import "./OptimisticRewarderToken.sol"; - -/** - * @notice The common optimistic rewarder contract. It is both the contract that pays out the rewards and the ERC721 - * token itself. - */ -contract OptimisticRewarder is OptimisticRewarderBase, OptimisticRewarderToken { - /** - * @notice Constructor. - * @param _name name for the ERC721 token. - * @param _symbol symbol for the ERC721 token. - * @param _baseUri prefix to each ERC721 tokenId's name. - * @param _liveness liveness period between submission and verification of a reward. - * @param _bondToken ERC20 token that the bond is paid in. - * @param _bond size of the bond. - * @param _identifier identifier that should be passed to the optimistic oracle on dispute. - * @param _customAncillaryData custom ancillary data that should be sent to the optimistic oracle on dispute. - * @param _finder finder to look up UMA contract addresses. - */ - constructor( - string memory _name, - string memory _symbol, - string memory _baseUri, - uint256 _liveness, - IERC20 _bondToken, - uint256 _bond, - bytes32 _identifier, - bytes memory _customAncillaryData, - FinderInterface _finder - ) - OptimisticRewarderBase(_liveness, _bondToken, _bond, _identifier, _customAncillaryData, _finder) - OptimisticRewarderToken(_name, _symbol, _baseUri) - {} - - /** - * @notice Used to mint the next ERC721 tokenId. - * @param recipient the recipient of the newly minted token. - */ - function mintNextToken( - address recipient - ) public virtual override(OptimisticRewarderBase, OptimisticRewarderToken) returns (uint256) { - return OptimisticRewarderToken.mintNextToken(recipient); - } - - /** - * @notice Used to check the owner of the token. - * @dev this override is a formality required by solidity. It forwards the call to the internal ERC721 - * immplentation. - * @param tokenId the tokenId to check the owner of. - */ - function ownerOf(uint256 tokenId) public view virtual override(OptimisticRewarderBase, ERC721) returns (address) { - return ERC721.ownerOf(tokenId); - } -} - -/** - * @notice The optimistic rewarder that does not contain the ERC721 token. It allows the user to pass in an external - * ERC721 token. - * @dev this setup allows for graceful migrations to new rewarder contracts. It also allows external ERC721 tokens, - * like uniswap v3 positions to be rewarded. - */ -contract OptimisticRewarderNoToken is OptimisticRewarderBase { - OptimisticRewarderToken public token; - - /** - * @notice Constructor. - * @param _token external ERC721 token for the rewarder to base redemptions on. Note: this token doesn't - * necessarily need to implement the mintNextToken method. See mintNextToken below for details. - * @param _liveness liveness period between submission and verification of a reward. - * @param _bondToken ERC20 token that the bond is paid in. - * @param _bond size of the bond. - * @param _identifier identifier that should be passed to the optimistic oracle on dispute. - * @param _customAncillaryData custom ancillary data that should be sent to the optimistic oracle on dispute. - * @param _finder finder to look up UMA contract addresses. - */ - constructor( - OptimisticRewarderToken _token, - uint256 _liveness, - IERC20 _bondToken, - uint256 _bond, - bytes32 _identifier, - bytes memory _customAncillaryData, - FinderInterface _finder - ) OptimisticRewarderBase(_liveness, _bondToken, _bond, _identifier, _customAncillaryData, _finder) { - token = _token; - } - - /** - * @notice Used to mint the next ERC721 tokenId. - * @dev even if token contract does not support the `mintNextToken` function, this contract can still function - * correctly assuming there is some other way to mint the ERC721 tokens. An issue in this method will only - * affect the mint token in the base contract. Other methods will work fine. - * @param recipient the recipient of the newly minted token. - */ - function mintNextToken(address recipient) public virtual override returns (uint256) { - return token.mintNextToken(recipient); - } - - /** - * @notice Used to check the owner of the token. - * @dev this override forwards the call to the external token contract. - * @param tokenId the tokenId to check the owner of. - */ - function ownerOf(uint256 tokenId) public view virtual override returns (address) { - return token.ownerOf(tokenId); - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderBase.sol b/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderBase.sol deleted file mode 100644 index 4fd0ffd93..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderBase.sol +++ /dev/null @@ -1,400 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; - -import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; -import "../../common/implementation/Lockable.sol"; -import "../../common/implementation/MultiCaller.sol"; -import "../../data-verification-mechanism/interfaces/StoreInterface.sol"; -import "../../data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol"; -import "../../data-verification-mechanism/implementation/Constants.sol"; -import "../../optimistic-oracle-v2/interfaces/SkinnyOptimisticOracleInterface.sol"; -import "../../common/implementation/AncillaryData.sol"; -import "../../common/interfaces/AddressWhitelistInterface.sol"; - -/** - * @notice The base rewarder contract. This manages depositing rewards and paying them out to token holders using values - * backed by the OptimisticOracle's dispute process. - */ -abstract contract OptimisticRewarderBase is Lockable, MultiCaller { - using SafeERC20 for IERC20; - - struct RedemptionAmount { - uint256 amount; - IERC20 token; - } - - struct Redemption { - uint256 finalFee; - uint256 expiryTime; - } - - // Constants. - FinderInterface public finder; - bytes public customAncillaryData; - IERC20 public bondToken; - bytes32 public identifier; - - // Note: setters are intentionally absent for these parameters. If a deployer intends to modify these parameters, - // this contract suite offers a simple migration path where a new Rewarder is created and the existing ERC721 token - // can be passed in and used as the reward token there as well. This would be minimally painful for users. - uint256 public liveness; - uint256 public bond; - - // Parameters that can be synced and stored in the contract. - uint256 public finalFee; - StoreInterface public store; - SkinnyOptimisticOracleInterface public optimisticOracle; - - // Mapping to track redemptions. - mapping(bytes32 => Redemption) public redemptions; - - // Mapping to track the past total cumulative redemptions for tokenIds. - mapping(uint256 => mapping(IERC20 => uint256)) public redeemedAmounts; - - /**************************************** - * EVENTS * - ****************************************/ - - // This allows other contracts to publish reward updates. - event UpdateToken(uint256 indexed tokenId, address indexed caller, bytes data); - - event Deposited(address indexed depositor, IERC20 indexed token, uint256 amount); - - // Lifecycle events for redemptions. - event Requested( - uint256 indexed tokenId, - bytes32 indexed redemptionId, - RedemptionAmount[] cumulativeRedemptions, - uint256 expiryTime - ); - event Canceled(uint256 indexed tokenId, bytes32 indexed redemptionId, uint256 expiryTime); - event Disputed(uint256 indexed tokenId, bytes32 indexed redemptionId, uint256 expiryTime); - event Redeemed(uint256 indexed tokenId, bytes32 indexed redemptionId, uint256 expiryTime); - - /** - * @notice Constructor. - * @param _liveness liveness period between submission and verification of a reward. - * @param _bondToken ERC20 token that the bond is paid in. - * @param _bond size of the bond. - * @param _identifier identifier that should be passed to the optimistic oracle on dispute. - * @param _customAncillaryData custom ancillary data that should be sent to the optimistic oracle on dispute. - * @param _finder finder to look up UMA contract addresses. - */ - constructor( - uint256 _liveness, - IERC20 _bondToken, - uint256 _bond, - bytes32 _identifier, - bytes memory _customAncillaryData, - FinderInterface _finder - ) { - require(_liveness > 0, "liveness can't be 0"); - liveness = _liveness; - finder = _finder; - require(_getCollateralWhitelist().isOnWhitelist(address(_bondToken)), "bond token not supported"); - bondToken = _bondToken; - bond = _bond; - require(_getIdentifierWhitelist().isIdentifierSupported(_identifier), "identifier not supported"); - identifier = _identifier; - SkinnyOptimisticOracleInterface skinnyOptimisticOracle = _getOptimisticOracle(); - require( - skinnyOptimisticOracle - .stampAncillaryData( - AncillaryData.appendKeyValueBytes32(_customAncillaryData, "redemptionId", bytes32(0)), - address(this) - ) - .length <= skinnyOptimisticOracle.ancillaryBytesLimit(), - "ancillary data too long" - ); - customAncillaryData = _customAncillaryData; - _sync(); - } - - /**************************************** - * GLOBAL PUBLIC FUNCTIONS * - ****************************************/ - - /** - * @notice Allows anyone to deposit reward tokens into the contract. Presumably, this would be the deployer or - * protocol that wishes to reward the users interacting with the system. - * @dev Once tokens are deposited, they cannot be withdrawn without claiming a reward. If a deployer wants an - * "escape hatch", they can create a special tokenId for this purpose. - * @param token ERC20 token that is being deposited. - * @param amount amount of rewards to deposit. - */ - function depositRewards(IERC20 token, uint256 amount) public nonReentrant { - token.safeTransferFrom(msg.sender, address(this), amount); - emit Deposited(msg.sender, token, amount); - } - - /** - * @notice Allows the caller to mint a token to the receiver and include a reward-relevant update event with it. - * This is intended to be used when the user first interacts with a reward-granting protocol. - * @dev if the user prefers to only mint a new token, they should call the mintNextToken function. - * @param receiver user that will receive the newly minted token. - * @param data arbitrary caller-generated data that will be associated with this update. - * @return tokenId of the newly minted token. - */ - function mint(address receiver, bytes memory data) public nonReentrant returns (uint256 tokenId) { - tokenId = mintNextToken(receiver); - emit UpdateToken(tokenId, msg.sender, data); - } - - /** - * @notice Applies a reward-relevant update to an existing token. - * @param tokenId the existing tokenId that the update should be applied to. - * @param data arbitrary caller-generated data that will be associated with this update. - */ - function updateToken(uint256 tokenId, bytes memory data) public nonReentrant { - emit UpdateToken(tokenId, msg.sender, data); - } - - /** - * @notice Requests a redemption for any tokenId. This can be called by anyone. - * @dev If called by someone who doesn't own the token, they are effectively gifting their bond to the owner. - * @param tokenId the tokenId the redemption is for. - * @param cumulativeRedemptions the cumulative token addresses and amounts that this tokenId is eligible for - * at the current timestamp. cumulative redemptions that are too low should be considered to be valid. - * @return totalBond sum of finalFee and bond paid by the caller of this function. - */ - function requestRedemption( - uint256 tokenId, - RedemptionAmount[] memory cumulativeRedemptions - ) public nonReentrant returns (uint256 totalBond) { - bytes32 redemptionId = getRedemptionId(tokenId, cumulativeRedemptions); - require(redemptions[redemptionId].expiryTime == 0, "Redemption already exists"); - require(ownerOf(tokenId) != address(0), "tokenId is invalid"); - // Note: it's important to put _some_ limit on the length of data passed in here. Otherwise, it is possible to - // create values that are so long that this transaction would fit within the block gas limit, but the dispute - // transaction would not. - require(cumulativeRedemptions.length <= 100, "too many token transfers"); - - uint256 expiryTime = getCurrentTime() + liveness; - - totalBond = finalFee + bond; - bondToken.safeTransferFrom(msg.sender, address(this), totalBond); - - redemptions[redemptionId] = Redemption({ finalFee: finalFee, expiryTime: expiryTime }); - - emit Requested(tokenId, redemptionId, cumulativeRedemptions, expiryTime); - } - - /** - * @notice Disputes a redemption request. - * @dev will cancel a request if the final fee changes or something causes the optimistic oracle proposal to fail. - * @param tokenId the tokenId the redemption is for. - * @param cumulativeRedemptions the cumulative redemptions that were provided in the original request. - */ - function dispute(uint256 tokenId, RedemptionAmount[] memory cumulativeRedemptions) public nonReentrant { - bytes32 redemptionId = getRedemptionId(tokenId, cumulativeRedemptions); - - // This automatically checks that redemptions[redemptionId] != 0. - // Check that it has not passed liveness. - Redemption storage redemption = redemptions[redemptionId]; - uint256 currentTime = getCurrentTime(); - require(currentTime < redemption.expiryTime, "redemption expired or nonexistent"); - - // Final fees don't match to those in the current store, which means the bond the initial caller provided was - // incorrect. Cancel the request to allow the requester to resubmit with the correct params. - // Note: we pull the store directly from the finder to avoid any issues with an outdated store causing the - // final fee to appear to be correct, but actually be outdated due to the OptimisticOracle pulling from a - // newer store deployment. - if (redemption.finalFee != _getStore().computeFinalFee(address(bondToken)).rawValue) { - _cancelRedemption(tokenId, redemptionId); - return; - } else { - uint256 totalBond = bond + redemption.finalFee; - bondToken.safeIncreaseAllowance(address(optimisticOracle), totalBond * 2); - bytes memory ancillaryData = AncillaryData.appendKeyValueBytes32( - customAncillaryData, - "redemptionId", - redemptionId - ); - uint32 requestTimestamp = uint32(redemption.expiryTime - liveness); - address proposer = ownerOf(tokenId); - - try - optimisticOracle.requestAndProposePriceFor( - identifier, - requestTimestamp, - ancillaryData, - bondToken, - 0, // Reward = 0 - bond, // Bond (on top of the final fee) for the proposer and disputer. - liveness, - proposer, - int256(1e18) // Canonical value representing "True"; i.e. the proposed redemption is valid. - ) - returns (uint256) {} catch { - // There are various cases that can cause an OptimisticOracle proposal to fail. These are unlikely, but - // this is intended as a worst-case fallback to avoid undisputable requests. - // A few examples: - // 1. The token ceases to be approved. - // 2. The identifier ceases to be approved. - // 3. The request has been submitted before (same identifier, timestap, ancillary data, and requester). - // This should be impossible for this contract. - // 4. The money bond + final fee is larger than approved or in the contract's balance. This should also - // be impossible in this contract. - _cancelRedemption(tokenId, redemptionId); - bondToken.safeApprove(address(optimisticOracle), 0); // Reset allowance. - return; - } - - SkinnyOptimisticOracleInterface.Request memory request = SkinnyOptimisticOracleInterface.Request({ - proposer: proposer, - disputer: address(0), - currency: bondToken, - settled: false, - proposedPrice: int256(1e18), - resolvedPrice: 0, - expirationTime: currentTime + liveness, - reward: 0, - finalFee: redemption.finalFee, - bond: bond, - customLiveness: liveness - }); - - // Note: don't pull funds until here to avoid any transfers that aren't needed. - bondToken.safeTransferFrom(msg.sender, address(this), totalBond); - - // Dispute the request that we just sent. - optimisticOracle.disputePriceFor( - identifier, - requestTimestamp, - ancillaryData, - request, - msg.sender, - address(this) - ); - - emit Disputed(tokenId, redemptionId, redemption.expiryTime); - } - - delete redemptions[redemptionId]; - } - - /** - * @notice Redeem a redemption request that has passed liveness. - * @dev returns the bond that was paid with the initial proposal. - * @param tokenId the tokenId the redemption is for. - * @param cumulativeRedemptions the cumulative redemptions that were provided in the original request. - */ - function redeem(uint256 tokenId, RedemptionAmount[] memory cumulativeRedemptions) public nonReentrant { - bytes32 redemptionId = getRedemptionId(tokenId, cumulativeRedemptions); - - // Can only be redeemed by owner. - require(msg.sender == ownerOf(tokenId), "must be called by token owner"); - - // Check that the redemption is initialized and that it passed liveness. - require( - redemptions[redemptionId].expiryTime != 0 && getCurrentTime() >= redemptions[redemptionId].expiryTime, - "unexpired or nonexistent" - ); - - for (uint256 i = 0; i < cumulativeRedemptions.length; i++) { - IERC20 token = cumulativeRedemptions[i].token; - uint256 currentRedemptionTotal = redeemedAmounts[tokenId][token]; - uint256 proposedRedemptionTotal = cumulativeRedemptions[i].amount; - - // Only pay if the cumulative amount specified in the request is larger than the amount paid out already. - // Note: disallow payments of the bond token even if it's in the approved request is passed to ensure - // rewards don't interfere with bond bookkeeping. This is checked here rather than at the initiation of the - // request to avoid the cost of looping over the array twice in the lifecycle. - if (proposedRedemptionTotal > currentRedemptionTotal && token != bondToken) { - uint256 amountToPay = proposedRedemptionTotal - currentRedemptionTotal; - redeemedAmounts[tokenId][token] = proposedRedemptionTotal; - token.safeTransfer(msg.sender, amountToPay); - } - } - - // Return the bond to the owner. - bondToken.safeTransfer(msg.sender, bond + redemptions[redemptionId].finalFee); - - emit Redeemed(tokenId, redemptionId, redemptions[redemptionId].expiryTime); - - delete redemptions[redemptionId]; - } - - /** - * @notice Syncs external addresses and parameters into the contract. - * @dev These are stored rather than read on each run to avoid expensive external calls in the happy-path. - */ - function sync() public nonReentrant { - _sync(); - } - - /** - * @notice gets the current time. Can be overridden for testing. - * @return current block timestamp. - */ - function getCurrentTime() public view virtual returns (uint256) { - return block.timestamp; - } - - /** - * @notice Abstract function that is called to mint the next ERC721 tokenId. - * @param recipient the recipient of the newly minted token. - * @return index of the next minted token. - */ - function mintNextToken(address recipient) public virtual returns (uint256); - - /** - * @notice Abstract function that is called to check the owner of the token. - * @dev this matches the ERC721 ownerOf interface. - * @param tokenId the tokenId to check the owner of. - * @return owner of a particular tokenId. - */ - function ownerOf(uint256 tokenId) public view virtual returns (address); - - /** - * @notice Generates a redemption id for the tokenId and the claim amounts. - * @param tokenId the tokenId that the claim is for. - * @param cumulativeRedemptions the cumulative redemptions that were provided in the request. - * @return redemption id. This is a hash of the tokenId and the cumulative redemptions. - */ - function getRedemptionId( - uint256 tokenId, - RedemptionAmount[] memory cumulativeRedemptions - ) public pure returns (bytes32) { - return keccak256(abi.encode(tokenId, cumulativeRedemptions)); - } - - /**************************************** - * INTERNAL FUNCTIONS * - ****************************************/ - - function _sync() internal { - store = _getStore(); - finalFee = store.computeFinalFee(address(bondToken)).rawValue; - optimisticOracle = _getOptimisticOracle(); - } - - function _getStore() internal view returns (StoreInterface) { - return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store)); - } - - function _getOptimisticOracle() internal view returns (SkinnyOptimisticOracleInterface) { - return - SkinnyOptimisticOracleInterface(finder.getImplementationAddress(OracleInterfaces.SkinnyOptimisticOracle)); - } - - function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) { - return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); - } - - function _getCollateralWhitelist() internal view returns (AddressWhitelistInterface) { - return AddressWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist)); - } - - function _cancelRedemption(uint256 tokenId, bytes32 redemptionId) internal { - // On cancellation, perform a sync to ensure the contract has the most up-to-date addresses and params. - _sync(); - Redemption storage redemption = redemptions[redemptionId]; - bondToken.safeTransfer(ownerOf(tokenId), redemption.finalFee + bond); - emit Canceled(tokenId, redemptionId, redemption.expiryTime); - delete redemptions[redemptionId]; - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderCreator.sol b/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderCreator.sol deleted file mode 100644 index 9d6151ef4..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderCreator.sol +++ /dev/null @@ -1,91 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; - -import "../../common/implementation/Lockable.sol"; - -import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; -import "./OptimisticRewarder.sol"; - -/** - * @notice The creator contract for optimistic rewarders. Using this contract is totally optional. It only aids in - * creating a simpler deployment experience with a guarantee of repeatable verification and easier tracking through - * events. - */ -contract OptimisticRewarderCreator is Lockable { - FinderInterface public finder; - - event CreatedOptimisticRewarder(address indexed optimisticRewarder, bool includesToken); - - constructor(FinderInterface _finder) { - finder = _finder; - } - - /** - * @notice Deploys an optimistic rewarder. - * @param _name name for the ERC721 token. - * @param _symbol symbol for the ERC721 token. - * @param _baseUri prefix to each ERC721 tokenId's name. - * @param _liveness liveness period between submission and verification of a reward. - * @param _bondToken ERC20 token that the bond is paid in. - * @param _bond size of the bond. - * @param _identifier identifier that should be passed to the optimistic oracle on dispute. - * @param _customAncillaryData custom ancillary data that should be sent to the optimistic oracle on dispute. - */ - function createOptimisticRewarder( - string memory _name, - string memory _symbol, - string memory _baseUri, - uint256 _liveness, - IERC20 _bondToken, - uint256 _bond, - bytes32 _identifier, - bytes memory _customAncillaryData - ) public nonReentrant returns (address) { - OptimisticRewarder optimisticRewarder = new OptimisticRewarder( - _name, - _symbol, - _baseUri, - _liveness, - _bondToken, - _bond, - _identifier, - _customAncillaryData, - finder - ); - emit CreatedOptimisticRewarder(address(optimisticRewarder), true); - return address(optimisticRewarder); - } - - /** - * @notice Deploys an optimistic rewarder with an external ERC721 token. - * @param _token external ERC721 token for the rewarder to base redemptions on. - * @param _liveness liveness period between submission and verification of a reward. - * @param _bondToken ERC20 token that the bond is paid in. - * @param _bond size of the bond. - * @param _identifier identifier that should be passed to the optimistic oracle on dispute. - * @param _customAncillaryData custom ancillary data that should be sent to the optimistic oracle on dispute. - */ - function createOptimisticRewarderNoToken( - OptimisticRewarderToken _token, - uint256 _liveness, - IERC20 _bondToken, - uint256 _bond, - bytes32 _identifier, - bytes memory _customAncillaryData - ) public nonReentrant returns (address) { - OptimisticRewarderNoToken optimisticRewarder = new OptimisticRewarderNoToken( - _token, - _liveness, - _bondToken, - _bond, - _identifier, - _customAncillaryData, - finder - ); - emit CreatedOptimisticRewarder(address(optimisticRewarder), false); - return address(optimisticRewarder); - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderToken.sol b/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderToken.sol deleted file mode 100644 index 15baa27f7..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticRewarderToken.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/token/ERC721/ERC721.sol"; - -contract OptimisticRewarderToken is ERC721 { - string public baseUri; - uint256 public nextTokenId; - - /** - * @notice Constructor. - * @param _name name for the ERC721 token. - * @param _symbol symbol for the ERC721 token. - * @param _baseUri prefix to each ERC721 tokenId's name. - */ - constructor(string memory _name, string memory _symbol, string memory _baseUri) ERC721(_name, _symbol) { - nextTokenId = 0; - baseUri = _baseUri; - } - - /** - * @notice Used to mint the next ERC721 tokenId. - * @param recipient the recipient of the newly minted token. - */ - function mintNextToken(address recipient) public virtual returns (uint256 tokenId) { - tokenId = nextTokenId++; - _safeMint(recipient, tokenId); - } - - function _baseURI() internal view override returns (string memory) { - return baseUri; - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticStaker.sol b/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticStaker.sol deleted file mode 100644 index 27f23a7a2..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/OptimisticStaker.sol +++ /dev/null @@ -1,91 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; - -import "../../common/implementation/Lockable.sol"; -import "../../common/implementation/MultiCaller.sol"; -import "./OptimisticRewarderBase.sol"; - -/** - * @notice An example use case of the OptimisticRewarder in use by a contract that allows users to stake an ERC20 to - * earn rewards. - */ -contract OptimisticStaker is Lockable, MultiCaller { - using SafeERC20 for IERC20; - - // Optimistic rewarder contract used to pay out user rewards. - OptimisticRewarderBase public optimisticRewarder; - - // Staked ERC20 token. - IERC20 public stakedToken; - - // Balances by tokenId. - mapping(uint256 => uint256) public balances; - - event Deposit(uint256 indexed tokenId, uint256 amount); - event Withdraw(uint256 indexed tokenId, uint256 amount); - - /** - * @notice Constructor. - * @param _optimisticRewarder Optimistic rewarder contract used to pay out user rewards. - * @param _stakedToken staked ERC20 token. - */ - constructor(OptimisticRewarderBase _optimisticRewarder, IERC20 _stakedToken) { - optimisticRewarder = _optimisticRewarder; - stakedToken = _stakedToken; - } - - modifier onlyTokenOwner(uint256 tokenId) { - require(optimisticRewarder.ownerOf(tokenId) == msg.sender, "caller != token owner"); - _; - } - - /** - * @notice Deposit the staked token into the contract and mint a fresh token to manage the position. - * @param amount the amount of the ERC20 to deposit. - * @return tokenId the token id for the freshly minted token. - */ - function depositNew(uint256 amount) public nonReentrant returns (uint256 tokenId) { - tokenId = optimisticRewarder.mint(msg.sender, msg.data); - _depositFor(tokenId, amount); - } - - /** - * @notice Deposit the staked token into the contract. - * @param tokenId the tokenId that will own this liquidity. User must be the owner of this tokenId. - * @param amount the amount of the ERC20 to deposit. - */ - function deposit(uint256 tokenId, uint256 amount) public nonReentrant onlyTokenOwner(tokenId) { - _depositFor(tokenId, amount); - } - - /** - * @notice Deposit staked tokens on behalf of a tokenId that the user may not control. - * @param tokenId the tokenId that will own this liquidity. - * @param amount the amount of the ERC20 to deposit. - */ - function depositFor(uint256 tokenId, uint256 amount) public nonReentrant { - _depositFor(tokenId, amount); - } - - /** - * @notice Withdraw the staked tokens. - * @param tokenId the tokenId that owns this liquidity. User must own this tokenId. - * @param amount the amount of the ERC20 to withdraw. - */ - function withdraw(uint256 tokenId, uint256 amount) public nonReentrant onlyTokenOwner(tokenId) { - balances[tokenId] -= amount; - stakedToken.safeTransfer(msg.sender, amount); - optimisticRewarder.updateToken(tokenId, msg.data); - emit Withdraw(tokenId, amount); - } - - function _depositFor(uint256 tokenId, uint256 amount) internal { - balances[tokenId] += amount; - stakedToken.safeTransferFrom(msg.sender, address(this), amount); - optimisticRewarder.updateToken(tokenId, msg.data); - emit Deposit(tokenId, amount); - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/test/OptimisticRewarderTest.sol b/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/test/OptimisticRewarderTest.sol deleted file mode 100644 index f6678b084..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/optimistic-rewarder/test/OptimisticRewarderTest.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../OptimisticRewarder.sol"; -import "../../../common/implementation/Testable.sol"; - -// Test contract to add controllable timing to the OptimisticRewarder. -contract OptimisticRewarderTest is OptimisticRewarder, Testable { - constructor( - string memory _name, - string memory _symbol, - string memory _baseUri, - uint256 _liveness, - IERC20 _bondToken, - uint256 _bond, - bytes32 _identifier, - bytes memory _customAncillaryData, - FinderInterface _finder, - address _timerAddress - ) - Testable(_timerAddress) - OptimisticRewarder( - _name, - _symbol, - _baseUri, - _liveness, - _bondToken, - _bond, - _identifier, - _customAncillaryData, - _finder - ) - {} - - function getCurrentTime() public view override(OptimisticRewarderBase, Testable) returns (uint256) { - return Testable.getCurrentTime(); - } -} - -// Test contract to add controllable timing to the OptimisticRewarderNoToken. -contract OptimisticRewarderNoTokenTest is OptimisticRewarderNoToken, Testable { - constructor( - OptimisticRewarderToken _token, - uint256 _liveness, - IERC20 _bondToken, - uint256 _bond, - bytes32 _identifier, - bytes memory _customAncillaryData, - FinderInterface _finder, - address _timerAddress - ) - Testable(_timerAddress) - OptimisticRewarderNoToken(_token, _liveness, _bondToken, _bond, _identifier, _customAncillaryData, _finder) - {} - - function getCurrentTime() public view override(OptimisticRewarderBase, Testable) returns (uint256) { - return Testable.getCurrentTime(); - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/ConfigStore.sol b/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/ConfigStore.sol deleted file mode 100644 index c61360db8..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/ConfigStore.sol +++ /dev/null @@ -1,184 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; -import "@openzeppelin/contracts-v4/access/Ownable.sol"; - -import "./ConfigStoreInterface.sol"; -import "../../common/implementation/Testable.sol"; -import "../../common/implementation/Lockable.sol"; -import "../../common/implementation/FixedPoint.sol"; - -/** - * @notice ConfigStore stores configuration settings for a perpetual contract and provides an interface for it - * to query settings such as reward rates, proposal bond sizes, etc. The configuration settings can be upgraded - * by a privileged account and the upgraded changes are timelocked. - */ -contract ConfigStore is ConfigStoreInterface, Testable, Lockable, Ownable { - using SafeMath for uint256; - using FixedPoint for FixedPoint.Unsigned; - - /**************************************** - * STORE DATA STRUCTURES * - ****************************************/ - - // Make currentConfig private to force user to call getCurrentConfig, which returns the pendingConfig - // if its liveness has expired. - ConfigStoreInterface.ConfigSettings private currentConfig; - - // Beginning on `pendingPassedTimestamp`, the `pendingConfig` can be published as the current config. - ConfigStoreInterface.ConfigSettings public pendingConfig; - uint256 public pendingPassedTimestamp; - - /**************************************** - * EVENTS * - ****************************************/ - - event ProposedNewConfigSettings( - address indexed proposer, - uint256 rewardRatePerSecond, - uint256 proposerBondPercentage, - uint256 timelockLiveness, - int256 maxFundingRate, - int256 minFundingRate, - uint256 proposalTimePastLimit, - uint256 proposalPassedTimestamp - ); - event ChangedConfigSettings( - uint256 rewardRatePerSecond, - uint256 proposerBondPercentage, - uint256 timelockLiveness, - int256 maxFundingRate, - int256 minFundingRate, - uint256 proposalTimePastLimit - ); - - /**************************************** - * MODIFIERS * - ****************************************/ - - // Update config settings if possible. - modifier updateConfig() { - _updateConfig(); - _; - } - - /** - * @notice Construct the Config Store. An initial configuration is provided and set on construction. - * @param _initialConfig Configuration settings to initialize `currentConfig` with. - * @param _timerAddress Address of testable Timer contract. - */ - constructor(ConfigSettings memory _initialConfig, address _timerAddress) Testable(_timerAddress) { - _validateConfig(_initialConfig); - currentConfig = _initialConfig; - } - - /** - * @notice Returns current config or pending config if pending liveness has expired. - * @return ConfigSettings config settings that calling financial contract should view as "live". - */ - function updateAndGetCurrentConfig() - external - override - updateConfig - nonReentrant - returns (ConfigStoreInterface.ConfigSettings memory) - { - return currentConfig; - } - - /** - * @notice Propose new configuration settings. New settings go into effect after a liveness period passes. - * @param newConfig Configuration settings to publish after `currentConfig.timelockLiveness` passes from block.timestamp. - * @dev Callable only by owner. Calling this while there is already a pending proposal will overwrite the pending proposal. - */ - function proposeNewConfig(ConfigSettings memory newConfig) external onlyOwner nonReentrant updateConfig { - _validateConfig(newConfig); - - // Warning: This overwrites a pending proposal! - pendingConfig = newConfig; - - // Use current config's liveness period to timelock this proposal. - pendingPassedTimestamp = getCurrentTime().add(currentConfig.timelockLiveness); - - emit ProposedNewConfigSettings( - msg.sender, - newConfig.rewardRatePerSecond.rawValue, - newConfig.proposerBondPercentage.rawValue, - newConfig.timelockLiveness, - newConfig.maxFundingRate.rawValue, - newConfig.minFundingRate.rawValue, - newConfig.proposalTimePastLimit, - pendingPassedTimestamp - ); - } - - /** - * @notice Publish any pending configuration settings if there is a pending proposal that has passed liveness. - */ - function publishPendingConfig() external nonReentrant updateConfig {} - - /**************************************** - * INTERNAL FUNCTIONS * - ****************************************/ - - // Check if pending proposal can overwrite the current config. - function _updateConfig() internal { - // If liveness has passed, publish proposed configuration settings. - if (_pendingProposalPassed()) { - currentConfig = pendingConfig; - - _deletePendingConfig(); - - emit ChangedConfigSettings( - currentConfig.rewardRatePerSecond.rawValue, - currentConfig.proposerBondPercentage.rawValue, - currentConfig.timelockLiveness, - currentConfig.maxFundingRate.rawValue, - currentConfig.minFundingRate.rawValue, - currentConfig.proposalTimePastLimit - ); - } - } - - function _deletePendingConfig() internal { - delete pendingConfig; - pendingPassedTimestamp = 0; - } - - function _pendingProposalPassed() internal view returns (bool) { - return (pendingPassedTimestamp != 0 && pendingPassedTimestamp <= getCurrentTime()); - } - - // Use this method to constrain values with which you can set ConfigSettings. - function _validateConfig(ConfigStoreInterface.ConfigSettings memory config) internal pure { - // We don't set limits on proposal timestamps because there are already natural limits: - // - Future: price requests to the OptimisticOracle must be in the past---we can't add further constraints. - // - Past: proposal times must always be after the last update time, and a reasonable past limit would be 30 - // mins, meaning that no proposal timestamp can be more than 30 minutes behind the current time. - - // Make sure timelockLiveness is not too long, otherwise contract might not be able to fix itself - // before a vulnerability drains its collateral. - require(config.timelockLiveness <= 7 days && config.timelockLiveness >= 1 days, "Invalid timelockLiveness"); - - // The reward rate should be modified as needed to incentivize honest proposers appropriately. - // Additionally, the rate should be less than 100% a year => 100% / 360 days / 24 hours / 60 mins / 60 secs - // = 0.0000033 - FixedPoint.Unsigned memory maxRewardRatePerSecond = FixedPoint.fromUnscaledUint(33).div(1e7); - require(config.rewardRatePerSecond.isLessThan(maxRewardRatePerSecond), "Invalid rewardRatePerSecond"); - - // We don't set a limit on the proposer bond because it is a defense against dishonest proposers. If a proposer - // were to successfully propose a very high or low funding rate, then their PfC would be very high. The proposer - // could theoretically keep their "evil" funding rate alive indefinitely by continuously disputing honest - // proposers, so we would want to be able to set the proposal bond (equal to the dispute bond) higher than their - // PfC for each proposal liveness window. The downside of not limiting this is that the config store owner - // can set it arbitrarily high and preclude a new funding rate from ever coming in. We suggest setting the - // proposal bond based on the configuration's funding rate range like in this discussion: - // https://github.com/UMAprotocol/protocol/issues/2039#issuecomment-719734383 - - // We also don't set a limit on the funding rate max/min because we might need to allow very high magnitude - // funding rates in extraordinarily volatile market situations. Note, that even though we do not bound - // the max/min, we still recommend that the deployer of this contract set the funding rate max/min values - // to bound the PfC of a dishonest proposer. A reasonable range might be the equivalent of [+200%/year, -200%/year]. - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/ConfigStoreInterface.sol b/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/ConfigStoreInterface.sol deleted file mode 100644 index 5b65186c3..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/ConfigStoreInterface.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/FixedPoint.sol"; - -interface ConfigStoreInterface { - // All of the configuration settings available for querying by a perpetual. - struct ConfigSettings { - // Liveness period (in seconds) for an update to currentConfig to become official. - uint256 timelockLiveness; - // Reward rate paid to successful proposers. Percentage of 1 E.g., .1 is 10%. - FixedPoint.Unsigned rewardRatePerSecond; - // Bond % (of given contract's PfC) that must be staked by proposers. Percentage of 1, e.g. 0.0005 is 0.05%. - FixedPoint.Unsigned proposerBondPercentage; - // Maximum funding rate % per second that can be proposed. - FixedPoint.Signed maxFundingRate; - // Minimum funding rate % per second that can be proposed. - FixedPoint.Signed minFundingRate; - // Funding rate proposal timestamp cannot be more than this amount of seconds in the past from the latest - // update time. - uint256 proposalTimePastLimit; - } - - function updateAndGetCurrentConfig() external returns (ConfigSettings memory); -} diff --git a/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/Perpetual.sol b/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/Perpetual.sol deleted file mode 100644 index 669e41f83..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/Perpetual.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "./PerpetualLiquidatable.sol"; - -/** - * @title Perpetual Multiparty Contract. - * @notice Convenient wrapper for Liquidatable. - */ -contract Perpetual is PerpetualLiquidatable { - /** - * @notice Constructs the Perpetual contract. - * @param params struct to define input parameters for construction of Liquidatable. Some params - * are fed directly into the PositionManager's constructor within the inheritance tree. - */ - constructor( - ConstructorParams memory params - ) PerpetualLiquidatable(params) // Note: since there is no logic here, there is no need to add a re-entrancy guard. - { - - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualCreator.sol b/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualCreator.sol deleted file mode 100644 index b6ace10f1..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualCreator.sol +++ /dev/null @@ -1,167 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../data-verification-mechanism/implementation/ContractCreator.sol"; -import "../../common/interfaces/ExpandedIERC20.sol"; -import "../../common/interfaces/IERC20Standard.sol"; -import "../../common/implementation/Testable.sol"; -import "../../common/implementation/AddressWhitelist.sol"; -import "../../common/implementation/Lockable.sol"; -import "../common/TokenFactory.sol"; -import "../common/SyntheticToken.sol"; -import "./PerpetualLib.sol"; -import "./ConfigStore.sol"; - -/** - * @title Perpetual Contract creator. - * @notice Factory contract to create and register new instances of perpetual contracts. - * Responsible for constraining the parameters used to construct a new perpetual. This creator contains a number of constraints - * that are applied to newly created contract. These constraints can evolve over time and are - * initially constrained to conservative values in this first iteration. Technically there is nothing in the - * Perpetual contract requiring these constraints. However, because `createPerpetual()` is intended - * to be the only way to create valid financial contracts that are registered with the DVM (via _registerContract), - we can enforce deployment configurations here. - */ -contract PerpetualCreator is ContractCreator, Testable, Lockable { - using FixedPoint for FixedPoint.Unsigned; - - /**************************************** - * PERP CREATOR DATA STRUCTURES * - ****************************************/ - - // Immutable params for perpetual contract. - struct Params { - address collateralAddress; - bytes32 priceFeedIdentifier; - bytes32 fundingRateIdentifier; - string syntheticName; - string syntheticSymbol; - FixedPoint.Unsigned collateralRequirement; - FixedPoint.Unsigned disputeBondPercentage; - FixedPoint.Unsigned sponsorDisputeRewardPercentage; - FixedPoint.Unsigned disputerDisputeRewardPercentage; - FixedPoint.Unsigned minSponsorTokens; - FixedPoint.Unsigned tokenScaling; - uint256 withdrawalLiveness; - uint256 liquidationLiveness; - } - // Address of TokenFactory used to create a new synthetic token. - address public tokenFactoryAddress; - - event CreatedPerpetual(address indexed perpetualAddress, address indexed deployerAddress); - event CreatedConfigStore(address indexed configStoreAddress, address indexed ownerAddress); - - /** - * @notice Constructs the Perpetual contract. - * @param _finderAddress UMA protocol Finder used to discover other protocol contracts. - * @param _tokenFactoryAddress ERC20 token factory used to deploy synthetic token instances. - * @param _timerAddress Contract that stores the current time in a testing environment. - */ - constructor( - address _finderAddress, - address _tokenFactoryAddress, - address _timerAddress - ) ContractCreator(_finderAddress) Testable(_timerAddress) nonReentrant() { - tokenFactoryAddress = _tokenFactoryAddress; - } - - /** - * @notice Creates an instance of perpetual and registers it within the registry. - * @param params is a `ConstructorParams` object from Perpetual. - * @return address of the deployed contract. - */ - function createPerpetual( - Params memory params, - ConfigStore.ConfigSettings memory configSettings - ) public nonReentrant returns (address) { - require(bytes(params.syntheticName).length != 0, "Missing synthetic name"); - require(bytes(params.syntheticSymbol).length != 0, "Missing synthetic symbol"); - - // Create new config settings store for this contract and reset ownership to the deployer. - ConfigStore configStore = new ConfigStore(configSettings, timerAddress); - configStore.transferOwnership(msg.sender); - emit CreatedConfigStore(address(configStore), configStore.owner()); - - // Create a new synthetic token using the params. - TokenFactory tf = TokenFactory(tokenFactoryAddress); - - // If the collateral token does not have a `decimals()` method, - // then a default precision of 18 will be applied to the newly created synthetic token. - uint8 syntheticDecimals = _getSyntheticDecimals(params.collateralAddress); - ExpandedIERC20 tokenCurrency = tf.createToken(params.syntheticName, params.syntheticSymbol, syntheticDecimals); - address derivative = PerpetualLib.deploy(_convertParams(params, tokenCurrency, address(configStore))); - - // Give permissions to new derivative contract and then hand over ownership. - tokenCurrency.addMinter(derivative); - tokenCurrency.addBurner(derivative); - tokenCurrency.resetOwner(derivative); - - _registerContract(new address[](0), derivative); - - emit CreatedPerpetual(derivative, msg.sender); - - return derivative; - } - - /**************************************** - * PRIVATE FUNCTIONS * - ****************************************/ - - // Converts createPerpetual params to Perpetual constructor params. - function _convertParams( - Params memory params, - ExpandedIERC20 newTokenCurrency, - address configStore - ) private view returns (Perpetual.ConstructorParams memory constructorParams) { - // Known from creator deployment. - constructorParams.finderAddress = finderAddress; - constructorParams.timerAddress = timerAddress; - - // Enforce configuration constraints. - require(params.withdrawalLiveness != 0, "Withdrawal liveness cannot be 0"); - require(params.liquidationLiveness != 0, "Liquidation liveness cannot be 0"); - _requireWhitelistedCollateral(params.collateralAddress); - - // We don't want perpetual deployers to be able to intentionally or unintentionally set - // liveness periods that could induce arithmetic overflow, but we also don't want - // to be opinionated about what livenesses are "correct", so we will somewhat - // arbitrarily set the liveness upper bound to 100 years (5200 weeks). In practice, liveness - // periods even greater than a few days would make the perpetual unusable for most users. - require(params.withdrawalLiveness < 5200 weeks, "Withdrawal liveness too large"); - require(params.liquidationLiveness < 5200 weeks, "Liquidation liveness too large"); - - // To avoid precision loss or overflows, prevent the token scaling from being too large or too small. - FixedPoint.Unsigned memory minScaling = FixedPoint.Unsigned(1e8); // 1e-10 - FixedPoint.Unsigned memory maxScaling = FixedPoint.Unsigned(1e28); // 1e10 - require( - params.tokenScaling.isGreaterThan(minScaling) && params.tokenScaling.isLessThan(maxScaling), - "Invalid tokenScaling" - ); - - // Input from function call. - constructorParams.configStoreAddress = configStore; - constructorParams.tokenAddress = address(newTokenCurrency); - constructorParams.collateralAddress = params.collateralAddress; - constructorParams.priceFeedIdentifier = params.priceFeedIdentifier; - constructorParams.fundingRateIdentifier = params.fundingRateIdentifier; - constructorParams.collateralRequirement = params.collateralRequirement; - constructorParams.disputeBondPercentage = params.disputeBondPercentage; - constructorParams.sponsorDisputeRewardPercentage = params.sponsorDisputeRewardPercentage; - constructorParams.disputerDisputeRewardPercentage = params.disputerDisputeRewardPercentage; - constructorParams.minSponsorTokens = params.minSponsorTokens; - constructorParams.withdrawalLiveness = params.withdrawalLiveness; - constructorParams.liquidationLiveness = params.liquidationLiveness; - constructorParams.tokenScaling = params.tokenScaling; - } - - // IERC20Standard.decimals() will revert if the collateral contract has not implemented the decimals() method, - // which is possible since the method is only an OPTIONAL method in the ERC20 standard: - // https://eips.ethereum.org/EIPS/eip-20#methods. - function _getSyntheticDecimals(address _collateralAddress) public view returns (uint8 decimals) { - try IERC20Standard(_collateralAddress).decimals() returns (uint8 _decimals) { - return _decimals; - } catch { - return 18; - } - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualLib.sol b/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualLib.sol deleted file mode 100644 index 496c83aa4..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualLib.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "./Perpetual.sol"; - -/** - * @title Provides convenient Perpetual Multi Party contract utilities. - * @dev Using this library to deploy Perpetuals allows calling contracts to avoid importing the full bytecode. - */ -library PerpetualLib { - /** - * @notice Returns address of new Perpetual deployed with given `params` configuration. - * @dev Caller will need to register new Perpetual with the Registry to begin requesting prices. Caller is also - * responsible for enforcing constraints on `params`. - * @param params is a `ConstructorParams` object from Perpetual. - * @return address of the deployed Perpetual contract - */ - function deploy(Perpetual.ConstructorParams memory params) public returns (address) { - Perpetual derivative = new Perpetual(params); - return address(derivative); - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualLiquidatable.sol b/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualLiquidatable.sol deleted file mode 100644 index 08419e95f..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualLiquidatable.sol +++ /dev/null @@ -1,595 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; - -import "./PerpetualPositionManager.sol"; - -import "../../common/implementation/FixedPoint.sol"; - -/** - * @title PerpetualLiquidatable - * @notice Adds logic to a position-managing contract that enables callers to liquidate an undercollateralized position. - * @dev The liquidation has a liveness period before expiring successfully, during which someone can "dispute" the - * liquidation, which sends a price request to the relevant Oracle to settle the final collateralization ratio based on - * a DVM price. The contract enforces dispute rewards in order to incentivize disputers to correctly dispute false - * liquidations and compensate position sponsors who had their position incorrectly liquidated. Importantly, a - * prospective disputer must deposit a dispute bond that they can lose in the case of an unsuccessful dispute. - * NOTE: this contract does _not_ work with ERC777 collateral currencies or any others that call into the receiver on - * transfer(). Using an ERC777 token would allow a user to maliciously grief other participants (while also losing - * money themselves). - */ -contract PerpetualLiquidatable is PerpetualPositionManager { - using FixedPoint for FixedPoint.Unsigned; - using SafeMath for uint256; - using SafeERC20 for IERC20; - using SafeERC20 for ExpandedIERC20; - - /**************************************** - * LIQUIDATION DATA STRUCTURES * - ****************************************/ - - // Because of the check in withdrawable(), the order of these enum values should not change. - enum Status { - Uninitialized, - NotDisputed, - Disputed, - DisputeSucceeded, - DisputeFailed - } - - struct LiquidationData { - // Following variables set upon creation of liquidation: - address sponsor; // Address of the liquidated position's sponsor - address liquidator; // Address who created this liquidation - Status state; // Liquidated (and expired or not), Pending a Dispute, or Dispute has resolved - uint256 liquidationTime; // Time when liquidation is initiated, needed to get price from Oracle - // Following variables determined by the position that is being liquidated: - FixedPoint.Unsigned tokensOutstanding; // Synthetic tokens required to be burned by liquidator to initiate dispute - FixedPoint.Unsigned lockedCollateral; // Collateral locked by contract and released upon expiry or post-dispute - // Amount of collateral being liquidated, which could be different from - // lockedCollateral if there were pending withdrawals at the time of liquidation - FixedPoint.Unsigned liquidatedCollateral; - // Unit value (starts at 1) that is used to track the fees per unit of collateral over the course of the liquidation. - FixedPoint.Unsigned rawUnitCollateral; - // Following variable set upon initiation of a dispute: - address disputer; // Person who is disputing a liquidation - // Following variable set upon a resolution of a dispute: - FixedPoint.Unsigned settlementPrice; // Final price as determined by an Oracle following a dispute - FixedPoint.Unsigned finalFee; - } - - // Define the contract's constructor parameters as a struct to enable more variables to be specified. - // This is required to enable more params, over and above Solidity's limits. - struct ConstructorParams { - // Params for PerpetualPositionManager only. - uint256 withdrawalLiveness; - address configStoreAddress; - address collateralAddress; - address tokenAddress; - address finderAddress; - address timerAddress; - bytes32 priceFeedIdentifier; - bytes32 fundingRateIdentifier; - FixedPoint.Unsigned minSponsorTokens; - FixedPoint.Unsigned tokenScaling; - // Params specifically for PerpetualLiquidatable. - uint256 liquidationLiveness; - FixedPoint.Unsigned collateralRequirement; - FixedPoint.Unsigned disputeBondPercentage; - FixedPoint.Unsigned sponsorDisputeRewardPercentage; - FixedPoint.Unsigned disputerDisputeRewardPercentage; - } - - // This struct is used in the `withdrawLiquidation` method that disperses liquidation and dispute rewards. - // `payToX` stores the total collateral to withdraw from the contract to pay X. This value might differ - // from `paidToX` due to precision loss between accounting for the `rawCollateral` versus the - // fee-adjusted collateral. These variables are stored within a struct to avoid the stack too deep error. - struct RewardsData { - FixedPoint.Unsigned payToSponsor; - FixedPoint.Unsigned payToLiquidator; - FixedPoint.Unsigned payToDisputer; - FixedPoint.Unsigned paidToSponsor; - FixedPoint.Unsigned paidToLiquidator; - FixedPoint.Unsigned paidToDisputer; - } - - // Liquidations are unique by ID per sponsor - mapping(address => LiquidationData[]) public liquidations; - - // Total collateral in liquidation. - FixedPoint.Unsigned public rawLiquidationCollateral; - - // Immutable contract parameters: - // Amount of time for pending liquidation before expiry. - // !!Note: The lower the liquidation liveness value, the more risk incurred by sponsors. - // Extremely low liveness values increase the chance that opportunistic invalid liquidations - // expire without dispute, thereby decreasing the usability for sponsors and increasing the risk - // for the contract as a whole. An insolvent contract is extremely risky for any sponsor or synthetic - // token holder for the contract. - uint256 public liquidationLiveness; - // Required collateral:TRV ratio for a position to be considered sufficiently collateralized. - FixedPoint.Unsigned public collateralRequirement; - // Percent of a Liquidation/Position's lockedCollateral to be deposited by a potential disputer - // Represented as a multiplier, for example 1.5e18 = "150%" and 0.05e18 = "5%" - FixedPoint.Unsigned public disputeBondPercentage; - // Percent of oraclePrice paid to sponsor in the Disputed state (i.e. following a successful dispute) - // Represented as a multiplier, see above. - FixedPoint.Unsigned public sponsorDisputeRewardPercentage; - // Percent of oraclePrice paid to disputer in the Disputed state (i.e. following a successful dispute) - // Represented as a multiplier, see above. - FixedPoint.Unsigned public disputerDisputeRewardPercentage; - - /**************************************** - * EVENTS * - ****************************************/ - - event LiquidationCreated( - address indexed sponsor, - address indexed liquidator, - uint256 indexed liquidationId, - uint256 tokensOutstanding, - uint256 lockedCollateral, - uint256 liquidatedCollateral, - uint256 liquidationTime - ); - event LiquidationDisputed( - address indexed sponsor, - address indexed liquidator, - address indexed disputer, - uint256 liquidationId, - uint256 disputeBondAmount - ); - event DisputeSettled( - address indexed caller, - address indexed sponsor, - address indexed liquidator, - address disputer, - uint256 liquidationId, - bool disputeSucceeded - ); - event LiquidationWithdrawn( - address indexed caller, - uint256 paidToLiquidator, - uint256 paidToDisputer, - uint256 paidToSponsor, - Status indexed liquidationStatus, - uint256 settlementPrice - ); - - /**************************************** - * MODIFIERS * - ****************************************/ - - modifier disputable(uint256 liquidationId, address sponsor) { - _disputable(liquidationId, sponsor); - _; - } - - modifier withdrawable(uint256 liquidationId, address sponsor) { - _withdrawable(liquidationId, sponsor); - _; - } - - /** - * @notice Constructs the liquidatable contract. - * @param params struct to define input parameters for construction of Liquidatable. Some params - * are fed directly into the PositionManager's constructor within the inheritance tree. - */ - constructor( - ConstructorParams memory params - ) - PerpetualPositionManager( - params.withdrawalLiveness, - params.collateralAddress, - params.tokenAddress, - params.finderAddress, - params.priceFeedIdentifier, - params.fundingRateIdentifier, - params.minSponsorTokens, - params.configStoreAddress, - params.tokenScaling, - params.timerAddress - ) - { - require(params.collateralRequirement.isGreaterThan(1)); - require(params.sponsorDisputeRewardPercentage.add(params.disputerDisputeRewardPercentage).isLessThan(1)); - - // Set liquidatable specific variables. - liquidationLiveness = params.liquidationLiveness; - collateralRequirement = params.collateralRequirement; - disputeBondPercentage = params.disputeBondPercentage; - sponsorDisputeRewardPercentage = params.sponsorDisputeRewardPercentage; - disputerDisputeRewardPercentage = params.disputerDisputeRewardPercentage; - } - - /**************************************** - * LIQUIDATION FUNCTIONS * - ****************************************/ - - /** - * @notice Liquidates the sponsor's position if the caller has enough - * synthetic tokens to retire the position's outstanding tokens. Liquidations above - * a minimum size also reset an ongoing "slow withdrawal"'s liveness. - * @dev This method generates an ID that will uniquely identify liquidation for the sponsor. This contract must be - * approved to spend at least `tokensLiquidated` of `tokenCurrency` and at least `finalFeeBond` of `collateralCurrency`. - * @dev This contract must have the Burner role for the `tokenCurrency`. - * @param sponsor address of the sponsor to liquidate. - * @param minCollateralPerToken abort the liquidation if the position's collateral per token is below this value. - * @param maxCollateralPerToken abort the liquidation if the position's collateral per token exceeds this value. - * @param maxTokensToLiquidate max number of tokens to liquidate. - * @param deadline abort the liquidation if the transaction is mined after this timestamp. - * @return liquidationId ID of the newly created liquidation. - * @return tokensLiquidated amount of synthetic tokens removed and liquidated from the `sponsor`'s position. - * @return finalFeeBond amount of collateral to be posted by liquidator and returned if not disputed successfully. - */ - function createLiquidation( - address sponsor, - FixedPoint.Unsigned calldata minCollateralPerToken, - FixedPoint.Unsigned calldata maxCollateralPerToken, - FixedPoint.Unsigned calldata maxTokensToLiquidate, - uint256 deadline - ) - external - notEmergencyShutdown - fees - nonReentrant - returns ( - uint256 liquidationId, - FixedPoint.Unsigned memory tokensLiquidated, - FixedPoint.Unsigned memory finalFeeBond - ) - { - // Check that this transaction was mined pre-deadline. - require(getCurrentTime() <= deadline); - - // Retrieve Position data for sponsor - PositionData storage positionToLiquidate = _getPositionData(sponsor); - - tokensLiquidated = FixedPoint.min(maxTokensToLiquidate, positionToLiquidate.tokensOutstanding); - require(tokensLiquidated.isGreaterThan(0)); - - // Starting values for the Position being liquidated. If withdrawal request amount is > position's collateral, - // then set this to 0, otherwise set it to (startCollateral - withdrawal request amount). - FixedPoint.Unsigned memory startCollateral = _getFeeAdjustedCollateral(positionToLiquidate.rawCollateral); - FixedPoint.Unsigned memory startCollateralNetOfWithdrawal = FixedPoint.fromUnscaledUint(0); - if (positionToLiquidate.withdrawalRequestAmount.isLessThanOrEqual(startCollateral)) { - startCollateralNetOfWithdrawal = startCollateral.sub(positionToLiquidate.withdrawalRequestAmount); - } - - // Scoping to get rid of a stack too deep error. - { - FixedPoint.Unsigned memory startTokens = positionToLiquidate.tokensOutstanding; - - // The Position's collateralization ratio must be between [minCollateralPerToken, maxCollateralPerToken]. - require(maxCollateralPerToken.mul(startTokens).isGreaterThanOrEqual(startCollateralNetOfWithdrawal)); - // minCollateralPerToken >= startCollateralNetOfWithdrawal / startTokens. - require(minCollateralPerToken.mul(startTokens).isLessThanOrEqual(startCollateralNetOfWithdrawal)); - } - - // Compute final fee at time of liquidation. - finalFeeBond = _computeFinalFees(); - - // These will be populated within the scope below. - FixedPoint.Unsigned memory lockedCollateral; - FixedPoint.Unsigned memory liquidatedCollateral; - - // Scoping to get rid of a stack too deep error. The amount of tokens to remove from the position - // are not funding-rate adjusted because the multiplier only affects their redemption value, not their - // notional. - { - FixedPoint.Unsigned memory ratio = tokensLiquidated.div(positionToLiquidate.tokensOutstanding); - - // The actual amount of collateral that gets moved to the liquidation. - lockedCollateral = startCollateral.mul(ratio); - - // For purposes of disputes, it's actually this liquidatedCollateral value that's used. This value is net of - // withdrawal requests. - liquidatedCollateral = startCollateralNetOfWithdrawal.mul(ratio); - - // Part of the withdrawal request is also removed. Ideally: - // liquidatedCollateral + withdrawalAmountToRemove = lockedCollateral. - FixedPoint.Unsigned memory withdrawalAmountToRemove = positionToLiquidate.withdrawalRequestAmount.mul( - ratio - ); - _reduceSponsorPosition(sponsor, tokensLiquidated, lockedCollateral, withdrawalAmountToRemove); - } - - // Add to the global liquidation collateral count. - _addCollateral(rawLiquidationCollateral, lockedCollateral.add(finalFeeBond)); - - // Construct liquidation object. - // Note: All dispute-related values are zeroed out until a dispute occurs. liquidationId is the index of the new - // LiquidationData that is pushed into the array, which is equal to the current length of the array pre-push. - liquidationId = liquidations[sponsor].length; - liquidations[sponsor].push( - LiquidationData({ - sponsor: sponsor, - liquidator: msg.sender, - state: Status.NotDisputed, - liquidationTime: getCurrentTime(), - tokensOutstanding: _getFundingRateAppliedTokenDebt(tokensLiquidated), - lockedCollateral: lockedCollateral, - liquidatedCollateral: liquidatedCollateral, - rawUnitCollateral: _convertToRawCollateral(FixedPoint.fromUnscaledUint(1)), - disputer: address(0), - settlementPrice: FixedPoint.fromUnscaledUint(0), - finalFee: finalFeeBond - }) - ); - - // If this liquidation is a subsequent liquidation on the position, and the liquidation size is larger than - // some "griefing threshold", then re-set the liveness. This enables a liquidation against a withdraw request to be - // "dragged out" if the position is very large and liquidators need time to gather funds. The griefing threshold - // is enforced so that liquidations for trivially small # of tokens cannot drag out an honest sponsor's slow withdrawal. - - // We arbitrarily set the "griefing threshold" to `minSponsorTokens` because it is the only parameter - // denominated in token currency units and we can avoid adding another parameter. - FixedPoint.Unsigned memory griefingThreshold = minSponsorTokens; - if ( - positionToLiquidate.withdrawalRequestPassTimestamp > 0 && // The position is undergoing a slow withdrawal. - positionToLiquidate.withdrawalRequestPassTimestamp > getCurrentTime() && // The slow withdrawal has not yet expired. - tokensLiquidated.isGreaterThanOrEqual(griefingThreshold) // The liquidated token count is above a "griefing threshold". - ) { - positionToLiquidate.withdrawalRequestPassTimestamp = getCurrentTime().add(withdrawalLiveness); - } - - emit LiquidationCreated( - sponsor, - msg.sender, - liquidationId, - _getFundingRateAppliedTokenDebt(tokensLiquidated).rawValue, - lockedCollateral.rawValue, - liquidatedCollateral.rawValue, - getCurrentTime() - ); - - // Destroy tokens - tokenCurrency.safeTransferFrom(msg.sender, address(this), tokensLiquidated.rawValue); - tokenCurrency.burn(tokensLiquidated.rawValue); - - // Pull final fee from liquidator. - collateralCurrency.safeTransferFrom(msg.sender, address(this), finalFeeBond.rawValue); - } - - /** - * @notice Disputes a liquidation, if the caller has enough collateral to post a dispute bond and pay a fixed final - * fee charged on each price request. - * @dev Can only dispute a liquidation before the liquidation expires and if there are no other pending disputes. - * This contract must be approved to spend at least the dispute bond amount of `collateralCurrency`. This dispute - * bond amount is calculated from `disputeBondPercentage` times the collateral in the liquidation. - * @param liquidationId of the disputed liquidation. - * @param sponsor the address of the sponsor whose liquidation is being disputed. - * @return totalPaid amount of collateral charged to disputer (i.e. final fee bond + dispute bond). - */ - function dispute( - uint256 liquidationId, - address sponsor - ) external disputable(liquidationId, sponsor) fees nonReentrant returns (FixedPoint.Unsigned memory totalPaid) { - LiquidationData storage disputedLiquidation = _getLiquidationData(sponsor, liquidationId); - - // Multiply by the unit collateral so the dispute bond is a percentage of the locked collateral after fees. - FixedPoint.Unsigned memory disputeBondAmount = disputedLiquidation - .lockedCollateral - .mul(disputeBondPercentage) - .mul(_getFeeAdjustedCollateral(disputedLiquidation.rawUnitCollateral)); - _addCollateral(rawLiquidationCollateral, disputeBondAmount); - - // Request a price from DVM. Liquidation is pending dispute until DVM returns a price. - disputedLiquidation.state = Status.Disputed; - disputedLiquidation.disputer = msg.sender; - - // Enqueue a request with the DVM. - _requestOraclePrice(disputedLiquidation.liquidationTime); - - emit LiquidationDisputed( - sponsor, - disputedLiquidation.liquidator, - msg.sender, - liquidationId, - disputeBondAmount.rawValue - ); - totalPaid = disputeBondAmount.add(disputedLiquidation.finalFee); - - // Pay the final fee for requesting price from the DVM. - _payFinalFees(msg.sender, disputedLiquidation.finalFee); - - // Transfer the dispute bond amount from the caller to this contract. - collateralCurrency.safeTransferFrom(msg.sender, address(this), disputeBondAmount.rawValue); - } - - /** - * @notice After a dispute has settled or after a non-disputed liquidation has expired, - * anyone can call this method to disperse payments to the sponsor, liquidator, and disputer. - * @dev If the dispute SUCCEEDED: the sponsor, liquidator, and disputer are eligible for payment. - * If the dispute FAILED: only the liquidator receives payment. This method deletes the liquidation data. - * This method will revert if rewards have already been dispersed. - * @param liquidationId uniquely identifies the sponsor's liquidation. - * @param sponsor address of the sponsor associated with the liquidation. - * @return data about rewards paid out. - */ - function withdrawLiquidation( - uint256 liquidationId, - address sponsor - ) public withdrawable(liquidationId, sponsor) fees nonReentrant returns (RewardsData memory) { - LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId); - - // Settles the liquidation if necessary. This call will revert if the price has not resolved yet. - _settle(liquidationId, sponsor); - - // Calculate rewards as a function of the TRV. - // Note1: all payouts are scaled by the unit collateral value so all payouts are charged the fees pro rata. - // Note2: the tokenRedemptionValue uses the tokensOutstanding which was calculated using the funding rate at - // liquidation time from _getFundingRateAppliedTokenDebt. Therefore the TRV considers the full debt value at that time. - FixedPoint.Unsigned memory feeAttenuation = _getFeeAdjustedCollateral(liquidation.rawUnitCollateral); - FixedPoint.Unsigned memory settlementPrice = liquidation.settlementPrice; - FixedPoint.Unsigned memory tokenRedemptionValue = liquidation.tokensOutstanding.mul(settlementPrice).mul( - feeAttenuation - ); - FixedPoint.Unsigned memory collateral = liquidation.lockedCollateral.mul(feeAttenuation); - FixedPoint.Unsigned memory disputerDisputeReward = disputerDisputeRewardPercentage.mul(tokenRedemptionValue); - FixedPoint.Unsigned memory sponsorDisputeReward = sponsorDisputeRewardPercentage.mul(tokenRedemptionValue); - FixedPoint.Unsigned memory disputeBondAmount = collateral.mul(disputeBondPercentage); - FixedPoint.Unsigned memory finalFee = liquidation.finalFee.mul(feeAttenuation); - - // There are three main outcome states: either the dispute succeeded, failed or was not updated. - // Based on the state, different parties of a liquidation receive different amounts. - // After assigning rewards based on the liquidation status, decrease the total collateral held in this contract - // by the amount to pay each party. The actual amounts withdrawn might differ if _removeCollateral causes - // precision loss. - RewardsData memory rewards; - if (liquidation.state == Status.DisputeSucceeded) { - // If the dispute is successful then all three users should receive rewards: - - // Pay DISPUTER: disputer reward + dispute bond + returned final fee - rewards.payToDisputer = disputerDisputeReward.add(disputeBondAmount).add(finalFee); - - // Pay SPONSOR: remaining collateral (collateral - TRV) + sponsor reward - rewards.payToSponsor = sponsorDisputeReward.add(collateral.sub(tokenRedemptionValue)); - - // Pay LIQUIDATOR: TRV - dispute reward - sponsor reward - // If TRV > Collateral, then subtract rewards from collateral - // NOTE: This should never be below zero since we prevent (sponsorDisputePercentage+disputerDisputePercentage) >= 0 in - // the constructor when these params are set. - rewards.payToLiquidator = tokenRedemptionValue.sub(sponsorDisputeReward).sub(disputerDisputeReward); - - // Transfer rewards and debit collateral - rewards.paidToLiquidator = _removeCollateral(rawLiquidationCollateral, rewards.payToLiquidator); - rewards.paidToSponsor = _removeCollateral(rawLiquidationCollateral, rewards.payToSponsor); - rewards.paidToDisputer = _removeCollateral(rawLiquidationCollateral, rewards.payToDisputer); - - collateralCurrency.safeTransfer(liquidation.disputer, rewards.paidToDisputer.rawValue); - collateralCurrency.safeTransfer(liquidation.liquidator, rewards.paidToLiquidator.rawValue); - collateralCurrency.safeTransfer(liquidation.sponsor, rewards.paidToSponsor.rawValue); - - // In the case of a failed dispute only the liquidator can withdraw. - } else if (liquidation.state == Status.DisputeFailed) { - // Pay LIQUIDATOR: collateral + dispute bond + returned final fee - rewards.payToLiquidator = collateral.add(disputeBondAmount).add(finalFee); - - // Transfer rewards and debit collateral - rewards.paidToLiquidator = _removeCollateral(rawLiquidationCollateral, rewards.payToLiquidator); - - collateralCurrency.safeTransfer(liquidation.liquidator, rewards.paidToLiquidator.rawValue); - - // If the state is pre-dispute but time has passed liveness then there was no dispute. We represent this - // state as a dispute failed and the liquidator can withdraw. - } else if (liquidation.state == Status.NotDisputed) { - // Pay LIQUIDATOR: collateral + returned final fee - rewards.payToLiquidator = collateral.add(finalFee); - - // Transfer rewards and debit collateral - rewards.paidToLiquidator = _removeCollateral(rawLiquidationCollateral, rewards.payToLiquidator); - - collateralCurrency.safeTransfer(liquidation.liquidator, rewards.paidToLiquidator.rawValue); - } - - emit LiquidationWithdrawn( - msg.sender, - rewards.paidToLiquidator.rawValue, - rewards.paidToDisputer.rawValue, - rewards.paidToSponsor.rawValue, - liquidation.state, - settlementPrice.rawValue - ); - - // Free up space after collateral is withdrawn by removing the liquidation object from the array. - delete liquidations[sponsor][liquidationId]; - - return rewards; - } - - /** - * @notice Gets all liquidation information for a given sponsor address. - * @param sponsor address of the position sponsor. - * @return liquidationData array of all liquidation information for the given sponsor address. - */ - function getLiquidations( - address sponsor - ) external view nonReentrantView returns (LiquidationData[] memory liquidationData) { - return liquidations[sponsor]; - } - - /**************************************** - * INTERNAL FUNCTIONS * - ****************************************/ - - // This settles a liquidation if it is in the Disputed state. If not, it will immediately return. - // If the liquidation is in the Disputed state, but a price is not available, this will revert. - function _settle(uint256 liquidationId, address sponsor) internal { - LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId); - - // Settlement only happens when state == Disputed and will only happen once per liquidation. - // If this liquidation is not ready to be settled, this method should return immediately. - if (liquidation.state != Status.Disputed) { - return; - } - - // Get the returned price from the oracle. If this has not yet resolved will revert. - liquidation.settlementPrice = _getOraclePrice(liquidation.liquidationTime); - - // Find the value of the tokens in the underlying collateral. - FixedPoint.Unsigned memory tokenRedemptionValue = liquidation.tokensOutstanding.mul( - liquidation.settlementPrice - ); - - // The required collateral is the value of the tokens in underlying * required collateral ratio. - FixedPoint.Unsigned memory requiredCollateral = tokenRedemptionValue.mul(collateralRequirement); - - // If the position has more than the required collateral it is solvent and the dispute is valid (liquidation is invalid) - // Note that this check uses the liquidatedCollateral not the lockedCollateral as this considers withdrawals. - bool disputeSucceeded = liquidation.liquidatedCollateral.isGreaterThanOrEqual(requiredCollateral); - liquidation.state = disputeSucceeded ? Status.DisputeSucceeded : Status.DisputeFailed; - - emit DisputeSettled( - msg.sender, - sponsor, - liquidation.liquidator, - liquidation.disputer, - liquidationId, - disputeSucceeded - ); - } - - function _pfc() internal view override returns (FixedPoint.Unsigned memory) { - return super._pfc().add(_getFeeAdjustedCollateral(rawLiquidationCollateral)); - } - - function _getLiquidationData( - address sponsor, - uint256 liquidationId - ) internal view returns (LiquidationData storage liquidation) { - LiquidationData[] storage liquidationArray = liquidations[sponsor]; - - // Revert if the caller is attempting to access an invalid liquidation - // (one that has never been created or one has never been initialized). - require( - liquidationId < liquidationArray.length && liquidationArray[liquidationId].state != Status.Uninitialized - ); - return liquidationArray[liquidationId]; - } - - function _getLiquidationExpiry(LiquidationData storage liquidation) internal view returns (uint256) { - return liquidation.liquidationTime.add(liquidationLiveness); - } - - // These internal functions are supposed to act identically to modifiers, but re-used modifiers - // unnecessarily increase contract bytecode size. - // source: https://blog.polymath.network/solidity-tips-and-tricks-to-save-gas-and-reduce-bytecode-size-c44580b218e6 - function _disputable(uint256 liquidationId, address sponsor) internal view { - LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId); - require((getCurrentTime() < _getLiquidationExpiry(liquidation)) && (liquidation.state == Status.NotDisputed)); - } - - function _withdrawable(uint256 liquidationId, address sponsor) internal view { - LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId); - Status state = liquidation.state; - - // Must be disputed or the liquidation has passed expiry. - require( - (state > Status.NotDisputed) || - ((_getLiquidationExpiry(liquidation) <= getCurrentTime()) && (state == Status.NotDisputed)) - ); - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualPositionManager.sol b/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualPositionManager.sol deleted file mode 100644 index 185315a67..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/perpetual-multiparty/PerpetualPositionManager.sol +++ /dev/null @@ -1,756 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; - -import "../../common/implementation/FixedPoint.sol"; -import "../../common/interfaces/ExpandedIERC20.sol"; - -import "../../data-verification-mechanism/interfaces/OracleInterface.sol"; -import "../../data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol"; -import "../../data-verification-mechanism/implementation/Constants.sol"; - -import "../common/FundingRateApplier.sol"; - -/** - * @title Financial contract with priceless position management. - * @notice Handles positions for multiple sponsors in an optimistic (i.e., priceless) way without relying - * on a price feed. On construction, deploys a new ERC20, managed by this contract, that is the synthetic token. - */ - -contract PerpetualPositionManager is FundingRateApplier { - using SafeMath for uint256; - using FixedPoint for FixedPoint.Unsigned; - using SafeERC20 for IERC20; - using SafeERC20 for ExpandedIERC20; - - /**************************************** - * PRICELESS POSITION DATA STRUCTURES * - ****************************************/ - - // Represents a single sponsor's position. All collateral is held by this contract. - // This struct acts as bookkeeping for how much of that collateral is allocated to each sponsor. - struct PositionData { - FixedPoint.Unsigned tokensOutstanding; - // Tracks pending withdrawal requests. A withdrawal request is pending if `withdrawalRequestPassTimestamp != 0`. - uint256 withdrawalRequestPassTimestamp; - FixedPoint.Unsigned withdrawalRequestAmount; - // Raw collateral value. This value should never be accessed directly -- always use _getFeeAdjustedCollateral(). - // To add or remove collateral, use _addCollateral() and _removeCollateral(). - FixedPoint.Unsigned rawCollateral; - } - - // Maps sponsor addresses to their positions. Each sponsor can have only one position. - mapping(address => PositionData) public positions; - - // Keep track of the total collateral and tokens across all positions to enable calculating the - // global collateralization ratio without iterating over all positions. - FixedPoint.Unsigned public totalTokensOutstanding; - - // Similar to the rawCollateral in PositionData, this value should not be used directly. - // _getFeeAdjustedCollateral(), _addCollateral() and _removeCollateral() must be used to access and adjust. - FixedPoint.Unsigned public rawTotalPositionCollateral; - - // Synthetic token created by this contract. - ExpandedIERC20 public tokenCurrency; - - // Unique identifier for DVM price feed ticker. - bytes32 public priceIdentifier; - - // Time that has to elapse for a withdrawal request to be considered passed, if no liquidations occur. - // !!Note: The lower the withdrawal liveness value, the more risk incurred by the contract. - // Extremely low liveness values increase the chance that opportunistic invalid withdrawal requests - // expire without liquidation, thereby increasing the insolvency risk for the contract as a whole. An insolvent - // contract is extremely risky for any sponsor or synthetic token holder for the contract. - uint256 public withdrawalLiveness; - - // Minimum number of tokens in a sponsor's position. - FixedPoint.Unsigned public minSponsorTokens; - - // Expiry price pulled from the DVM in the case of an emergency shutdown. - FixedPoint.Unsigned public emergencyShutdownPrice; - - /**************************************** - * EVENTS * - ****************************************/ - - event Deposit(address indexed sponsor, uint256 indexed collateralAmount); - event Withdrawal(address indexed sponsor, uint256 indexed collateralAmount); - event RequestWithdrawal(address indexed sponsor, uint256 indexed collateralAmount); - event RequestWithdrawalExecuted(address indexed sponsor, uint256 indexed collateralAmount); - event RequestWithdrawalCanceled(address indexed sponsor, uint256 indexed collateralAmount); - event PositionCreated(address indexed sponsor, uint256 indexed collateralAmount, uint256 indexed tokenAmount); - event NewSponsor(address indexed sponsor); - event EndedSponsorPosition(address indexed sponsor); - event Redeem(address indexed sponsor, uint256 indexed collateralAmount, uint256 indexed tokenAmount); - event Repay(address indexed sponsor, uint256 indexed numTokensRepaid, uint256 indexed newTokenCount); - event EmergencyShutdown(address indexed caller, uint256 shutdownTimestamp); - event SettleEmergencyShutdown( - address indexed caller, - uint256 indexed collateralReturned, - uint256 indexed tokensBurned - ); - - /**************************************** - * MODIFIERS * - ****************************************/ - - modifier onlyCollateralizedPosition(address sponsor) { - _onlyCollateralizedPosition(sponsor); - _; - } - - modifier noPendingWithdrawal(address sponsor) { - _positionHasNoPendingWithdrawal(sponsor); - _; - } - - /** - * @notice Construct the PerpetualPositionManager. - * @dev Deployer of this contract should consider carefully which parties have ability to mint and burn - * the synthetic tokens referenced by `_tokenAddress`. This contract's security assumes that no external accounts - * can mint new tokens, which could be used to steal all of this contract's locked collateral. - * We recommend to only use synthetic token contracts whose sole Owner role (the role capable of adding & removing roles) - * is assigned to this contract, whose sole Minter role is assigned to this contract, and whose - * total supply is 0 prior to construction of this contract. - * @param _withdrawalLiveness liveness delay, in seconds, for pending withdrawals. - * @param _collateralAddress ERC20 token used as collateral for all positions. - * @param _tokenAddress ERC20 token used as synthetic token. - * @param _finderAddress UMA protocol Finder used to discover other protocol contracts. - * @param _priceIdentifier registered in the DVM for the synthetic. - * @param _fundingRateIdentifier Unique identifier for DVM price feed ticker for child financial contract. - * @param _minSponsorTokens minimum number of tokens that must exist at any time in a position. - * @param _tokenScaling initial scaling to apply to the token value (i.e. scales the tracking index). - * @param _timerAddress Contract that stores the current time in a testing environment. Set to 0x0 for production. - */ - constructor( - uint256 _withdrawalLiveness, - address _collateralAddress, - address _tokenAddress, - address _finderAddress, - bytes32 _priceIdentifier, - bytes32 _fundingRateIdentifier, - FixedPoint.Unsigned memory _minSponsorTokens, - address _configStoreAddress, - FixedPoint.Unsigned memory _tokenScaling, - address _timerAddress - ) - FundingRateApplier( - _fundingRateIdentifier, - _collateralAddress, - _finderAddress, - _configStoreAddress, - _tokenScaling, - _timerAddress - ) - { - require(_getIdentifierWhitelist().isIdentifierSupported(_priceIdentifier)); - - withdrawalLiveness = _withdrawalLiveness; - tokenCurrency = ExpandedIERC20(_tokenAddress); - minSponsorTokens = _minSponsorTokens; - priceIdentifier = _priceIdentifier; - } - - /**************************************** - * POSITION FUNCTIONS * - ****************************************/ - - /** - * @notice Transfers `collateralAmount` of `collateralCurrency` into the specified sponsor's position. - * @dev Increases the collateralization level of a position after creation. This contract must be approved to spend - * at least `collateralAmount` of `collateralCurrency`. - * @param sponsor the sponsor to credit the deposit to. - * @param collateralAmount total amount of collateral tokens to be sent to the sponsor's position. - */ - function depositTo( - address sponsor, - FixedPoint.Unsigned memory collateralAmount - ) public notEmergencyShutdown noPendingWithdrawal(sponsor) fees nonReentrant { - require(collateralAmount.isGreaterThan(0)); - PositionData storage positionData = _getPositionData(sponsor); - - // Increase the position and global collateral balance by collateral amount. - _incrementCollateralBalances(positionData, collateralAmount); - - emit Deposit(sponsor, collateralAmount.rawValue); - - // Move collateral currency from sender to contract. - collateralCurrency.safeTransferFrom(msg.sender, address(this), collateralAmount.rawValue); - } - - /** - * @notice Transfers `collateralAmount` of `collateralCurrency` into the caller's position. - * @dev Increases the collateralization level of a position after creation. This contract must be approved to spend - * at least `collateralAmount` of `collateralCurrency`. - * @param collateralAmount total amount of collateral tokens to be sent to the sponsor's position. - */ - function deposit(FixedPoint.Unsigned memory collateralAmount) public { - // This is just a thin wrapper over depositTo that specified the sender as the sponsor. - depositTo(msg.sender, collateralAmount); - } - - /** - * @notice Transfers `collateralAmount` of `collateralCurrency` from the sponsor's position to the sponsor. - * @dev Reverts if the withdrawal puts this position's collateralization ratio below the global collateralization - * ratio. In that case, use `requestWithdrawal`. Might not withdraw the full requested amount to account for precision loss. - * @param collateralAmount is the amount of collateral to withdraw. - * @return amountWithdrawn The actual amount of collateral withdrawn. - */ - function withdraw( - FixedPoint.Unsigned memory collateralAmount - ) - public - notEmergencyShutdown - noPendingWithdrawal(msg.sender) - fees - nonReentrant - returns (FixedPoint.Unsigned memory amountWithdrawn) - { - require(collateralAmount.isGreaterThan(0)); - PositionData storage positionData = _getPositionData(msg.sender); - - // Decrement the sponsor's collateral and global collateral amounts. Check the GCR between decrement to ensure - // position remains above the GCR within the withdrawal. If this is not the case the caller must submit a request. - amountWithdrawn = _decrementCollateralBalancesCheckGCR(positionData, collateralAmount); - - emit Withdrawal(msg.sender, amountWithdrawn.rawValue); - - // Move collateral currency from contract to sender. - // Note: that we move the amount of collateral that is decreased from rawCollateral (inclusive of fees) - // instead of the user requested amount. This eliminates precision loss that could occur - // where the user withdraws more collateral than rawCollateral is decremented by. - collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue); - } - - /** - * @notice Starts a withdrawal request that, if passed, allows the sponsor to withdraw from their position. - * @dev The request will be pending for `withdrawalLiveness`, during which the position can be liquidated. - * @param collateralAmount the amount of collateral requested to withdraw - */ - function requestWithdrawal( - FixedPoint.Unsigned memory collateralAmount - ) public notEmergencyShutdown noPendingWithdrawal(msg.sender) nonReentrant { - PositionData storage positionData = _getPositionData(msg.sender); - require( - collateralAmount.isGreaterThan(0) && - collateralAmount.isLessThanOrEqual(_getFeeAdjustedCollateral(positionData.rawCollateral)) - ); - - // Update the position object for the user. - positionData.withdrawalRequestPassTimestamp = getCurrentTime().add(withdrawalLiveness); - positionData.withdrawalRequestAmount = collateralAmount; - - emit RequestWithdrawal(msg.sender, collateralAmount.rawValue); - } - - /** - * @notice After a passed withdrawal request (i.e., by a call to `requestWithdrawal` and waiting - * `withdrawalLiveness`), withdraws `positionData.withdrawalRequestAmount` of collateral currency. - * @dev Might not withdraw the full requested amount in order to account for precision loss or if the full requested - * amount exceeds the collateral in the position (due to paying fees). - * @return amountWithdrawn The actual amount of collateral withdrawn. - */ - function withdrawPassedRequest() - external - notEmergencyShutdown - fees - nonReentrant - returns (FixedPoint.Unsigned memory amountWithdrawn) - { - PositionData storage positionData = _getPositionData(msg.sender); - require( - positionData.withdrawalRequestPassTimestamp != 0 && - positionData.withdrawalRequestPassTimestamp <= getCurrentTime() - ); - - // If withdrawal request amount is > position collateral, then withdraw the full collateral amount. - // This situation is possible due to fees charged since the withdrawal was originally requested. - FixedPoint.Unsigned memory amountToWithdraw = positionData.withdrawalRequestAmount; - if (positionData.withdrawalRequestAmount.isGreaterThan(_getFeeAdjustedCollateral(positionData.rawCollateral))) { - amountToWithdraw = _getFeeAdjustedCollateral(positionData.rawCollateral); - } - - // Decrement the sponsor's collateral and global collateral amounts. - amountWithdrawn = _decrementCollateralBalances(positionData, amountToWithdraw); - - // Reset withdrawal request by setting withdrawal amount and withdrawal timestamp to 0. - _resetWithdrawalRequest(positionData); - - // Transfer approved withdrawal amount from the contract to the caller. - collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue); - - emit RequestWithdrawalExecuted(msg.sender, amountWithdrawn.rawValue); - } - - /** - * @notice Cancels a pending withdrawal request. - */ - function cancelWithdrawal() external notEmergencyShutdown nonReentrant { - PositionData storage positionData = _getPositionData(msg.sender); - // No pending withdrawal require message removed to save bytecode. - require(positionData.withdrawalRequestPassTimestamp != 0); - - emit RequestWithdrawalCanceled(msg.sender, positionData.withdrawalRequestAmount.rawValue); - - // Reset withdrawal request by setting withdrawal amount and withdrawal timestamp to 0. - _resetWithdrawalRequest(positionData); - } - - /** - * @notice Creates tokens by creating a new position or by augmenting an existing position. Pulls `collateralAmount - * ` into the sponsor's position and mints `numTokens` of `tokenCurrency`. - * @dev This contract must have the Minter role for the `tokenCurrency`. - * @dev Reverts if minting these tokens would put the position's collateralization ratio below the - * global collateralization ratio. This contract must be approved to spend at least `collateralAmount` of - * `collateralCurrency`. - * @param collateralAmount is the number of collateral tokens to collateralize the position with - * @param numTokens is the number of tokens to mint from the position. - */ - function create( - FixedPoint.Unsigned memory collateralAmount, - FixedPoint.Unsigned memory numTokens - ) public notEmergencyShutdown fees nonReentrant { - PositionData storage positionData = positions[msg.sender]; - - // Either the new create ratio or the resultant position CR must be above the current GCR. - require( - (_checkCollateralization( - _getFeeAdjustedCollateral(positionData.rawCollateral).add(collateralAmount), - positionData.tokensOutstanding.add(numTokens) - ) || _checkCollateralization(collateralAmount, numTokens)) - ); - - require(positionData.withdrawalRequestPassTimestamp == 0); - if (positionData.tokensOutstanding.isEqual(0)) { - require(numTokens.isGreaterThanOrEqual(minSponsorTokens)); - emit NewSponsor(msg.sender); - } - - // Increase the position and global collateral balance by collateral amount. - _incrementCollateralBalances(positionData, collateralAmount); - - // Add the number of tokens created to the position's outstanding tokens. - positionData.tokensOutstanding = positionData.tokensOutstanding.add(numTokens); - - totalTokensOutstanding = totalTokensOutstanding.add(numTokens); - - emit PositionCreated(msg.sender, collateralAmount.rawValue, numTokens.rawValue); - - // Transfer tokens into the contract from caller and mint corresponding synthetic tokens to the caller's address. - collateralCurrency.safeTransferFrom(msg.sender, address(this), collateralAmount.rawValue); - - // Note: revert reason removed to save bytecode. - require(tokenCurrency.mint(msg.sender, numTokens.rawValue)); - } - - /** - * @notice Burns `numTokens` of `tokenCurrency` and sends back the proportional amount of `collateralCurrency`. - * @dev Can only be called by a token sponsor. Might not redeem the full proportional amount of collateral - * in order to account for precision loss. This contract must be approved to spend at least `numTokens` of - * `tokenCurrency`. - * @dev This contract must have the Burner role for the `tokenCurrency`. - * @param numTokens is the number of tokens to be burnt for a commensurate amount of collateral. - * @return amountWithdrawn The actual amount of collateral withdrawn. - */ - function redeem( - FixedPoint.Unsigned memory numTokens - ) - public - notEmergencyShutdown - noPendingWithdrawal(msg.sender) - fees - nonReentrant - returns (FixedPoint.Unsigned memory amountWithdrawn) - { - PositionData storage positionData = _getPositionData(msg.sender); - require(numTokens.isLessThanOrEqual(positionData.tokensOutstanding)); - - FixedPoint.Unsigned memory fractionRedeemed = numTokens.div(positionData.tokensOutstanding); - FixedPoint.Unsigned memory collateralRedeemed = fractionRedeemed.mul( - _getFeeAdjustedCollateral(positionData.rawCollateral) - ); - - // If redemption returns all tokens the sponsor has then we can delete their position. Else, downsize. - if (positionData.tokensOutstanding.isEqual(numTokens)) { - amountWithdrawn = _deleteSponsorPosition(msg.sender); - } else { - // Decrement the sponsor's collateral and global collateral amounts. - amountWithdrawn = _decrementCollateralBalances(positionData, collateralRedeemed); - - // Decrease the sponsors position tokens size. Ensure it is above the min sponsor size. - FixedPoint.Unsigned memory newTokenCount = positionData.tokensOutstanding.sub(numTokens); - require(newTokenCount.isGreaterThanOrEqual(minSponsorTokens)); - positionData.tokensOutstanding = newTokenCount; - - // Update the totalTokensOutstanding after redemption. - totalTokensOutstanding = totalTokensOutstanding.sub(numTokens); - } - - emit Redeem(msg.sender, amountWithdrawn.rawValue, numTokens.rawValue); - - // Transfer collateral from contract to caller and burn callers synthetic tokens. - collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue); - tokenCurrency.safeTransferFrom(msg.sender, address(this), numTokens.rawValue); - tokenCurrency.burn(numTokens.rawValue); - } - - /** - * @notice Burns `numTokens` of `tokenCurrency` to decrease sponsors position size, without sending back `collateralCurrency`. - * This is done by a sponsor to increase position CR. Resulting size is bounded by minSponsorTokens. - * @dev Can only be called by token sponsor. This contract must be approved to spend `numTokens` of `tokenCurrency`. - * @dev This contract must have the Burner role for the `tokenCurrency`. - * @param numTokens is the number of tokens to be burnt from the sponsor's debt position. - */ - function repay( - FixedPoint.Unsigned memory numTokens - ) public notEmergencyShutdown noPendingWithdrawal(msg.sender) fees nonReentrant { - PositionData storage positionData = _getPositionData(msg.sender); - require(numTokens.isLessThanOrEqual(positionData.tokensOutstanding)); - - // Decrease the sponsors position tokens size. Ensure it is above the min sponsor size. - FixedPoint.Unsigned memory newTokenCount = positionData.tokensOutstanding.sub(numTokens); - require(newTokenCount.isGreaterThanOrEqual(minSponsorTokens)); - positionData.tokensOutstanding = newTokenCount; - - // Update the totalTokensOutstanding after redemption. - totalTokensOutstanding = totalTokensOutstanding.sub(numTokens); - - emit Repay(msg.sender, numTokens.rawValue, newTokenCount.rawValue); - - // Transfer the tokens back from the sponsor and burn them. - tokenCurrency.safeTransferFrom(msg.sender, address(this), numTokens.rawValue); - tokenCurrency.burn(numTokens.rawValue); - } - - /** - * @notice If the contract is emergency shutdown then all token holders and sponsors can redeem their tokens or - * remaining collateral for underlying at the prevailing price defined by a DVM vote. - * @dev This burns all tokens from the caller of `tokenCurrency` and sends back the resolved settlement value of - * `collateralCurrency`. Might not redeem the full proportional amount of collateral in order to account for - * precision loss. This contract must be approved to spend `tokenCurrency` at least up to the caller's full balance. - * @dev This contract must have the Burner role for the `tokenCurrency`. - * @dev Note that this function does not call the updateFundingRate modifier to update the funding rate as this - * function is only called after an emergency shutdown & there should be no funding rate updates after the shutdown. - * @return amountWithdrawn The actual amount of collateral withdrawn. - */ - function settleEmergencyShutdown() - external - isEmergencyShutdown - fees - nonReentrant - returns (FixedPoint.Unsigned memory amountWithdrawn) - { - // Set the emergency shutdown price as resolved from the DVM. If DVM has not resolved will revert. - if (emergencyShutdownPrice.isEqual(FixedPoint.fromUnscaledUint(0))) { - emergencyShutdownPrice = _getOracleEmergencyShutdownPrice(); - } - - // Get caller's tokens balance and calculate amount of underlying entitled to them. - FixedPoint.Unsigned memory tokensToRedeem = FixedPoint.Unsigned(tokenCurrency.balanceOf(msg.sender)); - FixedPoint.Unsigned memory totalRedeemableCollateral = _getFundingRateAppliedTokenDebt(tokensToRedeem).mul( - emergencyShutdownPrice - ); - - // If the caller is a sponsor with outstanding collateral they are also entitled to their excess collateral after their debt. - PositionData storage positionData = positions[msg.sender]; - if (_getFeeAdjustedCollateral(positionData.rawCollateral).isGreaterThan(0)) { - // Calculate the underlying entitled to a token sponsor. This is collateral - debt in underlying with - // the funding rate applied to the outstanding token debt. - - FixedPoint.Unsigned memory tokenDebtValueInCollateral = _getFundingRateAppliedTokenDebt( - positionData.tokensOutstanding - ).mul(emergencyShutdownPrice); - FixedPoint.Unsigned memory positionCollateral = _getFeeAdjustedCollateral(positionData.rawCollateral); - - // If the debt is greater than the remaining collateral, they cannot redeem anything. - FixedPoint.Unsigned memory positionRedeemableCollateral = tokenDebtValueInCollateral.isLessThan( - positionCollateral - ) - ? positionCollateral.sub(tokenDebtValueInCollateral) - : FixedPoint.Unsigned(0); - - // Add the number of redeemable tokens for the sponsor to their total redeemable collateral. - totalRedeemableCollateral = totalRedeemableCollateral.add(positionRedeemableCollateral); - - // Reset the position state as all the value has been removed after settlement. - delete positions[msg.sender]; - emit EndedSponsorPosition(msg.sender); - } - - // Take the min of the remaining collateral and the collateral "owed". If the contract is undercapitalized, - // the caller will get as much collateral as the contract can pay out. - FixedPoint.Unsigned memory payout = FixedPoint.min( - _getFeeAdjustedCollateral(rawTotalPositionCollateral), - totalRedeemableCollateral - ); - - // Decrement total contract collateral and outstanding debt. - amountWithdrawn = _removeCollateral(rawTotalPositionCollateral, payout); - totalTokensOutstanding = totalTokensOutstanding.sub(tokensToRedeem); - - emit SettleEmergencyShutdown(msg.sender, amountWithdrawn.rawValue, tokensToRedeem.rawValue); - - // Transfer tokens & collateral and burn the redeemed tokens. - collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue); - tokenCurrency.safeTransferFrom(msg.sender, address(this), tokensToRedeem.rawValue); - tokenCurrency.burn(tokensToRedeem.rawValue); - } - - /**************************************** - * GLOBAL STATE FUNCTIONS * - ****************************************/ - - /** - * @notice Premature contract settlement under emergency circumstances. - * @dev Only the governor can call this function as they are permissioned within the `FinancialContractAdmin`. - * Upon emergency shutdown, the contract settlement time is set to the shutdown time. This enables withdrawal - * to occur via the `settleEmergencyShutdown` function. - */ - function emergencyShutdown() external override notEmergencyShutdown fees nonReentrant { - // Note: revert reason removed to save bytecode. - require(msg.sender == _getFinancialContractsAdminAddress()); - - emergencyShutdownTimestamp = getCurrentTime(); - _requestOraclePrice(emergencyShutdownTimestamp); - - emit EmergencyShutdown(msg.sender, emergencyShutdownTimestamp); - } - - /** - * @notice Theoretically supposed to pay fees and move money between margin accounts to make sure they - * reflect the NAV of the contract. However, this functionality doesn't apply to this contract. - * @dev This is supposed to be implemented by any contract that inherits `AdministrateeInterface` and callable - * only by the Governor contract. This method is therefore minimally implemented in this contract and does nothing. - */ - function remargin() external pure override { - return; - } - - /** - * @notice Accessor method for a sponsor's collateral. - * @dev This is necessary because the struct returned by the positions() method shows - * rawCollateral, which isn't a user-readable value. - * @dev This method accounts for pending regular fees that have not yet been withdrawn from this contract, for - * example if the `lastPaymentTime != currentTime`. - * @param sponsor address whose collateral amount is retrieved. - * @return collateralAmount amount of collateral within a sponsors position. - */ - function getCollateral( - address sponsor - ) external view nonReentrantView returns (FixedPoint.Unsigned memory collateralAmount) { - // Note: do a direct access to avoid the validity check. - return _getPendingRegularFeeAdjustedCollateral(_getFeeAdjustedCollateral(positions[sponsor].rawCollateral)); - } - - /** - * @notice Accessor method for the total collateral stored within the PerpetualPositionManager. - * @return totalCollateral amount of all collateral within the position manager. - */ - function totalPositionCollateral() - external - view - nonReentrantView - returns (FixedPoint.Unsigned memory totalCollateral) - { - return _getPendingRegularFeeAdjustedCollateral(_getFeeAdjustedCollateral(rawTotalPositionCollateral)); - } - - function getFundingRateAppliedTokenDebt( - FixedPoint.Unsigned memory rawTokenDebt - ) external view nonReentrantView returns (FixedPoint.Unsigned memory totalCollateral) { - return _getFundingRateAppliedTokenDebt(rawTokenDebt); - } - - /**************************************** - * INTERNAL FUNCTIONS * - ****************************************/ - - // Reduces a sponsor's position and global counters by the specified parameters. Handles deleting the entire - // position if the entire position is being removed. Does not make any external transfers. - function _reduceSponsorPosition( - address sponsor, - FixedPoint.Unsigned memory tokensToRemove, - FixedPoint.Unsigned memory collateralToRemove, - FixedPoint.Unsigned memory withdrawalAmountToRemove - ) internal { - PositionData storage positionData = _getPositionData(sponsor); - - // If the entire position is being removed, delete it instead. - if ( - tokensToRemove.isEqual(positionData.tokensOutstanding) && - _getFeeAdjustedCollateral(positionData.rawCollateral).isEqual(collateralToRemove) - ) { - _deleteSponsorPosition(sponsor); - return; - } - - // Decrement the sponsor's collateral and global collateral amounts. - _decrementCollateralBalances(positionData, collateralToRemove); - - // Ensure that the sponsor will meet the min position size after the reduction. - positionData.tokensOutstanding = positionData.tokensOutstanding.sub(tokensToRemove); - require(positionData.tokensOutstanding.isGreaterThanOrEqual(minSponsorTokens)); - - // Decrement the position's withdrawal amount. - positionData.withdrawalRequestAmount = positionData.withdrawalRequestAmount.sub(withdrawalAmountToRemove); - - // Decrement the total outstanding tokens in the overall contract. - totalTokensOutstanding = totalTokensOutstanding.sub(tokensToRemove); - } - - // Deletes a sponsor's position and updates global counters. Does not make any external transfers. - function _deleteSponsorPosition(address sponsor) internal returns (FixedPoint.Unsigned memory) { - PositionData storage positionToLiquidate = _getPositionData(sponsor); - - FixedPoint.Unsigned memory startingGlobalCollateral = _getFeeAdjustedCollateral(rawTotalPositionCollateral); - - // Remove the collateral and outstanding from the overall total position. - rawTotalPositionCollateral = rawTotalPositionCollateral.sub(positionToLiquidate.rawCollateral); - totalTokensOutstanding = totalTokensOutstanding.sub(positionToLiquidate.tokensOutstanding); - - // Reset the sponsors position to have zero outstanding and collateral. - delete positions[sponsor]; - - emit EndedSponsorPosition(sponsor); - - // Return fee-adjusted amount of collateral deleted from position. - return startingGlobalCollateral.sub(_getFeeAdjustedCollateral(rawTotalPositionCollateral)); - } - - function _pfc() internal view virtual override returns (FixedPoint.Unsigned memory) { - return _getFeeAdjustedCollateral(rawTotalPositionCollateral); - } - - function _getPositionData( - address sponsor - ) internal view onlyCollateralizedPosition(sponsor) returns (PositionData storage) { - return positions[sponsor]; - } - - function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) { - return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); - } - - function _getOracle() internal view returns (OracleInterface) { - return OracleInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); - } - - function _getFinancialContractsAdminAddress() internal view returns (address) { - return finder.getImplementationAddress(OracleInterfaces.FinancialContractsAdmin); - } - - // Requests a price for `priceIdentifier` at `requestedTime` from the Oracle. - function _requestOraclePrice(uint256 requestedTime) internal { - _getOracle().requestPrice(priceIdentifier, requestedTime); - } - - // Fetches a resolved Oracle price from the Oracle. Reverts if the Oracle hasn't resolved for this request. - function _getOraclePrice(uint256 requestedTime) internal view returns (FixedPoint.Unsigned memory price) { - // Create an instance of the oracle and get the price. If the price is not resolved revert. - int256 oraclePrice = _getOracle().getPrice(priceIdentifier, requestedTime); - - // For now we don't want to deal with negative prices in positions. - if (oraclePrice < 0) { - oraclePrice = 0; - } - return FixedPoint.Unsigned(uint256(oraclePrice)); - } - - // Fetches a resolved Oracle price from the Oracle. Reverts if the Oracle hasn't resolved for this request. - function _getOracleEmergencyShutdownPrice() internal view returns (FixedPoint.Unsigned memory) { - return _getOraclePrice(emergencyShutdownTimestamp); - } - - // Reset withdrawal request by setting the withdrawal request and withdrawal timestamp to 0. - function _resetWithdrawalRequest(PositionData storage positionData) internal { - positionData.withdrawalRequestAmount = FixedPoint.fromUnscaledUint(0); - positionData.withdrawalRequestPassTimestamp = 0; - } - - // Ensure individual and global consistency when increasing collateral balances. Returns the change to the position. - function _incrementCollateralBalances( - PositionData storage positionData, - FixedPoint.Unsigned memory collateralAmount - ) internal returns (FixedPoint.Unsigned memory) { - _addCollateral(positionData.rawCollateral, collateralAmount); - return _addCollateral(rawTotalPositionCollateral, collateralAmount); - } - - // Ensure individual and global consistency when decrementing collateral balances. Returns the change to the - // position. We elect to return the amount that the global collateral is decreased by, rather than the individual - // position's collateral, because we need to maintain the invariant that the global collateral is always - // <= the collateral owned by the contract to avoid reverts on withdrawals. The amount returned = amount withdrawn. - function _decrementCollateralBalances( - PositionData storage positionData, - FixedPoint.Unsigned memory collateralAmount - ) internal returns (FixedPoint.Unsigned memory) { - _removeCollateral(positionData.rawCollateral, collateralAmount); - return _removeCollateral(rawTotalPositionCollateral, collateralAmount); - } - - // Ensure individual and global consistency when decrementing collateral balances. Returns the change to the position. - // This function is similar to the _decrementCollateralBalances function except this function checks position GCR - // between the decrements. This ensures that collateral removal will not leave the position undercollateralized. - function _decrementCollateralBalancesCheckGCR( - PositionData storage positionData, - FixedPoint.Unsigned memory collateralAmount - ) internal returns (FixedPoint.Unsigned memory) { - _removeCollateral(positionData.rawCollateral, collateralAmount); - require(_checkPositionCollateralization(positionData)); - return _removeCollateral(rawTotalPositionCollateral, collateralAmount); - } - - // These internal functions are supposed to act identically to modifiers, but re-used modifiers - // unnecessarily increase contract bytecode size. - // source: https://blog.polymath.network/solidity-tips-and-tricks-to-save-gas-and-reduce-bytecode-size-c44580b218e6 - function _onlyCollateralizedPosition(address sponsor) internal view { - require(_getFeeAdjustedCollateral(positions[sponsor].rawCollateral).isGreaterThan(0)); - } - - // Note: This checks whether an already existing position has a pending withdrawal. This cannot be used on the - // `create` method because it is possible that `create` is called on a new position (i.e. one without any collateral - // or tokens outstanding) which would fail the `onlyCollateralizedPosition` modifier on `_getPositionData`. - function _positionHasNoPendingWithdrawal(address sponsor) internal view { - require(_getPositionData(sponsor).withdrawalRequestPassTimestamp == 0); - } - - /**************************************** - * PRIVATE FUNCTIONS * - ****************************************/ - - function _checkPositionCollateralization(PositionData storage positionData) private view returns (bool) { - return - _checkCollateralization( - _getFeeAdjustedCollateral(positionData.rawCollateral), - positionData.tokensOutstanding - ); - } - - // Checks whether the provided `collateral` and `numTokens` have a collateralization ratio above the global - // collateralization ratio. - function _checkCollateralization( - FixedPoint.Unsigned memory collateral, - FixedPoint.Unsigned memory numTokens - ) private view returns (bool) { - FixedPoint.Unsigned memory global = _getCollateralizationRatio( - _getFeeAdjustedCollateral(rawTotalPositionCollateral), - totalTokensOutstanding - ); - FixedPoint.Unsigned memory thisChange = _getCollateralizationRatio(collateral, numTokens); - return !global.isGreaterThan(thisChange); - } - - function _getCollateralizationRatio( - FixedPoint.Unsigned memory collateral, - FixedPoint.Unsigned memory numTokens - ) private pure returns (FixedPoint.Unsigned memory ratio) { - return numTokens.isLessThanOrEqual(0) ? FixedPoint.fromUnscaledUint(0) : collateral.div(numTokens); - } - - function _getTokenAddress() internal view override returns (address) { - return address(tokenCurrency); - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/test/ExpiringMultiPartyMock.sol b/contracts/external/uma/core/contracts/financial-templates/test/ExpiringMultiPartyMock.sol deleted file mode 100644 index f3c313ae5..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/test/ExpiringMultiPartyMock.sol +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/Testable.sol"; -import "../../common/implementation/FixedPoint.sol"; -import "../common/financial-product-libraries/expiring-multiparty-libraries/FinancialProductLibrary.sol"; - -contract ExpiringMultiPartyMock is Testable { - using FixedPoint for FixedPoint.Unsigned; - - FinancialProductLibrary public financialProductLibrary; - uint256 public expirationTimestamp; - FixedPoint.Unsigned public collateralRequirement; - bytes32 public priceIdentifier; - - constructor( - address _financialProductLibraryAddress, - uint256 _expirationTimestamp, - FixedPoint.Unsigned memory _collateralRequirement, - bytes32 _priceIdentifier, - address _timerAddress - ) Testable(_timerAddress) { - expirationTimestamp = _expirationTimestamp; - collateralRequirement = _collateralRequirement; - financialProductLibrary = FinancialProductLibrary(_financialProductLibraryAddress); - priceIdentifier = _priceIdentifier; - } - - function transformPrice( - FixedPoint.Unsigned memory price, - uint256 requestTime - ) public view returns (FixedPoint.Unsigned memory) { - if (address(financialProductLibrary) == address(0)) return price; - try financialProductLibrary.transformPrice(price, requestTime) returns ( - FixedPoint.Unsigned memory transformedPrice - ) { - return transformedPrice; - } catch { - return price; - } - } - - function transformCollateralRequirement( - FixedPoint.Unsigned memory price - ) public view returns (FixedPoint.Unsigned memory) { - if (address(financialProductLibrary) == address(0)) return collateralRequirement; - try financialProductLibrary.transformCollateralRequirement(price, collateralRequirement) returns ( - FixedPoint.Unsigned memory transformedCollateralRequirement - ) { - return transformedCollateralRequirement; - } catch { - return collateralRequirement; - } - } - - function transformPriceIdentifier(uint256 requestTime) public view returns (bytes32) { - if (address(financialProductLibrary) == address(0)) return priceIdentifier; - try financialProductLibrary.transformPriceIdentifier(priceIdentifier, requestTime) returns ( - bytes32 transformedIdentifier - ) { - return transformedIdentifier; - } catch { - return priceIdentifier; - } - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/test/FinancialProductLibraryTest.sol b/contracts/external/uma/core/contracts/financial-templates/test/FinancialProductLibraryTest.sol deleted file mode 100644 index 15231967c..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/test/FinancialProductLibraryTest.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; -import "../common/financial-product-libraries/expiring-multiparty-libraries/FinancialProductLibrary.sol"; - -// Implements a simple FinancialProductLibrary to test price and collateral requirement transoformations. -contract FinancialProductLibraryTest is FinancialProductLibrary { - using FixedPoint for FixedPoint.Unsigned; - - FixedPoint.Unsigned public priceTransformationScalar; - FixedPoint.Unsigned public collateralRequirementTransformationScalar; - bytes32 public transformedPriceIdentifier; - bool public shouldRevert; - - constructor( - FixedPoint.Unsigned memory _priceTransformationScalar, - FixedPoint.Unsigned memory _collateralRequirementTransformationScalar, - bytes32 _transformedPriceIdentifier - ) { - priceTransformationScalar = _priceTransformationScalar; - collateralRequirementTransformationScalar = _collateralRequirementTransformationScalar; - transformedPriceIdentifier = _transformedPriceIdentifier; - } - - // Set the mocked methods to revert to test failed library computation. - function setShouldRevert(bool _shouldRevert) public { - shouldRevert = _shouldRevert; - } - - // Create a simple price transformation function that scales the input price by the scalar for testing. - function transformPrice( - FixedPoint.Unsigned memory oraclePrice, - uint256 - ) public view override returns (FixedPoint.Unsigned memory) { - require(!shouldRevert, "set to always reverts"); - return oraclePrice.mul(priceTransformationScalar); - } - - // Create a simple collateral requirement transformation that doubles the input collateralRequirement. - function transformCollateralRequirement( - FixedPoint.Unsigned memory, - FixedPoint.Unsigned memory collateralRequirement - ) public view override returns (FixedPoint.Unsigned memory) { - require(!shouldRevert, "set to always reverts"); - return collateralRequirement.mul(collateralRequirementTransformationScalar); - } - - // Create a simple transformPriceIdentifier function that returns the transformed price identifier. - function transformPriceIdentifier(bytes32, uint256) public view override returns (bytes32) { - require(!shouldRevert, "set to always reverts"); - return transformedPriceIdentifier; - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/test/FundingRateApplierTest.sol b/contracts/external/uma/core/contracts/financial-templates/test/FundingRateApplierTest.sol deleted file mode 100644 index 9053990a6..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/test/FundingRateApplierTest.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../common/FundingRateApplier.sol"; -import "../../common/implementation/FixedPoint.sol"; - -// Implements FundingRateApplier internal methods to enable unit testing. -contract FundingRateApplierTest is FundingRateApplier { - constructor( - bytes32 _fundingRateIdentifier, - address _collateralAddress, - address _finderAddress, - address _configStoreAddress, - FixedPoint.Unsigned memory _tokenScaling, - address _timerAddress - ) - FundingRateApplier( - _fundingRateIdentifier, - _collateralAddress, - _finderAddress, - _configStoreAddress, - _tokenScaling, - _timerAddress - ) - {} - - function calculateEffectiveFundingRate( - uint256 paymentPeriodSeconds, - FixedPoint.Signed memory fundingRatePerSecond, - FixedPoint.Unsigned memory currentCumulativeFundingRateMultiplier - ) public pure returns (FixedPoint.Unsigned memory) { - return - _calculateEffectiveFundingRate( - paymentPeriodSeconds, - fundingRatePerSecond, - currentCumulativeFundingRateMultiplier - ); - } - - // Required overrides. - function _pfc() internal view virtual override returns (FixedPoint.Unsigned memory currentPfc) { - return FixedPoint.Unsigned(collateralCurrency.balanceOf(address(this))); - } - - function emergencyShutdown() external override {} - - function remargin() external override {} - - function _getTokenAddress() internal view override returns (address) { - return address(collateralCurrency); - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/test/LongShortPairFinancialProjectLibraryTest.sol b/contracts/external/uma/core/contracts/financial-templates/test/LongShortPairFinancialProjectLibraryTest.sol deleted file mode 100644 index 91c11f8fd..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/test/LongShortPairFinancialProjectLibraryTest.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; -import "../common/financial-product-libraries/long-short-pair-libraries/LongShortPairFinancialProductLibrary.sol"; - -// Implements a simple FinancialProductLibrary to test price and collateral requirement transoformations. -contract LongShortPairFinancialProjectLibraryTest is LongShortPairFinancialProductLibrary { - using FixedPoint for FixedPoint.Unsigned; - - uint256 public valueToReturn; - - function setValueToReturn(uint256 value) public { - valueToReturn = value; - } - - function percentageLongCollateralAtExpiry(int256 /*expiryPrice*/) public view override returns (uint256) { - return valueToReturn; - } -} diff --git a/contracts/external/uma/core/contracts/financial-templates/test/LongShortPairMock.sol b/contracts/external/uma/core/contracts/financial-templates/test/LongShortPairMock.sol deleted file mode 100644 index 4bd5015d2..000000000 --- a/contracts/external/uma/core/contracts/financial-templates/test/LongShortPairMock.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -contract LongShortPairMock { - uint256 public expirationTimestamp; - uint256 public collateralPerPair; - - constructor(uint256 _expirationTimestamp, uint256 _collateralPerPair) { - expirationTimestamp = _expirationTimestamp; - collateralPerPair = _collateralPerPair; - } -} diff --git a/contracts/external/uma/core/contracts/optimistic-governor/test/TestAvatar.sol b/contracts/external/uma/core/contracts/optimistic-governor/test/TestAvatar.sol deleted file mode 100644 index 5174ea2a6..000000000 --- a/contracts/external/uma/core/contracts/optimistic-governor/test/TestAvatar.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity >=0.8.0; - -contract TestAvatar { - address public module; - - receive() external payable {} - - function setModule(address _module) external { - module = _module; - } - - function exec(address payable to, uint256 value, bytes calldata data) external { - bool success; - bytes memory response; - (success, response) = to.call{ value: value }(data); - if (!success) { - assembly { - revert(add(response, 0x20), mload(response)) - } - } - } - - function execTransactionFromModule( - address payable to, - uint256 value, - bytes calldata data, - uint8 operation - ) external returns (bool success) { - require(msg.sender == module, "Not authorized"); - if (operation == 1) (success, ) = to.delegatecall(data); - else (success, ) = to.call{ value: value }(data); - } -} diff --git a/contracts/external/uma/core/contracts/optimistic-governor/test/TestModuleProxyFactory.sol b/contracts/external/uma/core/contracts/optimistic-governor/test/TestModuleProxyFactory.sol deleted file mode 100644 index 8fc457231..000000000 --- a/contracts/external/uma/core/contracts/optimistic-governor/test/TestModuleProxyFactory.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.6; - -import "@gnosis.pm/zodiac/contracts/factory/ModuleProxyFactory.sol"; - -contract TestModuleProxyFactory is ModuleProxyFactory {} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v2/implementation/OptimisticOracleV2.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v2/implementation/OptimisticOracleV2.sol deleted file mode 100644 index 0bc59c746..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v2/implementation/OptimisticOracleV2.sol +++ /dev/null @@ -1,711 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; -import "@openzeppelin/contracts-v4/utils/Address.sol"; - -import "../../data-verification-mechanism/interfaces/StoreInterface.sol"; -import "../../data-verification-mechanism/interfaces/OracleAncillaryInterface.sol"; -import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; -import "../../data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol"; -import "../../data-verification-mechanism/implementation/Constants.sol"; - -import "../interfaces/OptimisticOracleV2Interface.sol"; - -import "../../common/implementation/Testable.sol"; -import "../../common/implementation/Lockable.sol"; -import "../../common/implementation/FixedPoint.sol"; -import "../../common/implementation/AncillaryData.sol"; -import "../../common/implementation/AddressWhitelist.sol"; - -/** - * @title Optimistic Requester. - * @notice Optional interface that requesters can implement to receive callbacks. - * @dev this contract does _not_ work with ERC777 collateral currencies or any others that call into the receiver on - * transfer(). Using an ERC777 token would allow a user to maliciously grief other participants (while also losing - * money themselves). - */ -interface OptimisticRequester { - /** - * @notice Callback for proposals. - * @param identifier price identifier being requested. - * @param timestamp timestamp of the price being requested. - * @param ancillaryData ancillary data of the price being requested. - */ - function priceProposed(bytes32 identifier, uint256 timestamp, bytes memory ancillaryData) external; - - /** - * @notice Callback for disputes. - * @param identifier price identifier being requested. - * @param timestamp timestamp of the price being requested. - * @param ancillaryData ancillary data of the price being requested. - * @param refund refund received in the case that refundOnDispute was enabled. - */ - function priceDisputed(bytes32 identifier, uint256 timestamp, bytes memory ancillaryData, uint256 refund) external; - - /** - * @notice Callback for settlement. - * @param identifier price identifier being requested. - * @param timestamp timestamp of the price being requested. - * @param ancillaryData ancillary data of the price being requested. - * @param price price that was resolved by the escalation process. - */ - function priceSettled(bytes32 identifier, uint256 timestamp, bytes memory ancillaryData, int256 price) external; -} - -/** - * @title Optimistic Oracle. - * @notice Pre-DVM escalation contract that allows faster settlement. - */ -contract OptimisticOracleV2 is OptimisticOracleV2Interface, Testable, Lockable { - using SafeMath for uint256; - using SafeERC20 for IERC20; - using Address for address; - - // Finder to provide addresses for DVM contracts. - FinderInterface public override finder; - - // Default liveness value for all price requests. - uint256 public override defaultLiveness; - - // This is effectively the extra ancillary data to add ",ooRequester:0000000000000000000000000000000000000000". - uint256 private constant MAX_ADDED_ANCILLARY_DATA = 53; - uint256 public constant OO_ANCILLARY_DATA_LIMIT = ancillaryBytesLimit - MAX_ADDED_ANCILLARY_DATA; - int256 public constant TOO_EARLY_RESPONSE = type(int256).min; - - /** - * @notice Constructor. - * @param _liveness default liveness applied to each price request. - * @param _finderAddress finder to use to get addresses of DVM contracts. - * @param _timerAddress address of the timer contract. Should be 0x0 in prod. - */ - constructor(uint256 _liveness, address _finderAddress, address _timerAddress) Testable(_timerAddress) { - finder = FinderInterface(_finderAddress); - _validateLiveness(_liveness); - defaultLiveness = _liveness; - } - - /** - * @notice Requests a new price. - * @param identifier price identifier being requested. - * @param timestamp timestamp of the price being requested. - * @param ancillaryData ancillary data representing additional args being passed with the price request. - * @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM. - * @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0, - * which could make sense if the contract requests and proposes the value in the same call or - * provides its own reward system. - * @return totalBond default bond (final fee) + final fee that the proposer and disputer will be required to pay. - * This can be changed with a subsequent call to setBond(). - */ - function requestPrice( - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData, - IERC20 currency, - uint256 reward - ) external override nonReentrant returns (uint256 totalBond) { - require(_getState(msg.sender, identifier, timestamp, ancillaryData) == State.Invalid, "requestPrice: Invalid"); - require(_getIdentifierWhitelist().isIdentifierSupported(identifier), "Unsupported identifier"); - require(_getCollateralWhitelist().isOnWhitelist(address(currency)), "Unsupported currency"); - require(timestamp <= getCurrentTime(), "Timestamp in future"); - - // This ensures that the ancillary data is <= the OO limit, which is lower than the DVM limit because the - // OO adds some data before sending to the DVM. - require(ancillaryData.length <= OO_ANCILLARY_DATA_LIMIT, "Ancillary Data too long"); - - uint256 finalFee = _getStore().computeFinalFee(address(currency)).rawValue; - requests[_getId(msg.sender, identifier, timestamp, ancillaryData)] = Request({ - proposer: address(0), - disputer: address(0), - currency: currency, - settled: false, - requestSettings: RequestSettings({ - eventBased: false, - refundOnDispute: false, - callbackOnPriceProposed: false, - callbackOnPriceDisputed: false, - callbackOnPriceSettled: false, - bond: finalFee, - customLiveness: 0 - }), - proposedPrice: 0, - resolvedPrice: 0, - expirationTime: 0, - reward: reward, - finalFee: finalFee - }); - - if (reward > 0) { - currency.safeTransferFrom(msg.sender, address(this), reward); - } - - emit RequestPrice(msg.sender, identifier, timestamp, ancillaryData, address(currency), reward, finalFee); - - // This function returns the initial proposal bond for this request, which can be customized by calling - // setBond() with the same identifier and timestamp. - return finalFee.mul(2); - } - - /** - * @notice Set the proposal bond associated with a price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param bond custom bond amount to set. - * @return totalBond new bond + final fee that the proposer and disputer will be required to pay. This can be - * changed again with a subsequent call to setBond(). - */ - function setBond( - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData, - uint256 bond - ) external override nonReentrant returns (uint256 totalBond) { - require(_getState(msg.sender, identifier, timestamp, ancillaryData) == State.Requested, "setBond: Requested"); - Request storage request = _getRequest(msg.sender, identifier, timestamp, ancillaryData); - request.requestSettings.bond = bond; - - // Total bond is the final fee + the newly set bond. - return bond.add(request.finalFee); - } - - /** - * @notice Sets the request to refund the reward if the proposal is disputed. This can help to "hedge" the caller - * in the event of a dispute-caused delay. Note: in the event of a dispute, the winner still receives the other's - * bond, so there is still profit to be made even if the reward is refunded. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - */ - function setRefundOnDispute( - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) external override nonReentrant { - require( - _getState(msg.sender, identifier, timestamp, ancillaryData) == State.Requested, - "setRefundOnDispute: Requested" - ); - _getRequest(msg.sender, identifier, timestamp, ancillaryData).requestSettings.refundOnDispute = true; - } - - /** - * @notice Sets a custom liveness value for the request. Liveness is the amount of time a proposal must wait before - * being auto-resolved. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param customLiveness new custom liveness. - */ - function setCustomLiveness( - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData, - uint256 customLiveness - ) external override nonReentrant { - require( - _getState(msg.sender, identifier, timestamp, ancillaryData) == State.Requested, - "setCustomLiveness: Requested" - ); - _validateLiveness(customLiveness); - _getRequest(msg.sender, identifier, timestamp, ancillaryData).requestSettings.customLiveness = customLiveness; - } - - /** - * @notice Sets the request to be an "event-based" request. - * @dev Calling this method has a few impacts on the request: - * - * 1. The timestamp at which the request is evaluated is the time of the proposal, not the timestamp associated - * with the request. - * - * 2. The proposer cannot propose the "too early" value (TOO_EARLY_RESPONSE). This is to ensure that a proposer who - * prematurely proposes a response loses their bond. - * - * 3. RefundoOnDispute is automatically set, meaning disputes trigger the reward to be automatically refunded to - * the requesting contract. - * - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - */ - function setEventBased( - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) external override nonReentrant { - require( - _getState(msg.sender, identifier, timestamp, ancillaryData) == State.Requested, - "setEventBased: Requested" - ); - Request storage request = _getRequest(msg.sender, identifier, timestamp, ancillaryData); - request.requestSettings.eventBased = true; - request.requestSettings.refundOnDispute = true; - } - - /** - * @notice Sets which callbacks should be enabled for the request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param callbackOnPriceProposed whether to enable the callback onPriceProposed. - * @param callbackOnPriceDisputed whether to enable the callback onPriceDisputed. - * @param callbackOnPriceSettled whether to enable the callback onPriceSettled. - */ - function setCallbacks( - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData, - bool callbackOnPriceProposed, - bool callbackOnPriceDisputed, - bool callbackOnPriceSettled - ) external override nonReentrant { - require( - _getState(msg.sender, identifier, timestamp, ancillaryData) == State.Requested, - "setCallbacks: Requested" - ); - Request storage request = _getRequest(msg.sender, identifier, timestamp, ancillaryData); - request.requestSettings.callbackOnPriceProposed = callbackOnPriceProposed; - request.requestSettings.callbackOnPriceDisputed = callbackOnPriceDisputed; - request.requestSettings.callbackOnPriceSettled = callbackOnPriceSettled; - } - - /** - * @notice Proposes a price value on another address' behalf. Note: this address will receive any rewards that come - * from this proposal. However, any bonds are pulled from the caller. - * @param proposer address to set as the proposer. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param proposedPrice price being proposed. - * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to - * the proposer once settled if the proposal is correct. - */ - function proposePriceFor( - address proposer, - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData, - int256 proposedPrice - ) public override nonReentrant returns (uint256 totalBond) { - require(proposer != address(0), "proposer address must be non 0"); - require( - _getState(requester, identifier, timestamp, ancillaryData) == State.Requested, - "proposePriceFor: Requested" - ); - Request storage request = _getRequest(requester, identifier, timestamp, ancillaryData); - if (request.requestSettings.eventBased) - require(proposedPrice != TOO_EARLY_RESPONSE, "Cannot propose 'too early'"); - request.proposer = proposer; - request.proposedPrice = proposedPrice; - - // If a custom liveness has been set, use it instead of the default. - request.expirationTime = getCurrentTime().add( - request.requestSettings.customLiveness != 0 ? request.requestSettings.customLiveness : defaultLiveness - ); - - totalBond = request.requestSettings.bond.add(request.finalFee); - if (totalBond > 0) request.currency.safeTransferFrom(msg.sender, address(this), totalBond); - - emit ProposePrice( - requester, - proposer, - identifier, - timestamp, - ancillaryData, - proposedPrice, - request.expirationTime, - address(request.currency) - ); - - // End the re-entrancy guard early to allow the caller to potentially take OO-related actions inside this callback. - _startReentrantGuardDisabled(); - // Callback. - if (address(requester).isContract() && request.requestSettings.callbackOnPriceProposed) - OptimisticRequester(requester).priceProposed(identifier, timestamp, ancillaryData); - _endReentrantGuardDisabled(); - } - - /** - * @notice Proposes a price value for an existing price request. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param proposedPrice price being proposed. - * @return totalBond the amount that's pulled from the proposer's wallet as a bond. The bond will be returned to - * the proposer once settled if the proposal is correct. - */ - function proposePrice( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData, - int256 proposedPrice - ) external override returns (uint256 totalBond) { - // Note: re-entrancy guard is done in the inner call. - return proposePriceFor(msg.sender, requester, identifier, timestamp, ancillaryData, proposedPrice); - } - - /** - * @notice Disputes a price request with an active proposal on another address' behalf. Note: this address will - * receive any rewards that come from this dispute. However, any bonds are pulled from the caller. - * @param disputer address to set as the disputer. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to - * the disputer once settled if the dispute was valid (the proposal was incorrect). - */ - function disputePriceFor( - address disputer, - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) public override nonReentrant returns (uint256 totalBond) { - require(disputer != address(0), "disputer address must be non 0"); - require( - _getState(requester, identifier, timestamp, ancillaryData) == State.Proposed, - "disputePriceFor: Proposed" - ); - Request storage request = _getRequest(requester, identifier, timestamp, ancillaryData); - request.disputer = disputer; - - uint256 finalFee = request.finalFee; - uint256 bond = request.requestSettings.bond; - totalBond = bond.add(finalFee); - if (totalBond > 0) { - request.currency.safeTransferFrom(msg.sender, address(this), totalBond); - } - - StoreInterface store = _getStore(); - - // Along with the final fee, "burn" part of the loser's bond to ensure that a larger bond always makes it - // proportionally more expensive to delay the resolution even if the proposer and disputer are the same - // party. - - // The total fee is the burned bond and the final fee added together. - uint256 totalFee = finalFee.add(_computeBurnedBond(request)); - if (totalFee > 0) { - request.currency.safeIncreaseAllowance(address(store), totalFee); - _getStore().payOracleFeesErc20(address(request.currency), FixedPoint.Unsigned(totalFee)); - } - - _getOracle().requestPrice( - identifier, - _getTimestampForDvmRequest(request, timestamp), - _stampAncillaryData(ancillaryData, requester) - ); - - // Compute refund. - uint256 refund = 0; - if (request.reward > 0 && request.requestSettings.refundOnDispute) { - refund = request.reward; - request.reward = 0; - request.currency.safeTransfer(requester, refund); - } - - emit DisputePrice( - requester, - request.proposer, - disputer, - identifier, - timestamp, - ancillaryData, - request.proposedPrice - ); - - // End the re-entrancy guard early to allow the caller to potentially re-request inside this callback. - _startReentrantGuardDisabled(); - // Callback. - if (address(requester).isContract() && request.requestSettings.callbackOnPriceDisputed) - OptimisticRequester(requester).priceDisputed(identifier, timestamp, ancillaryData, refund); - _endReentrantGuardDisabled(); - } - - /** - * @notice Disputes a price value for an existing price request with an active proposal. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @return totalBond the amount that's pulled from the disputer's wallet as a bond. The bond will be returned to - * the disputer once settled if the dispute was valid (the proposal was incorrect). - */ - function disputePrice( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) external override returns (uint256 totalBond) { - // Note: re-entrancy guard is done in the inner call. - return disputePriceFor(msg.sender, requester, identifier, timestamp, ancillaryData); - } - - /** - * @notice Retrieves a price that was previously requested by a caller. Reverts if the request is not settled - * or settleable. Note: this method is not view so that this call may actually settle the price request if it - * hasn't been settled. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @return resolved price. - */ - function settleAndGetPrice( - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) external override nonReentrant returns (int256) { - if (_getState(msg.sender, identifier, timestamp, ancillaryData) != State.Settled) { - _settle(msg.sender, identifier, timestamp, ancillaryData); - } - - return _getRequest(msg.sender, identifier, timestamp, ancillaryData).resolvedPrice; - } - - /** - * @notice Attempts to settle an outstanding price request. Will revert if it isn't settleable. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @return payout the amount that the "winner" (proposer or disputer) receives on settlement. This amount includes - * the returned bonds as well as additional rewards. - */ - function settle( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) external override nonReentrant returns (uint256 payout) { - return _settle(requester, identifier, timestamp, ancillaryData); - } - - /** - * @notice Gets the current data structure containing all information about a price request. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @return the Request data structure. - */ - function getRequest( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) public view override nonReentrantView returns (Request memory) { - return _getRequest(requester, identifier, timestamp, ancillaryData); - } - - /** - * @notice Computes the current state of a price request. See the State enum for more details. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @return the State. - */ - function getState( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) public view override nonReentrantView returns (State) { - return _getState(requester, identifier, timestamp, ancillaryData); - } - - /** - * @notice Checks if a given request has resolved, expired or been settled (i.e the optimistic oracle has a price). - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @return boolean indicating true if price exists and false if not. - */ - function hasPrice( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) public view override nonReentrantView returns (bool) { - State state = _getState(requester, identifier, timestamp, ancillaryData); - return state == State.Settled || state == State.Resolved || state == State.Expired; - } - - /** - * @notice Generates stamped ancillary data in the format that it would be used in the case of a price dispute. - * @param ancillaryData ancillary data of the price being requested. - * @param requester sender of the initial price request. - * @return the stamped ancillary bytes. - */ - function stampAncillaryData( - bytes memory ancillaryData, - address requester - ) public pure override returns (bytes memory) { - return _stampAncillaryData(ancillaryData, requester); - } - - function _getId( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) private pure returns (bytes32) { - return keccak256(abi.encodePacked(requester, identifier, timestamp, ancillaryData)); - } - - function _settle( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) private returns (uint256 payout) { - State state = _getState(requester, identifier, timestamp, ancillaryData); - - // Set it to settled so this function can never be entered again. - Request storage request = _getRequest(requester, identifier, timestamp, ancillaryData); - request.settled = true; - - if (state == State.Expired) { - // In the expiry case, just pay back the proposer's bond and final fee along with the reward. - request.resolvedPrice = request.proposedPrice; - payout = request.requestSettings.bond.add(request.finalFee).add(request.reward); - request.currency.safeTransfer(request.proposer, payout); - } else if (state == State.Resolved) { - // In the Resolved case, pay either the disputer or the proposer the entire payout (+ bond and reward). - request.resolvedPrice = _getOracle().getPrice( - identifier, - _getTimestampForDvmRequest(request, timestamp), - _stampAncillaryData(ancillaryData, requester) - ); - bool disputeSuccess = request.resolvedPrice != request.proposedPrice; - uint256 bond = request.requestSettings.bond; - - // Unburned portion of the loser's bond = 1 - burned bond. - uint256 unburnedBond = bond.sub(_computeBurnedBond(request)); - - // Winner gets: - // - Their bond back. - // - The unburned portion of the loser's bond. - // - Their final fee back. - // - The request reward (if not already refunded -- if refunded, it will be set to 0). - payout = bond.add(unburnedBond).add(request.finalFee).add(request.reward); - request.currency.safeTransfer(disputeSuccess ? request.disputer : request.proposer, payout); - } else revert("_settle: not settleable"); - - emit Settle( - requester, - request.proposer, - request.disputer, - identifier, - timestamp, - ancillaryData, - request.resolvedPrice, - payout - ); - - // Temporarily disable the re-entrancy guard early to allow the caller to take an OO-related action inside this callback. - _startReentrantGuardDisabled(); - // Callback. - if (address(requester).isContract() && request.requestSettings.callbackOnPriceSettled) - OptimisticRequester(requester).priceSettled(identifier, timestamp, ancillaryData, request.resolvedPrice); - _endReentrantGuardDisabled(); - } - - function _getRequest( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) private view returns (Request storage) { - return requests[_getId(requester, identifier, timestamp, ancillaryData)]; - } - - function _computeBurnedBond(Request storage request) private view returns (uint256) { - // burnedBond = floor(bond / 2) - return request.requestSettings.bond.div(2); - } - - function _validateLiveness(uint256 _liveness) private pure { - require(_liveness < 5200 weeks, "Liveness too large"); - require(_liveness > 0, "Liveness cannot be 0"); - } - - function _getState( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) internal view returns (State) { - Request storage request = _getRequest(requester, identifier, timestamp, ancillaryData); - - if (address(request.currency) == address(0)) return State.Invalid; - - if (request.proposer == address(0)) return State.Requested; - - if (request.settled) return State.Settled; - - if (request.disputer == address(0)) - return request.expirationTime <= getCurrentTime() ? State.Expired : State.Proposed; - - return - _getOracle().hasPrice( - identifier, - _getTimestampForDvmRequest(request, timestamp), - _stampAncillaryData(ancillaryData, requester) - ) - ? State.Resolved - : State.Disputed; - } - - function _getOracle() internal view returns (OracleAncillaryInterface) { - return OracleAncillaryInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); - } - - function _getCollateralWhitelist() internal view returns (AddressWhitelist) { - return AddressWhitelist(finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist)); - } - - function _getStore() internal view returns (StoreInterface) { - return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store)); - } - - function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) { - return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); - } - - function _getTimestampForDvmRequest( - Request storage request, - uint256 requestTimestamp - ) internal view returns (uint256) { - if (request.requestSettings.eventBased) { - uint256 liveness = request.requestSettings.customLiveness != 0 - ? request.requestSettings.customLiveness - : defaultLiveness; - return request.expirationTime.sub(liveness); - } else { - return requestTimestamp; - } - } - - /** - * @dev We don't handle specifically the case where `ancillaryData` is not already readily translateable in utf8. - * For those cases, we assume that the client will be able to strip out the utf8-translateable part of the - * ancillary data that this contract stamps. - */ - function _stampAncillaryData(bytes memory ancillaryData, address requester) internal pure returns (bytes memory) { - // Since this contract will be the one to formally submit DVM price requests, its useful for voters to know who - // the original requester was. - return AncillaryData.appendKeyValueAddress(ancillaryData, "ooRequester", requester); - } - - function getCurrentTime() public view override(Testable, OptimisticOracleV2Interface) returns (uint256) { - return Testable.getCurrentTime(); - } -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v2/implementation/SkinnyOptimisticOracleV2.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v2/implementation/SkinnyOptimisticOracleV2.sol deleted file mode 100644 index e2eb701ad..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v2/implementation/SkinnyOptimisticOracleV2.sol +++ /dev/null @@ -1,686 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; -import "@openzeppelin/contracts-v4/utils/Address.sol"; - -import "../../data-verification-mechanism/interfaces/StoreInterface.sol"; -import "../../data-verification-mechanism/interfaces/OracleAncillaryInterface.sol"; -import "../interfaces/OptimisticOracleInterface.sol"; -import "../interfaces/SkinnyOptimisticOracleV2Interface.sol"; -import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; -import "../../data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol"; -import "../../data-verification-mechanism/implementation/Constants.sol"; - -import "../../common/implementation/Testable.sol"; -import "../../common/implementation/Lockable.sol"; -import "../../common/implementation/FixedPoint.sol"; -import "../../common/implementation/AncillaryData.sol"; -import "../../common/implementation/AddressWhitelist.sol"; - -/** - * @title Optimistic Requester. - * @notice Optional interface that requesters can implement to receive callbacks. - * @dev This contract does _not_ work with ERC777 collateral currencies or any others that call into the receiver on - * transfer(). Using an ERC777 token would allow a user to maliciously grief other participants (while also losing - * money themselves). - */ -interface OptimisticRequesterV2 { - /** - * @notice Callback for proposals. - * @param identifier price identifier being requested. - * @param timestamp timestamp of the price being requested. - * @param ancillaryData ancillary data of the price being requested. - * @param request request params after proposal. - */ - function priceProposed( - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - SkinnyOptimisticOracleV2Interface.Request memory request - ) external; - - /** - * @notice Callback for disputes. - * @param identifier price identifier being requested. - * @param timestamp timestamp of the price being requested. - * @param ancillaryData ancillary data of the price being requested. - * @param request request params after dispute. - */ - function priceDisputed( - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - SkinnyOptimisticOracleV2Interface.Request memory request - ) external; - - /** - * @notice Callback for settlement. - * @param identifier price identifier being requested. - * @param timestamp timestamp of the price being requested. - * @param ancillaryData ancillary data of the price being requested. - * @param request request params after settlement. - */ - function priceSettled( - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - SkinnyOptimisticOracleV2Interface.Request memory request - ) external; -} - -/** - * @title Optimistic Oracle with a different interface and fewer features that emphasizes gas cost reductions. - * @notice Pre-DVM escalation contract that allows faster settlement. - */ -contract SkinnyOptimisticOracleV2 is SkinnyOptimisticOracleV2Interface, Testable, Lockable { - using SafeMath for uint256; - using SafeERC20 for IERC20; - using Address for address; - - event RequestPrice( - address indexed requester, - bytes32 indexed identifier, - uint32 timestamp, - bytes ancillaryData, - Request request - ); - event ProposePrice( - address indexed requester, - bytes32 indexed identifier, - uint32 timestamp, - bytes ancillaryData, - Request request - ); - event DisputePrice( - address indexed requester, - bytes32 indexed identifier, - uint32 timestamp, - bytes ancillaryData, - Request request - ); - event Settle( - address indexed requester, - bytes32 indexed identifier, - uint32 timestamp, - bytes ancillaryData, - Request request - ); - - // Maps hash of unique request params {identifier, timestamp, ancillary data} to customizable variables such as - // reward and bond amounts. - mapping(bytes32 => bytes32) public requests; - - // Finder to provide addresses for DVM contracts. - FinderInterface public finder; - - // Default liveness value for all price requests. - uint256 public defaultLiveness; - - /** - * @notice Constructor. - * @param _liveness default liveness applied to each price request. - * @param _finderAddress finder to use to get addresses of DVM contracts. - * @param _timerAddress address of the timer contract. Should be 0x0 in prod. - */ - constructor(uint256 _liveness, address _finderAddress, address _timerAddress) Testable(_timerAddress) { - finder = FinderInterface(_finderAddress); - _validateLiveness(_liveness); - defaultLiveness = _liveness; - } - - /** - * @notice Requests a new price. - * @param identifier price identifier being requested. - * @param timestamp timestamp of the price being requested. - * @param ancillaryData ancillary data representing additional args being passed with the price request. - * @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM. - * @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0, - * which could make sense if the contract requests and proposes the value in the same call or - * provides its own reward system. - * @param requestSettings settings for the request. - * @return totalBond default bond + final fee that the proposer and disputer will be required to pay. - */ - function requestPrice( - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - IERC20 currency, - uint256 reward, - RequestSettings memory requestSettings - ) external override nonReentrant returns (uint256 totalBond) { - bytes32 requestId = _getId(msg.sender, identifier, timestamp, ancillaryData); - require(requests[requestId] == bytes32(0), "Request already initialized"); - require(_getIdentifierWhitelist().isIdentifierSupported(identifier), "Unsupported identifier"); - require(_getCollateralWhitelist().isOnWhitelist(address(currency)), "Unsupported currency"); - require(timestamp <= getCurrentTime(), "Timestamp in future"); - require( - _stampAncillaryData(ancillaryData, msg.sender).length <= ancillaryBytesLimit, - "Ancillary Data too long" - ); - uint256 finalFee = _getStore().computeFinalFee(address(currency)).rawValue; - - // Associate new request with ID - Request memory request; - request.currency = currency; - request.reward = reward; - request.finalFee = finalFee; - request.requestSettings = requestSettings; - request.requestSettings.bond = requestSettings.bond != 0 ? requestSettings.bond : finalFee; - _storeRequestHash(requestId, request); - - if (reward > 0) currency.safeTransferFrom(msg.sender, address(this), reward); - - emit RequestPrice(msg.sender, identifier, timestamp, ancillaryData, request); - - return request.requestSettings.bond.add(finalFee); - } - - /** - * @notice Proposes a price value on another address' behalf. Note: this address will receive any rewards that come - * from this proposal. However, any bonds are pulled from the caller. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param request price request parameters whose hash must match the request that the caller wants to - * propose a price for. - * @param proposer address to set as the proposer. - * @param proposedPrice price being proposed. - * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to - * the proposer once settled if the proposal is correct. - */ - function proposePriceFor( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request, - address proposer, - int256 proposedPrice - ) public override nonReentrant returns (uint256 totalBond) { - require(proposer != address(0), "Proposer address must be non 0"); - require( - _getState(requester, identifier, timestamp, ancillaryData, request) == - OptimisticOracleInterface.State.Requested, - "Must be requested" - ); - bytes32 requestId = _getId(requester, identifier, timestamp, ancillaryData); - _validateRequestHash(requestId, request); - - // Associate newly proposed request params with ID - Request memory proposedRequest = Request({ - proposer: proposer, // Modified - disputer: request.disputer, - currency: request.currency, - settled: request.settled, - proposedPrice: proposedPrice, // Modified - resolvedPrice: request.resolvedPrice, - expirationTime: getCurrentTime().add( - request.requestSettings.customLiveness != 0 ? request.requestSettings.customLiveness : defaultLiveness - ), // Modified - reward: request.reward, - finalFee: request.finalFee, - requestSettings: request.requestSettings - }); - _storeRequestHash(requestId, proposedRequest); - - totalBond = request.requestSettings.bond.add(request.finalFee); - if (totalBond > 0) request.currency.safeTransferFrom(msg.sender, address(this), totalBond); - - emit ProposePrice(requester, identifier, timestamp, ancillaryData, proposedRequest); - - // Callback. - if (address(requester).isContract() && request.requestSettings.callbackOnPriceProposed) - OptimisticRequesterV2(requester).priceProposed(identifier, timestamp, ancillaryData, proposedRequest); - } - - /** - * @notice Proposes a price value where caller is the proposer. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param request price request parameters whose hash must match the request that the caller wants to - * propose a price for. - * @param proposedPrice price being proposed. - * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to - * the proposer once settled if the proposal is correct. - */ - function proposePrice( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request, - int256 proposedPrice - ) external override returns (uint256 totalBond) { - // Note: re-entrancy guard is done in the inner call. - return proposePriceFor(requester, identifier, timestamp, ancillaryData, request, msg.sender, proposedPrice); - } - - /** - * @notice Combines logic of requestPrice and proposePrice while taking advantage of gas savings from not having to - * overwrite Request params that a normal requestPrice() => proposePrice() flow would entail. Note: The proposer - * will receive any rewards that come from this proposal. However, any bonds are pulled from the caller. - * @dev The caller is the requester, but the proposer can be customized. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM. - * @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0, - * which could make sense if the contract requests and proposes the value in the same call or - * provides its own reward system. - * @param requestSettings settings for the request. - * @param proposer address to set as the proposer. - * @param proposedPrice price being proposed. - * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to - * the proposer once settled if the proposal is correct. - */ - function requestAndProposePriceFor( - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - IERC20 currency, - uint256 reward, - RequestSettings memory requestSettings, - address proposer, - int256 proposedPrice - ) external override nonReentrant returns (uint256 totalBond) { - bytes32 requestId = _getId(msg.sender, identifier, timestamp, ancillaryData); - require(requests[requestId] == bytes32(0), "Request already initialized"); - require(proposer != address(0), "proposer address must be non 0"); - require(_getIdentifierWhitelist().isIdentifierSupported(identifier), "Unsupported identifier"); - require(_getCollateralWhitelist().isOnWhitelist(address(currency)), "Unsupported currency"); - require(timestamp <= getCurrentTime(), "Timestamp in future"); - require( - _stampAncillaryData(ancillaryData, msg.sender).length <= ancillaryBytesLimit, - "Ancillary Data too long" - ); - uint256 finalFee = _getStore().computeFinalFee(address(currency)).rawValue; - - // Associate new request with ID - Request memory request; - request.currency = currency; - request.reward = reward; - request.finalFee = finalFee; - request.requestSettings = requestSettings; - request.requestSettings.bond = requestSettings.bond != 0 ? requestSettings.bond : finalFee; - request.proposer = proposer; - request.proposedPrice = proposedPrice; - request.expirationTime = getCurrentTime().add( - requestSettings.customLiveness != 0 ? requestSettings.customLiveness : defaultLiveness - ); - _storeRequestHash(requestId, request); - - // Pull reward from requester, who is the caller. - if (reward > 0) currency.safeTransferFrom(msg.sender, address(this), reward); - // Pull proposal bond from caller. - totalBond = request.requestSettings.bond.add(request.finalFee); - if (totalBond > 0) currency.safeTransferFrom(msg.sender, address(this), totalBond); - - emit RequestPrice(msg.sender, identifier, timestamp, ancillaryData, request); - emit ProposePrice(msg.sender, identifier, timestamp, ancillaryData, request); - - // Callback. - if (address(msg.sender).isContract() && requestSettings.callbackOnPriceProposed) - OptimisticRequesterV2(msg.sender).priceProposed(identifier, timestamp, ancillaryData, request); - } - - /** - * @notice Disputes a price request with an active proposal on another address' behalf. Note: this address will - * receive any rewards that come from this dispute. However, any bonds are pulled from the caller. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param request price request parameters whose hash must match the request that the caller wants to - * dispute. - * @param disputer address to set as the disputer. - * @param requester sender of the initial price request. - * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to - * the disputer once settled if the dispute was valid (the proposal was incorrect). - */ - function disputePriceFor( - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request, - address disputer, - address requester - ) public override nonReentrant returns (uint256 totalBond) { - require(disputer != address(0), "disputer address must be non 0"); - require( - _getState(requester, identifier, timestamp, ancillaryData, request) == - OptimisticOracleInterface.State.Proposed, - "Must be proposed" - ); - bytes32 requestId = _getId(requester, identifier, timestamp, ancillaryData); - _validateRequestHash(requestId, request); - - // Associate newly disputed request params with ID - Request memory disputedRequest = Request({ - proposer: request.proposer, - disputer: disputer, // Modified - currency: request.currency, - settled: request.settled, - proposedPrice: request.proposedPrice, - resolvedPrice: request.resolvedPrice, - expirationTime: request.expirationTime, - reward: request.reward, - finalFee: request.finalFee, - requestSettings: request.requestSettings - }); - _storeRequestHash(requestId, disputedRequest); - - totalBond = request.requestSettings.bond.add(request.finalFee); - if (totalBond > 0) request.currency.safeTransferFrom(msg.sender, address(this), totalBond); - - StoreInterface store = _getStore(); - - // Avoids stack too deep compilation error. - { - // Along with the final fee, "burn" part of the loser's bond to ensure that a larger bond always makes it - // proportionally more expensive to delay the resolution even if the proposer and disputer are the same - // party. - uint256 burnedBond = _computeBurnedBond(disputedRequest); - - // The total fee is the burned bond and the final fee added together. - uint256 totalFee = request.finalFee.add(burnedBond); - - if (totalFee > 0) { - request.currency.safeIncreaseAllowance(address(store), totalFee); - _getStore().payOracleFeesErc20(address(request.currency), FixedPoint.Unsigned(totalFee)); - } - } - - _getOracle().requestPrice(identifier, timestamp, _stampAncillaryData(ancillaryData, requester)); - - emit DisputePrice(requester, identifier, timestamp, ancillaryData, disputedRequest); - - // Callback. - if (address(requester).isContract() && request.requestSettings.callbackOnPriceDisputed) - OptimisticRequesterV2(requester).priceDisputed(identifier, timestamp, ancillaryData, disputedRequest); - } - - /** - * @notice Disputes a price request with an active proposal where caller is the disputer. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param request price request parameters whose hash must match the request that the caller wants to - * dispute. - * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to - * the disputer once settled if the dispute was valid (the proposal was incorrect). - */ - function disputePrice( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request - ) external override returns (uint256 totalBond) { - // Note: re-entrancy guard is done in the inner call. - return disputePriceFor(identifier, timestamp, ancillaryData, request, msg.sender, requester); - } - - /** - * @notice Attempts to settle an outstanding price request. Will revert if it isn't settleable. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param request price request parameters whose hash must match the request that the caller wants to - * settle. - * @return payout the amount that the "winner" (proposer or disputer) receives on settlement. This amount includes - * the returned bonds as well as additional rewards. - * @return resolvedPrice the price that the request settled to. - */ - function settle( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request - ) external override nonReentrant returns (uint256 payout, int256 resolvedPrice) { - return _settle(requester, identifier, timestamp, ancillaryData, request); - } - - /** - * @notice Computes the current state of a price request. See the State enum for more details. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param request price request parameters. - * @return the State. - */ - function getState( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request - ) external override nonReentrant returns (OptimisticOracleInterface.State) { - return _getState(requester, identifier, timestamp, ancillaryData, request); - } - - /** - * @notice Checks if a given request has resolved, expired or been settled (i.e the optimistic oracle has a price). - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param request price request parameters. The hash of these parameters must match with the request hash that is - * associated with the price request unique ID {requester, identifier, timestamp, ancillaryData}, or this method - * will revert. - * @return boolean indicating true if price exists and false if not. - */ - function hasPrice( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request - ) public override nonReentrant returns (bool) { - bytes32 requestId = _getId(requester, identifier, timestamp, ancillaryData); - _validateRequestHash(requestId, request); - OptimisticOracleInterface.State state = _getState(requester, identifier, timestamp, ancillaryData, request); - return - state == OptimisticOracleInterface.State.Settled || - state == OptimisticOracleInterface.State.Resolved || - state == OptimisticOracleInterface.State.Expired; - } - - /** - * @notice Generates stamped ancillary data in the format that it would be used in the case of a price dispute. - * @param ancillaryData ancillary data of the price being requested. - * @param requester sender of the initial price request. - * @return the stamped ancillary bytes. - */ - function stampAncillaryData( - bytes memory ancillaryData, - address requester - ) public pure override returns (bytes memory) { - return _stampAncillaryData(ancillaryData, requester); - } - - /**************************************** - * PRIVATE AND INTERNAL FUNCTIONS * - ****************************************/ - // Returns hash of unique request identifiers. This contract maps request ID hashes to hashes of the request's - // parameters. - function _getId( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData - ) private pure returns (bytes32) { - return keccak256(abi.encode(requester, identifier, timestamp, ancillaryData)); - } - - // Returns hash of request parameters. These are mapped to the unique request ID to track a request's lifecycle. - function _getRequestHash(Request memory request) private pure returns (bytes32) { - return keccak256(abi.encode(request)); - } - - // Resolves a price request that has expired or been disputed and a price is available from the DVM. This will - // revert if the unique request ID does not match the hashed request parameters. This also marks the request - // as settled, therefore this method can only be triggered once per eligible request. - function _settle( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request - ) private returns (uint256 payout, int256 resolvedPrice) { - bytes32 requestId = _getId(requester, identifier, timestamp, ancillaryData); - _validateRequestHash(requestId, request); - - // Associate settled request params with ID. - Request memory settledRequest = Request({ - proposer: request.proposer, - disputer: request.disputer, - currency: request.currency, - settled: true, // Modified - proposedPrice: request.proposedPrice, - resolvedPrice: request.resolvedPrice, - expirationTime: request.expirationTime, - reward: request.reward, - finalFee: request.finalFee, - requestSettings: request.requestSettings - }); - - OptimisticOracleInterface.State state = _getState(requester, identifier, timestamp, ancillaryData, request); - if (state == OptimisticOracleInterface.State.Expired) { - // In the expiry case, just pay back the proposer's bond and final fee along with the reward. - resolvedPrice = request.proposedPrice; - settledRequest.resolvedPrice = resolvedPrice; - payout = request.requestSettings.bond.add(request.finalFee).add(request.reward); - request.currency.safeTransfer(request.proposer, payout); - } else if (state == OptimisticOracleInterface.State.Resolved) { - // In the Resolved case, pay either the disputer or the proposer the entire payout (+ bond and reward). - resolvedPrice = _getOracle().getPrice(identifier, timestamp, _stampAncillaryData(ancillaryData, requester)); - settledRequest.resolvedPrice = resolvedPrice; - bool disputeSuccess = settledRequest.resolvedPrice != request.proposedPrice; - - // Winner gets: - // - Their bond back. - // - The unburned portion of the loser's bond: proposal bond (not including final fee) - burned bond. - // - Their final fee back. - // - The request reward (if not already refunded -- if refunded, it will be set to 0). - payout = request - .requestSettings - .bond - .add(request.requestSettings.bond.sub(_computeBurnedBond(settledRequest))) - .add(request.finalFee) - .add(request.reward); - request.currency.safeTransfer(disputeSuccess ? request.disputer : request.proposer, payout); - } else { - revert("Already settled or not settleable"); - } - - _storeRequestHash(requestId, settledRequest); - emit Settle(requester, identifier, timestamp, ancillaryData, settledRequest); - - // Callback. - if (address(requester).isContract() && request.requestSettings.callbackOnPriceSettled) - OptimisticRequesterV2(requester).priceSettled(identifier, timestamp, ancillaryData, settledRequest); - } - - function _computeBurnedBond(Request memory request) private pure returns (uint256) { - // burnedBond = floor(bond / 2) - return request.requestSettings.bond.div(2); - } - - function _validateLiveness(uint256 liveness) private pure { - require(liveness < 5200 weeks, "Liveness too large"); - require(liveness > 0, "Liveness cannot be 0"); - } - - function _validateRequestHash(bytes32 requestId, Request memory request) private view { - require( - requests[requestId] == _getRequestHash(request), - "Hashed request params do not match existing request hash" - ); - } - - function _storeRequestHash(bytes32 requestId, Request memory request) internal { - requests[requestId] = _getRequestHash(request); - } - - function _getState( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request - ) internal view returns (OptimisticOracleInterface.State) { - // Note: This function does not check whether all of the _request parameter values are correct. For example, - // the request.reward could be any value and it would not impact this function's return value. Therefore, it - // is the caller's responsibility to check that _request matches with the expected ID corresponding to - // {requester, identifier, timestamp, ancillaryData} via _validateRequestHash(). - if (address(request.currency) == address(0)) return OptimisticOracleInterface.State.Invalid; - - if (request.proposer == address(0)) return OptimisticOracleInterface.State.Requested; - - if (request.settled) return OptimisticOracleInterface.State.Settled; - - if (request.disputer == address(0)) - return - request.expirationTime <= getCurrentTime() - ? OptimisticOracleInterface.State.Expired - : OptimisticOracleInterface.State.Proposed; - - return - _getOracle().hasPrice(identifier, timestamp, _stampAncillaryData(ancillaryData, requester)) - ? OptimisticOracleInterface.State.Resolved - : OptimisticOracleInterface.State.Disputed; - } - - function _getOracle() internal view returns (OracleAncillaryInterface) { - return OracleAncillaryInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); - } - - function _getCollateralWhitelist() internal view returns (AddressWhitelist) { - return AddressWhitelist(finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist)); - } - - function _getStore() internal view returns (StoreInterface) { - return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store)); - } - - function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) { - return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); - } - - /** - * @dev We don't handle specifically the case where `ancillaryData` is not already readily translateable in utf8. - * For those cases, we assume that the client will be able to strip out the utf8-translateable part of the - * ancillary data that this contract stamps. - */ - function _stampAncillaryData(bytes memory ancillaryData, address requester) internal pure returns (bytes memory) { - // Since this contract will be the one to formally submit DVM price requests, its useful for voters to know who - // the original requester was. - return AncillaryData.appendKeyValueAddress(ancillaryData, "ooRequester", requester); - } -} - -/** - * @notice This is the SkinnyOptimisticOracle contract that should be deployed on live networks. It is exactly the same - * as the regular SkinnyOptimisticOracle contract, but it overrides getCurrentTime to make the call a simply return - * block.timestamp with no branching or storage queries. - */ -contract SkinnyOptimisticOracleV2Prod is SkinnyOptimisticOracleV2 { - constructor( - uint256 _liveness, - address _finderAddress, - address _timerAddress - ) SkinnyOptimisticOracleV2(_liveness, _finderAddress, _timerAddress) {} - - function getCurrentTime() public view virtual override returns (uint256) { - return block.timestamp; - } -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/OptimisticOracleV2Interface.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/OptimisticOracleV2Interface.sol deleted file mode 100644 index 6e1cff600..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/OptimisticOracleV2Interface.sol +++ /dev/null @@ -1,351 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; - -/** - * @title Financial contract facing Oracle interface. - * @dev Interface used by financial contracts to interact with the Oracle. Voters will use a different interface. - */ -abstract contract OptimisticOracleV2Interface { - event RequestPrice( - address indexed requester, - bytes32 identifier, - uint256 timestamp, - bytes ancillaryData, - address currency, - uint256 reward, - uint256 finalFee - ); - event ProposePrice( - address indexed requester, - address indexed proposer, - bytes32 identifier, - uint256 timestamp, - bytes ancillaryData, - int256 proposedPrice, - uint256 expirationTimestamp, - address currency - ); - event DisputePrice( - address indexed requester, - address indexed proposer, - address indexed disputer, - bytes32 identifier, - uint256 timestamp, - bytes ancillaryData, - int256 proposedPrice - ); - event Settle( - address indexed requester, - address indexed proposer, - address indexed disputer, - bytes32 identifier, - uint256 timestamp, - bytes ancillaryData, - int256 price, - uint256 payout - ); - // Struct representing the state of a price request. - enum State { - Invalid, // Never requested. - Requested, // Requested, no other actions taken. - Proposed, // Proposed, but not expired or disputed yet. - Expired, // Proposed, not disputed, past liveness. - Disputed, // Disputed, but no DVM price returned yet. - Resolved, // Disputed and DVM price is available. - Settled // Final price has been set in the contract (can get here from Expired or Resolved). - } - - struct RequestSettings { - bool eventBased; // True if the request is set to be event-based. - bool refundOnDispute; // True if the requester should be refunded their reward on dispute. - bool callbackOnPriceProposed; // True if callbackOnPriceProposed callback is required. - bool callbackOnPriceDisputed; // True if callbackOnPriceDisputed callback is required. - bool callbackOnPriceSettled; // True if callbackOnPriceSettled callback is required. - uint256 bond; // Bond that the proposer and disputer must pay on top of the final fee. - uint256 customLiveness; // Custom liveness value set by the requester. - } - - // Struct representing a price request. - struct Request { - address proposer; // Address of the proposer. - address disputer; // Address of the disputer. - IERC20 currency; // ERC20 token used to pay rewards and fees. - bool settled; // True if the request is settled. - RequestSettings requestSettings; // Custom settings associated with a request. - int256 proposedPrice; // Price that the proposer submitted. - int256 resolvedPrice; // Price resolved once the request is settled. - uint256 expirationTime; // Time at which the request auto-settles without a dispute. - uint256 reward; // Amount of the currency to pay to the proposer on settlement. - uint256 finalFee; // Final fee to pay to the Store upon request to the DVM. - } - - // This value must be <= the Voting contract's `ancillaryBytesLimit` value otherwise it is possible - // that a price can be requested to this contract successfully, but cannot be disputed because the DVM refuses - // to accept a price request made with ancillary data length over a certain size. - uint256 public constant ancillaryBytesLimit = 8192; - - function defaultLiveness() external view virtual returns (uint256); - - function finder() external view virtual returns (FinderInterface); - - function getCurrentTime() external view virtual returns (uint256); - - // Note: this is required so that typechain generates a return value with named fields. - mapping(bytes32 => Request) public requests; - - /** - * @notice Requests a new price. - * @param identifier price identifier being requested. - * @param timestamp timestamp of the price being requested. - * @param ancillaryData ancillary data representing additional args being passed with the price request. - * @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM. - * @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0, - * which could make sense if the contract requests and proposes the value in the same call or - * provides its own reward system. - * @return totalBond default bond (final fee) + final fee that the proposer and disputer will be required to pay. - * This can be changed with a subsequent call to setBond(). - */ - function requestPrice( - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData, - IERC20 currency, - uint256 reward - ) external virtual returns (uint256 totalBond); - - /** - * @notice Set the proposal bond associated with a price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param bond custom bond amount to set. - * @return totalBond new bond + final fee that the proposer and disputer will be required to pay. This can be - * changed again with a subsequent call to setBond(). - */ - function setBond( - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData, - uint256 bond - ) external virtual returns (uint256 totalBond); - - /** - * @notice Sets the request to refund the reward if the proposal is disputed. This can help to "hedge" the caller - * in the event of a dispute-caused delay. Note: in the event of a dispute, the winner still receives the other's - * bond, so there is still profit to be made even if the reward is refunded. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - */ - function setRefundOnDispute(bytes32 identifier, uint256 timestamp, bytes memory ancillaryData) external virtual; - - /** - * @notice Sets a custom liveness value for the request. Liveness is the amount of time a proposal must wait before - * being auto-resolved. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param customLiveness new custom liveness. - */ - function setCustomLiveness( - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData, - uint256 customLiveness - ) external virtual; - - /** - * @notice Sets the request to be an "event-based" request. - * @dev Calling this method has a few impacts on the request: - * - * 1. The timestamp at which the request is evaluated is the time of the proposal, not the timestamp associated - * with the request. - * - * 2. The proposer cannot propose the "too early" value (TOO_EARLY_RESPONSE). This is to ensure that a proposer who - * prematurely proposes a response loses their bond. - * - * 3. RefundoOnDispute is automatically set, meaning disputes trigger the reward to be automatically refunded to - * the requesting contract. - * - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - */ - function setEventBased(bytes32 identifier, uint256 timestamp, bytes memory ancillaryData) external virtual; - - /** - * @notice Sets which callbacks should be enabled for the request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param callbackOnPriceProposed whether to enable the callback onPriceProposed. - * @param callbackOnPriceDisputed whether to enable the callback onPriceDisputed. - * @param callbackOnPriceSettled whether to enable the callback onPriceSettled. - */ - function setCallbacks( - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData, - bool callbackOnPriceProposed, - bool callbackOnPriceDisputed, - bool callbackOnPriceSettled - ) external virtual; - - /** - * @notice Proposes a price value on another address' behalf. Note: this address will receive any rewards that come - * from this proposal. However, any bonds are pulled from the caller. - * @param proposer address to set as the proposer. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param proposedPrice price being proposed. - * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to - * the proposer once settled if the proposal is correct. - */ - function proposePriceFor( - address proposer, - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData, - int256 proposedPrice - ) public virtual returns (uint256 totalBond); - - /** - * @notice Proposes a price value for an existing price request. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param proposedPrice price being proposed. - * @return totalBond the amount that's pulled from the proposer's wallet as a bond. The bond will be returned to - * the proposer once settled if the proposal is correct. - */ - function proposePrice( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData, - int256 proposedPrice - ) external virtual returns (uint256 totalBond); - - /** - * @notice Disputes a price request with an active proposal on another address' behalf. Note: this address will - * receive any rewards that come from this dispute. However, any bonds are pulled from the caller. - * @param disputer address to set as the disputer. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to - * the disputer once settled if the dispute was value (the proposal was incorrect). - */ - function disputePriceFor( - address disputer, - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) public virtual returns (uint256 totalBond); - - /** - * @notice Disputes a price value for an existing price request with an active proposal. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @return totalBond the amount that's pulled from the disputer's wallet as a bond. The bond will be returned to - * the disputer once settled if the dispute was valid (the proposal was incorrect). - */ - function disputePrice( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) external virtual returns (uint256 totalBond); - - /** - * @notice Retrieves a price that was previously requested by a caller. Reverts if the request is not settled - * or settleable. Note: this method is not view so that this call may actually settle the price request if it - * hasn't been settled. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @return resolved price. - */ - function settleAndGetPrice( - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) external virtual returns (int256); - - /** - * @notice Attempts to settle an outstanding price request. Will revert if it isn't settleable. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @return payout the amount that the "winner" (proposer or disputer) receives on settlement. This amount includes - * the returned bonds as well as additional rewards. - */ - function settle( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) external virtual returns (uint256 payout); - - /** - * @notice Gets the current data structure containing all information about a price request. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @return the Request data structure. - */ - function getRequest( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) public view virtual returns (Request memory); - - /** - * @notice Returns the state of a price request. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @return the State enum value. - */ - function getState( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) public view virtual returns (State); - - /** - * @notice Checks if a given request has resolved or been settled (i.e the optimistic oracle has a price). - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @return true if price has resolved or settled, false otherwise. - */ - function hasPrice( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) public view virtual returns (bool); - - function stampAncillaryData( - bytes memory ancillaryData, - address requester - ) public view virtual returns (bytes memory); -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/SkinnyOptimisticOracleV2Interface.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/SkinnyOptimisticOracleV2Interface.sol deleted file mode 100644 index 481958dc7..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v2/interfaces/SkinnyOptimisticOracleV2Interface.sol +++ /dev/null @@ -1,253 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "../interfaces/OptimisticOracleInterface.sol"; - -/** - * @title Interface for the gas-cost-reduced version of the OptimisticOracle. - * @notice Differences from normal OptimisticOracle: - * - refundOnDispute: flag is removed, by default there are no refunds on disputes. - * - customizing request parameters: In the OptimisticOracle, parameters like `bond` and `customLiveness` can be reset - * after a request is already made via `requestPrice`. In the SkinnyOptimisticOracle, these parameters can only be - * set in `requestPrice`, which has an expanded input set. - * - settleAndGetPrice: Replaced by `settle`, which can only be called once per settleable request. The resolved price - * can be fetched via the `Settle` event or the return value of `settle`. - * - general changes to interface: Functions that interact with existing requests all require the parameters of the - * request to modify to be passed as input. These parameters must match with the existing request parameters or the - * function will revert. This change reflects the internal refactor to store hashed request parameters instead of the - * full request struct. - * @dev Interface used by financial contracts to interact with the Oracle. Voters will use a different interface. - */ -abstract contract SkinnyOptimisticOracleV2Interface { - struct RequestSettings { - bool callbackOnPriceProposed; // True if callbackOnPriceProposed callback is required. - bool callbackOnPriceDisputed; // True if callbackOnPriceDisputed callback is required. - bool callbackOnPriceSettled; // True if callbackOnPriceSettled callback is required. - uint256 bond; // Bond that the proposer and disputer must pay on top of the final fee. - uint256 customLiveness; // Custom liveness value set by the requester. - } - - // Struct representing a price request. Note that this differs from the OptimisticOracleInterface's Request struct - // in that refundOnDispute is removed. - struct Request { - address proposer; // Address of the proposer. - address disputer; // Address of the disputer. - IERC20 currency; // ERC20 token used to pay rewards and fees. - bool settled; // True if the request is settled. - RequestSettings requestSettings; // Custom settings associated with a request. - int256 proposedPrice; // Price that the proposer submitted. - int256 resolvedPrice; // Price resolved once the request is settled. - uint256 expirationTime; // Time at which the request auto-settles without a dispute. - uint256 reward; // Amount of the currency to pay to the proposer on settlement. - uint256 finalFee; // Final fee to pay to the Store upon request to the DVM. - } - - // This value must be <= the Voting contract's `ancillaryBytesLimit` value otherwise it is possible - // that a price can be requested to this contract successfully, but cannot be disputed because the DVM refuses - // to accept a price request made with ancillary data length over a certain size. - uint256 public constant ancillaryBytesLimit = 8192; - - /** - * @notice Requests a new price. - * @param identifier price identifier being requested. - * @param timestamp timestamp of the price being requested. - * @param ancillaryData ancillary data representing additional args being passed with the price request. - * @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM. - * @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0, - * which could make sense if the contract requests and proposes the value in the same call or - * provides its own reward system. - * @param requestSettings settings for the request. - * @return totalBond default bond + final fee that the proposer and disputer will be required to pay. - */ - function requestPrice( - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - IERC20 currency, - uint256 reward, - RequestSettings memory requestSettings - ) external virtual returns (uint256 totalBond); - - /** - * @notice Proposes a price value on another address' behalf. Note: this address will receive any rewards that come - * from this proposal. However, any bonds are pulled from the caller. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param request price request parameters whose hash must match the request that the caller wants to - * propose a price for. - * @param proposer address to set as the proposer. - * @param proposedPrice price being proposed. - * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to - * the proposer once settled if the proposal is correct. - */ - function proposePriceFor( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request, - address proposer, - int256 proposedPrice - ) public virtual returns (uint256 totalBond); - - /** - * @notice Proposes a price value where caller is the proposer. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param request price request parameters whose hash must match the request that the caller wants to - * propose a price for. - * @param proposedPrice price being proposed. - * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to - * the proposer once settled if the proposal is correct. - */ - function proposePrice( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request, - int256 proposedPrice - ) external virtual returns (uint256 totalBond); - - /** - * @notice Combines logic of requestPrice and proposePrice while taking advantage of gas savings from not having to - * overwrite Request params that a normal requestPrice() => proposePrice() flow would entail. Note: The proposer - * will receive any rewards that come from this proposal. However, any bonds are pulled from the caller. - * @dev The caller is the requester, but the proposer can be customized. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM. - * @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0, - * which could make sense if the contract requests and proposes the value in the same call or - * provides its own reward system. - * @param requestSettings settings for the request. - * @param proposer address to set as the proposer. - * @param proposedPrice price being proposed. - * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to - * the proposer once settled if the proposal is correct. - */ - function requestAndProposePriceFor( - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - IERC20 currency, - uint256 reward, - RequestSettings memory requestSettings, - address proposer, - int256 proposedPrice - ) external virtual returns (uint256 totalBond); - - /** - * @notice Disputes a price request with an active proposal on another address' behalf. Note: this address will - * receive any rewards that come from this dispute. However, any bonds are pulled from the caller. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param request price request parameters whose hash must match the request that the caller wants to - * dispute. - * @param disputer address to set as the disputer. - * @param requester sender of the initial price request. - * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to - * the disputer once settled if the dispute was valid (the proposal was incorrect). - */ - function disputePriceFor( - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request, - address disputer, - address requester - ) public virtual returns (uint256 totalBond); - - /** - * @notice Disputes a price request with an active proposal where caller is the disputer. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param request price request parameters whose hash must match the request that the caller wants to - * dispute. - * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to - * the disputer once settled if the dispute was valid (the proposal was incorrect). - */ - function disputePrice( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request - ) external virtual returns (uint256 totalBond); - - /** - * @notice Attempts to settle an outstanding price request. Will revert if it isn't settleable. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param request price request parameters whose hash must match the request that the caller wants to - * settle. - * @return payout the amount that the "winner" (proposer or disputer) receives on settlement. This amount includes - * the returned bonds as well as additional rewards. - * @return resolvedPrice the price that the request settled to. - */ - function settle( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request - ) external virtual returns (uint256 payout, int256 resolvedPrice); - - /** - * @notice Computes the current state of a price request. See the State enum for more details. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param request price request parameters. - * @return the State. - */ - function getState( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request - ) external virtual returns (OptimisticOracleInterface.State); - - /** - * @notice Checks if a given request has resolved, expired or been settled (i.e the optimistic oracle has a price). - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param request price request parameters. The hash of these parameters must match with the request hash that is - * associated with the price request unique ID {requester, identifier, timestamp, ancillaryData}, or this method - * will revert. - * @return boolean indicating true if price exists and false if not. - */ - function hasPrice( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request - ) public virtual returns (bool); - - /** - * @notice Generates stamped ancillary data in the format that it would be used in the case of a price dispute. - * @param ancillaryData ancillary data of the price being requested. - * @param requester sender of the initial price request. - * @return the stamped ancillary bytes. - */ - function stampAncillaryData( - bytes memory ancillaryData, - address requester - ) public pure virtual returns (bytes memory); -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v2/previous-versions/OptimisticOracle.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v2/previous-versions/OptimisticOracle.sol deleted file mode 100644 index 5d0178d21..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v2/previous-versions/OptimisticOracle.sol +++ /dev/null @@ -1,629 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -// WARNING: This contract has been deprecated! It is left in the UMA Protocol repo for backwards compatibility reasons. -// You should refer to the latest implementation of the Optimistic Oracle which is named OptimisticOracleV2 and can -// be found in the UMA Finder under the same name. - -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; -import "@openzeppelin/contracts-v4/utils/Address.sol"; - -import "../../data-verification-mechanism/interfaces/StoreInterface.sol"; -import "../../data-verification-mechanism/interfaces/OracleAncillaryInterface.sol"; -import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; -import "../../data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol"; -import "../interfaces/OptimisticOracleInterface.sol"; -import "../../data-verification-mechanism/implementation/Constants.sol"; - -import "../../common/implementation/Testable.sol"; -import "../../common/implementation/Lockable.sol"; -import "../../common/implementation/FixedPoint.sol"; -import "../../common/implementation/AncillaryData.sol"; -import "../../common/implementation/AddressWhitelist.sol"; - -/** - * @title Optimistic Requester. - * @notice Optional interface that requesters can implement to receive callbacks. - * @dev this contract does _not_ work with ERC777 collateral currencies or any others that call into the receiver on - * transfer(). Using an ERC777 token would allow a user to maliciously grief other participants (while also losing - * money themselves). - */ -interface OptimisticRequester { - /** - * @notice Callback for proposals. - * @param identifier price identifier being requested. - * @param timestamp timestamp of the price being requested. - * @param ancillaryData ancillary data of the price being requested. - */ - function priceProposed(bytes32 identifier, uint256 timestamp, bytes memory ancillaryData) external; - - /** - * @notice Callback for disputes. - * @param identifier price identifier being requested. - * @param timestamp timestamp of the price being requested. - * @param ancillaryData ancillary data of the price being requested. - * @param refund refund received in the case that refundOnDispute was enabled. - */ - function priceDisputed(bytes32 identifier, uint256 timestamp, bytes memory ancillaryData, uint256 refund) external; - - /** - * @notice Callback for settlement. - * @param identifier price identifier being requested. - * @param timestamp timestamp of the price being requested. - * @param ancillaryData ancillary data of the price being requested. - * @param price price that was resolved by the escalation process. - */ - function priceSettled(bytes32 identifier, uint256 timestamp, bytes memory ancillaryData, int256 price) external; -} - -/** - * @title Optimistic Oracle. - * @notice Pre-DVM escalation contract that allows faster settlement. - */ -contract OptimisticOracle is OptimisticOracleInterface, Testable, Lockable { - using SafeMath for uint256; - using SafeERC20 for IERC20; - using Address for address; - - // Finder to provide addresses for DVM contracts. - FinderInterface public override finder; - - // Default liveness value for all price requests. - uint256 public override defaultLiveness; - - /** - * @notice Constructor. - * @param _liveness default liveness applied to each price request. - * @param _finderAddress finder to use to get addresses of DVM contracts. - * @param _timerAddress address of the timer contract. Should be 0x0 in prod. - */ - constructor(uint256 _liveness, address _finderAddress, address _timerAddress) Testable(_timerAddress) { - finder = FinderInterface(_finderAddress); - _validateLiveness(_liveness); - defaultLiveness = _liveness; - } - - /** - * @notice Requests a new price. - * @param identifier price identifier being requested. - * @param timestamp timestamp of the price being requested. - * @param ancillaryData ancillary data representing additional args being passed with the price request. - * @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM. - * @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0, - * which could make sense if the contract requests and proposes the value in the same call or - * provides its own reward system. - * @return totalBond default bond (final fee) + final fee that the proposer and disputer will be required to pay. - * This can be changed with a subsequent call to setBond(). - */ - function requestPrice( - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData, - IERC20 currency, - uint256 reward - ) external override nonReentrant returns (uint256 totalBond) { - require(_getState(msg.sender, identifier, timestamp, ancillaryData) == State.Invalid, "requestPrice: Invalid"); - require(_getIdentifierWhitelist().isIdentifierSupported(identifier), "Unsupported identifier"); - require(_getCollateralWhitelist().isOnWhitelist(address(currency)), "Unsupported currency"); - require(timestamp <= getCurrentTime(), "Timestamp in future"); - require( - _stampAncillaryData(ancillaryData, msg.sender).length <= ancillaryBytesLimit, - "Ancillary Data too long" - ); - uint256 finalFee = _getStore().computeFinalFee(address(currency)).rawValue; - requests[_getId(msg.sender, identifier, timestamp, ancillaryData)] = Request({ - proposer: address(0), - disputer: address(0), - currency: currency, - settled: false, - refundOnDispute: false, - proposedPrice: 0, - resolvedPrice: 0, - expirationTime: 0, - reward: reward, - finalFee: finalFee, - bond: finalFee, - customLiveness: 0 - }); - - if (reward > 0) { - currency.safeTransferFrom(msg.sender, address(this), reward); - } - - emit RequestPrice(msg.sender, identifier, timestamp, ancillaryData, address(currency), reward, finalFee); - - // This function returns the initial proposal bond for this request, which can be customized by calling - // setBond() with the same identifier and timestamp. - return finalFee.mul(2); - } - - /** - * @notice Set the proposal bond associated with a price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param bond custom bond amount to set. - * @return totalBond new bond + final fee that the proposer and disputer will be required to pay. This can be - * changed again with a subsequent call to setBond(). - */ - function setBond( - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData, - uint256 bond - ) external override nonReentrant returns (uint256 totalBond) { - require(_getState(msg.sender, identifier, timestamp, ancillaryData) == State.Requested, "setBond: Requested"); - Request storage request = _getRequest(msg.sender, identifier, timestamp, ancillaryData); - request.bond = bond; - - // Total bond is the final fee + the newly set bond. - return bond.add(request.finalFee); - } - - /** - * @notice Sets the request to refund the reward if the proposal is disputed. This can help to "hedge" the caller - * in the event of a dispute-caused delay. Note: in the event of a dispute, the winner still receives the other's - * bond, so there is still profit to be made even if the reward is refunded. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - */ - function setRefundOnDispute( - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) external override nonReentrant { - require( - _getState(msg.sender, identifier, timestamp, ancillaryData) == State.Requested, - "setRefundOnDispute: Requested" - ); - _getRequest(msg.sender, identifier, timestamp, ancillaryData).refundOnDispute = true; - } - - /** - * @notice Sets a custom liveness value for the request. Liveness is the amount of time a proposal must wait before - * being auto-resolved. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param customLiveness new custom liveness. - */ - function setCustomLiveness( - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData, - uint256 customLiveness - ) external override nonReentrant { - require( - _getState(msg.sender, identifier, timestamp, ancillaryData) == State.Requested, - "setCustomLiveness: Requested" - ); - _validateLiveness(customLiveness); - _getRequest(msg.sender, identifier, timestamp, ancillaryData).customLiveness = customLiveness; - } - - /** - * @notice Proposes a price value on another address' behalf. Note: this address will receive any rewards that come - * from this proposal. However, any bonds are pulled from the caller. - * @param proposer address to set as the proposer. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param proposedPrice price being proposed. - * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to - * the proposer once settled if the proposal is correct. - */ - function proposePriceFor( - address proposer, - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData, - int256 proposedPrice - ) public override nonReentrant returns (uint256 totalBond) { - require(proposer != address(0), "proposer address must be non 0"); - require( - _getState(requester, identifier, timestamp, ancillaryData) == State.Requested, - "proposePriceFor: Requested" - ); - Request storage request = _getRequest(requester, identifier, timestamp, ancillaryData); - request.proposer = proposer; - request.proposedPrice = proposedPrice; - - // If a custom liveness has been set, use it instead of the default. - request.expirationTime = getCurrentTime().add( - request.customLiveness != 0 ? request.customLiveness : defaultLiveness - ); - - totalBond = request.bond.add(request.finalFee); - if (totalBond > 0) { - request.currency.safeTransferFrom(msg.sender, address(this), totalBond); - } - - emit ProposePrice( - requester, - proposer, - identifier, - timestamp, - ancillaryData, - proposedPrice, - request.expirationTime, - address(request.currency) - ); - - // Callback. - if (address(requester).isContract()) - try OptimisticRequester(requester).priceProposed(identifier, timestamp, ancillaryData) {} catch {} - } - - /** - * @notice Proposes a price value for an existing price request. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param proposedPrice price being proposed. - * @return totalBond the amount that's pulled from the proposer's wallet as a bond. The bond will be returned to - * the proposer once settled if the proposal is correct. - */ - function proposePrice( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData, - int256 proposedPrice - ) external override returns (uint256 totalBond) { - // Note: re-entrancy guard is done in the inner call. - return proposePriceFor(msg.sender, requester, identifier, timestamp, ancillaryData, proposedPrice); - } - - /** - * @notice Disputes a price request with an active proposal on another address' behalf. Note: this address will - * receive any rewards that come from this dispute. However, any bonds are pulled from the caller. - * @param disputer address to set as the disputer. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to - * the disputer once settled if the dispute was valid (the proposal was incorrect). - */ - function disputePriceFor( - address disputer, - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) public override nonReentrant returns (uint256 totalBond) { - require(disputer != address(0), "disputer address must be non 0"); - require( - _getState(requester, identifier, timestamp, ancillaryData) == State.Proposed, - "disputePriceFor: Proposed" - ); - Request storage request = _getRequest(requester, identifier, timestamp, ancillaryData); - request.disputer = disputer; - - uint256 finalFee = request.finalFee; - uint256 bond = request.bond; - totalBond = bond.add(finalFee); - if (totalBond > 0) { - request.currency.safeTransferFrom(msg.sender, address(this), totalBond); - } - - StoreInterface store = _getStore(); - - // Avoids stack too deep compilation error. - { - // Along with the final fee, "burn" part of the loser's bond to ensure that a larger bond always makes it - // proportionally more expensive to delay the resolution even if the proposer and disputer are the same - // party. - uint256 burnedBond = _computeBurnedBond(request); - - // The total fee is the burned bond and the final fee added together. - uint256 totalFee = finalFee.add(burnedBond); - - if (totalFee > 0) { - request.currency.safeIncreaseAllowance(address(store), totalFee); - _getStore().payOracleFeesErc20(address(request.currency), FixedPoint.Unsigned(totalFee)); - } - } - - _getOracle().requestPrice(identifier, timestamp, _stampAncillaryData(ancillaryData, requester)); - - // Compute refund. - uint256 refund = 0; - if (request.reward > 0 && request.refundOnDispute) { - refund = request.reward; - request.reward = 0; - request.currency.safeTransfer(requester, refund); - } - - emit DisputePrice( - requester, - request.proposer, - disputer, - identifier, - timestamp, - ancillaryData, - request.proposedPrice - ); - - // Callback. - if (address(requester).isContract()) - try OptimisticRequester(requester).priceDisputed(identifier, timestamp, ancillaryData, refund) {} catch {} - } - - /** - * @notice Disputes a price value for an existing price request with an active proposal. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @return totalBond the amount that's pulled from the disputer's wallet as a bond. The bond will be returned to - * the disputer once settled if the dispute was valid (the proposal was incorrect). - */ - function disputePrice( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) external override returns (uint256 totalBond) { - // Note: re-entrancy guard is done in the inner call. - return disputePriceFor(msg.sender, requester, identifier, timestamp, ancillaryData); - } - - /** - * @notice Retrieves a price that was previously requested by a caller. Reverts if the request is not settled - * or settleable. Note: this method is not view so that this call may actually settle the price request if it - * hasn't been settled. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @return resolved price. - */ - function settleAndGetPrice( - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) external override nonReentrant returns (int256) { - if (_getState(msg.sender, identifier, timestamp, ancillaryData) != State.Settled) { - _settle(msg.sender, identifier, timestamp, ancillaryData); - } - - return _getRequest(msg.sender, identifier, timestamp, ancillaryData).resolvedPrice; - } - - /** - * @notice Attempts to settle an outstanding price request. Will revert if it isn't settleable. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @return payout the amount that the "winner" (proposer or disputer) receives on settlement. This amount includes - * the returned bonds as well as additional rewards. - */ - function settle( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) external override nonReentrant returns (uint256 payout) { - return _settle(requester, identifier, timestamp, ancillaryData); - } - - /** - * @notice Gets the current data structure containing all information about a price request. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @return the Request data structure. - */ - function getRequest( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) public view override nonReentrantView returns (Request memory) { - return _getRequest(requester, identifier, timestamp, ancillaryData); - } - - /** - * @notice Computes the current state of a price request. See the State enum for more details. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @return the State. - */ - function getState( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) public view override nonReentrantView returns (State) { - return _getState(requester, identifier, timestamp, ancillaryData); - } - - /** - * @notice Checks if a given request has resolved, expired or been settled (i.e the optimistic oracle has a price). - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @return boolean indicating true if price exists and false if not. - */ - function hasPrice( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) public view override nonReentrantView returns (bool) { - State state = _getState(requester, identifier, timestamp, ancillaryData); - return state == State.Settled || state == State.Resolved || state == State.Expired; - } - - /** - * @notice Generates stamped ancillary data in the format that it would be used in the case of a price dispute. - * @param ancillaryData ancillary data of the price being requested. - * @param requester sender of the initial price request. - * @return the stamped ancillary bytes. - */ - function stampAncillaryData( - bytes memory ancillaryData, - address requester - ) public pure override returns (bytes memory) { - return _stampAncillaryData(ancillaryData, requester); - } - - function _getId( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) private pure returns (bytes32) { - return keccak256(abi.encodePacked(requester, identifier, timestamp, ancillaryData)); - } - - function _settle( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) private returns (uint256 payout) { - State state = _getState(requester, identifier, timestamp, ancillaryData); - - // Set it to settled so this function can never be entered again. - Request storage request = _getRequest(requester, identifier, timestamp, ancillaryData); - request.settled = true; - - if (state == State.Expired) { - // In the expiry case, just pay back the proposer's bond and final fee along with the reward. - request.resolvedPrice = request.proposedPrice; - payout = request.bond.add(request.finalFee).add(request.reward); - request.currency.safeTransfer(request.proposer, payout); - } else if (state == State.Resolved) { - // In the Resolved case, pay either the disputer or the proposer the entire payout (+ bond and reward). - request.resolvedPrice = _getOracle().getPrice( - identifier, - timestamp, - _stampAncillaryData(ancillaryData, requester) - ); - bool disputeSuccess = request.resolvedPrice != request.proposedPrice; - uint256 bond = request.bond; - - // Unburned portion of the loser's bond = 1 - burned bond. - uint256 unburnedBond = bond.sub(_computeBurnedBond(request)); - - // Winner gets: - // - Their bond back. - // - The unburned portion of the loser's bond. - // - Their final fee back. - // - The request reward (if not already refunded -- if refunded, it will be set to 0). - payout = bond.add(unburnedBond).add(request.finalFee).add(request.reward); - request.currency.safeTransfer(disputeSuccess ? request.disputer : request.proposer, payout); - } else { - revert("_settle: not settleable"); - } - - emit Settle( - requester, - request.proposer, - request.disputer, - identifier, - timestamp, - ancillaryData, - request.resolvedPrice, - payout - ); - - // Callback. - if (address(requester).isContract()) - try - OptimisticRequester(requester).priceSettled(identifier, timestamp, ancillaryData, request.resolvedPrice) - {} catch {} - } - - function _getRequest( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) private view returns (Request storage) { - return requests[_getId(requester, identifier, timestamp, ancillaryData)]; - } - - function _computeBurnedBond(Request storage request) private view returns (uint256) { - // burnedBond = floor(bond / 2) - return request.bond.div(2); - } - - function _validateLiveness(uint256 _liveness) private pure { - require(_liveness < 5200 weeks, "Liveness too large"); - require(_liveness > 0, "Liveness cannot be 0"); - } - - function _getState( - address requester, - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData - ) internal view returns (State) { - Request storage request = _getRequest(requester, identifier, timestamp, ancillaryData); - - if (address(request.currency) == address(0)) { - return State.Invalid; - } - - if (request.proposer == address(0)) { - return State.Requested; - } - - if (request.settled) { - return State.Settled; - } - - if (request.disputer == address(0)) { - return request.expirationTime <= getCurrentTime() ? State.Expired : State.Proposed; - } - - return - _getOracle().hasPrice(identifier, timestamp, _stampAncillaryData(ancillaryData, requester)) - ? State.Resolved - : State.Disputed; - } - - function _getOracle() internal view returns (OracleAncillaryInterface) { - return OracleAncillaryInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); - } - - function _getCollateralWhitelist() internal view returns (AddressWhitelist) { - return AddressWhitelist(finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist)); - } - - function _getStore() internal view returns (StoreInterface) { - return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store)); - } - - function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) { - return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); - } - - /** - * @dev We don't handle specifically the case where `ancillaryData` is not already readily translateable in utf8. - * For those cases, we assume that the client will be able to strip out the utf8-translateable part of the - * ancillary data that this contract stamps. - */ - function _stampAncillaryData(bytes memory ancillaryData, address requester) internal pure returns (bytes memory) { - // Since this contract will be the one to formally submit DVM price requests, its useful for voters to know who - // the original requester was. - return AncillaryData.appendKeyValueAddress(ancillaryData, "ooRequester", requester); - } - - function getCurrentTime() public view override(Testable, OptimisticOracleInterface) returns (uint256) { - return Testable.getCurrentTime(); - } -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v2/previous-versions/SkinnyOptimisticOracle.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v2/previous-versions/SkinnyOptimisticOracle.sol deleted file mode 100644 index 79e953ab0..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v2/previous-versions/SkinnyOptimisticOracle.sol +++ /dev/null @@ -1,695 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; -import "@openzeppelin/contracts-v4/utils/Address.sol"; - -import "../interfaces/OptimisticOracleInterface.sol"; -import "../interfaces/SkinnyOptimisticOracleInterface.sol"; - -import "../../data-verification-mechanism/interfaces/StoreInterface.sol"; -import "../../data-verification-mechanism/interfaces/OracleAncillaryInterface.sol"; -import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; -import "../../data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol"; -import "../../data-verification-mechanism/implementation/Constants.sol"; - -import "../../common/implementation/Testable.sol"; -import "../../common/implementation/Lockable.sol"; -import "../../common/implementation/FixedPoint.sol"; -import "../../common/implementation/AncillaryData.sol"; -import "../../common/implementation/AddressWhitelist.sol"; - -/** - * @title Optimistic Requester. - * @notice Optional interface that requesters can implement to receive callbacks. - * @dev This contract does _not_ work with ERC777 collateral currencies or any others that call into the receiver on - * transfer(). Using an ERC777 token would allow a user to maliciously grief other participants (while also losing - * money themselves). - */ -interface OptimisticRequester { - /** - * @notice Callback for proposals. - * @param identifier price identifier being requested. - * @param timestamp timestamp of the price being requested. - * @param ancillaryData ancillary data of the price being requested. - * @param request request params after proposal. - */ - function priceProposed( - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - SkinnyOptimisticOracleInterface.Request memory request - ) external; - - /** - * @notice Callback for disputes. - * @param identifier price identifier being requested. - * @param timestamp timestamp of the price being requested. - * @param ancillaryData ancillary data of the price being requested. - * @param request request params after dispute. - */ - function priceDisputed( - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - SkinnyOptimisticOracleInterface.Request memory request - ) external; - - /** - * @notice Callback for settlement. - * @param identifier price identifier being requested. - * @param timestamp timestamp of the price being requested. - * @param ancillaryData ancillary data of the price being requested. - * @param request request params after settlement. - */ - function priceSettled( - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - SkinnyOptimisticOracleInterface.Request memory request - ) external; -} - -/** - * @title Optimistic Oracle with a different interface and fewer features that emphasizes gas cost reductions. - * @notice Pre-DVM escalation contract that allows faster settlement. - */ -contract SkinnyOptimisticOracle is SkinnyOptimisticOracleInterface, Testable, Lockable { - using SafeMath for uint256; - using SafeERC20 for IERC20; - using Address for address; - - event RequestPrice( - address indexed requester, - bytes32 indexed identifier, - uint32 timestamp, - bytes ancillaryData, - Request request - ); - event ProposePrice( - address indexed requester, - bytes32 indexed identifier, - uint32 timestamp, - bytes ancillaryData, - Request request - ); - event DisputePrice( - address indexed requester, - bytes32 indexed identifier, - uint32 timestamp, - bytes ancillaryData, - Request request - ); - event Settle( - address indexed requester, - bytes32 indexed identifier, - uint32 timestamp, - bytes ancillaryData, - Request request - ); - - // Maps hash of unique request params {identifier, timestamp, ancillary data} to customizable variables such as - // reward and bond amounts. - mapping(bytes32 => bytes32) public requests; - - // Finder to provide addresses for DVM contracts. - FinderInterface public finder; - - // Default liveness value for all price requests. - uint256 public defaultLiveness; - - /** - * @notice Constructor. - * @param _liveness default liveness applied to each price request. - * @param _finderAddress finder to use to get addresses of DVM contracts. - * @param _timerAddress address of the timer contract. Should be 0x0 in prod. - */ - constructor(uint256 _liveness, address _finderAddress, address _timerAddress) Testable(_timerAddress) { - finder = FinderInterface(_finderAddress); - _validateLiveness(_liveness); - defaultLiveness = _liveness; - } - - /** - * @notice Requests a new price. - * @param identifier price identifier being requested. - * @param timestamp timestamp of the price being requested. - * @param ancillaryData ancillary data representing additional args being passed with the price request. - * @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM. - * @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0, - * which could make sense if the contract requests and proposes the value in the same call or - * provides its own reward system. - * @param bond custom proposal bond to set for request. If set to 0, defaults to the final fee. - * @param customLiveness custom proposal liveness to set for request. - * @return totalBond default bond + final fee that the proposer and disputer will be required to pay. - */ - function requestPrice( - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - IERC20 currency, - uint256 reward, - uint256 bond, - uint256 customLiveness - ) external override nonReentrant returns (uint256 totalBond) { - bytes32 requestId = _getId(msg.sender, identifier, timestamp, ancillaryData); - require(requests[requestId] == bytes32(0), "Request already initialized"); - require(_getIdentifierWhitelist().isIdentifierSupported(identifier), "Unsupported identifier"); - require(_getCollateralWhitelist().isOnWhitelist(address(currency)), "Unsupported currency"); - require(timestamp <= getCurrentTime(), "Timestamp in future"); - require( - _stampAncillaryData(ancillaryData, msg.sender).length <= ancillaryBytesLimit, - "Ancillary Data too long" - ); - uint256 finalFee = _getStore().computeFinalFee(address(currency)).rawValue; - - // Associate new request with ID - Request memory request; - request.currency = currency; - request.reward = reward; - request.finalFee = finalFee; - request.bond = bond != 0 ? bond : finalFee; - request.customLiveness = customLiveness; - _storeRequestHash(requestId, request); - - if (reward > 0) currency.safeTransferFrom(msg.sender, address(this), reward); - - emit RequestPrice(msg.sender, identifier, timestamp, ancillaryData, request); - - return request.bond.add(finalFee); - } - - /** - * @notice Proposes a price value on another address' behalf. Note: this address will receive any rewards that come - * from this proposal. However, any bonds are pulled from the caller. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param request price request parameters whose hash must match the request that the caller wants to - * propose a price for. - * @param proposer address to set as the proposer. - * @param proposedPrice price being proposed. - * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to - * the proposer once settled if the proposal is correct. - */ - function proposePriceFor( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request, - address proposer, - int256 proposedPrice - ) public override nonReentrant returns (uint256 totalBond) { - require(proposer != address(0), "Proposer address must be non 0"); - require( - _getState(requester, identifier, timestamp, ancillaryData, request) == - OptimisticOracleInterface.State.Requested, - "Must be requested" - ); - bytes32 requestId = _getId(requester, identifier, timestamp, ancillaryData); - _validateRequestHash(requestId, request); - - // Associate newly proposed request params with ID - Request memory proposedRequest = Request({ - proposer: proposer, // Modified - disputer: request.disputer, - currency: request.currency, - settled: request.settled, - proposedPrice: proposedPrice, // Modified - resolvedPrice: request.resolvedPrice, - expirationTime: getCurrentTime().add( - request.customLiveness != 0 ? request.customLiveness : defaultLiveness - ), // Modified - reward: request.reward, - finalFee: request.finalFee, - bond: request.bond, - customLiveness: request.customLiveness - }); - _storeRequestHash(requestId, proposedRequest); - - totalBond = request.bond.add(request.finalFee); - if (totalBond > 0) request.currency.safeTransferFrom(msg.sender, address(this), totalBond); - - emit ProposePrice(requester, identifier, timestamp, ancillaryData, proposedRequest); - - // Callback. - if (address(requester).isContract()) - try - OptimisticRequester(requester).priceProposed(identifier, timestamp, ancillaryData, proposedRequest) - {} catch {} - } - - /** - * @notice Proposes a price value where caller is the proposer. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param request price request parameters whose hash must match the request that the caller wants to - * propose a price for. - * @param proposedPrice price being proposed. - * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to - * the proposer once settled if the proposal is correct. - */ - function proposePrice( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request, - int256 proposedPrice - ) external override returns (uint256 totalBond) { - // Note: re-entrancy guard is done in the inner call. - return proposePriceFor(requester, identifier, timestamp, ancillaryData, request, msg.sender, proposedPrice); - } - - /** - * @notice Combines logic of requestPrice and proposePrice while taking advantage of gas savings from not having to - * overwrite Request params that a normal requestPrice() => proposePrice() flow would entail. Note: The proposer - * will receive any rewards that come from this proposal. However, any bonds are pulled from the caller. - * @dev The caller is the requester, but the proposer can be customized. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM. - * @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0, - * which could make sense if the contract requests and proposes the value in the same call or - * provides its own reward system. - * @param bond custom proposal bond to set for request. If set to 0, defaults to the final fee. - * @param customLiveness custom proposal liveness to set for request. - * @param proposer address to set as the proposer. - * @param proposedPrice price being proposed. - * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to - * the proposer once settled if the proposal is correct. - */ - function requestAndProposePriceFor( - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - IERC20 currency, - uint256 reward, - uint256 bond, - uint256 customLiveness, - address proposer, - int256 proposedPrice - ) external override nonReentrant returns (uint256 totalBond) { - bytes32 requestId = _getId(msg.sender, identifier, timestamp, ancillaryData); - require(requests[requestId] == bytes32(0), "Request already initialized"); - require(proposer != address(0), "proposer address must be non 0"); - require(_getIdentifierWhitelist().isIdentifierSupported(identifier), "Unsupported identifier"); - require(_getCollateralWhitelist().isOnWhitelist(address(currency)), "Unsupported currency"); - require(timestamp <= getCurrentTime(), "Timestamp in future"); - require( - _stampAncillaryData(ancillaryData, msg.sender).length <= ancillaryBytesLimit, - "Ancillary Data too long" - ); - uint256 finalFee = _getStore().computeFinalFee(address(currency)).rawValue; - - // Associate new request with ID - Request memory request; - request.currency = currency; - request.reward = reward; - request.finalFee = finalFee; - request.bond = bond != 0 ? bond : finalFee; - request.customLiveness = customLiveness; - request.proposer = proposer; - request.proposedPrice = proposedPrice; - request.expirationTime = getCurrentTime().add(customLiveness != 0 ? customLiveness : defaultLiveness); - _storeRequestHash(requestId, request); - - // Pull reward from requester, who is the caller. - if (reward > 0) currency.safeTransferFrom(msg.sender, address(this), reward); - // Pull proposal bond from caller. - totalBond = request.bond.add(request.finalFee); - if (totalBond > 0) currency.safeTransferFrom(msg.sender, address(this), totalBond); - - emit RequestPrice(msg.sender, identifier, timestamp, ancillaryData, request); - emit ProposePrice(msg.sender, identifier, timestamp, ancillaryData, request); - - // Callback. - if (address(msg.sender).isContract()) - try OptimisticRequester(msg.sender).priceProposed(identifier, timestamp, ancillaryData, request) {} catch {} - } - - /** - * @notice Disputes a price request with an active proposal on another address' behalf. Note: this address will - * receive any rewards that come from this dispute. However, any bonds are pulled from the caller. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param request price request parameters whose hash must match the request that the caller wants to - * dispute. - * @param disputer address to set as the disputer. - * @param requester sender of the initial price request. - * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to - * the disputer once settled if the dispute was valid (the proposal was incorrect). - */ - function disputePriceFor( - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request, - address disputer, - address requester - ) public override nonReentrant returns (uint256 totalBond) { - require(disputer != address(0), "disputer address must be non 0"); - require( - _getState(requester, identifier, timestamp, ancillaryData, request) == - OptimisticOracleInterface.State.Proposed, - "Must be proposed" - ); - bytes32 requestId = _getId(requester, identifier, timestamp, ancillaryData); - _validateRequestHash(requestId, request); - - // Associate newly disputed request params with ID - Request memory disputedRequest = Request({ - proposer: request.proposer, - disputer: disputer, // Modified - currency: request.currency, - settled: request.settled, - proposedPrice: request.proposedPrice, - resolvedPrice: request.resolvedPrice, - expirationTime: request.expirationTime, - reward: request.reward, - finalFee: request.finalFee, - bond: request.bond, - customLiveness: request.customLiveness - }); - _storeRequestHash(requestId, disputedRequest); - - totalBond = request.bond.add(request.finalFee); - if (totalBond > 0) request.currency.safeTransferFrom(msg.sender, address(this), totalBond); - - StoreInterface store = _getStore(); - - // Avoids stack too deep compilation error. - { - // Along with the final fee, "burn" part of the loser's bond to ensure that a larger bond always makes it - // proportionally more expensive to delay the resolution even if the proposer and disputer are the same - // party. - uint256 burnedBond = _computeBurnedBond(disputedRequest); - - // The total fee is the burned bond and the final fee added together. - uint256 totalFee = request.finalFee.add(burnedBond); - - if (totalFee > 0) { - request.currency.safeIncreaseAllowance(address(store), totalFee); - _getStore().payOracleFeesErc20(address(request.currency), FixedPoint.Unsigned(totalFee)); - } - } - - _getOracle().requestPrice(identifier, timestamp, _stampAncillaryData(ancillaryData, requester)); - - emit DisputePrice(requester, identifier, timestamp, ancillaryData, disputedRequest); - - // Callback. - if (address(requester).isContract()) - try - OptimisticRequester(requester).priceDisputed(identifier, timestamp, ancillaryData, disputedRequest) - {} catch {} - } - - /** - * @notice Disputes a price request with an active proposal where caller is the disputer. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param request price request parameters whose hash must match the request that the caller wants to - * dispute. - * @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to - * the disputer once settled if the dispute was valid (the proposal was incorrect). - */ - function disputePrice( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request - ) external override returns (uint256 totalBond) { - // Note: re-entrancy guard is done in the inner call. - return disputePriceFor(identifier, timestamp, ancillaryData, request, msg.sender, requester); - } - - /** - * @notice Attempts to settle an outstanding price request. Will revert if it isn't settleable. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param request price request parameters whose hash must match the request that the caller wants to - * settle. - * @return payout the amount that the "winner" (proposer or disputer) receives on settlement. This amount includes - * the returned bonds as well as additional rewards. - * @return resolvedPrice the price that the request settled to. - */ - function settle( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request - ) external override nonReentrant returns (uint256 payout, int256 resolvedPrice) { - return _settle(requester, identifier, timestamp, ancillaryData, request); - } - - /** - * @notice Computes the current state of a price request. See the State enum for more details. - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param request price request parameters. - * @return the State. - */ - function getState( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request - ) external override nonReentrant returns (OptimisticOracleInterface.State) { - return _getState(requester, identifier, timestamp, ancillaryData, request); - } - - /** - * @notice Checks if a given request has resolved, expired or been settled (i.e the optimistic oracle has a price). - * @param requester sender of the initial price request. - * @param identifier price identifier to identify the existing request. - * @param timestamp timestamp to identify the existing request. - * @param ancillaryData ancillary data of the price being requested. - * @param request price request parameters. The hash of these parameters must match with the request hash that is - * associated with the price request unique ID {requester, identifier, timestamp, ancillaryData}, or this method - * will revert. - * @return boolean indicating true if price exists and false if not. - */ - function hasPrice( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request - ) public override nonReentrant returns (bool) { - bytes32 requestId = _getId(requester, identifier, timestamp, ancillaryData); - _validateRequestHash(requestId, request); - OptimisticOracleInterface.State state = _getState(requester, identifier, timestamp, ancillaryData, request); - return - state == OptimisticOracleInterface.State.Settled || - state == OptimisticOracleInterface.State.Resolved || - state == OptimisticOracleInterface.State.Expired; - } - - /** - * @notice Generates stamped ancillary data in the format that it would be used in the case of a price dispute. - * @param ancillaryData ancillary data of the price being requested. - * @param requester sender of the initial price request. - * @return the stamped ancillary bytes. - */ - function stampAncillaryData( - bytes memory ancillaryData, - address requester - ) public pure override returns (bytes memory) { - return _stampAncillaryData(ancillaryData, requester); - } - - /**************************************** - * PRIVATE AND INTERNAL FUNCTIONS * - ****************************************/ - // Returns hash of unique request identifiers. This contract maps request ID hashes to hashes of the request's - // parameters. - function _getId( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData - ) private pure returns (bytes32) { - return keccak256(abi.encode(requester, identifier, timestamp, ancillaryData)); - } - - // Returns hash of request parameters. These are mapped to the unique request ID to track a request's lifecycle. - function _getRequestHash(Request memory request) private pure returns (bytes32) { - return keccak256(abi.encode(request)); - } - - // Resolves a price request that has expired or been disputed and a price is available from the DVM. This will - // revert if the unique request ID does not match the hashed request parameters. This also marks the request - // as settled, therefore this method can only be triggered once per eligible request. - function _settle( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request - ) private returns (uint256 payout, int256 resolvedPrice) { - bytes32 requestId = _getId(requester, identifier, timestamp, ancillaryData); - _validateRequestHash(requestId, request); - - // Associate settled request params with ID. - Request memory settledRequest = Request({ - proposer: request.proposer, - disputer: request.disputer, - currency: request.currency, - settled: true, // Modified - proposedPrice: request.proposedPrice, - resolvedPrice: request.resolvedPrice, - expirationTime: request.expirationTime, - reward: request.reward, - finalFee: request.finalFee, - bond: request.bond, - customLiveness: request.customLiveness - }); - - OptimisticOracleInterface.State state = _getState(requester, identifier, timestamp, ancillaryData, request); - if (state == OptimisticOracleInterface.State.Expired) { - // In the expiry case, just pay back the proposer's bond and final fee along with the reward. - resolvedPrice = request.proposedPrice; - settledRequest.resolvedPrice = resolvedPrice; - payout = request.bond.add(request.finalFee).add(request.reward); - request.currency.safeTransfer(request.proposer, payout); - } else if (state == OptimisticOracleInterface.State.Resolved) { - // In the Resolved case, pay either the disputer or the proposer the entire payout (+ bond and reward). - resolvedPrice = _getOracle().getPrice(identifier, timestamp, _stampAncillaryData(ancillaryData, requester)); - settledRequest.resolvedPrice = resolvedPrice; - bool disputeSuccess = settledRequest.resolvedPrice != request.proposedPrice; - - // Winner gets: - // - Their bond back. - // - The unburned portion of the loser's bond: proposal bond (not including final fee) - burned bond. - // - Their final fee back. - // - The request reward (if not already refunded -- if refunded, it will be set to 0). - payout = request.bond.add(request.bond.sub(_computeBurnedBond(settledRequest))).add(request.finalFee).add( - request.reward - ); - request.currency.safeTransfer(disputeSuccess ? request.disputer : request.proposer, payout); - } else { - revert("Already settled or not settleable"); - } - - _storeRequestHash(requestId, settledRequest); - emit Settle(requester, identifier, timestamp, ancillaryData, settledRequest); - - // Callback. - if (address(requester).isContract()) - try - OptimisticRequester(requester).priceSettled(identifier, timestamp, ancillaryData, settledRequest) - {} catch {} - } - - function _computeBurnedBond(Request memory request) private pure returns (uint256) { - // burnedBond = floor(bond / 2) - return request.bond.div(2); - } - - function _validateLiveness(uint256 liveness) private pure { - require(liveness < 5200 weeks, "Liveness too large"); - require(liveness > 0, "Liveness cannot be 0"); - } - - function _validateRequestHash(bytes32 requestId, Request memory request) private view { - require( - requests[requestId] == _getRequestHash(request), - "Hashed request params do not match existing request hash" - ); - } - - function _storeRequestHash(bytes32 requestId, Request memory request) internal { - requests[requestId] = _getRequestHash(request); - } - - function _getState( - address requester, - bytes32 identifier, - uint32 timestamp, - bytes memory ancillaryData, - Request memory request - ) internal view returns (OptimisticOracleInterface.State) { - // Note: This function does not check whether all of the _request parameter values are correct. For example, - // the request.reward could be any value and it would not impact this function's return value. Therefore, it - // is the caller's responsibility to check that _request matches with the expected ID corresponding to - // {requester, identifier, timestamp, ancillaryData} via _validateRequestHash(). - if (address(request.currency) == address(0)) return OptimisticOracleInterface.State.Invalid; - - if (request.proposer == address(0)) return OptimisticOracleInterface.State.Requested; - - if (request.settled) return OptimisticOracleInterface.State.Settled; - - if (request.disputer == address(0)) - return - request.expirationTime <= getCurrentTime() - ? OptimisticOracleInterface.State.Expired - : OptimisticOracleInterface.State.Proposed; - - return - _getOracle().hasPrice(identifier, timestamp, _stampAncillaryData(ancillaryData, requester)) - ? OptimisticOracleInterface.State.Resolved - : OptimisticOracleInterface.State.Disputed; - } - - function _getOracle() internal view returns (OracleAncillaryInterface) { - return OracleAncillaryInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); - } - - function _getCollateralWhitelist() internal view returns (AddressWhitelist) { - return AddressWhitelist(finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist)); - } - - function _getStore() internal view returns (StoreInterface) { - return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store)); - } - - function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) { - return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); - } - - /** - * @dev We don't handle specifically the case where `ancillaryData` is not already readily translateable in utf8. - * For those cases, we assume that the client will be able to strip out the utf8-translateable part of the - * ancillary data that this contract stamps. - */ - function _stampAncillaryData(bytes memory ancillaryData, address requester) internal pure returns (bytes memory) { - // Since this contract will be the one to formally submit DVM price requests, its useful for voters to know who - // the original requester was. - return AncillaryData.appendKeyValueAddress(ancillaryData, "ooRequester", requester); - } -} - -/** - * @notice This is the SkinnyOptimisticOracle contract that should be deployed on live networks. It is exactly the same - * as the regular SkinnyOptimisticOracle contract, but it overrides getCurrentTime to make the call a simply return - * block.timestamp with no branching or storage queries. - */ -contract SkinnyOptimisticOracleProd is SkinnyOptimisticOracle { - constructor( - uint256 _liveness, - address _finderAddress, - address _timerAddress - ) SkinnyOptimisticOracle(_liveness, _finderAddress, _timerAddress) {} - - function getCurrentTime() public view virtual override returns (uint256) { - return block.timestamp; - } -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v2/test/OptimisticRequesterTest.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v2/test/OptimisticRequesterTest.sol deleted file mode 100644 index bd52fdd20..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v2/test/OptimisticRequesterTest.sol +++ /dev/null @@ -1,139 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; - -import "../implementation/OptimisticOracleV2.sol"; - -// This is just a test contract to make requests to the optimistic oracle. -contract OptimisticRequesterTest is OptimisticRequester { - OptimisticOracleV2 optimisticOracle; - bool public shouldRevert = false; - - // State variables to track incoming calls. - bytes32 public identifier; - uint256 public timestamp; - bytes public ancillaryData; - uint256 public refund; - int256 public price; - - // Implement collateralCurrency so that this contract simulates a financial contract whose collateral - // token can be fetched by off-chain clients. - IERC20 public collateralCurrency; - - // Manually set an expiration timestamp to simulate expiry price requests - uint256 public expirationTimestamp; - - constructor(OptimisticOracleV2 _optimisticOracle) { - optimisticOracle = _optimisticOracle; - } - - function requestPrice( - bytes32 _identifier, - uint256 _timestamp, - bytes memory _ancillaryData, - IERC20 currency, - uint256 reward - ) external { - // Set collateral currency to last requested currency: - collateralCurrency = currency; - - currency.approve(address(optimisticOracle), reward); - optimisticOracle.requestPrice(_identifier, _timestamp, _ancillaryData, currency, reward); - } - - function settleAndGetPrice( - bytes32 _identifier, - uint256 _timestamp, - bytes memory _ancillaryData - ) external returns (int256) { - return optimisticOracle.settleAndGetPrice(_identifier, _timestamp, _ancillaryData); - } - - function setBond(bytes32 _identifier, uint256 _timestamp, bytes memory _ancillaryData, uint256 bond) external { - optimisticOracle.setBond(_identifier, _timestamp, _ancillaryData, bond); - } - - function setRefundOnDispute(bytes32 _identifier, uint256 _timestamp, bytes memory _ancillaryData) external { - optimisticOracle.setRefundOnDispute(_identifier, _timestamp, _ancillaryData); - } - - function setCustomLiveness( - bytes32 _identifier, - uint256 _timestamp, - bytes memory _ancillaryData, - uint256 customLiveness - ) external { - optimisticOracle.setCustomLiveness(_identifier, _timestamp, _ancillaryData, customLiveness); - } - - function setEventBased(bytes32 _identifier, uint256 _timestamp, bytes memory _ancillaryData) external { - optimisticOracle.setEventBased(_identifier, _timestamp, _ancillaryData); - } - - function setCallbacks( - bytes32 _identifier, - uint256 _timestamp, - bytes memory _ancillaryData, - bool _callbackOnPriceProposed, - bool _callbackOnPriceDisputed, - bool _callbackOnPriceSettled - ) external { - optimisticOracle.setCallbacks( - _identifier, - _timestamp, - _ancillaryData, - _callbackOnPriceProposed, - _callbackOnPriceDisputed, - _callbackOnPriceSettled - ); - } - - function setRevert(bool _shouldRevert) external { - shouldRevert = _shouldRevert; - } - - function setExpirationTimestamp(uint256 _expirationTimestamp) external { - expirationTimestamp = _expirationTimestamp; - } - - function clearState() external { - delete identifier; - delete timestamp; - delete refund; - delete price; - } - - function priceProposed(bytes32 _identifier, uint256 _timestamp, bytes memory _ancillaryData) external override { - require(!shouldRevert); - identifier = _identifier; - timestamp = _timestamp; - ancillaryData = _ancillaryData; - } - - function priceDisputed( - bytes32 _identifier, - uint256 _timestamp, - bytes memory _ancillaryData, - uint256 _refund - ) external override { - require(!shouldRevert); - identifier = _identifier; - timestamp = _timestamp; - ancillaryData = _ancillaryData; - refund = _refund; - } - - function priceSettled( - bytes32 _identifier, - uint256 _timestamp, - bytes memory _ancillaryData, - int256 _price - ) external override { - require(!shouldRevert); - identifier = _identifier; - timestamp = _timestamp; - ancillaryData = _ancillaryData; - price = _price; - } -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v2/test/SkinnyOptimisticRequesterTest.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v2/test/SkinnyOptimisticRequesterTest.sol deleted file mode 100644 index 5c5d4b761..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v2/test/SkinnyOptimisticRequesterTest.sol +++ /dev/null @@ -1,126 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; - -import "../../data-verification-mechanism/interfaces/StoreInterface.sol"; -import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; -import "../previous-versions/SkinnyOptimisticOracle.sol"; -import "../../data-verification-mechanism/implementation/Constants.sol"; - -// This is just a test contract to make requests to the optimistic oracle. -contract SkinnyOptimisticRequesterTest { - using SafeMath for uint256; - - SkinnyOptimisticOracle optimisticOracle; - bool public shouldRevert = false; - - // Finder to provide addresses for DVM contracts. - FinderInterface public finder; - - // State variables to track incoming calls. - bytes32 public identifier; - uint32 public timestamp; - bytes public ancillaryData; - SkinnyOptimisticOracle.Request public request; - - // Manually set an expiration timestamp to simulate expiry price requests - uint256 public expirationTimestamp; - - constructor(SkinnyOptimisticOracle _optimisticOracle, FinderInterface _finderAddress) { - optimisticOracle = _optimisticOracle; - finder = _finderAddress; - } - - function requestAndProposePriceFor( - bytes32 _identifier, - uint32 _timestamp, - bytes memory _ancillaryData, - IERC20 currency, - uint256 reward, - uint256 bond, - uint256 customLiveness, - address proposer, - int256 proposedPrice - ) external { - uint256 finalFee = _getStore().computeFinalFee(address(currency)).rawValue; - - currency.approve(address(optimisticOracle), reward.add(bond).add(finalFee)); - optimisticOracle.requestAndProposePriceFor( - _identifier, - _timestamp, - _ancillaryData, - currency, - reward, - bond, - customLiveness, - proposer, - proposedPrice - ); - } - - function requestPrice( - bytes32 _identifier, - uint32 _timestamp, - bytes memory _ancillaryData, - IERC20 currency, - uint256 reward, - uint256 bond, - uint256 customLiveness - ) external { - currency.approve(address(optimisticOracle), reward); - optimisticOracle.requestPrice(_identifier, _timestamp, _ancillaryData, currency, reward, bond, customLiveness); - } - - function setExpirationTimestamp(uint256 _expirationTimestamp) external { - expirationTimestamp = _expirationTimestamp; - } - - function setRevert(bool _shouldRevert) external { - shouldRevert = _shouldRevert; - } - - function priceProposed( - bytes32 _identifier, - uint32 _timestamp, - bytes memory _ancillaryData, - SkinnyOptimisticOracle.Request memory _request - ) external { - require(!shouldRevert); - identifier = _identifier; - timestamp = _timestamp; - ancillaryData = _ancillaryData; - request = _request; - } - - function priceDisputed( - bytes32 _identifier, - uint32 _timestamp, - bytes memory _ancillaryData, - SkinnyOptimisticOracle.Request memory _request - ) external { - require(!shouldRevert); - identifier = _identifier; - timestamp = _timestamp; - ancillaryData = _ancillaryData; - request = _request; - } - - function priceSettled( - bytes32 _identifier, - uint32 _timestamp, - bytes memory _ancillaryData, - SkinnyOptimisticOracle.Request memory _request - ) external { - require(!shouldRevert); - identifier = _identifier; - timestamp = _timestamp; - ancillaryData = _ancillaryData; - request = _request; - } - - function _getStore() internal view returns (StoreInterface) { - return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store)); - } -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v2/test/SkinnyOptimisticV2RequesterTest.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v2/test/SkinnyOptimisticV2RequesterTest.sol deleted file mode 100644 index bb7486a6b..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v2/test/SkinnyOptimisticV2RequesterTest.sol +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; - -import "../../data-verification-mechanism/interfaces/StoreInterface.sol"; -import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; -import "../../data-verification-mechanism/implementation/Constants.sol"; -import "../implementation/SkinnyOptimisticOracleV2.sol"; - -// This is just a test contract to make requests to the optimistic oracle. -contract SkinnyOptimisticV2RequesterTest { - using SafeMath for uint256; - - SkinnyOptimisticOracleV2 optimisticOracle; - bool public shouldRevert = false; - - // Finder to provide addresses for DVM contracts. - FinderInterface public finder; - - // State variables to track incoming calls. - bytes32 public identifier; - uint32 public timestamp; - bytes public ancillaryData; - SkinnyOptimisticOracleV2.Request public request; - - // Manually set an expiration timestamp to simulate expiry price requests - uint256 public expirationTimestamp; - - constructor(SkinnyOptimisticOracleV2 _optimisticOracle, FinderInterface _finderAddress) { - optimisticOracle = _optimisticOracle; - finder = _finderAddress; - } - - function requestAndProposePriceFor( - bytes32 _identifier, - uint32 _timestamp, - bytes memory _ancillaryData, - IERC20 currency, - uint256 reward, - SkinnyOptimisticOracleV2Interface.RequestSettings memory requestSettings, - address proposer, - int256 proposedPrice - ) external { - uint256 finalFee = _getStore().computeFinalFee(address(currency)).rawValue; - - currency.approve(address(optimisticOracle), reward.add(requestSettings.bond).add(finalFee)); - optimisticOracle.requestAndProposePriceFor( - _identifier, - _timestamp, - _ancillaryData, - currency, - reward, - requestSettings, - proposer, - proposedPrice - ); - } - - function requestPrice( - bytes32 _identifier, - uint32 _timestamp, - bytes memory _ancillaryData, - IERC20 currency, - uint256 reward, - SkinnyOptimisticOracleV2Interface.RequestSettings memory requestSettings - ) external { - currency.approve(address(optimisticOracle), reward); - optimisticOracle.requestPrice(_identifier, _timestamp, _ancillaryData, currency, reward, requestSettings); - } - - function setExpirationTimestamp(uint256 _expirationTimestamp) external { - expirationTimestamp = _expirationTimestamp; - } - - function setRevert(bool _shouldRevert) external { - shouldRevert = _shouldRevert; - } - - function priceProposed( - bytes32 _identifier, - uint32 _timestamp, - bytes memory _ancillaryData, - SkinnyOptimisticOracleV2.Request memory _request - ) external { - require(!shouldRevert); - identifier = _identifier; - timestamp = _timestamp; - ancillaryData = _ancillaryData; - request = _request; - } - - function priceDisputed( - bytes32 _identifier, - uint32 _timestamp, - bytes memory _ancillaryData, - SkinnyOptimisticOracleV2.Request memory _request - ) external { - require(!shouldRevert); - identifier = _identifier; - timestamp = _timestamp; - ancillaryData = _ancillaryData; - request = _request; - } - - function priceSettled( - bytes32 _identifier, - uint32 _timestamp, - bytes memory _ancillaryData, - SkinnyOptimisticOracleV2.Request memory _request - ) external { - require(!shouldRevert); - identifier = _identifier; - timestamp = _timestamp; - ancillaryData = _ancillaryData; - request = _request; - } - - function _getStore() internal view returns (StoreInterface) { - return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store)); - } -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/ClaimData.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/ClaimData.sol deleted file mode 100644 index 31698d73d..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/ClaimData.sol +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import { AncillaryData as ClaimData } from "../../common/implementation/AncillaryData.sol"; diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/OptimisticOracleV3.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/OptimisticOracleV3.sol deleted file mode 100644 index c3d5ff1a8..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/OptimisticOracleV3.sol +++ /dev/null @@ -1,488 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "@openzeppelin/contracts-v4/access/Ownable.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; - -import "../interfaces/OptimisticOracleV3CallbackRecipientInterface.sol"; -import "../interfaces/OptimisticOracleV3Interface.sol"; -import "../interfaces/EscalationManagerInterface.sol"; - -import "../../data-verification-mechanism/implementation/Constants.sol"; -import "../../data-verification-mechanism/interfaces/FinderInterface.sol"; -import "../../data-verification-mechanism/interfaces/IdentifierWhitelistInterface.sol"; -import "../../data-verification-mechanism/interfaces/OracleAncillaryInterface.sol"; -import "../../data-verification-mechanism/interfaces/StoreInterface.sol"; - -import "../../common/implementation/AddressWhitelist.sol"; -import "../../common/implementation/AncillaryData.sol"; -import "../../common/implementation/Lockable.sol"; -import "../../common/implementation/MultiCaller.sol"; - -/** - * @title Optimistic Oracle V3. - * @notice The OOv3 is used to assert truths about the world which are verified using an optimistic escalation game. - * @dev Core idea: an asserter makes a statement about a truth, calling "assertTruth". If this statement is not - * challenged, it is taken as the state of the world. If challenged, it is arbitrated using the UMA DVM, or if - * configured, an escalation manager. Escalation managers enable integrations to define their own security properties and - * tradeoffs, enabling the notion of "sovereign security". - */ - -contract OptimisticOracleV3 is OptimisticOracleV3Interface, Lockable, Ownable, MultiCaller { - using SafeERC20 for IERC20; - - FinderInterface public immutable finder; // Finder used to discover other UMA ecosystem contracts. - - // Cached UMA parameters. - address public cachedOracle; - mapping(address => WhitelistedCurrency) public cachedCurrencies; - mapping(bytes32 => bool) public cachedIdentifiers; - - mapping(bytes32 => Assertion) public assertions; // All assertions made by the Optimistic Oracle V3. - - uint256 public burnedBondPercentage; // Percentage of the bond that is paid to the UMA store if the assertion is disputed. - - bytes32 public constant defaultIdentifier = "ASSERT_TRUTH"; - int256 public constant numericalTrue = 1e18; // Numerical representation of true. - IERC20 public defaultCurrency; - uint64 public defaultLiveness; - - /** - * @notice Construct the OptimisticOracleV3 contract. - * @param _finder keeps track of all contracts within the UMA system based on their interfaceName. - * @param _defaultCurrency the default currency to bond asserters in assertTruthWithDefaults. - * @param _defaultLiveness the default liveness for assertions in assertTruthWithDefaults. - */ - constructor(FinderInterface _finder, IERC20 _defaultCurrency, uint64 _defaultLiveness) { - finder = _finder; - setAdminProperties(_defaultCurrency, _defaultLiveness, 0.5e18); - } - - /** - * @notice Sets the default currency, liveness, and burned bond percentage. - * @dev Only callable by the contract owner (UMA governor). - * @param _defaultCurrency the default currency to bond asserters in assertTruthWithDefaults. - * @param _defaultLiveness the default liveness for assertions in assertTruthWithDefaults. - * @param _burnedBondPercentage the percentage of the bond that is sent as fee to UMA Store contract on disputes. - */ - function setAdminProperties( - IERC20 _defaultCurrency, - uint64 _defaultLiveness, - uint256 _burnedBondPercentage - ) public onlyOwner { - require(_burnedBondPercentage <= 1e18, "Burned bond percentage > 100"); - require(_burnedBondPercentage > 0, "Burned bond percentage is 0"); - burnedBondPercentage = _burnedBondPercentage; - defaultCurrency = _defaultCurrency; - defaultLiveness = _defaultLiveness; - syncUmaParams(defaultIdentifier, address(_defaultCurrency)); - - emit AdminPropertiesSet(_defaultCurrency, _defaultLiveness, _burnedBondPercentage); - } - - /** - * @notice Asserts a truth about the world, using the default currency and liveness. No callback recipient or - * escalation manager is enabled. The caller is expected to provide a bond of finalFee/burnedBondPercentage - * (with burnedBondPercentage set to 50%, the bond is 2x final fee) of the default currency. - * @dev The caller must approve this contract to spend at least the result of getMinimumBond(defaultCurrency). - * @param claim the truth claim being asserted. This is an assertion about the world, and is verified by disputers. - * @param asserter account that receives bonds back at settlement. This could be msg.sender or - * any other account that the caller wants to receive the bond at settlement time. - * @return assertionId unique identifier for this assertion. - */ - - function assertTruthWithDefaults(bytes calldata claim, address asserter) external returns (bytes32) { - // Note: re-entrancy guard is done in the inner call. - return - assertTruth( - claim, - asserter, // asserter - address(0), // callbackRecipient - address(0), // escalationManager - defaultLiveness, - defaultCurrency, - getMinimumBond(address(defaultCurrency)), - defaultIdentifier, - bytes32(0) - ); - } - - /** - * @notice Asserts a truth about the world, using a fully custom configuration. - * @dev The caller must approve this contract to spend at least bond amount of currency. - * @param claim the truth claim being asserted. This is an assertion about the world, and is verified by disputers. - * @param asserter account that receives bonds back at settlement. This could be msg.sender or - * any other account that the caller wants to receive the bond at settlement time. - * @param callbackRecipient if configured, this address will receive a function call assertionResolvedCallback and - * assertionDisputedCallback at resolution or dispute respectively. Enables dynamic responses to these events. The - * recipient _must_ implement these callbacks and not revert or the assertion resolution will be blocked. - * @param escalationManager if configured, this address will control escalation properties of the assertion. This - * means a) choosing to arbitrate via the UMA DVM, b) choosing to discard assertions on dispute, or choosing to - * validate disputes. Combining these, the asserter can define their own security properties for the assertion. - * escalationManager also _must_ implement the same callbacks as callbackRecipient. - * @param liveness time to wait before the assertion can be resolved. Assertion can be disputed in this time. - * @param currency bond currency pulled from the caller and held in escrow until the assertion is resolved. - * @param bond amount of currency to pull from the caller and hold in escrow until the assertion is resolved. This - * must be >= getMinimumBond(address(currency)). - * @param identifier UMA DVM identifier to use for price requests in the event of a dispute. Must be pre-approved. - * @param domainId optional domain that can be used to relate this assertion to others in the escalationManager and - * can be used by the configured escalationManager to define custom behavior for groups of assertions. This is - * typically used for "escalation games" by changing bonds or other assertion properties based on the other - * assertions that have come before. If not needed this value should be 0 to save gas. - * @return assertionId unique identifier for this assertion. - */ - function assertTruth( - bytes memory claim, - address asserter, - address callbackRecipient, - address escalationManager, - uint64 liveness, - IERC20 currency, - uint256 bond, - bytes32 identifier, - bytes32 domainId - ) public nonReentrant returns (bytes32 assertionId) { - uint64 time = uint64(getCurrentTime()); - assertionId = _getId(claim, bond, time, liveness, currency, callbackRecipient, escalationManager, identifier); - - require(asserter != address(0), "Asserter cant be 0"); - require(assertions[assertionId].asserter == address(0), "Assertion already exists"); - require(_validateAndCacheIdentifier(identifier), "Unsupported identifier"); - require(_validateAndCacheCurrency(address(currency)), "Unsupported currency"); - require(bond >= getMinimumBond(address(currency)), "Bond amount too low"); - - assertions[assertionId] = Assertion({ - escalationManagerSettings: EscalationManagerSettings({ - arbitrateViaEscalationManager: false, // Default behavior: use the DVM as an oracle. - discardOracle: false, // Default behavior: respect the Oracle result. - validateDisputers: false, // Default behavior: disputer will not be validated. - escalationManager: escalationManager, - assertingCaller: msg.sender - }), - asserter: asserter, - disputer: address(0), - callbackRecipient: callbackRecipient, - currency: currency, - domainId: domainId, - identifier: identifier, - bond: bond, - settled: false, - settlementResolution: false, - assertionTime: time, - expirationTime: time + liveness - }); - - { - EscalationManagerInterface.AssertionPolicy memory assertionPolicy = _getAssertionPolicy(assertionId); - require(!assertionPolicy.blockAssertion, "Assertion not allowed"); // Check if the assertion is permitted. - EscalationManagerSettings storage emSettings = assertions[assertionId].escalationManagerSettings; - (emSettings.arbitrateViaEscalationManager, emSettings.discardOracle, emSettings.validateDisputers) = ( - // Choose which oracle to arbitrate disputes via. If set to true then the escalation manager will - // arbitrate disputes. Else, the DVM arbitrates disputes. This lets integrations "unplug" the DVM. - assertionPolicy.arbitrateViaEscalationManager, - // Choose whether to discard the Oracle result. If true then "throw away" the assertion. To get an - // assertion to be true it must be re-asserted and not disputed. - assertionPolicy.discardOracle, - // Configures if the escalation manager should validate the disputer on assertions. This enables you - // to construct setups such as whitelisted disputers. - assertionPolicy.validateDisputers - ); - } - - currency.safeTransferFrom(msg.sender, address(this), bond); // Pull the bond from the caller. - - emit AssertionMade( - assertionId, - domainId, - claim, - asserter, - callbackRecipient, - escalationManager, - msg.sender, - time + liveness, - currency, - bond, - identifier - ); - } - - /** - * @notice Disputes an assertion. Depending on how the assertion was configured, this may either escalate to the UMA - * DVM or the configured escalation manager for arbitration. - * @dev The caller must approve this contract to spend at least bond amount of currency for the associated assertion. - * @param assertionId unique identifier for the assertion to dispute. - * @param disputer receives bonds back at settlement. - */ - function disputeAssertion(bytes32 assertionId, address disputer) external nonReentrant { - require(disputer != address(0), "Disputer can't be 0"); - Assertion storage assertion = assertions[assertionId]; - require(assertion.asserter != address(0), "Assertion does not exist"); - require(assertion.disputer == address(0), "Assertion already disputed"); - require(assertion.expirationTime > getCurrentTime(), "Assertion is expired"); - require(_isDisputeAllowed(assertionId), "Dispute not allowed"); - - assertion.disputer = disputer; - - assertion.currency.safeTransferFrom(msg.sender, address(this), assertion.bond); - - _oracleRequestPrice(assertionId, assertion.identifier, assertion.assertionTime); - - _callbackOnAssertionDispute(assertionId); - - // Send resolve callback if dispute resolution is discarded - if (assertion.escalationManagerSettings.discardOracle) _callbackOnAssertionResolve(assertionId, false); - - emit AssertionDisputed(assertionId, msg.sender, disputer); - } - - /** - * @notice Resolves an assertion. If the assertion has not been disputed, the assertion is resolved as true and the - * asserter receives the bond. If the assertion has been disputed, the assertion is resolved depending on the oracle - * result. Based on the result, the asserter or disputer receives the bond. If the assertion was disputed then an - * amount of the bond is sent to the UMA Store as an oracle fee based on the burnedBondPercentage. The remainder of - * the bond is returned to the asserter or disputer. - * @param assertionId unique identifier for the assertion to resolve. - */ - function settleAssertion(bytes32 assertionId) public nonReentrant { - Assertion storage assertion = assertions[assertionId]; - require(assertion.asserter != address(0), "Assertion does not exist"); // Revert if assertion does not exist. - require(!assertion.settled, "Assertion already settled"); // Revert if assertion already settled. - assertion.settled = true; - if (assertion.disputer == address(0)) { - // No dispute, settle with the asserter - require(assertion.expirationTime <= getCurrentTime(), "Assertion not expired"); // Revert if not expired. - assertion.settlementResolution = true; - assertion.currency.safeTransfer(assertion.asserter, assertion.bond); - _callbackOnAssertionResolve(assertionId, true); - - emit AssertionSettled(assertionId, assertion.asserter, false, true, msg.sender); - } else { - // Dispute, settle with the disputer. Reverts if price not resolved. - int256 resolvedPrice = _oracleGetPrice(assertionId, assertion.identifier, assertion.assertionTime); - - // If set to discard settlement resolution then false. Else, use oracle value to find resolution. - if (assertion.escalationManagerSettings.discardOracle) assertion.settlementResolution = false; - else assertion.settlementResolution = resolvedPrice == numericalTrue; - - address bondRecipient = resolvedPrice == numericalTrue ? assertion.asserter : assertion.disputer; - - // Calculate oracle fee and the remaining amount of bonds to send to the correct party (asserter or disputer). - uint256 oracleFee = (burnedBondPercentage * assertion.bond) / 1e18; - uint256 bondRecipientAmount = assertion.bond * 2 - oracleFee; - - // Pay out the oracle fee and remaining bonds to the correct party. Note: the oracle fee is sent to the - // Store contract, even if the escalation manager is used to arbitrate disputes. - assertion.currency.safeTransfer(address(_getStore()), oracleFee); - assertion.currency.safeTransfer(bondRecipient, bondRecipientAmount); - - if (!assertion.escalationManagerSettings.discardOracle) - _callbackOnAssertionResolve(assertionId, assertion.settlementResolution); - - emit AssertionSettled(assertionId, bondRecipient, true, assertion.settlementResolution, msg.sender); - } - } - - /** - * @notice Settles an assertion and returns the resolution. - * @param assertionId unique identifier for the assertion to resolve and return the resolution for. - * @return resolution of the assertion. - */ - function settleAndGetAssertionResult(bytes32 assertionId) external returns (bool) { - // Note: re-entrancy guard is done in the inner settleAssertion call. - if (!assertions[assertionId].settled) settleAssertion(assertionId); - return getAssertionResult(assertionId); - } - - /** - * @notice Fetches information about a specific identifier & currency from the UMA contracts and stores a local copy - * of the information within this contract. This is used to save gas when making assertions as we can avoid an - * external call to the UMA contracts to fetch this. - * @param identifier identifier to fetch information for and store locally. - * @param currency currency to fetch information for and store locally. - */ - function syncUmaParams(bytes32 identifier, address currency) public { - cachedOracle = finder.getImplementationAddress(OracleInterfaces.Oracle); - cachedIdentifiers[identifier] = _getIdentifierWhitelist().isIdentifierSupported(identifier); - cachedCurrencies[currency].isWhitelisted = _getCollateralWhitelist().isOnWhitelist(currency); - cachedCurrencies[currency].finalFee = _getStore().computeFinalFee(currency).rawValue; - } - - /** - * @notice Fetches information about a specific assertion and returns it. - * @param assertionId unique identifier for the assertion to fetch information for. - * @return assertion information about the assertion. - */ - function getAssertion(bytes32 assertionId) external view returns (Assertion memory) { - return assertions[assertionId]; - } - - /** - * @notice Fetches the resolution of a specific assertion and returns it. If the assertion has not been settled then - * this will revert. If the assertion was disputed and configured to discard the oracle resolution return false. - * @param assertionId unique identifier for the assertion to fetch the resolution for. - * @return resolution of the assertion. - */ - function getAssertionResult(bytes32 assertionId) public view returns (bool) { - Assertion memory assertion = assertions[assertionId]; - // Return early if not using answer from resolved dispute. - if (assertion.disputer != address(0) && assertion.escalationManagerSettings.discardOracle) return false; - require(assertion.settled, "Assertion not settled"); // Revert if assertion not settled. - return assertion.settlementResolution; - } - - /** - * @notice Returns the current block timestamp. - * @dev Can be overridden to control contract time. - * @return current block timestamp. - */ - function getCurrentTime() public view virtual returns (uint256) { - return block.timestamp; - } - - /** - * @notice Appends information onto an assertionId to construct ancillary data used for dispute resolution. - * @param assertionId unique identifier for the assertion to construct ancillary data for. - * @return ancillaryData stamped assertion information. - */ - function stampAssertion(bytes32 assertionId) public view returns (bytes memory) { - return _stampAssertion(assertionId); - } - - /** - * @notice Returns the minimum bond amount required to make an assertion. This is calculated as the final fee of the - * currency divided by the burnedBondPercentage. If burn percentage is 50% then the min bond is 2x the final fee. - * @param currency currency to calculate the minimum bond for. - * @return minimum bond amount. - */ - function getMinimumBond(address currency) public view returns (uint256) { - uint256 finalFee = cachedCurrencies[currency].finalFee; - return (finalFee * 1e18) / burnedBondPercentage; - } - - // Returns the unique identifier for this assertion. This identifier is used to identify the assertion. - function _getId( - bytes memory claim, - uint256 bond, - uint256 time, - uint64 liveness, - IERC20 currency, - address callbackRecipient, - address escalationManager, - bytes32 identifier - ) internal view returns (bytes32) { - return - keccak256( - abi.encode( - claim, - bond, - time, - liveness, - currency, - callbackRecipient, - escalationManager, - identifier, - msg.sender - ) - ); - } - - // Returns ancillary data for the Oracle request containing assertionId and asserter. - function _stampAssertion(bytes32 assertionId) internal view returns (bytes memory) { - return - AncillaryData.appendKeyValueAddress( - AncillaryData.appendKeyValueBytes32("", "assertionId", assertionId), - "ooAsserter", - assertions[assertionId].asserter - ); - } - - // Returns the Address Whitelist contract to validate the currency. - function _getCollateralWhitelist() internal view returns (AddressWhitelist) { - return AddressWhitelist(finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist)); - } - - // Returns the Identifier Whitelist contract to validate the identifier. - function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) { - return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); - } - - // Returns the Store contract to fetch the final fee. - function _getStore() internal view returns (StoreInterface) { - return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store)); - } - - // Returns the Oracle contract to use on dispute. This can be either UMA DVM or the escalation manager. - function _getOracle(bytes32 assertionId) internal view returns (OracleAncillaryInterface) { - if (assertions[assertionId].escalationManagerSettings.arbitrateViaEscalationManager) - return OracleAncillaryInterface(_getEscalationManager(assertionId)); - return OracleAncillaryInterface(cachedOracle); - } - - // Requests resolving dispute from the Oracle (UMA DVM or escalation manager). - function _oracleRequestPrice(bytes32 assertionId, bytes32 identifier, uint256 time) internal { - _getOracle(assertionId).requestPrice(identifier, time, _stampAssertion(assertionId)); - } - - // Returns the resolved resolution from the Oracle (UMA DVM or escalation manager). - function _oracleGetPrice(bytes32 assertionId, bytes32 identifier, uint256 time) internal view returns (int256) { - return _getOracle(assertionId).getPrice(identifier, time, _stampAssertion(assertionId)); - } - - // Returns the escalation manager address for the assertion. - function _getEscalationManager(bytes32 assertionId) internal view returns (address) { - return assertions[assertionId].escalationManagerSettings.escalationManager; - } - - // Returns the assertion policy parameters from the escalation manager. If no escalation manager is set then return - // default values. - function _getAssertionPolicy( - bytes32 assertionId - ) internal view returns (EscalationManagerInterface.AssertionPolicy memory) { - address em = _getEscalationManager(assertionId); - if (em == address(0)) return EscalationManagerInterface.AssertionPolicy(false, false, false, false); - return EscalationManagerInterface(em).getAssertionPolicy(assertionId); - } - - // Returns whether the dispute is allowed by the escalation manager. If no escalation manager is set or the - // escalation manager is not configured to validate disputers then return true. - function _isDisputeAllowed(bytes32 assertionId) internal view returns (bool) { - if (!assertions[assertionId].escalationManagerSettings.validateDisputers) return true; - address em = assertions[assertionId].escalationManagerSettings.escalationManager; - if (em == address(0)) return true; - return EscalationManagerInterface(em).isDisputeAllowed(assertionId, msg.sender); - } - - // Validates if the identifier is whitelisted by first checking the cache. If not whitelisted in the cache then - // checks it from the identifier whitelist contract and caches result. - function _validateAndCacheIdentifier(bytes32 identifier) internal returns (bool) { - if (cachedIdentifiers[identifier]) return true; - cachedIdentifiers[identifier] = _getIdentifierWhitelist().isIdentifierSupported(identifier); - return cachedIdentifiers[identifier]; - } - - // Validates if the currency is whitelisted by first checking the cache. If not whitelisted in the cache then - // checks it from the collateral whitelist contract and caches whitelist status and final fee. - function _validateAndCacheCurrency(address currency) internal returns (bool) { - if (cachedCurrencies[currency].isWhitelisted) return true; - cachedCurrencies[currency].isWhitelisted = _getCollateralWhitelist().isOnWhitelist(currency); - cachedCurrencies[currency].finalFee = _getStore().computeFinalFee(currency).rawValue; - return cachedCurrencies[currency].isWhitelisted; - } - - // Sends assertion resolved callback to the callback recipient and escalation manager (if set). - function _callbackOnAssertionResolve(bytes32 assertionId, bool assertedTruthfully) internal { - address cr = assertions[assertionId].callbackRecipient; - address em = _getEscalationManager(assertionId); - if (cr != address(0)) - OptimisticOracleV3CallbackRecipientInterface(cr).assertionResolvedCallback(assertionId, assertedTruthfully); - if (em != address(0)) EscalationManagerInterface(em).assertionResolvedCallback(assertionId, assertedTruthfully); - } - - // Sends assertion disputed callback to the callback recipient and escalation manager (if set). - function _callbackOnAssertionDispute(bytes32 assertionId) internal { - address cr = assertions[assertionId].callbackRecipient; - address em = _getEscalationManager(assertionId); - if (cr != address(0)) OptimisticOracleV3CallbackRecipientInterface(cr).assertionDisputedCallback(assertionId); - if (em != address(0)) EscalationManagerInterface(em).assertionDisputedCallback(assertionId); - } -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/BaseEscalationManager.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/BaseEscalationManager.sol deleted file mode 100644 index 5158f4b08..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/BaseEscalationManager.sol +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "../../interfaces/EscalationManagerInterface.sol"; -import "../../interfaces/OptimisticOracleV3Interface.sol"; - -/** - * @title BaseEscalationManager - * @notice Base contract for escalation managers. This contract is responsible for managing the escalation policy for - * assertions. This base implementation simply exposes the required interface and provides a default implementation - * (returning default values or doing nothing). - */ -contract BaseEscalationManager is EscalationManagerInterface { - OptimisticOracleV3Interface public immutable optimisticOracleV3; - - event PriceRequestAdded(bytes32 indexed identifier, uint256 time, bytes ancillaryData); - - /** - * @notice Reverts unless the configured Optimistic Oracle V3 is the caller. - */ - modifier onlyOptimisticOracleV3() { - require(msg.sender == address(optimisticOracleV3), "Not the Optimistic Oracle V3"); - _; - } - - /** - * @notice Constructs the escalation manager. - * @param _optimisticOracleV3 the Optimistic Oracle V3 to use. - */ - constructor(address _optimisticOracleV3) { - optimisticOracleV3 = OptimisticOracleV3Interface(_optimisticOracleV3); - } - - /** - * @notice Returns the assertion policy for the given assertionId. - * @param assertionId the assertionId to get the assertion policy for. - * @return the assertion policy for the given assertionId. - */ - function getAssertionPolicy(bytes32 assertionId) public view virtual returns (AssertionPolicy memory) { - return - AssertionPolicy({ - blockAssertion: false, - arbitrateViaEscalationManager: false, - discardOracle: false, - validateDisputers: false - }); - } - - /** - * @notice Callback function that is called by Optimistic Oracle V3 when an assertion is disputed. Used to validate - * if the dispute should be allowed based on the escalation policy. - * @param assertionId the assertionId to validate the dispute for. - * @param disputeCaller the caller of the dispute function. - * @return bool true if the dispute is allowed, false otherwise. - */ - function isDisputeAllowed(bytes32 assertionId, address disputeCaller) public view virtual returns (bool) { - return true; - } - - /** - * @notice Implements price getting logic. This method is called by Optimistic Oracle V3 settling an assertion that - * is configured to use the escalation manager as the oracle. The interface is constructed to mimic the UMA DVM. - * @param identifier price identifier being requested. - * @param time timestamp of the price being requested. - * @param ancillaryData ancillary data of the price being requested. - * @return price from the escalation manager to inform the resolution of the dispute. - */ - function getPrice( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) public view virtual returns (int256) {} - - /** - * @notice Implements price requesting logic for the escalation manager. This function is called by the Optimistic - * Oracle V3 on dispute and is constructed to mimic that of the UMA DVM interface. - * @param identifier the identifier to fetch the price for. - * @param time the time to fetch the price for. - * @param ancillaryData ancillary data of the price being requested. - */ - function requestPrice( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) public virtual onlyOptimisticOracleV3 { - emit PriceRequestAdded(identifier, time, ancillaryData); - } - - /** - * @notice Callback function that is called by Optimistic Oracle V3 when an assertion is resolved. - * @param assertionId The identifier of the assertion that was resolved. - * @param assertedTruthfully Whether the assertion was resolved as truthful or not. - */ - function assertionResolvedCallback( - bytes32 assertionId, - bool assertedTruthfully - ) public virtual onlyOptimisticOracleV3 {} - - /** - * @notice Callback function that is called by Optimistic Oracle V3 when an assertion is disputed. - * @param assertionId The identifier of the assertion that was disputed. - */ - function assertionDisputedCallback(bytes32 assertionId) public virtual onlyOptimisticOracleV3 {} -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/DisputeLimitingEscalationManager.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/DisputeLimitingEscalationManager.sol deleted file mode 100644 index 35ee59ac4..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/DisputeLimitingEscalationManager.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "@openzeppelin/contracts-v4/access/Ownable.sol"; -import "./BaseEscalationManager.sol"; - -// This Escalation Manager blocks all assertions till the blocking dispute is resolved by Oracle. In order to avoid -// interference among different applications this Escalation Manager allows assertions only from one requesting contract. -// This is useful to create a system where only one assertion dispute can occur at a time. -contract DisputeLimitingEscalationManager is BaseEscalationManager, Ownable { - // Address of linked requesting contract. Before this is set via setAssertingCaller all assertions will be blocked. - address public assertingCaller; - - bytes32 public disputedAssertionId; - - event AssertingCallerSet(address indexed assertingCaller); - - constructor(address _optimisticOracleV3) BaseEscalationManager(_optimisticOracleV3) {} - - // Set the address of the contract that will be allowed to use Optimistic Oracle V3. - // This can only be set once. We do not set this at constructor just to allow for some flexibility in the ordering - // of how contracts are deployed. - function setAssertingCaller(address _assertingCaller) public onlyOwner { - require(_assertingCaller != address(0), "Invalid asserting caller"); - require(assertingCaller == address(0), "Asserting caller already set"); - assertingCaller = _assertingCaller; - emit AssertingCallerSet(_assertingCaller); - } - - function getAssertionPolicy(bytes32 assertionId) public view override returns (AssertionPolicy memory) { - return - AssertionPolicy({ - blockAssertion: _checkIfShouldBlockAssertion(assertionId), - arbitrateViaEscalationManager: false, - discardOracle: false, - validateDisputers: false - }); - } - - // Callback function that is called by Optimistic Oracle V3 when an assertion is disputed. - function assertionDisputedCallback(bytes32 assertionId) public override onlyOptimisticOracleV3 { - // Only apply new assertion block if the dispute is related to the linked client contract. - if (optimisticOracleV3.getAssertion(assertionId).escalationManagerSettings.assertingCaller == assertingCaller) { - disputedAssertionId = assertionId; - } - } - - // Callback function that is called by Optimistic Oracle V3 when an assertion is resolved. - function assertionResolvedCallback(bytes32 assertionId, bool) public override onlyOptimisticOracleV3 { - // Remove assertion block if the disputed assertion was resolved. - if (assertionId == disputedAssertionId) disputedAssertionId = bytes32(0); - } - - function _checkIfShouldBlockAssertion(bytes32 assertionId) internal view returns (bool) { - OptimisticOracleV3Interface.Assertion memory assertion = optimisticOracleV3.getAssertion(assertionId); - if (assertion.escalationManagerSettings.assertingCaller != assertingCaller) return true; // Only allow assertions through linked client contract. - return disputedAssertionId != bytes32(0); // Block if there is outstanding dispute. - } -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/FullPolicyEscalationManager.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/FullPolicyEscalationManager.sol deleted file mode 100644 index 0a328e3ef..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/FullPolicyEscalationManager.sol +++ /dev/null @@ -1,222 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "@openzeppelin/contracts-v4/access/Ownable.sol"; -import "./BaseEscalationManager.sol"; - -/** - * @title The FullPolicyEscalationManager enables the owner to configure all policy parameters and store the arbitration - * resolutions for the Escalation Manager. Optionally, assertion blocking can be enabled using a whitelist of - * assertingCallers or assertingCallers and asserters. On the other hand, it enables the determination of whether to - * arbitrate via the escalation manager as opposed to the DVM, whether to disregard the resolution of a potential - * dispute arbitrated by the Oracle, and whether to restrict who can register disputes via whitelistedDisputeCallers. - * @dev If nothing is configured using the setters and configureEscalationManager method upon deployment, the - * FullPolicyEscalationManager will return a default policy with all values set to false. - */ -contract FullPolicyEscalationManager is BaseEscalationManager, Ownable { - // Struct to store the arbitration resolution for a given identifier, time, and ancillary data. - struct ArbitrationResolution { - bool valueSet; // True if the resolution has been set. - bool resolution; // True or false depending on the resolution. - } - - event EscalationManagerConfigured( - bool blockByAssertingCaller, - bool blockByAsserter, - bool validateDisputers, - bool arbitrateViaEscalationManager, - bool discardOracle - ); - - event ArbitrationResolutionSet(bytes32 indexed identifier, uint256 time, bytes ancillaryData, bool resolution); - - event DisputeCallerWhitelistSet(address indexed disputeCaller, bool whitelisted); - - event AssertingCallerWhitelistSet(address indexed assertingCaller, bool whitelisted); - - event AsserterWhitelistSet(address indexed asserter, bool whitelisted); - - int256 public constant numericalTrue = 1e18; // Numerical representation of true. - - bool public blockByAssertingCaller; // True if assertions are allowed only by whitelisted asserting callers. - - bool public blockByAsserter; // True if assertions are allowed only by whitelisted asserters. - - bool public arbitrateViaEscalationManager; // True if it is determined that the escalation manager should arbitrate. - - bool public discardOracle; // True if escalation manager should disregard the Oracle's resolution. - - bool public validateDisputers; // True if escalation manager should validate disputers via whitelistedDisputeCallers. - - mapping(bytes32 => ArbitrationResolution) public arbitrationResolutions; // Arbitration resolutions for a given identifier, time, and ancillary data. - - mapping(address => bool) public whitelistedDisputeCallers; // Whitelisted disputer that can file disputes. - - mapping(address => bool) public whitelistedAssertingCallers; // Whitelisted assertingCallers that can assert prices. - - mapping(address => bool) public whitelistedAsserters; // Whitelisted asserters that can assert prices. - - /** - * @notice Constructs the escalation manager. - * @param _optimisticOracleV3 the Optimistic Oracle V3 to use. - */ - constructor(address _optimisticOracleV3) BaseEscalationManager(_optimisticOracleV3) {} - - /** - * @notice Returns the Assertion Policy defined by this contract's parameters and functions. - * @param assertionId the ID of the assertion to get the policy for. - * @return the Assertion Policy defined by this contract's parameters and functions. - * @dev If no configuration is done after deployment, this function returns an all false default policy. - */ - function getAssertionPolicy(bytes32 assertionId) public view override returns (AssertionPolicy memory) { - bool blocked = _checkIfAssertionBlocked(assertionId); - return - AssertionPolicy({ - blockAssertion: blocked, // Block assertion if it is blocked. - arbitrateViaEscalationManager: arbitrateViaEscalationManager, // Arbitrate via escalation manager if configured. - discardOracle: discardOracle, // Ignore Oracle (DVM or EM) resolution if configured. - validateDisputers: validateDisputers // Validate disputers if configured. - }); - } - - /** - * @notice Gets the price for identifier and time if it has already been requested and resolved. - * @dev If the price is not available, the method reverts. - * @param identifier uniquely identifies the price requested. - * @param time unix timestamp of the price request. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - * @return int256 representing the resolved price for the given identifier and timestamp. - * @dev This function replicates the interface of the corresponding DVM function to allow the user to use his own - * dispute arbitration system when arbitrating via the escalation manager in a DVM-compatible manner. Refer to the - * UMA Voting and VotingV2 contracts for further details. - */ - function getPrice( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) public view override returns (int256) { - bytes32 requestId = getRequestId(identifier, time, ancillaryData); - require(arbitrationResolutions[requestId].valueSet, "Arbitration resolution not set"); - if (arbitrationResolutions[requestId].resolution) return numericalTrue; - return 0; - } - - /** - * @notice Returns, given an assertionId and a disputerCaller address, if the disputerCaller is authorised to - * dispute the assertion. - * @param assertionId the ID of the assertion to check the disputerCaller for. - * @param disputeCaller the address of the disputeCaller to check. - * @return true if the disputerCaller is authorised to dispute the assertion. - * @dev In order for this function to be used by the Optimistic Oracle V3, validateDisputers must be set to true. - */ - function isDisputeAllowed(bytes32 assertionId, address disputeCaller) public view override returns (bool) { - return whitelistedDisputeCallers[disputeCaller]; - } - - /** - * @notice Defines how the assertion policy for each configuration's rules is to be defined. - * @param _blockByAssertingCaller true if assertions are allowed only by whitelisted asserting callers. - * @param _blockByAsserter true if assertions are allowed only by whitelisted asserters. - * @param _validateDisputers true if the escalation manager should validate disputers via whitelistedDisputeCallers. - * @param _arbitrateViaEscalationManager true if the escalation manager should arbitrate instead of the DVM. - * @param _discardOracle true if the escalation manager should disregard the Oracle's (DVM or EM) resolution. - * @dev This setting just activates the rules that will be executed; each rule must additionally be defined using - * the other functions. - */ - function configureEscalationManager( - bool _blockByAssertingCaller, - bool _blockByAsserter, - bool _validateDisputers, - bool _arbitrateViaEscalationManager, - bool _discardOracle - ) public onlyOwner { - require(!_blockByAsserter || _blockByAssertingCaller, "Cannot block only by asserter"); - blockByAssertingCaller = _blockByAssertingCaller; - blockByAsserter = _blockByAsserter; - validateDisputers = _validateDisputers; - arbitrateViaEscalationManager = _arbitrateViaEscalationManager; - discardOracle = _discardOracle; - emit EscalationManagerConfigured( - _blockByAssertingCaller, - _blockByAsserter, - _validateDisputers, - _arbitrateViaEscalationManager, - _discardOracle - ); - } - - /** - * @notice Set the arbitration resolution for a given identifier, time, and ancillary data. - * @param identifier uniquely identifies the price requested. - * @param time unix timestamp of the price request. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - * @param arbitrationResolution true if the assertion should be resolved as true, false otherwise. - * @dev The owner should use this function whenever a dispute arises and it should be arbitrated by the Escalation - * Manager; it is up to the owner to determine how to resolve the dispute. See the requestPrice implementation in - * BaseEscalationManager, which escalates a dispute to the Escalation Manager for resolution. - */ - function setArbitrationResolution( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData, - bool arbitrationResolution - ) public onlyOwner { - bytes32 requestId = getRequestId(identifier, time, ancillaryData); - require(arbitrationResolutions[requestId].valueSet == false, "Arbitration already resolved"); - arbitrationResolutions[requestId] = ArbitrationResolution(true, arbitrationResolution); - emit ArbitrationResolutionSet(identifier, time, ancillaryData, arbitrationResolution); - } - - /** - * @notice Adds/removes a disputeCaller to the whitelist of disputers that can file disputes. - * @param disputeCaller the address of the disputeCaller to add. - * @param value true represents adding and false represents removing the disputeCaller from the whitelist. - * @dev This function is only used if validateDisputers is set to true. - */ - function setDisputeCallerInWhitelist(address disputeCaller, bool value) public onlyOwner { - whitelistedDisputeCallers[disputeCaller] = value; - emit DisputeCallerWhitelistSet(disputeCaller, value); - } - - /** - * @notice Adds/removes an asserter to the whitelist of assertingCallers that can make assertions. - * @param assertingCaller the address of the assertingCaller to add. - * @param value true represents adding and false represents removing the assertingCaller from the whitelist. - */ - function setWhitelistedAssertingCallers(address assertingCaller, bool value) public onlyOwner { - whitelistedAssertingCallers[assertingCaller] = value; - emit AssertingCallerWhitelistSet(assertingCaller, value); - } - - /** - * @notice Adds/removes an asserter to the whitelist of asserters that can make assertions. - * @param asserter the address of the asserter to add. - * @param value true represents adding and false represents removing the asserter from the whitelist. - * @dev This function must be used in conjunction with setWhitelistedAssertingCallers in order to have an effect. - */ - function setWhitelistedAsserters(address asserter, bool value) public onlyOwner { - whitelistedAsserters[asserter] = value; - emit AsserterWhitelistSet(asserter, value); - } - - /** - * @notice Calculates price request identifier for a given identifier, time, and ancillary data. - * @param identifier uniquely identifies the price requested. - * @param time unix timestamp of the price request. - * @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller. - * @return price request identifier. - */ - function getRequestId(bytes32 identifier, uint256 time, bytes memory ancillaryData) public pure returns (bytes32) { - return keccak256(abi.encode(identifier, time, ancillaryData)); - } - - // Checks if an assertion is blocked depending on the blockByAssertingCaller / blockByAsserter settings and the - // assertion's properties. - function _checkIfAssertionBlocked(bytes32 assertionId) internal view returns (bool) { - OptimisticOracleV3Interface.Assertion memory assertion = optimisticOracleV3.getAssertion(assertionId); - return - (blockByAssertingCaller && - !whitelistedAssertingCallers[assertion.escalationManagerSettings.assertingCaller]) || - (blockByAsserter && !whitelistedAsserters[assertion.asserter]); - } -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/OwnerDiscardOracleEscalationManager.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/OwnerDiscardOracleEscalationManager.sol deleted file mode 100644 index 7cfd84ae7..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/OwnerDiscardOracleEscalationManager.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "@openzeppelin/contracts-v4/access/Ownable.sol"; -import "./BaseEscalationManager.sol"; - -contract OwnerDiscardOracleEscalationManager is BaseEscalationManager, Ownable { - bool public discardOracle; - - constructor(address _optimisticOracleV3) BaseEscalationManager(_optimisticOracleV3) {} - - function setDiscardOracle(bool value) public onlyOwner { - discardOracle = value; - } - - function getAssertionPolicy(bytes32 assertionId) public view override returns (AssertionPolicy memory) { - return - AssertionPolicy({ - blockAssertion: false, - arbitrateViaEscalationManager: false, - discardOracle: discardOracle, - validateDisputers: false - }); - } -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/OwnerSelectOracleEscalationManager.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/OwnerSelectOracleEscalationManager.sol deleted file mode 100644 index eb8cc2959..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/OwnerSelectOracleEscalationManager.sol +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "@openzeppelin/contracts-v4/access/Ownable.sol"; -import "./BaseEscalationManager.sol"; - -contract OwnerSelectOracleEscalationManager is BaseEscalationManager, Ownable { - struct ArbitrationResolution { - bool valueSet; - bool resolution; - } - - mapping(bytes32 => ArbitrationResolution) arbitrationResolutions; - - bool arbitrateViaEscalationManager; - - constructor(address _optimisticOracleV3) BaseEscalationManager(_optimisticOracleV3) {} - - function setArbitrationResolution( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData, - bool arbitrationResolution - ) public onlyOwner { - bytes32 requestId = keccak256(abi.encode(identifier, time, ancillaryData)); - arbitrationResolutions[requestId] = ArbitrationResolution(true, arbitrationResolution); - } - - function setArbitrateViaSs(bool value) public onlyOwner { - arbitrateViaEscalationManager = value; - } - - function getAssertionPolicy(bytes32 assertionId) public view override returns (AssertionPolicy memory) { - return - AssertionPolicy({ - blockAssertion: false, - arbitrateViaEscalationManager: arbitrateViaEscalationManager, - discardOracle: false, - validateDisputers: false - }); - } - - function getPrice( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) public view override returns (int256) { - bytes32 requestId = keccak256(abi.encode(identifier, time, ancillaryData)); - require(arbitrationResolutions[requestId].valueSet, "Arbitration resolution not set"); - if (arbitrationResolutions[requestId].resolution) return 1e18; - return 0; - } -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/SuperbondEscalationManager.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/SuperbondEscalationManager.sol deleted file mode 100644 index be79d4e5e..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/SuperbondEscalationManager.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "./BaseEscalationManager.sol"; -import "@openzeppelin/contracts-v4/access/Ownable.sol"; - -// This EscalationManager allows to arbitrate for each assertion based on its bond via the DVM or the EscalationManager. -// If the bond is greater than the superbond, it is arbitrated automatically through the EscalationManager; otherwise, -// it is arbitrated through the DVM. -contract SuperbondEscalationManager is BaseEscalationManager, Ownable { - uint256 public superbond; - address public superbondCurrency; - - constructor(address _optimisticOracleV3) BaseEscalationManager(_optimisticOracleV3) {} - - function setSuperbond(uint256 newSuperbond) public onlyOwner { - superbond = newSuperbond; - } - - function setSuperbondCurrency(address newSuperbondCurrency) public onlyOwner { - superbondCurrency = newSuperbondCurrency; - } - - function getAssertionPolicy(bytes32 assertionId) public view override returns (AssertionPolicy memory) { - OptimisticOracleV3Interface.Assertion memory assertion = optimisticOracleV3.getAssertion(assertionId); - bool isSuperbondCurrency = address(assertion.currency) == superbondCurrency; - return - AssertionPolicy({ - blockAssertion: false, - arbitrateViaEscalationManager: isSuperbondCurrency ? assertion.bond > superbond : false, - discardOracle: false, - validateDisputers: false - }); - } -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistAsserterEscalationManager.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistAsserterEscalationManager.sol deleted file mode 100644 index 55dfece9c..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistAsserterEscalationManager.sol +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "@openzeppelin/contracts-v4/access/Ownable.sol"; -import "./BaseEscalationManager.sol"; - -contract WhitelistAsserterEscalationManager is BaseEscalationManager, Ownable { - // Address of linked requesting contract. Before this is set via setAssertingCaller all assertions will be blocked. - // Security of returning correct policy depends on requesting contract passing msg.sender as asserter. - address public assertingCaller; - - mapping(address => bool) public whitelistedAsserters; - - event AssertingCallerSet(address indexed assertingCaller); - - constructor(address _optimisticOracleV3) BaseEscalationManager(_optimisticOracleV3) {} - - // Set the address of the contract that will be allowed to use Optimistic Oracle V3. - // This can only be set once. We do not set this at constructor just to allow for some flexibility in the ordering - // of how contracts are deployed. - function setAssertingCaller(address _assertingCaller) public onlyOwner { - require(_assertingCaller != address(0), "Invalid asserting caller"); - require(assertingCaller == address(0), "Asserting caller already set"); - assertingCaller = _assertingCaller; - emit AssertingCallerSet(_assertingCaller); - } - - function setAsserterInWhitelist(address asserter, bool value) public onlyOwner { - whitelistedAsserters[asserter] = value; - } - - function getAssertionPolicy(bytes32 assertionId) public view override returns (AssertionPolicy memory) { - OptimisticOracleV3Interface.Assertion memory assertion = optimisticOracleV3.getAssertion(assertionId); - bool blocked = _checkIfAssertionBlocked(assertion); - return - AssertionPolicy({ - blockAssertion: blocked, - arbitrateViaEscalationManager: false, - discardOracle: false, - validateDisputers: false - }); - } - - function _checkIfAssertionBlocked( - OptimisticOracleV3Interface.Assertion memory assertion - ) internal view returns (bool) { - if (assertion.escalationManagerSettings.assertingCaller != assertingCaller) return true; // Only allow assertions through linked client contract. - return !whitelistedAsserters[assertion.asserter]; // Return if asserter is not whitelisted. - } -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistCallerEscalationManager.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistCallerEscalationManager.sol deleted file mode 100644 index b83af10a4..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistCallerEscalationManager.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "@openzeppelin/contracts-v4/access/Ownable.sol"; -import "./BaseEscalationManager.sol"; - -contract WhitelistCallerEscalationManager is BaseEscalationManager, Ownable { - mapping(address => bool) whitelistedAssertingCallers; - - constructor(address _optimisticOracleV3) BaseEscalationManager(_optimisticOracleV3) {} - - function setAssertingCallerInWhitelist(address assertingCaller, bool value) public onlyOwner { - whitelistedAssertingCallers[assertingCaller] = value; - } - - function getAssertionPolicy(bytes32 assertionId) public view override returns (AssertionPolicy memory) { - return - AssertionPolicy({ - blockAssertion: !whitelistedAssertingCallers[ - optimisticOracleV3.getAssertion(assertionId).escalationManagerSettings.assertingCaller - ], - arbitrateViaEscalationManager: false, - discardOracle: false, - validateDisputers: false - }); - } -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistDisputerEscalationManager.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistDisputerEscalationManager.sol deleted file mode 100644 index ea1c73407..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/escalation-manager/WhitelistDisputerEscalationManager.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "@openzeppelin/contracts-v4/access/Ownable.sol"; -import "./BaseEscalationManager.sol"; - -contract WhitelistDisputerEscalationManager is BaseEscalationManager, Ownable { - mapping(address => bool) whitelistedDisputeCallers; - - constructor(address _optimisticOracleV3) BaseEscalationManager(_optimisticOracleV3) {} - - function getAssertionPolicy(bytes32 assertionId) public view override returns (AssertionPolicy memory) { - return - AssertionPolicy({ - blockAssertion: false, - arbitrateViaEscalationManager: false, - discardOracle: false, - validateDisputers: true - }); - } - - function setDisputeCallerInWhitelist(address disputeCaller, bool value) public onlyOwner { - whitelistedDisputeCallers[disputeCaller] = value; - } - - function isDisputeAllowed(bytes32 assertionId, address disputeCaller) public view override returns (bool) { - return whitelistedDisputeCallers[disputeCaller]; - } -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/DataAsserter.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/DataAsserter.sol deleted file mode 100644 index 853feb44c..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/DataAsserter.sol +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../implementation/ClaimData.sol"; -import "../../interfaces/OptimisticOracleV3Interface.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; - -// This contract allows assertions on any form of data to be made using the UMA Optimistic Oracle V3 and stores the -// proposed value so that it may be retrieved on chain. The dataId is intended to be an arbitrary value that uniquely -// identifies a specific piece of information in the consuming contract and is replaceable. Similarly, any data -// structure can be used to replace the asserted data. -contract DataAsserter { - using SafeERC20 for IERC20; - IERC20 public immutable defaultCurrency; - OptimisticOracleV3Interface public immutable oo; - uint64 public constant assertionLiveness = 7200; - bytes32 public immutable defaultIdentifier; - - struct DataAssertion { - bytes32 dataId; // The dataId that was asserted. - bytes32 data; // This could be an arbitrary data type. - address asserter; // The address that made the assertion. - bool resolved; // Whether the assertion has been resolved. - } - - mapping(bytes32 => DataAssertion) public assertionsData; - - event DataAsserted(bytes32 indexed dataId, bytes32 data, address indexed asserter, bytes32 assertionId); - - event DataAssertionResolved(bytes32 indexed dataId, bytes32 data, address indexed asserter, bytes32 assertionId); - - constructor(address _defaultCurrency, address _optimisticOracleV3) { - defaultCurrency = IERC20(_defaultCurrency); - oo = OptimisticOracleV3Interface(_optimisticOracleV3); - defaultIdentifier = oo.defaultIdentifier(); - } - - // For a given assertionId, returns a boolean indicating whether the data is accessible and the data itself. - function getData(bytes32 assertionId) public view returns (bool, bytes32) { - if (!assertionsData[assertionId].resolved) return (false, 0); - return (true, assertionsData[assertionId].data); - } - - // Asserts data for a specific dataId on behalf of an asserter address. - // Data can be asserted many times with the same combination of arguments, resulting in unique assertionIds. This is - // because the block.timestamp is included in the claim. The consumer contract must store the returned assertionId - // identifiers to able to get the information using getData. - function assertDataFor(bytes32 dataId, bytes32 data, address asserter) public returns (bytes32 assertionId) { - asserter = asserter == address(0) ? msg.sender : asserter; - uint256 bond = oo.getMinimumBond(address(defaultCurrency)); - defaultCurrency.safeTransferFrom(msg.sender, address(this), bond); - defaultCurrency.safeApprove(address(oo), bond); - - // The claim we want to assert is the first argument of assertTruth. It must contain all of the relevant - // details so that anyone may verify the claim without having to read any further information on chain. As a - // result, the claim must include both the data id and data, as well as a set of instructions that allow anyone - // to verify the information in publicly available sources. - // See the UMIP corresponding to the defaultIdentifier used in the OptimisticOracleV3 "ASSERT_TRUTH" for more - // information on how to construct the claim. - assertionId = oo.assertTruth( - abi.encodePacked( - "Data asserted: 0x", // in the example data is type bytes32 so we add the hex prefix 0x. - ClaimData.toUtf8Bytes(data), - " for dataId: 0x", - ClaimData.toUtf8Bytes(dataId), - " and asserter: 0x", - ClaimData.toUtf8BytesAddress(asserter), - " at timestamp: ", - ClaimData.toUtf8BytesUint(block.timestamp), - " in the DataAsserter contract at 0x", - ClaimData.toUtf8BytesAddress(address(this)), - " is valid." - ), - asserter, - address(this), - address(0), // No sovereign security. - assertionLiveness, - defaultCurrency, - bond, - defaultIdentifier, - bytes32(0) // No domain. - ); - assertionsData[assertionId] = DataAssertion(dataId, data, asserter, false); - emit DataAsserted(dataId, data, asserter, assertionId); - } - - // OptimisticOracleV3 resolve callback. - function assertionResolvedCallback(bytes32 assertionId, bool assertedTruthfully) public { - require(msg.sender == address(oo)); - // If the assertion was true, then the data assertion is resolved. - if (assertedTruthfully) { - assertionsData[assertionId].resolved = true; - DataAssertion memory dataAssertion = assertionsData[assertionId]; - emit DataAssertionResolved(dataAssertion.dataId, dataAssertion.data, dataAssertion.asserter, assertionId); - // Else delete the data assertion if it was false to save gas. - } else delete assertionsData[assertionId]; - } - - // If assertion is disputed, do nothing and wait for resolution. - // This OptimisticOracleV3 callback function needs to be defined so the OOv3 doesn't revert when it tries to call it. - function assertionDisputedCallback(bytes32 assertionId) public {} -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/Insurance.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/Insurance.sol deleted file mode 100644 index b4eb9e2f9..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/Insurance.sol +++ /dev/null @@ -1,109 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../implementation/ClaimData.sol"; -import "../../interfaces/OptimisticOracleV3Interface.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; - -// This Isurance contract enables for the issuance of a single unlimited time policy per event/payout recipient There is -// no limit to the number of payout requests that can be made of the same policy; however, only the first asserted -// request will settle the insurance payment, whereas OOv3 will settle bonds for all requestors. -contract Insurance { - using SafeERC20 for IERC20; - IERC20 public immutable defaultCurrency; - OptimisticOracleV3Interface public immutable oo; - uint64 public constant assertionLiveness = 7200; - bytes32 public immutable defaultIdentifier; - - struct Policy { - uint256 insuranceAmount; - address payoutAddress; - bytes insuredEvent; - bool settled; - } - - mapping(bytes32 => bytes32) public assertedPolicies; - - mapping(bytes32 => Policy) public policies; - - event InsuranceIssued( - bytes32 indexed policyId, - bytes insuredEvent, - uint256 insuranceAmount, - address indexed payoutAddress - ); - - event InsurancePayoutRequested(bytes32 indexed policyId, bytes32 indexed assertionId); - - event InsurancePayoutSettled(bytes32 indexed policyId, bytes32 indexed assertionId); - - constructor(address _defaultCurrency, address _optimisticOracleV3) { - defaultCurrency = IERC20(_defaultCurrency); - oo = OptimisticOracleV3Interface(_optimisticOracleV3); - defaultIdentifier = oo.defaultIdentifier(); - } - - function issueInsurance( - uint256 insuranceAmount, - address payoutAddress, - bytes memory insuredEvent - ) public returns (bytes32 policyId) { - policyId = keccak256(abi.encode(insuredEvent, payoutAddress)); - require(policies[policyId].payoutAddress == address(0), "Policy already exists"); - policies[policyId] = Policy({ - insuranceAmount: insuranceAmount, - payoutAddress: payoutAddress, - insuredEvent: insuredEvent, - settled: false - }); - defaultCurrency.safeTransferFrom(msg.sender, address(this), insuranceAmount); - emit InsuranceIssued(policyId, insuredEvent, insuranceAmount, payoutAddress); - } - - function requestPayout(bytes32 policyId) public returns (bytes32 assertionId) { - require(policies[policyId].payoutAddress != address(0), "Policy does not exist"); - uint256 bond = oo.getMinimumBond(address(defaultCurrency)); - defaultCurrency.safeTransferFrom(msg.sender, address(this), bond); - defaultCurrency.safeApprove(address(oo), bond); - assertionId = oo.assertTruth( - abi.encodePacked( - "Insurance contract is claiming that insurance event ", - policies[policyId].insuredEvent, - " had occurred as of ", - ClaimData.toUtf8BytesUint(block.timestamp), - "." - ), - msg.sender, - address(this), - address(0), // No sovereign security. - assertionLiveness, - defaultCurrency, - bond, - defaultIdentifier, - bytes32(0) // No domain. - ); - assertedPolicies[assertionId] = policyId; - emit InsurancePayoutRequested(policyId, assertionId); - } - - function assertionResolvedCallback(bytes32 assertionId, bool assertedTruthfully) public { - require(msg.sender == address(oo)); - // If the assertion was true, then the policy is settled. - if (assertedTruthfully) { - _settlePayout(assertionId); - } - } - - function assertionDisputedCallback(bytes32 assertionId) public {} - - function _settlePayout(bytes32 assertionId) internal { - // If already settled, do nothing. We don't revert because this function is called by the - // OptimisticOracleV3, which may block the assertion resolution. - bytes32 policyId = assertedPolicies[assertionId]; - Policy storage policy = policies[policyId]; - if (policy.settled) return; - policy.settled = true; - defaultCurrency.safeTransfer(policy.payoutAddress, policy.insuranceAmount); - emit InsurancePayoutSettled(policyId, assertionId); - } -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/PredictionMarket.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/PredictionMarket.sol deleted file mode 100644 index 86e89a924..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/examples/PredictionMarket.sol +++ /dev/null @@ -1,258 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; -import "../../../common/implementation/AddressWhitelist.sol"; -import "../../../common/implementation/ExpandedERC20.sol"; -import "../../../data-verification-mechanism/implementation/Constants.sol"; -import "../../../data-verification-mechanism/interfaces/FinderInterface.sol"; -import "../../implementation/ClaimData.sol"; -import "../../interfaces/OptimisticOracleV3Interface.sol"; -import "../../interfaces/OptimisticOracleV3CallbackRecipientInterface.sol"; - -// This contract allows to initialize prediction markets each having a pair of binary outcome tokens. Anyone can mint -// and burn the same amount of paired outcome tokens for the default payout currency. Trading of outcome tokens is -// outside the scope of this contract. Anyone can assert 3 possible outcomes (outcome 1, outcome 2 or split) that is -// verified through Optimistic Oracle V3. If the assertion is resolved true then holders of outcome tokens can settle -// them for the payout currency based on resolved market outcome. -contract PredictionMarket is OptimisticOracleV3CallbackRecipientInterface { - using SafeERC20 for IERC20; - - struct Market { - bool resolved; // True if the market has been resolved and payouts can be settled. - bytes32 assertedOutcomeId; // Hash of asserted outcome (outcome1, outcome2 or unresolvable). - ExpandedIERC20 outcome1Token; // ERC20 token representing the value of the first outcome. - ExpandedIERC20 outcome2Token; // ERC20 token representing the value of the second outcome. - uint256 reward; // Reward available for asserting true market outcome. - uint256 requiredBond; // Expected bond to assert market outcome (OOv3 can require higher bond). - bytes outcome1; // Short name of the first outcome. - bytes outcome2; // Short name of the second outcome. - bytes description; // Description of the market. - } - - struct AssertedMarket { - address asserter; // Address of the asserter used for reward payout. - bytes32 marketId; // Identifier for markets mapping. - } - - mapping(bytes32 => Market) public markets; // Maps marketId to Market struct. - - mapping(bytes32 => AssertedMarket) public assertedMarkets; // Maps assertionId to AssertedMarket. - - FinderInterface public immutable finder; // UMA protocol Finder used to discover other protocol contracts. - IERC20 public immutable currency; // Currency used for all prediction markets. - OptimisticOracleV3Interface public immutable oo; - uint64 public constant assertionLiveness = 7200; // 2 hours. - bytes32 public immutable defaultIdentifier; // Identifier used for all prediction markets. - bytes public constant unresolvable = "Unresolvable"; // Name of the unresolvable outcome where payouts are split. - - event MarketInitialized( - bytes32 indexed marketId, - string outcome1, - string outcome2, - string description, - address outcome1Token, - address outcome2Token, - uint256 reward, - uint256 requiredBond - ); - event MarketAsserted(bytes32 indexed marketId, string assertedOutcome, bytes32 indexed assertionId); - event MarketResolved(bytes32 indexed marketId); - event TokensCreated(bytes32 indexed marketId, address indexed account, uint256 tokensCreated); - event TokensRedeemed(bytes32 indexed marketId, address indexed account, uint256 tokensRedeemed); - event TokensSettled( - bytes32 indexed marketId, - address indexed account, - uint256 payout, - uint256 outcome1Tokens, - uint256 outcome2Tokens - ); - - constructor(address _finder, address _currency, address _optimisticOracleV3) { - finder = FinderInterface(_finder); - require(_getCollateralWhitelist().isOnWhitelist(_currency), "Unsupported currency"); - currency = IERC20(_currency); - oo = OptimisticOracleV3Interface(_optimisticOracleV3); - defaultIdentifier = oo.defaultIdentifier(); - } - - function getMarket(bytes32 marketId) public view returns (Market memory) { - return markets[marketId]; - } - - function initializeMarket( - string memory outcome1, // Short name of the first outcome. - string memory outcome2, // Short name of the second outcome. - string memory description, // Description of the market. - uint256 reward, // Reward available for asserting true market outcome. - uint256 requiredBond // Expected bond to assert market outcome (OOv3 can require higher bond). - ) public returns (bytes32 marketId) { - require(bytes(outcome1).length > 0, "Empty first outcome"); - require(bytes(outcome2).length > 0, "Empty second outcome"); - require(keccak256(bytes(outcome1)) != keccak256(bytes(outcome2)), "Outcomes are the same"); - require(bytes(description).length > 0, "Empty description"); - marketId = keccak256(abi.encode(block.number, description)); - require(markets[marketId].outcome1Token == ExpandedIERC20(address(0)), "Market already exists"); - - // Create position tokens with this contract having minter and burner roles. - ExpandedIERC20 outcome1Token = new ExpandedERC20(string(abi.encodePacked(outcome1, " Token")), "O1T", 18); - ExpandedIERC20 outcome2Token = new ExpandedERC20(string(abi.encodePacked(outcome2, " Token")), "O2T", 18); - outcome1Token.addMinter(address(this)); - outcome2Token.addMinter(address(this)); - outcome1Token.addBurner(address(this)); - outcome2Token.addBurner(address(this)); - - markets[marketId] = Market({ - resolved: false, - assertedOutcomeId: bytes32(0), - outcome1Token: outcome1Token, - outcome2Token: outcome2Token, - reward: reward, - requiredBond: requiredBond, - outcome1: bytes(outcome1), - outcome2: bytes(outcome2), - description: bytes(description) - }); - if (reward > 0) currency.safeTransferFrom(msg.sender, address(this), reward); // Pull reward. - - emit MarketInitialized( - marketId, - outcome1, - outcome2, - description, - address(outcome1Token), - address(outcome2Token), - reward, - requiredBond - ); - } - - // Assert the market with any of 3 possible outcomes: names of outcome1, outcome2 or unresolvable. - // Only one concurrent assertion per market is allowed. - function assertMarket(bytes32 marketId, string memory assertedOutcome) public returns (bytes32 assertionId) { - Market storage market = markets[marketId]; - require(market.outcome1Token != ExpandedIERC20(address(0)), "Market does not exist"); - bytes32 assertedOutcomeId = keccak256(bytes(assertedOutcome)); - require(market.assertedOutcomeId == bytes32(0), "Assertion active or resolved"); - require( - assertedOutcomeId == keccak256(market.outcome1) || - assertedOutcomeId == keccak256(market.outcome2) || - assertedOutcomeId == keccak256(unresolvable), - "Invalid asserted outcome" - ); - - market.assertedOutcomeId = assertedOutcomeId; - uint256 minimumBond = oo.getMinimumBond(address(currency)); // OOv3 might require higher bond. - uint256 bond = market.requiredBond > minimumBond ? market.requiredBond : minimumBond; - bytes memory claim = _composeClaim(assertedOutcome, market.description); - - // Pull bond and make the assertion. - currency.safeTransferFrom(msg.sender, address(this), bond); - currency.safeApprove(address(oo), bond); - assertionId = _assertTruthWithDefaults(claim, bond); - - // Store the asserter and marketId for the assertionResolvedCallback. - assertedMarkets[assertionId] = AssertedMarket({ asserter: msg.sender, marketId: marketId }); - - emit MarketAsserted(marketId, assertedOutcome, assertionId); - } - - // Callback from settled assertion. - // If the assertion was resolved true, then the asserter gets the reward and the market is marked as resolved. - // Otherwise, assertedOutcomeId is reset and the market can be asserted again. - function assertionResolvedCallback(bytes32 assertionId, bool assertedTruthfully) public { - require(msg.sender == address(oo), "Not authorized"); - Market storage market = markets[assertedMarkets[assertionId].marketId]; - - if (assertedTruthfully) { - market.resolved = true; - if (market.reward > 0) currency.safeTransfer(assertedMarkets[assertionId].asserter, market.reward); - emit MarketResolved(assertedMarkets[assertionId].marketId); - } else market.assertedOutcomeId = bytes32(0); - delete assertedMarkets[assertionId]; - } - - // Dispute callback does nothing. - function assertionDisputedCallback(bytes32 assertionId) public {} - - // Mints pair of tokens representing the value of outcome1 and outcome2. Trading of outcome tokens is outside of the - // scope of this contract. The caller must approve this contract to spend the currency tokens. - function createOutcomeTokens(bytes32 marketId, uint256 tokensToCreate) public { - Market storage market = markets[marketId]; - require(market.outcome1Token != ExpandedIERC20(address(0)), "Market does not exist"); - - currency.safeTransferFrom(msg.sender, address(this), tokensToCreate); - - market.outcome1Token.mint(msg.sender, tokensToCreate); - market.outcome2Token.mint(msg.sender, tokensToCreate); - - emit TokensCreated(marketId, msg.sender, tokensToCreate); - } - - // Burns equal amount of outcome1 and outcome2 tokens returning settlement currency tokens. - function redeemOutcomeTokens(bytes32 marketId, uint256 tokensToRedeem) public { - Market storage market = markets[marketId]; - require(market.outcome1Token != ExpandedIERC20(address(0)), "Market does not exist"); - - market.outcome1Token.burnFrom(msg.sender, tokensToRedeem); - market.outcome2Token.burnFrom(msg.sender, tokensToRedeem); - - currency.safeTransfer(msg.sender, tokensToRedeem); - - emit TokensRedeemed(marketId, msg.sender, tokensToRedeem); - } - - // If the market is resolved, then all of caller's outcome tokens are burned and currency payout is made depending - // on the resolved market outcome and the amount of outcome tokens burned. If the market was resolved to the first - // outcome, then the payout equals balance of outcome1Token while outcome2Token provides nothing. If the market was - // resolved to the second outcome, then the payout equals balance of outcome2Token while outcome1Token provides - // nothing. If the market was resolved to the split outcome, then both outcome tokens provides half of their balance - // as currency payout. - function settleOutcomeTokens(bytes32 marketId) public returns (uint256 payout) { - Market storage market = markets[marketId]; - require(market.resolved, "Market not resolved"); - - uint256 outcome1Balance = market.outcome1Token.balanceOf(msg.sender); - uint256 outcome2Balance = market.outcome2Token.balanceOf(msg.sender); - - if (market.assertedOutcomeId == keccak256(market.outcome1)) payout = outcome1Balance; - else if (market.assertedOutcomeId == keccak256(market.outcome2)) payout = outcome2Balance; - else payout = (outcome1Balance + outcome2Balance) / 2; - - market.outcome1Token.burnFrom(msg.sender, outcome1Balance); - market.outcome2Token.burnFrom(msg.sender, outcome2Balance); - currency.safeTransfer(msg.sender, payout); - - emit TokensSettled(marketId, msg.sender, payout, outcome1Balance, outcome2Balance); - } - - function _getCollateralWhitelist() internal view returns (AddressWhitelist) { - return AddressWhitelist(finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist)); - } - - function _composeClaim(string memory outcome, bytes memory description) internal view returns (bytes memory) { - return - abi.encodePacked( - "As of assertion timestamp ", - ClaimData.toUtf8BytesUint(block.timestamp), - ", the described prediction market outcome is: ", - outcome, - ". The market description is: ", - description - ); - } - - function _assertTruthWithDefaults(bytes memory claim, uint256 bond) internal returns (bytes32 assertionId) { - assertionId = oo.assertTruth( - claim, - msg.sender, // Asserter - address(this), // Receive callback in this contract. - address(0), // No sovereign security. - assertionLiveness, - currency, - bond, - defaultIdentifier, - bytes32(0) // No domain. - ); - } -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/test/AssertingCallerTest.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/test/AssertingCallerTest.sol deleted file mode 100644 index 52cd08bbf..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/test/AssertingCallerTest.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "../OptimisticOracleV3.sol"; - -// Test contract used to wrap assertions for integration testing. -contract AssertingCallerTest { - using SafeERC20 for IERC20; - - OptimisticOracleV3 immutable optimisticOracleV3; - - constructor(OptimisticOracleV3 _optimisticOracleV3) { - optimisticOracleV3 = _optimisticOracleV3; - } - - // Wraps the OptimisticOracleV3 assertTruth function by passing msg.sender as the asserter and transferring the bond. - function assertTruth( - bytes memory claim, - address callbackRecipient, - address escalationManager, - uint64 liveness, - IERC20 currency, - uint256 bond, - bytes32 identifier, - bytes32 domainId - ) public returns (bytes32 assertionId) { - currency.safeTransferFrom(msg.sender, address(this), bond); - currency.safeApprove(address(optimisticOracleV3), bond); - - assertionId = optimisticOracleV3.assertTruth( - claim, - msg.sender, - callbackRecipient, - escalationManager, - liveness, - currency, - bond, - identifier, - domainId - ); - } -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/test/OptimisticOracleV3Test.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/test/OptimisticOracleV3Test.sol deleted file mode 100644 index 7169d0cff..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v3/implementation/test/OptimisticOracleV3Test.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "../OptimisticOracleV3.sol"; -import "../../../common/implementation/Testable.sol"; - -// Test contract used to manage the time for the contract in tests. -contract OptimisticOracleV3Test is OptimisticOracleV3, Testable { - constructor( - FinderInterface _finder, - IERC20 _defaultCurrency, - uint64 _defaultLiveness, - address _timerAddress - ) OptimisticOracleV3(_finder, _defaultCurrency, _defaultLiveness) Testable(_timerAddress) {} - - function getCurrentTime() public view override(OptimisticOracleV3, Testable) returns (uint256) { - return uint256(Testable.getCurrentTime()); - } -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/EscalationManagerInterface.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/EscalationManagerInterface.sol deleted file mode 100644 index 755e8a4a5..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/EscalationManagerInterface.sol +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.16; - -import "./OptimisticOracleV3CallbackRecipientInterface.sol"; - -/** - * @title Escalation Manager Interface - * @notice Interface for contracts that manage the escalation policy for assertions. - */ -interface EscalationManagerInterface is OptimisticOracleV3CallbackRecipientInterface { - // Assertion policy parameters as returned by the escalation manager. - struct AssertionPolicy { - bool blockAssertion; // If true, the the assertion should be blocked. - bool arbitrateViaEscalationManager; // If true, the escalation manager will arbitrate the assertion. - bool discardOracle; // If true, the Optimistic Oracle V3 should discard the oracle price. - bool validateDisputers; // If true, the escalation manager will validate the disputers. - } - - /** - * @notice Returns the assertion policy for the given assertion. - * @param assertionId the assertion identifier to get the assertion policy for. - * @return the assertion policy for the given assertion identifier. - */ - function getAssertionPolicy(bytes32 assertionId) external view returns (AssertionPolicy memory); - - /** - * @notice Callback function that is called by Optimistic Oracle V3 when an assertion is disputed. Used to validate - * if the dispute should be allowed based on the escalation policy. - * @param assertionId the assertionId to validate the dispute for. - * @param disputeCaller the caller of the dispute function. - * @return bool true if the dispute is allowed, false otherwise. - */ - function isDisputeAllowed(bytes32 assertionId, address disputeCaller) external view returns (bool); - - /** - * @notice Implements price getting logic. This method is called by Optimistic Oracle V3 settling an assertion that - * is configured to use the escalation manager as the oracle. The interface is constructed to mimic the UMA DVM. - * @param identifier price identifier being requested. - * @param time timestamp of the price being requested. - * @param ancillaryData ancillary data of the price being requested. - * @return price from the escalation manager to inform the resolution of the dispute. - */ - function getPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData) external returns (int256); - - /** - * @notice Implements price requesting logic for the escalation manager. This function is called by the Optimistic - * Oracle V3 on dispute and is constructed to mimic that of the UMA DVM interface. - * @param identifier the identifier to fetch the price for. - * @param time the time to fetch the price for. - * @param ancillaryData ancillary data of the price being requested. - */ - function requestPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData) external; -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/OptimisticOracleV3CallbackRecipientInterface.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/OptimisticOracleV3CallbackRecipientInterface.sol deleted file mode 100644 index c0744df08..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/OptimisticOracleV3CallbackRecipientInterface.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.16; - -/** - * @title Optimistic Oracle V3 Callback Recipient Interface - * @notice Interface for contracts implementing callbacks to be received from the Optimistic Oracle V3. - */ -interface OptimisticOracleV3CallbackRecipientInterface { - /** - * @notice Callback function that is called by Optimistic Oracle V3 when an assertion is resolved. - * @param assertionId The identifier of the assertion that was resolved. - * @param assertedTruthfully Whether the assertion was resolved as truthful or not. - */ - function assertionResolvedCallback(bytes32 assertionId, bool assertedTruthfully) external; - - /** - * @notice Callback function that is called by Optimistic Oracle V3 when an assertion is disputed. - * @param assertionId The identifier of the assertion that was disputed. - */ - function assertionDisputedCallback(bytes32 assertionId) external; -} diff --git a/contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/OptimisticOracleV3Interface.sol b/contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/OptimisticOracleV3Interface.sol deleted file mode 100644 index 4d7da5b16..000000000 --- a/contracts/external/uma/core/contracts/optimistic-oracle-v3/interfaces/OptimisticOracleV3Interface.sol +++ /dev/null @@ -1,178 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.16; - -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; - -/** - * @title Optimistic Oracle V3 Interface that callers must use to assert truths about the world. - */ -interface OptimisticOracleV3Interface { - // Struct grouping together the settings related to the escalation manager stored in the assertion. - struct EscalationManagerSettings { - bool arbitrateViaEscalationManager; // False if the DVM is used as an oracle (EscalationManager on True). - bool discardOracle; // False if Oracle result is used for resolving assertion after dispute. - bool validateDisputers; // True if the EM isDisputeAllowed should be checked on disputes. - address assertingCaller; // Stores msg.sender when assertion was made. - address escalationManager; // Address of the escalation manager (zero address if not configured). - } - - // Struct for storing properties and lifecycle of an assertion. - struct Assertion { - EscalationManagerSettings escalationManagerSettings; // Settings related to the escalation manager. - address asserter; // Address of the asserter. - uint64 assertionTime; // Time of the assertion. - bool settled; // True if the request is settled. - IERC20 currency; // ERC20 token used to pay rewards and fees. - uint64 expirationTime; // Unix timestamp marking threshold when the assertion can no longer be disputed. - bool settlementResolution; // Resolution of the assertion (false till resolved). - bytes32 domainId; // Optional domain that can be used to relate the assertion to others in the escalationManager. - bytes32 identifier; // UMA DVM identifier to use for price requests in the event of a dispute. - uint256 bond; // Amount of currency that the asserter has bonded. - address callbackRecipient; // Address that receives the callback. - address disputer; // Address of the disputer. - } - - // Struct for storing cached currency whitelist. - struct WhitelistedCurrency { - bool isWhitelisted; // True if the currency is whitelisted. - uint256 finalFee; // Final fee of the currency. - } - - /** - * @notice Disputes an assertion. Depending on how the assertion was configured, this may either escalate to the UMA - * DVM or the configured escalation manager for arbitration. - * @dev The caller must approve this contract to spend at least bond amount of currency for the associated assertion. - * @param assertionId unique identifier for the assertion to dispute. - * @param disputer receives bonds back at settlement. - */ - function disputeAssertion(bytes32 assertionId, address disputer) external; - - /** - * @notice Returns the default identifier used by the Optimistic Oracle V3. - * @return The default identifier. - */ - function defaultIdentifier() external view returns (bytes32); - - /** - * @notice Fetches information about a specific assertion and returns it. - * @param assertionId unique identifier for the assertion to fetch information for. - * @return assertion information about the assertion. - */ - function getAssertion(bytes32 assertionId) external view returns (Assertion memory); - - /** - * @notice Asserts a truth about the world, using the default currency and liveness. No callback recipient or - * escalation manager is enabled. The caller is expected to provide a bond of finalFee/burnedBondPercentage - * (with burnedBondPercentage set to 50%, the bond is 2x final fee) of the default currency. - * @dev The caller must approve this contract to spend at least the result of getMinimumBond(defaultCurrency). - * @param claim the truth claim being asserted. This is an assertion about the world, and is verified by disputers. - * @param asserter receives bonds back at settlement. This could be msg.sender or - * any other account that the caller wants to receive the bond at settlement time. - * @return assertionId unique identifier for this assertion. - */ - function assertTruthWithDefaults(bytes memory claim, address asserter) external returns (bytes32); - - /** - * @notice Asserts a truth about the world, using a fully custom configuration. - * @dev The caller must approve this contract to spend at least bond amount of currency. - * @param claim the truth claim being asserted. This is an assertion about the world, and is verified by disputers. - * @param asserter receives bonds back at settlement. This could be msg.sender or - * any other account that the caller wants to receive the bond at settlement time. - * @param callbackRecipient if configured, this address will receive a function call assertionResolvedCallback and - * assertionDisputedCallback at resolution or dispute respectively. Enables dynamic responses to these events. The - * recipient _must_ implement these callbacks and not revert or the assertion resolution will be blocked. - * @param escalationManager if configured, this address will control escalation properties of the assertion. This - * means a) choosing to arbitrate via the UMA DVM, b) choosing to discard assertions on dispute, or choosing to - * validate disputes. Combining these, the asserter can define their own security properties for the assertion. - * escalationManager also _must_ implement the same callbacks as callbackRecipient. - * @param liveness time to wait before the assertion can be resolved. Assertion can be disputed in this time. - * @param currency bond currency pulled from the caller and held in escrow until the assertion is resolved. - * @param bond amount of currency to pull from the caller and hold in escrow until the assertion is resolved. This - * must be >= getMinimumBond(address(currency)). - * @param identifier UMA DVM identifier to use for price requests in the event of a dispute. Must be pre-approved. - * @param domainId optional domain that can be used to relate this assertion to others in the escalationManager and - * can be used by the configured escalationManager to define custom behavior for groups of assertions. This is - * typically used for "escalation games" by changing bonds or other assertion properties based on the other - * assertions that have come before. If not needed this value should be 0 to save gas. - * @return assertionId unique identifier for this assertion. - */ - function assertTruth( - bytes memory claim, - address asserter, - address callbackRecipient, - address escalationManager, - uint64 liveness, - IERC20 currency, - uint256 bond, - bytes32 identifier, - bytes32 domainId - ) external returns (bytes32); - - /** - * @notice Fetches information about a specific identifier & currency from the UMA contracts and stores a local copy - * of the information within this contract. This is used to save gas when making assertions as we can avoid an - * external call to the UMA contracts to fetch this. - * @param identifier identifier to fetch information for and store locally. - * @param currency currency to fetch information for and store locally. - */ - function syncUmaParams(bytes32 identifier, address currency) external; - - /** - * @notice Resolves an assertion. If the assertion has not been disputed, the assertion is resolved as true and the - * asserter receives the bond. If the assertion has been disputed, the assertion is resolved depending on the oracle - * result. Based on the result, the asserter or disputer receives the bond. If the assertion was disputed then an - * amount of the bond is sent to the UMA Store as an oracle fee based on the burnedBondPercentage. The remainder of - * the bond is returned to the asserter or disputer. - * @param assertionId unique identifier for the assertion to resolve. - */ - function settleAssertion(bytes32 assertionId) external; - - /** - * @notice Settles an assertion and returns the resolution. - * @param assertionId unique identifier for the assertion to resolve and return the resolution for. - * @return resolution of the assertion. - */ - function settleAndGetAssertionResult(bytes32 assertionId) external returns (bool); - - /** - * @notice Fetches the resolution of a specific assertion and returns it. If the assertion has not been settled then - * this will revert. If the assertion was disputed and configured to discard the oracle resolution return false. - * @param assertionId unique identifier for the assertion to fetch the resolution for. - * @return resolution of the assertion. - */ - function getAssertionResult(bytes32 assertionId) external view returns (bool); - - /** - * @notice Returns the minimum bond amount required to make an assertion. This is calculated as the final fee of the - * currency divided by the burnedBondPercentage. If burn percentage is 50% then the min bond is 2x the final fee. - * @param currency currency to calculate the minimum bond for. - * @return minimum bond amount. - */ - function getMinimumBond(address currency) external view returns (uint256); - - event AssertionMade( - bytes32 indexed assertionId, - bytes32 domainId, - bytes claim, - address indexed asserter, - address callbackRecipient, - address escalationManager, - address caller, - uint64 expirationTime, - IERC20 currency, - uint256 bond, - bytes32 indexed identifier - ); - - event AssertionDisputed(bytes32 indexed assertionId, address indexed caller, address indexed disputer); - - event AssertionSettled( - bytes32 indexed assertionId, - address indexed bondRecipient, - bool disputed, - bool settlementResolution, - address settleCaller - ); - - event AdminPropertiesSet(IERC20 defaultCurrency, uint64 defaultLiveness, uint256 burnedBondPercentage); -} diff --git a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/GovernorChildTunnel.sol b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/GovernorChildTunnel.sol deleted file mode 100644 index 587752f2f..000000000 --- a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/GovernorChildTunnel.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@maticnetwork/fx-portal/contracts/tunnel/FxBaseChildTunnel.sol"; - -/** - * @title Governor contract deployed on sidechain that receives governance actions from Ethereum. - */ -contract GovernorChildTunnel is FxBaseChildTunnel { - event ExecutedGovernanceTransaction(address indexed to, bytes data); - - constructor(address _fxChild) FxBaseChildTunnel(_fxChild) {} - - /** - * @notice Executes governance transaction created on Ethereum. - * @dev The data will be received automatically from the state receiver when the state is synced between Ethereum - * and Polygon. This will revert if the Root chain sender is not the `fxRootTunnel` contract. - * @param sender The sender of `data` from the Root chain. - * @param data ABI encoded params to include in delegated transaction. - */ - function _processMessageFromRoot( - uint256 /* stateId */, - address sender, - bytes memory data - ) internal override validateSender(sender) { - (address to, bytes memory inputData) = abi.decode(data, (address, bytes)); - - require(_executeCall(to, inputData), "execute call failed"); - emit ExecutedGovernanceTransaction(to, inputData); - } - - // Note: this snippet of code is copied from Governor.sol. - function _executeCall(address to, bytes memory data) private returns (bool) { - // Note: this snippet of code is copied from Governor.sol. - // solhint-disable-next-line max-line-length - // https://github.com/gnosis/safe-contracts/blob/59cfdaebcd8b87a0a32f87b50fead092c10d3a05/contracts/base/Executor.sol#L23-L31 - // solhint-disable-next-line no-inline-assembly - - bool success; - assembly { - let inputData := add(data, 0x20) - let inputDataSize := mload(data) - // Hardcode value to be 0 for relayed governance calls in order to avoid addressing complexity of bridging - // value cross-chain. - success := call(gas(), to, 0, inputData, inputDataSize, 0, 0) - } - return success; - } -} diff --git a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/GovernorRootTunnel.sol b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/GovernorRootTunnel.sol deleted file mode 100644 index 187e64f74..000000000 --- a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/GovernorRootTunnel.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/access/Ownable.sol"; -import "@maticnetwork/fx-portal/contracts/tunnel/FxBaseRootTunnel.sol"; -import "../common/implementation/Lockable.sol"; - -/** - * @title Governance relayer contract to be deployed on Ethereum that receives messages from the owner (Governor) and - * sends them to sidechain. - */ -contract GovernorRootTunnel is Ownable, FxBaseRootTunnel, Lockable { - event RelayedGovernanceRequest(address indexed to, bytes data); - - constructor(address _checkpointManager, address _fxRoot) FxBaseRootTunnel(_checkpointManager, _fxRoot) {} - - /** - * @notice This should be called in order to relay a governance request to the `GovernorChildTunnel` contract - * deployed to the sidechain. Note: this can only be called by the owner (presumably the Ethereum Governor - * contract). - * @dev The transaction submitted to `to` on the sidechain with the calldata `data` is assumed to have 0 `value` - * in order to avoid the added complexity of sending cross-chain transactions with positive value. - */ - function relayGovernance(address to, bytes memory data) external nonReentrant onlyOwner { - _sendMessageToChild(abi.encode(to, data)); - emit RelayedGovernanceRequest(to, data); - } - - /** - * @notice Function called as callback from child tunnel. Should not do anything as governance actions should only - * be sent from root to child. - */ - function _processMessageFromChild(bytes memory data) internal override { - // no-op - } -} diff --git a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleBaseTunnel.sol b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleBaseTunnel.sol deleted file mode 100644 index d9ae51ec2..000000000 --- a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleBaseTunnel.sol +++ /dev/null @@ -1,81 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../data-verification-mechanism/interfaces/FinderInterface.sol"; -import "../data-verification-mechanism/implementation/Constants.sol"; - -/** - * @notice Enforces lifecycle of price requests for deriving contract. - */ -abstract contract OracleBaseTunnel { - enum RequestState { - NeverRequested, - Requested, - Resolved - } - - struct Price { - RequestState state; - int256 price; - } - - // Mapping of encoded price requests {identifier, time, ancillaryData} to Price objects. - mapping(bytes32 => Price) internal prices; - - // Finder to provide addresses for DVM system contracts. - FinderInterface public finder; - - event PriceRequestAdded(bytes32 indexed identifier, uint256 time, bytes ancillaryData, bytes32 indexed requestHash); - event PushedPrice( - bytes32 indexed identifier, - uint256 time, - bytes ancillaryData, - int256 price, - bytes32 indexed requestHash - ); - - /** - * @notice Constructor. - * @param _finderAddress finder to use to get addresses of DVM contracts. - */ - constructor(address _finderAddress) { - finder = FinderInterface(_finderAddress); - } - - /** - * @notice Enqueues a request (if a request isn't already present) for the given (identifier, time, - * ancillary data) combination. Will only emit an event if the request has never been requested. - */ - function _requestPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData) internal { - bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); - Price storage lookup = prices[priceRequestId]; - if (lookup.state == RequestState.NeverRequested) { - lookup.state = RequestState.Requested; - emit PriceRequestAdded(identifier, time, ancillaryData, priceRequestId); - } - } - - /** - * @notice Publishes price for a requested query. Will only emit an event if the request has never been resolved. - */ - function _publishPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData, int256 price) internal { - bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); - Price storage lookup = prices[priceRequestId]; - if (lookup.state == RequestState.Resolved) return; - lookup.price = price; - lookup.state = RequestState.Resolved; - emit PushedPrice(identifier, time, ancillaryData, lookup.price, priceRequestId); - } - - /** - * @notice Returns the convenient way to store price requests, uniquely identified by {identifier, time, - * ancillaryData }. - */ - function _encodePriceRequest( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) internal pure returns (bytes32) { - return keccak256(abi.encode(identifier, time, ancillaryData)); - } -} diff --git a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleChildTunnel.sol b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleChildTunnel.sol deleted file mode 100644 index 8580fc462..000000000 --- a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleChildTunnel.sol +++ /dev/null @@ -1,229 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "@maticnetwork/fx-portal/contracts/tunnel/FxBaseChildTunnel.sol"; -import "../cross-chain-oracle/AncillaryDataCompression.sol"; -import "../data-verification-mechanism/interfaces/OracleAncillaryInterface.sol"; -import "../data-verification-mechanism/interfaces/RegistryInterface.sol"; -import "./OracleBaseTunnel.sol"; -import "../common/implementation/Lockable.sol"; - -/** - * @title Adapter deployed on sidechain to give financial contracts the ability to trigger cross-chain price requests to - * the mainnet DVM. Also has the ability to receive published prices from mainnet. This contract can be treated as the - * "DVM" for this network, because a calling contract can request and access a resolved price request from this - * contract. - * @dev The intended client of this contract is an OptimisticOracle on sidechain that needs price - * resolution secured by the DVM on mainnet. - */ -contract OracleChildTunnel is OracleBaseTunnel, OracleAncillaryInterface, FxBaseChildTunnel, Lockable { - using AncillaryDataCompression for bytes; - - // Mapping of parent request ID to child request ID. - mapping(bytes32 => bytes32) public childRequestIds; - - event PriceRequestBridged( - address indexed requester, - bytes32 identifier, - uint256 time, - bytes ancillaryData, - bytes32 indexed childRequestId, - bytes32 indexed parentRequestId - ); - event ResolvedLegacyRequest( - bytes32 indexed identifier, - uint256 time, - bytes ancillaryData, - int256 price, - bytes32 indexed requestHash, - bytes32 indexed legacyRequestHash - ); - - constructor( - address _fxChild, - address _finderAddress - ) OracleBaseTunnel(_finderAddress) FxBaseChildTunnel(_fxChild) {} - - // This assumes that the local network has a Registry that resembles the mainnet registry. - modifier onlyRegisteredContract() { - RegistryInterface registry = RegistryInterface(finder.getImplementationAddress(OracleInterfaces.Registry)); - require(registry.isContractRegistered(msg.sender), "Caller must be registered"); - _; - } - - /** - * @notice This should be called to bridge a price request to mainnet. - * @dev Can be called only by a registered contract that is allowed to make DVM price requests. Will mark this - * price request as Requested, and therefore able to receive the price resolution data from mainnet. Emits a message - * that will be included in regular checkpoint of all sidechain transactions to mainnet. - * @param identifier Identifier of price request. - * @param time Timestamp of price request. - * @param ancillaryData extra data of price request. - */ - function requestPrice( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) public override nonReentrant onlyRegisteredContract { - address requester = msg.sender; - bytes32 childRequestId = _encodePriceRequest(identifier, time, ancillaryData); - Price storage lookup = prices[childRequestId]; - - // Send the request to mainnet if it has not been requested yet. - if (lookup.state != RequestState.NeverRequested) return; - lookup.state = RequestState.Requested; - - // Only the compressed ancillary data is sent to the mainnet. As it includes the request block number that is - // not available when getting the resolved price, we map the derived request ID. - bytes memory parentAncillaryData = ancillaryData.compress(requester, block.number); - bytes32 parentRequestId = _encodePriceRequest(identifier, time, parentAncillaryData); - childRequestIds[parentRequestId] = childRequestId; - - // Emit all required information so that voters on mainnet can track the origin of the request and full - // ancillary data by using the parentRequestId that is derived from identifier, time and ancillary data as - // observed on mainnet. - emit PriceRequestBridged(requester, identifier, time, ancillaryData, childRequestId, parentRequestId); - emit PriceRequestAdded(identifier, time, parentAncillaryData, parentRequestId); - - _sendMessageToRoot(abi.encode(identifier, time, parentAncillaryData)); - } - - /** - * @notice Resolves a price request. - * @dev The data will be received automatically from the state receiver when the state is synced between Ethereum - * and Polygon. This will revert if the Root chain sender is not the `fxRootTunnel` contract. - * @param sender The sender of `data` from the Root chain. - * @param data ABI encoded params with which to call `_publishPrice`. - */ - function _processMessageFromRoot( - uint256 /* stateId */, - address sender, - bytes memory data - ) internal override validateSender(sender) { - (bytes32 identifier, uint256 time, bytes memory ancillaryData, int256 price) = abi.decode( - data, - (bytes32, uint256, bytes, int256) - ); - bytes32 parentRequestId = _encodePriceRequest(identifier, time, ancillaryData); - - // Resolve the requestId used when requesting and checking the price. The childRequestIds value in the mapping - // could be uninitialized if the request was originated from: - // - the previous implementation of this contract, or - // - another chain and was pushed to this chain by mistake. - bytes32 priceRequestId = childRequestIds[parentRequestId]; - if (priceRequestId == bytes32(0)) priceRequestId = parentRequestId; - Price storage lookup = prices[priceRequestId]; - - // In order to support resolving the requests initiated from the previous implementation of this contract, we - // only update the state and emit an event if it has not yet been resolved. - if (lookup.state == RequestState.Resolved) return; - lookup.price = price; - lookup.state = RequestState.Resolved; - emit PushedPrice(identifier, time, ancillaryData, price, priceRequestId); - } - - /** - * @notice This method handles a special case when a price request was originated on the previous implementation of - * this contract, but was not settled before the upgrade. - * @dev Duplicates the resolved state from the legacy request to the new request where original ancillary data is - * used for request ID derivation. Will revert if the legacy request has not been pushed from mainnet. - * @param identifier Identifier of price request to resolve. - * @param time Timestamp of price request to resolve. - * @param ancillaryData Original ancillary data passed by the requester before stamping by the legacy spoke. - * @param childRequester Address of the requester that initiated the price request. - */ - function resolveLegacyRequest( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData, - address childRequester - ) external { - bytes32 legacyRequestId = _encodePriceRequest( - identifier, - time, - _legacyStampAncillaryData(ancillaryData, childRequester) - ); - Price storage legacyLookup = prices[legacyRequestId]; - require(legacyLookup.state == RequestState.Resolved, "Price has not been resolved"); - - bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); - Price storage lookup = prices[priceRequestId]; - - // Update the state and emit an event only if the legacy request has not been resolved yet. - if (lookup.state == RequestState.Resolved) return; - lookup.price = legacyLookup.price; - lookup.state = RequestState.Resolved; - emit ResolvedLegacyRequest(identifier, time, ancillaryData, lookup.price, priceRequestId, legacyRequestId); - } - - /** - * @notice Returns whether a price has resolved for the request. - * @param identifier Identifier of price request. - * @param time Timestamp of price request - * @param ancillaryData extra data of price request. - * @return True if a price is available, False otherwise. If true, then getPrice will succeed for the request. - */ - function hasPrice( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) public view override nonReentrantView onlyRegisteredContract returns (bool) { - bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); - return prices[priceRequestId].state == RequestState.Resolved; - } - - /** - * @notice Returns resolved price for the request. - * @dev Reverts if price is not available. - * @param identifier Identifier of price request. - * @param time Timestamp of price request - * @param ancillaryData extra data of price request. - * @return int256 Price, or reverts if no resolved price for any reason. - */ - function getPrice( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) public view override nonReentrantView onlyRegisteredContract returns (int256) { - bytes32 priceRequestId = _encodePriceRequest(identifier, time, ancillaryData); - Price storage lookup = prices[priceRequestId]; - require(lookup.state == RequestState.Resolved, "Price has not been resolved"); - return lookup.price; - } - - /** - * @notice Compresses ancillary data by providing sufficient information to track back the original ancillary data - * mainnet. - * @dev This is expected to be used in offchain infrastructure when speeding up requests to the mainnet. - * @param ancillaryData original ancillary data to be processed. - * @param requester address of the requester who initiated the price request. - * @param requestBlockNumber block number when the price request was initiated. - * @return compressed ancillary data. - */ - function compressAncillaryData( - bytes memory ancillaryData, - address requester, - uint256 requestBlockNumber - ) external view returns (bytes memory) { - return ancillaryData.compress(requester, requestBlockNumber); - } - - /** - * @dev This replicates the implementation of `_stampAncillaryData` from the previous version of this contract for - * the purpose of resolving legacy requests if they had not been resolved before the upgrade. - */ - function _legacyStampAncillaryData( - bytes memory ancillaryData, - address requester - ) internal view returns (bytes memory) { - // Price requests that originate from this method, on Polygon, will ultimately be submitted to the DVM on - // Ethereum via the OracleRootTunnel. Therefore this contract should stamp its requester's address in the - // ancillary data so voters can conveniently track the requests path to the DVM. - return - AncillaryData.appendKeyValueUint( - AncillaryData.appendKeyValueAddress(ancillaryData, "childRequester", requester), - "childChainId", - block.chainid - ); - } -} diff --git a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleRootTunnel.sol b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleRootTunnel.sol deleted file mode 100644 index 97278a9d6..000000000 --- a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/OracleRootTunnel.sol +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "@maticnetwork/fx-portal/contracts/tunnel/FxBaseRootTunnel.sol"; - -import "./OracleBaseTunnel.sol"; -import "../data-verification-mechanism/interfaces/OracleAncillaryInterface.sol"; -import "../common/implementation/Lockable.sol"; - -/** - * @title Adapter deployed on mainnet that validates and sends price requests from sidechain to the DVM on mainnet. - * @dev This contract must be a registered financial contract in order to make DVM price requests. - */ -contract OracleRootTunnel is OracleBaseTunnel, FxBaseRootTunnel, Lockable { - constructor( - address _checkpointManager, - address _fxRoot, - address _finderAddress - ) OracleBaseTunnel(_finderAddress) FxBaseRootTunnel(_checkpointManager, _fxRoot) {} - - /** - * @notice This is the first method that should be called in order to publish a price request to the sidechain. - * @dev Publishes the DVM resolved price for the price request, or reverts if not resolved yet. This contract must - * be registered with the DVM to query price requests. - * @param identifier Identifier of price request to resolve. - * @param time Timestamp of price request to resolve. - * @param ancillaryData extra data of price request to resolve. - */ - function publishPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData) public nonReentrant { - // `getPrice` will revert if there is no price. - int256 price = _getOracle().getPrice(identifier, time, ancillaryData); - // This implementation allows duplicate MessageSent events via _sendMessageToRoot. The child tunnel on the - // sidechain will not have a problem handling duplicate price resolutions (it will just ignore them). This is - // potentially a fallback in case the automatic state sync to the sidechain is missing the `publishPrice` - // transaction for some reason. There is little risk in duplicating MessageSent emissions because the sidechain - // bridge does not impose any rate-limiting. - _publishPrice(identifier, time, ancillaryData, price); - _sendMessageToChild(abi.encode(identifier, time, ancillaryData, price)); - } - - /** - * @notice Submits a price request. - * @dev This internal method will be called inside `receiveMessage(bytes memory inputData)`. The `inputData` is a - * proof of transaction that is derived from the transaction hash of the transaction on the child chain that - * originated the cross-chain price request via _sendMessageToRoot. This contract must be registered with the DVM - * to submit price requests. - * @param data ABI encoded params with which to call `requestPrice`. - */ - function _processMessageFromChild(bytes memory data) internal override { - (bytes32 identifier, uint256 time, bytes memory ancillaryData) = abi.decode(data, (bytes32, uint256, bytes)); - _requestPrice(identifier, time, ancillaryData); - _getOracle().requestPrice(identifier, time, ancillaryData); - } - - /** - * @notice Return DVM for this network. - */ - function _getOracle() internal view returns (OracleAncillaryInterface) { - return OracleAncillaryInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); - } -} diff --git a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/README.md b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/README.md deleted file mode 100644 index abb4034c2..000000000 --- a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# Polygon <> Ethereum State Transfer - -This document describes the architecture of how arbitrary messages can be passed between Ethereum and Polygon. For a more detailed explanation from the Polygon official docs site, go [here](https://docs.polygon.technology/docs/develop/l1-l2-communication/state-transfer/). - -# Two-way bridge between Root on Ethereum and Child on Polygon - -At a high level we are deploying "Root" and "Child" bridge contracts on both networks that communicate only with each other and the native [state sync](https://docs.polygon.technology/docs/contribute/state-sync/state-sync) infrastructure that Polygon uses to pass data between the Ethereum and Polygon EVM's. Polygon uses "tunnel" to describe what [other](https://docs.tokenbridge.net/amb-bridge/about-amb-bridge) [relayer](https://forum.makerdao.com/t/announcing-the-optimism-dai-bridge-with-fast-withdrawals/6938) [systems](https://developer.offchainlabs.com/docs/inside_arbitrum#bridging) call "bridges". - -Diagram of oracle tunnel system: ![image](https://user-images.githubusercontent.com/12886084/121140379-115e8e80-c83a-11eb-89e9-27a694e20814.png). - -# Root Tunnel Contract - -This contract is deployed on Ethereum and inherits from the official tunnel implementation called the ["FxBaseRootTunnel"](https://github.com/fx-portal/contracts/blob/baed24d22178201bca33140c303e0925661ec0ac/contracts/tunnel/FxBaseRootTunnel.sol) which implements `_processMessageFromChild(bytes memory data)` to receive messages from Polygon and enforces that the message originated from a Polygon transaction that has been provably [checkpointed](https://docs.matic.network/docs/contribute/heimdall/checkpoint/) to Ethereum. Notably, the root tunnel can only communicate with one child tunnel on Polygon, and the child tunnel address cannot be overwritten after being set. - -In order to send messages to Polygon, the tunnel contract must be initialized to point to a "FxRoot" contract that is [already deployed](https://etherscan.io/address/0xfe5e5D361b2ad62c541bAb87C45a0B9B018389a2#code) to Ethereum. The tunnel contract can send messages to Polygon via the "FxRoot" which has special permission to call `syncState(address receiver, bytes calldata data)` on the ["StateSender" contract](https://etherscan.io/address/0x28e4f3a7f651294b9564800b2d01f35189a5bfbe/advanced#code). - -To receive messages from Polygon, the tunnel contract is similarly initialized to point to a ["CheckpointManager"](https://etherscan.io/address/0x86e4dc95c7fbdbf52e33d563bbdb00823894c287) deployed on Ethereum. Checkpoints are snapshots of the [Polygon chain state](https://docs.polygon.technology/docs/contribute/heimdall/checkpoint) that are first validated by the Polygon validator set before being submitted to the "CheckpointManager" on Ethereum. Once a Polygon transaction is checkpointed to Ethereum, its arbitrary message can be used to trigger a function call on the Root Tunnel following an inclusion proof. This line in [FxBaseRootTunnel](https://github.com/fx-portal/contracts/blob/baed24d22178201bca33140c303e0925661ec0ac/contracts/tunnel/FxBaseRootTunnel.sol#L103) implements the inclusion proof verification, and the `_validateAndExtractMessage` internal method must pass before the Root Tunnel can submit a price request to the DVM. - -# Child Tunnel Contract - -This contract is deployed on Polygon and inherits from the official tunnel implementation called the ["FxBaseChildTunnel"](https://github.com/fx-portal/contracts/blob/baed24d22178201bca33140c303e0925661ec0ac/contracts/tunnel/FxBaseChildTunnel.sol) which implements `_processMessageFromRoot(bytes memory data)` to receive messages from Ethereum. Like the root tunnel, the child tunnel can only communicate with one root tunnel whose address cannot be overwritten after being set. - -To send messages to Ethereum, the tunnel contract emits a `MessageSent(bytes message)` event containing the message that can be [passed to the Root tunnel contract](https://github.com/fx-portal/contracts/blob/baed24d22178201bca33140c303e0925661ec0ac/contracts/tunnel/FxBaseRootTunnel.sol#L138) after the transaction that originally emitted the `MessageSent` event has been included in a Checkpoint. - -To receive messages from Ethereum, Polygon validators will automatically detect and submit `StateSynced(uint256 id, address contractAddress, bytes data)` to Polygon's "FxChild" contract and execute `onStateReceive(uint256 stateId, bytes _data)`, which will pass the message on to the Child tunnel contract. The Child tunnel must be initialized by pointing to the ["FxChild" contract](https://explorer-mainnet.maticvigil.com/address/0x8397259c983751DAf40400790063935a11afa28a/read-contract) deployed on Polygon which can only be called by the System [Superuser address](https://explorer-mainnet.maticvigil.com/address/0x0000000000000000000000000000000000001001/transactions) on Polygon. - -# Types of messages that can be relayed - -Any message can be relayed between Polygon and Ethereum provided that they are sent by contracts that implement the Polygon tunnel interface correctly (i.e. they can make calls to FxChild and FxRoot and they implement `_processMessage...` correctly). Each contract that wants to transfer state between Polygon and Ethereum must set up its own tunnel, so the relationship between Child and Root tunnels is 1-to-1. For example, UMA requires at least two tunnels to be set up: - -- Oracle Tunnel: Submitting price requests from Polygon to Ethereum and resolving prices from Ethereum to Polygon -- Governor Tunnel: Sending governance actions from Ethereum to Polygon that can be executed on Polygon - -# Special Permissions that Tunnel contracts need within UMA system - -- Oracle Tunnel: The Root tunnel must be able to submit price requests to the DVM and therefore must be registered with the `Registry`. - -# Security Considerations - -This system relies on the [Polygon consensus mechanism](https://docs.matic.network/docs/home/architecture/security-models#proof-of-stake-security) secured by validators in a Proof of Stake system. The validator set enforces the integrity of data passed between networks (i.e. downstream users need to trust that the validators are not modifying the arbitrary messages that are being sent between networks). - -Moreover, downstream users also rely on off-chain actors to relay messages in a timely fashion. Historically messages are sent once per hour. diff --git a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/FxChildMock.sol b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/FxChildMock.sol deleted file mode 100644 index 07edece58..000000000 --- a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/FxChildMock.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// IStateReceiver represents interface to receive state -interface IStateReceiver { - function onStateReceive(uint256 stateId, bytes calldata data) external; -} - -// IFxMessageProcessor represents interface to process message -interface IFxMessageProcessor { - function processMessageFromRoot(uint256 stateId, address rootMessageSender, bytes calldata data) external; -} - -/** - * @title FxChild child contract for state receiver - */ -contract FxChildMock is IStateReceiver { - address public fxRoot; - address public systemCaller; - - event NewFxMessage(address rootMessageSender, address receiver, bytes data); - - constructor(address _systemCaller) { - systemCaller = _systemCaller; - } - - function setFxRoot(address _fxRoot) public { - require(fxRoot == address(0x0)); - fxRoot = _fxRoot; - } - - function onStateReceive(uint256 stateId, bytes calldata _data) external override { - require(msg.sender == systemCaller, "Invalid sender: must be system super user"); - (address rootMessageSender, address receiver, bytes memory data) = abi.decode(_data, (address, address, bytes)); - emit NewFxMessage(rootMessageSender, receiver, data); - IFxMessageProcessor(receiver).processMessageFromRoot(stateId, rootMessageSender, data); - } -} diff --git a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/FxRootMock.sol b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/FxRootMock.sol deleted file mode 100644 index de6831908..000000000 --- a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/FxRootMock.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -interface IStateSender { - function syncState(address receiver, bytes calldata data) external; -} - -interface IFxStateSender { - function sendMessageToChild(address _receiver, bytes calldata _data) external; -} - -/** - * @title FxRoot root contract for fx-portal - */ -contract FxRootMock is IFxStateSender { - IStateSender public stateSender; - address public fxChild; - - constructor(address _stateSender) { - stateSender = IStateSender(_stateSender); - } - - function setFxChild(address _fxChild) public { - require(fxChild == address(0x0)); - fxChild = _fxChild; - } - - function sendMessageToChild(address _receiver, bytes calldata _data) public override { - bytes memory data = abi.encode(msg.sender, _receiver, _data); - stateSender.syncState(fxChild, data); - } -} diff --git a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/OracleBaseTunnelMock.sol b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/OracleBaseTunnelMock.sol deleted file mode 100644 index bd44ca023..000000000 --- a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/OracleBaseTunnelMock.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../OracleBaseTunnel.sol"; - -/** - * @title Test implementation of OracleBaseTunnel enabling unit tests on internal methods. - * @dev Unit tests should ensure that internal methods `_requestPrice` and `_publishPrice` emit the correct events - * and modify state as expected. - */ -contract OracleBaseTunnelMock is OracleBaseTunnel { - constructor(address _finderAddress) OracleBaseTunnel(_finderAddress) {} - - function requestPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData) public { - _requestPrice(identifier, time, ancillaryData); - } - - function encodePriceRequest( - bytes32 identifier, - uint256 time, - bytes memory ancillaryData - ) public pure returns (bytes32) { - return _encodePriceRequest(identifier, time, ancillaryData); - } - - function publishPrice(bytes32 identifier, uint256 time, bytes memory ancillaryData, int256 price) public { - _publishPrice(identifier, time, ancillaryData, price); - } -} diff --git a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/OracleRootTunnelMock.sol b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/OracleRootTunnelMock.sol deleted file mode 100644 index 43073ae88..000000000 --- a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/OracleRootTunnelMock.sol +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../OracleRootTunnel.sol"; - -contract OracleRootTunnelMock is OracleRootTunnel { - // If set to true, then always revert on calls to receiveMessage. - bool public revertReceiveMessage; - - // Error message to emit when receiveMessage reverts. - string public errorMessage; - - event ReceivedMessage(bytes indexed inputData); - - constructor( - address _checkpointManager, - address _fxRoot, - address _finderAddress - ) OracleRootTunnel(_checkpointManager, _fxRoot, _finderAddress) { - revertReceiveMessage = false; - errorMessage = "generic error message"; - } - - // Helper method to test _processMessageFromChild directly without having to call internal - // _validateAndExtractMessage - function processMessageFromChild(bytes memory message) public { - _processMessageFromChild(message); - } - - // Helper method to test receiveMessage. Will always succeed unless `revertReceiveMessage` is True, then will - // always revert. - function receiveMessage(bytes memory inputData) public override { - if (!revertReceiveMessage) { - emit ReceivedMessage(inputData); - } else { - require(false, errorMessage); - } - } - - function setRevertReceiveMessage(bool _revertReceiveMessage) public { - revertReceiveMessage = _revertReceiveMessage; - } - - function setRevertErrorMessage(string calldata _errorMessage) public { - errorMessage = _errorMessage; - } -} diff --git a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/StateSyncMock.sol b/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/StateSyncMock.sol deleted file mode 100644 index 1420e8b74..000000000 --- a/contracts/external/uma/core/contracts/polygon-cross-chain-oracle/test/StateSyncMock.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -/** - * @notice Dummy State Sender contract to simulate plasma state sender while testing - */ -contract StateSyncMock { - /** - * @notice Event emitted when when syncState is called - * @dev Heimdall bridge listens to this event and sends the data to receiver contract on child chain - * @param id Id of the sync, increamented for each event in case of actual state sender contract - * @param contractAddress the contract receiving data on child chain - * @param data bytes data to be sent - */ - event StateSynced(uint256 indexed id, address indexed contractAddress, bytes data); - - /** - * @notice called to send data to child chain - * @dev sender and receiver contracts need to be registered in case of actual state sender contract - * @param receiver the contract receiving data on child chain - * @param data bytes data to be sent - */ - function syncState(address receiver, bytes calldata data) external { - emit StateSynced(1, receiver, data); - } -} diff --git a/contracts/external/uma/core/contracts/proxy-scripts/atomic-disputer/ReserveCurrencyDisputer.sol b/contracts/external/uma/core/contracts/proxy-scripts/atomic-disputer/ReserveCurrencyDisputer.sol deleted file mode 100644 index 248d9012c..000000000 --- a/contracts/external/uma/core/contracts/proxy-scripts/atomic-disputer/ReserveCurrencyDisputer.sol +++ /dev/null @@ -1,137 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; -pragma abicoder v2; - -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; - -import "@uniswap/lib/contracts/libraries/TransferHelper.sol"; -import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol"; - -import "../../common/implementation/FixedPoint.sol"; - -/** - * @title ReserveCurrencyDisputer - * @notice Helper contract to enable a disputer to hold one reserver currency and dispute against any number of - * financial contracts. Is assumed to be called by a DSProxy which holds reserve currency. - */ - -contract ReserveCurrencyDisputer { - using SafeMath for uint256; - using FixedPoint for FixedPoint.Unsigned; - - /** - * @notice Swaps required amount of reserve currency to collateral currency which is then used to dispute a liquidation. - * @dev Any collateral the contract has will be used before anything is purchased on Uniswap. - * @param uniswapRouter address of the uniswap router used to facilitate trades. - * @param financialContract address of the financial contract on which the liquidation is occurring. - * @param reserveCurrency address of the token to swap for collateral. This is the common currency held by the DSProxy. - * @param sponsor address of the sponsor who's liquidation is disputed. - * @param liquidationId index of the liquidation for the given sponsor. - * @param maxReserveTokenSpent maximum number of reserve tokens to spend in the trade. Bounds slippage. - * @param deadline abort the trade and dispute if the transaction is mined after this timestamp. - **/ - function swapDispute( - address uniswapRouter, - address financialContract, - address reserveCurrency, - uint256 liquidationId, - address sponsor, - uint256 maxReserveTokenSpent, - uint256 deadline - ) public { - IFinancialContract fc = IFinancialContract(financialContract); - - // 1. Fetch information about the liquidation from the financial contract. - IFinancialContract.LiquidationData memory liquidationData = fc.liquidations(sponsor, liquidationId); - - // 2. Fetch the disputeBondPercentage from the financial contract. - FixedPoint.Unsigned memory disputeBondPercentage = fc.disputeBondPercentage(); - - // 3. Compute the disputeBondAmount. Multiply by the unit collateral so the dispute bond is a percentage of the - // locked collateral after fees. To add fees we simply multiply the rawUnitCollateral by the cumulativeFeeMultiplier. - FixedPoint.Unsigned memory disputeBondAmount = liquidationData.lockedCollateral.mul(disputeBondPercentage).mul( - (liquidationData.rawUnitCollateral).mul(fc.cumulativeFeeMultiplier()) - ); - - // 4. Calculate required collateral. Cost of a dispute is the dispute bond + the final fee. - FixedPoint.Unsigned memory totalCollateralRequired = disputeBondAmount.add(liquidationData.finalFee); - - // 5. Compute the collateral shortfall. This considers and collateral that is current in the contract. - FixedPoint.Unsigned memory collateralToBePurchased = subOrZero( - totalCollateralRequired, - getCollateralBalance(fc) - ); - - // 6. If there is collateral to be purchased, buy it on uniswap with the reserve currency. - if (collateralToBePurchased.isGreaterThan(0) && reserveCurrency != fc.collateralCurrency()) { - IUniswapV2Router01 router = IUniswapV2Router01(uniswapRouter); - address[] memory path = new address[](2); - path[0] = reserveCurrency; - path[1] = fc.collateralCurrency(); - - TransferHelper.safeApprove(reserveCurrency, address(router), maxReserveTokenSpent); - router.swapTokensForExactTokens( - collateralToBePurchased.rawValue, - maxReserveTokenSpent, - path, - address(this), - deadline - ); - } - - // 7. Finally, submit the dispute. - TransferHelper.safeApprove(fc.collateralCurrency(), address(fc), totalCollateralRequired.rawValue); - fc.dispute(liquidationId, sponsor); - } - - // Helper method to work around subtraction overflow in the case of: a - b with b > a. - function subOrZero( - FixedPoint.Unsigned memory a, - FixedPoint.Unsigned memory b - ) internal pure returns (FixedPoint.Unsigned memory) { - return b.isGreaterThanOrEqual(a) ? FixedPoint.fromUnscaledUint(0) : a.sub(b); - } - - // Helper method to return the collateral balance of this contract. - function getCollateralBalance(IFinancialContract fc) internal view returns (FixedPoint.Unsigned memory) { - return FixedPoint.Unsigned(IERC20(fc.collateralCurrency()).balanceOf(address(this))); - } -} - -// Define some simple interfaces for dealing with UMA contracts. -interface IFinancialContract { - enum Status { - Uninitialized, - NotDisputed, - Disputed, - DisputeSucceeded, - DisputeFailed - } - - struct LiquidationData { - address sponsor; - address liquidator; - Status state; - uint256 liquidationTime; - FixedPoint.Unsigned tokensOutstanding; - FixedPoint.Unsigned lockedCollateral; - FixedPoint.Unsigned liquidatedCollateral; - FixedPoint.Unsigned rawUnitCollateral; - address disputer; - FixedPoint.Unsigned settlementPrice; - FixedPoint.Unsigned finalFee; - } - - function liquidations(address sponsor, uint256 liquidationId) external view returns (LiquidationData memory); - - function disputeBondPercentage() external view returns (FixedPoint.Unsigned memory); - - function disputerDisputeRewardPct() external view returns (FixedPoint.Unsigned memory); - - function cumulativeFeeMultiplier() external view returns (FixedPoint.Unsigned memory); - - function collateralCurrency() external view returns (address); - - function dispute(uint256 liquidationId, address sponsor) external returns (FixedPoint.Unsigned memory totalPaid); -} diff --git a/contracts/external/uma/core/contracts/proxy-scripts/atomic-liquidator/ReserveCurrencyLiquidator.sol b/contracts/external/uma/core/contracts/proxy-scripts/atomic-liquidator/ReserveCurrencyLiquidator.sol deleted file mode 100644 index 3acafa0d9..000000000 --- a/contracts/external/uma/core/contracts/proxy-scripts/atomic-liquidator/ReserveCurrencyLiquidator.sol +++ /dev/null @@ -1,197 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; - -import "@uniswap/lib/contracts/libraries/TransferHelper.sol"; -import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol"; - -import "../../common/implementation/FixedPoint.sol"; - -/** - * @title ReserveCurrencyLiquidator - * @notice Helper contract to enable a liquidator to hold one reserver currency and liquidate against any number of - * financial contracts. Is assumed to be called by a DSProxy which holds reserve currency. - */ - -contract ReserveCurrencyLiquidator { - using SafeMath for uint256; - using FixedPoint for FixedPoint.Unsigned; - - /** - * @notice Swaps required amount of reserve currency to collateral currency which is then used to mint tokens to - * liquidate a position within one transaction. - * @dev After the liquidation is done the DSProxy that called this method will have an open position AND pending - * liquidation within the financial contract. The bot using the DSProxy should withdraw the liquidation once it has - * passed liveness. At this point the position can be manually unwound. - * @dev Any synthetics & collateral that the DSProxy already has are considered in the amount swapped and minted. - * These existing tokens will be used first before any swaps or mints are done. - * @dev If there is a token shortfall (either from not enough reserve to buy sufficient collateral or not enough - * collateral to begins with or due to slippage) the script will liquidate as much as possible given the reserves. - * @param uniswapRouter address of the uniswap router used to facilitate trades. - * @param financialContract address of the financial contract on which the liquidation is occurring. - * @param reserveCurrency address of the token to swap for collateral. THis is the common currency held by the DSProxy. - * @param liquidatedSponsor address of the sponsor to be liquidated. - * @param maxSlippage max slip the trade on uniswap will accept before reverting. - * @param minCollateralPerTokenLiquidated abort the liquidation if the position's collateral per token is below this value. - * @param maxCollateralPerTokenLiquidated abort the liquidation if the position's collateral per token exceeds this value. - * @param maxTokensToLiquidate max number of tokens to liquidate. For a full liquidation this is the full position debt. - * @param deadline abort the trade and liquidation if the transaction is mined after this timestamp. - **/ - function swapMintLiquidate( - address uniswapRouter, - address financialContract, - address reserveCurrency, - address liquidatedSponsor, - FixedPoint.Unsigned calldata minCollateralPerTokenLiquidated, - FixedPoint.Unsigned calldata maxCollateralPerTokenLiquidated, - FixedPoint.Unsigned memory maxTokensToLiquidate, - uint256 maxSlippage, - uint256 deadline - ) public { - IFinancialContract fc = IFinancialContract(financialContract); - - // 1. Calculate the token shortfall. This is the synthetics to liquidate minus any synthetics the DSProxy already - // has. If this number is negative(balance large than synthetics to liquidate) the return 0 (no shortfall). - FixedPoint.Unsigned memory tokenShortfall = subOrZero(maxTokensToLiquidate, getSyntheticBalance(fc)); - - // 2. Calculate how much collateral is needed to make up the token shortfall from minting new synthetics. - FixedPoint.Unsigned memory gcr = fc.pfc().divCeil(fc.totalTokensOutstanding()); - FixedPoint.Unsigned memory collateralToMintShortfall = tokenShortfall.mulCeil(gcr); - - // 3. Calculate the total collateral required. This considers the final fee for the given collateral type + any - // collateral needed to mint the token short fall. - - FixedPoint.Unsigned memory totalCollateralRequired = getFinalFee(fc).add(collateralToMintShortfall); - - // 4.a. Calculate how much collateral needs to be purchased. If the DSProxy already has some collateral then this - // will factor this in. If the DSProxy has more collateral than the total amount required the purchased = 0. - uint256 collateralToBePurchased = subOrZero(totalCollateralRequired, getCollateralBalance(fc)).rawValue; - - // 4.b. If there is some collateral to be purchased, execute a trade on uniswap to meet the shortfall. - // Note the path assumes a direct route from the reserve currency to the collateral currency. - // Note the maxInputAmount is computed by taking the 1000000 wei trade as the spot price from the router, - // multiplied by collateral to be purchased to arrive at a "zero slippage input amount". This would be the amount of - // required input to buy the amountOut, assuming zero slippage. This is then scalded by maxSlippage & swap fees - // to find the amountInMax that factors in the max tolerable exchange slippage. The maxSlippage is divided by two - // as slippage in an AMM is constituted by the inputToken going up and the outputToken going down in proportion. - // the +1e18 is used to offset the slippage percentage provided. i.e a 5% will be input at 0.05e18, offset by 1e18 - // to bring it up to 1.05e18. the *997 and *1000 in the numerator and denominator respectively are for uniswap fees. - if (collateralToBePurchased > 0 && reserveCurrency != fc.collateralCurrency()) { - IUniswapV2Router01 router = IUniswapV2Router01(uniswapRouter); - address[] memory path = new address[](2); - path[0] = reserveCurrency; - path[1] = fc.collateralCurrency(); - - TransferHelper.safeApprove(reserveCurrency, address(router), type(uint256).max); - router.swapTokensForExactTokens( - collateralToBePurchased, // amountOut - (router.getAmountsIn(1000000, path)[0] * collateralToBePurchased * (1e18 + maxSlippage / 2) * 997) / - (1000000 * 1e18 * 1000), // amountInMax - path, - address(this), - deadline - ); - } - - // 4.c. If at this point we were not able to get `the required amount of collateral (due to insufficient reserve - // or not enough collateral in the contract) the script should try to liquidate as much as it can regardless. - // Update the values of total collateral to the current collateral balance and re-compute the tokenShortfall - // as the maximum tokens that could be liquidated at the current GCR. - if (totalCollateralRequired.isGreaterThan(getCollateralBalance(fc))) { - totalCollateralRequired = getCollateralBalance(fc); - collateralToMintShortfall = totalCollateralRequired.sub(getFinalFee(fc)); - tokenShortfall = collateralToMintShortfall.divCeil(gcr); - } - // 5. Mint the shortfall synthetics with collateral. Note we are minting at the GCR. - // If the DSProxy already has enough tokens (tokenShortfall = 0) we still preform the approval on the collateral - // currency as this is needed to pay the final fee in the liquidation tx. - TransferHelper.safeApprove(fc.collateralCurrency(), address(fc), totalCollateralRequired.rawValue); - if (tokenShortfall.isGreaterThan(0)) fc.create(collateralToMintShortfall, tokenShortfall); - - // The liquidatableTokens is either the maxTokensToLiquidate (if we were able to buy/mint enough) or the full - // token token balance at this point if there was a shortfall. - if (maxTokensToLiquidate.isGreaterThan(getSyntheticBalance(fc))) maxTokensToLiquidate = getSyntheticBalance(fc); - - // 6. Liquidate position with newly minted synthetics. - TransferHelper.safeApprove(fc.tokenCurrency(), address(fc), maxTokensToLiquidate.rawValue); - fc.createLiquidation( - liquidatedSponsor, - minCollateralPerTokenLiquidated, - maxCollateralPerTokenLiquidated, - maxTokensToLiquidate, - deadline - ); - } - - // Helper method to work around subtraction overflow in the case of: a - b with b > a. - function subOrZero( - FixedPoint.Unsigned memory a, - FixedPoint.Unsigned memory b - ) internal pure returns (FixedPoint.Unsigned memory) { - return b.isGreaterThanOrEqual(a) ? FixedPoint.fromUnscaledUint(0) : a.sub(b); - } - - // Helper method to return the current final fee for a given financial contract instance. - function getFinalFee(IFinancialContract fc) internal view returns (FixedPoint.Unsigned memory) { - return IStore(IFinder(fc.finder()).getImplementationAddress("Store")).computeFinalFee(fc.collateralCurrency()); - } - - // Helper method to return the collateral balance of this contract. - function getCollateralBalance(IFinancialContract fc) internal view returns (FixedPoint.Unsigned memory) { - return FixedPoint.Unsigned(IERC20(fc.collateralCurrency()).balanceOf(address(this))); - } - - // Helper method to return the synthetic balance of this contract. - function getSyntheticBalance(IFinancialContract fc) internal view returns (FixedPoint.Unsigned memory) { - return FixedPoint.Unsigned(IERC20(fc.tokenCurrency()).balanceOf(address(this))); - } -} - -// Define some simple interfaces for dealing with UMA contracts. -interface IFinancialContract { - struct PositionData { - FixedPoint.Unsigned tokensOutstanding; - uint256 withdrawalRequestPassTimestamp; - FixedPoint.Unsigned withdrawalRequestAmount; - FixedPoint.Unsigned rawCollateral; - uint256 transferPositionRequestPassTimestamp; - } - - function positions(address sponsor) external view returns (PositionData memory); - - function collateralCurrency() external view returns (address); - - function tokenCurrency() external view returns (address); - - function finder() external view returns (address); - - function pfc() external view returns (FixedPoint.Unsigned memory); - - function totalTokensOutstanding() external view returns (FixedPoint.Unsigned memory); - - function create(FixedPoint.Unsigned memory collateralAmount, FixedPoint.Unsigned memory numTokens) external; - - function createLiquidation( - address sponsor, - FixedPoint.Unsigned calldata minCollateralPerToken, - FixedPoint.Unsigned calldata maxCollateralPerToken, - FixedPoint.Unsigned calldata maxTokensToLiquidate, - uint256 deadline - ) - external - returns ( - uint256 liquidationId, - FixedPoint.Unsigned memory tokensLiquidated, - FixedPoint.Unsigned memory finalFeeBond - ); -} - -interface IStore { - function computeFinalFee(address currency) external view returns (FixedPoint.Unsigned memory); -} - -interface IFinder { - function getImplementationAddress(bytes32 interfaceName) external view returns (address); -} diff --git a/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/LiquidationWithdrawer.sol b/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/LiquidationWithdrawer.sol deleted file mode 100644 index f2f10670a..000000000 --- a/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/LiquidationWithdrawer.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/FixedPoint.sol"; - -// Simple contract used to withdraw liquidations using a DSProxy from legacy contracts (1.2.2 and below). -contract LiquidationWithdrawer { - function withdrawLiquidation( - address financialContractAddress, - uint256 liquidationId, - address sponsor - ) public returns (FixedPoint.Unsigned memory) { - return IFinancialContract(financialContractAddress).withdrawLiquidation(liquidationId, sponsor); - } -} - -interface IFinancialContract { - function withdrawLiquidation( - uint256 liquidationId, - address sponsor - ) external returns (FixedPoint.Unsigned memory amountWithdrawn); -} diff --git a/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/PositionSettler.sol b/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/PositionSettler.sol deleted file mode 100644 index 0b50cb368..000000000 --- a/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/PositionSettler.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/FixedPoint.sol"; - -// Simple contract used to Settle expired positions using a DSProxy. -contract PositionSettler { - function settleExpired(address financialContractAddress) public returns (FixedPoint.Unsigned memory) { - return IFinancialContract(financialContractAddress).settleExpired(); - } -} - -interface IFinancialContract { - function settleExpired() external returns (FixedPoint.Unsigned memory amountWithdrawn); -} diff --git a/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/TokenRedeemer.sol b/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/TokenRedeemer.sol deleted file mode 100644 index 9ae17baf0..000000000 --- a/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/TokenRedeemer.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/FixedPoint.sol"; -import "@uniswap/lib/contracts/libraries/TransferHelper.sol"; - -// Simple contract used to redeem tokens using a DSProxy from an emp. -contract TokenRedeemer { - function redeem( - address financialContractAddress, - FixedPoint.Unsigned memory numTokens - ) public returns (FixedPoint.Unsigned memory) { - IFinancialContract fc = IFinancialContract(financialContractAddress); - TransferHelper.safeApprove(fc.tokenCurrency(), financialContractAddress, numTokens.rawValue); - return fc.redeem(numTokens); - } -} - -interface IFinancialContract { - function redeem(FixedPoint.Unsigned memory numTokens) external returns (FixedPoint.Unsigned memory amountWithdrawn); - - function tokenCurrency() external returns (address); -} diff --git a/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/TokenSender.sol b/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/TokenSender.sol deleted file mode 100644 index 5268a8958..000000000 --- a/contracts/external/uma/core/contracts/proxy-scripts/bot-action-wrappers/TokenSender.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../../common/implementation/ExpandedERC20.sol"; - -contract TokenSender { - function transferERC20(address tokenAddress, address recipientAddress, uint256 amount) public returns (bool) { - IERC20 token = IERC20(tokenAddress); - token.transfer(recipientAddress, amount); - return true; - } -} diff --git a/contracts/external/uma/core/contracts/proxy-scripts/lsp-broker/LspUniswapV2Broker.sol b/contracts/external/uma/core/contracts/proxy-scripts/lsp-broker/LspUniswapV2Broker.sol deleted file mode 100644 index 593b2f439..000000000 --- a/contracts/external/uma/core/contracts/proxy-scripts/lsp-broker/LspUniswapV2Broker.sol +++ /dev/null @@ -1,305 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; - -import "@uniswap/lib/contracts/libraries/Babylonian.sol"; -import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; - -import "@uniswap/lib/contracts/libraries/TransferHelper.sol"; -import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol"; - -import "../../common/implementation/FixedPoint.sol"; - -import "../../financial-templates/long-short-pair/LongShortPair.sol"; - -/** - * @title LspUniswapV2Broker - * @notice Helper contract to facilitate batched LSP and UniswapV2 transactions, including Mint+Sell and Mint+LP. - */ -contract LspUniswapV2Broker { - using FixedPoint for FixedPoint.Signed; - using FixedPoint for FixedPoint.Unsigned; - - /** - * @notice Mint long and short tokens and deposit them all into a UniV2 Pool. - * @dev The caller of this method needs to approve `amountCollateral` collateral to be spent by this contract. - * @param callingAsEOA If True, caller has balance of collateral and expects to receive back all LP tokens + dust. - * @param longShortPair LSP contract address to mint position on. - * @param router Contract to call to exchange long and short tokens. - * @param amountCollateral Amount of collateral to deposit and mint long and short tokens against. - * @param minLpTokens Minimum number of LP tokens to mint - * @param deadline Unix timestamp that will force the transaction to revert if minded after this time. - */ - function atomicMintAddLiquidity( - bool callingAsEOA, - LongShortPair longShortPair, - IUniswapV2Router01 router, - uint256 amountCollateral, - uint256 minLpTokens, - uint256 deadline - ) public { - require(address(longShortPair) != address(0), "Invalid long short pair"); - require(address(router) != address(0), "Invalid router"); - require(amountCollateral != 0, "Collateral to mint with"); - - IERC20 collateralToken = IERC20(longShortPair.collateralToken()); - IERC20 longToken = IERC20(longShortPair.longToken()); - IERC20 shortToken = IERC20(longShortPair.shortToken()); - - // 0) Pull collateral from caller if necessary and approve LSP to spend it. - if (callingAsEOA) - TransferHelper.safeTransferFrom(address(collateralToken), msg.sender, address(this), amountCollateral); - - // 1) Approve collateral to be spent by the LSP from this contract at size of amountCollateral. - TransferHelper.safeApprove(address(collateralToken), address(longShortPair), amountCollateral); - - // 2) Deposit collateral into LSP and mint long and short tokens. - uint256 tokensToCreate = FixedPoint - .fromUnscaledUint(amountCollateral) - .div(longShortPair.collateralPerPair()) - .rawValue; - longShortPair.create(tokensToCreate); - - require( - longToken.balanceOf(address(this)) == shortToken.balanceOf(address(this)) && - longToken.balanceOf(address(this)) == tokensToCreate, - "Create invariant failed" - ); - - { - // 3) Calculate if we need to sell the long token for the short or the short token for the long. Calculate - // the total trade size to bring the balance of long/short in this contract to equal the pool ratio after - // the trade. Note this computation must consider how the resultant pool ratios is impacted by the trade. - bool aToB; - uint256 tradeSize; - (uint256 reserveA, uint256 reserveB) = getReserves( - router.factory(), - address(longToken), - address(shortToken) - ); - (aToB, tradeSize) = computeSwapToMintAtPoolRatio( - FixedPoint.Signed(int256(tokensToCreate)), - FixedPoint.Signed(int256(reserveA)), - FixedPoint.Signed(int256(reserveB)) - ); - address[] memory path = new address[](2); - - // 3.a) If the trade is token a to b then we are selling long tokens for short tokens. In this case, we - // know the exact output number of tokens we want (short tokens) and use the router's - // swapTokensForExactTokens with the exact output number of tokens specified. - if (aToB && tradeSize > 0) { - path[0] = address(longToken); - path[1] = address(shortToken); - TransferHelper.safeApprove(address(longToken), address(router), tradeSize); - router.swapTokensForExactTokens(tradeSize, type(uint256).max, path, address(this), deadline); - } - if (!aToB && tradeSize > 0) { - // Else, if the trade is b to a then we are selling short tokens for long tokens. In this case, we - // know the exact input number of tokens that we need to sell (short tokens) and can use the router's - // swapExactTokensForTokens with the exact input specified. - path[0] = address(shortToken); - path[1] = address(longToken); - TransferHelper.safeApprove(address(shortToken), address(router), tradeSize); - router.swapExactTokensForTokens(tradeSize, 0, path, address(this), deadline); - } - } - - // 4) Add liquidity to the pool via the router. Approve both long and short tokens with the full account balance - // in this contract (i.e the amount of tokens minted +- that traded in step 2.). - TransferHelper.safeApprove(address(longToken), address(router), longToken.balanceOf(address(this))); - TransferHelper.safeApprove(address(shortToken), address(router), shortToken.balanceOf(address(this))); - router.addLiquidity( - address(longToken), - address(shortToken), - longToken.balanceOf(address(this)), - shortToken.balanceOf(address(this)), - 0, - 0, - address(this), - deadline - ); - - // 5) Validate that at least the minimum number of tokens have been minted. If not, revert. - { - IERC20 lPToken = IERC20(address(pairFor(router.factory(), address(longToken), address(shortToken)))); - require(lPToken.balanceOf(address(this)) > minLpTokens, "Failed to mint min LP tokens"); - - // 5) Finally, if the caller is an EOA, send back the LP tokens from minting + any dust that was left over. - // The dust is in the long and short tokens and will be left over as a result of small rounding errors in - // the calculation on how many long/short should be bought/sold to meet the pool ratio before minting. - if (callingAsEOA) { - // Send the LP tokens back to the minter. - TransferHelper.safeTransfer(address(lPToken), msg.sender, lPToken.balanceOf(address(this))); - - // Send any dust left over back to the minter that may have happened due to small rounding errors in - // the computation methods within computeSwapToMintAtPoolRatio. - TransferHelper.safeTransfer(address(longToken), msg.sender, longToken.balanceOf(address(this))); - TransferHelper.safeTransfer(address(shortToken), msg.sender, shortToken.balanceOf(address(this))); - } - } - } - - // For a given mint size (m) and pool balances (ra & rb), compute the trade size such that the resulting ratio - // of tokens, considering the mint and trade,is equal to the pool ratio after the trade. This is computed using the - // following logic: Assume m tokens are minted in equal proportion of long and short. This contract will therefore - // hold t_l=m+△l long tokens and t_s=m-△s after a trade of s for l tokens. The ratio t_l/t_s must equal the pool - // ratio after the trade. The pool ratio, considering the trade can be expressed as (R_a-△a)(R_b+△bλ)=k for a - // trade of △a for △b and a swap fee of λ=(1-swapFee). Using this, with a bit of algebra and simplification, we can - // solve for the resultant ratio to be (m+△a)/(m-△b)=(R_a-△a)/(R_b+△b). i.e the ratio of mint+trade must equal the - // ratio of token A and B in the pool after the trade has concluded. We also know that △a can be solved for as - // △a=(△bλR_a)/(R_b+△bλ) by manipulating a known uniswap equation. Using this equation and the expression of ratios - // we can solve simultaneously for △b. I.e how many short tokens do we need to sell such that the ratio of long and - // short after the trade equal the pool ratios. Numerically, this works out to this form: - // △b=(sqrt(R_b(R_a+m)(R_a(R_b(λ^2+2λ+1)+4λm)+R_b(λ^2-2λ+1)m))+R_b(-λ-1)(R_a+m))/(2λ(R_a+m)). For how this was solved - // from the algebraic solution above see https://bit.ly/3jn7AF6 on wolfram alpha showing the derivation. - function computeSwapToMintAtPoolRatio( - FixedPoint.Signed memory m, - FixedPoint.Signed memory ra, - FixedPoint.Signed memory rb - ) private pure returns (bool, uint256) { - FixedPoint.Signed memory numerator1 = sqrt( - rb.mul(ra.add(m)).mul( - ra.mul(rb.mul(lambda2().add(num(2).mul(lambda())).add(num(1))).add(num(4).mul(lambda().mul(m)))).add( - rb.mul(lambda2().sub(num(2).mul(lambda())).add(num(1))).mul(m) - ) - ) - ); - - FixedPoint.Signed memory numerator2 = rb.mul(ra.add(m)).mul(num(-1).sub(lambda())); - - FixedPoint.Signed memory numerator = numerator1.mul(1e9).add(numerator2); - - FixedPoint.Signed memory denominator = num(2).mul(lambda()).mul(ra.add(m)); - - FixedPoint.Signed memory tradeSize = numerator.div(denominator); - - // If the trade size is negative then we are traversing the equation backwards. In this case the contract must - // swap between swapTokensForExactTokens and swapExactTokensForTokens methods as we are always solving for △b. - // In other words, we are always solving for how many short tokens are needed to be bought/sold. If the number - // is positive then we are buying them. if it is negative then we are selling them. In the case we are selling - // them (negative) then we swap the polarity and apply the swap fee to the trade. - bool aToB = tradeSize.isLessThan(0); - return (aToB, uint256(aToB ? applySwapFee(tradeSize.mul(-1)).rawValue : tradeSize.rawValue)); - } - - // Syntactical sugar to calculate square root of a number. - function sqrt(FixedPoint.Signed memory _num) private pure returns (FixedPoint.Signed memory) { - return FixedPoint.Signed(int256(Babylonian.sqrt(uint256(_num.rawValue)))); - } - - // Syntactical sugar to convert a int256 to a Fixedpoint.Signed. - function num(int256 _num) private pure returns (FixedPoint.Signed memory) { - return FixedPoint.fromUnscaledInt(_num); - } - - // Takes an input fixedPoint.Signed num and returns the num scaled by 0.997. 0.3% swap fee as in uniswap. - function applySwapFee(FixedPoint.Signed memory _num) private pure returns (FixedPoint.Signed memory) { - return _num.mul(997).div(1000); - } - - // 1 with 0.3% fees applied. - function lambda() private pure returns (FixedPoint.Signed memory) { - return applySwapFee(FixedPoint.fromUnscaledInt(1)); - } - - // 1 with 0.3% fees applied twice. - function lambda2() private pure returns (FixedPoint.Signed memory) { - return applySwapFee(lambda()); - } - - /** - * @notice Mint long and short tokens and convert all of one side into the other. - * @dev The caller of this method needs to approve `amountCollateral` collateral to be spent by this contract. - * @param callingAsEOA If True, caller has balance of collateral and expects to receive back all long/short tokens. - * @param sellLong If True, converts all long tokens into short, else the opposite. - * @param longShortPair LSP contract address to mint position on. - * @param router Contract to call to exchange long and short tokens. - * @param amountCollateral Amount of collateral to deposit and mint long and short tokens against. - * @param swapPath `Router.swapExactTokensForTokens` param: path with which to swap token to sell for the other. - * @param deadline `Router.swapExactTokensForTokens` param: time before transaction must be mined. - */ - function atomicMintSellOneSide( - bool callingAsEOA, - bool sellLong, - LongShortPair longShortPair, - IUniswapV2Router01 router /* TODO: Should we allow `router` to be any exchange, such as a Matcha multihop? */, - uint256 amountCollateral, - address[] memory swapPath, - uint256 deadline - ) public { - require(address(longShortPair) != address(0), "Invalid long short pair"); - require(address(router) != address(0), "Invalid router"); - require(amountCollateral != 0, "Collateral to mint with"); - - IERC20 collateralToken = IERC20(longShortPair.collateralToken()); - - // 0) Pull collateral from caller if necessary and approve LSP to spend it. - if (callingAsEOA) - TransferHelper.safeTransferFrom(address(collateralToken), msg.sender, address(this), amountCollateral); - - TransferHelper.safeApprove(address(collateralToken), address(longShortPair), amountCollateral); - - // 1) Deposit collateral into LSP and mint long and short tokens. - uint256 tokensToCreate = FixedPoint - .fromUnscaledUint(amountCollateral) - .div(longShortPair.collateralPerPair()) - .rawValue; - longShortPair.create(tokensToCreate); - - // 2) Determine which token we are selling and convert it all into the other. - IERC20 soldToken = IERC20(sellLong ? longShortPair.shortToken() : longShortPair.longToken()); - TransferHelper.safeApprove(address(soldToken), address(router), soldToken.balanceOf(address(this))); - require(swapPath[0] == address(soldToken), "Sold token != 0th swapPath"); - router.swapExactTokensForTokens( - soldToken.balanceOf(address(this)), // sell all of the sold tokens held by the contract. - 0, - swapPath, - address(this), - deadline - ); - - // 3) Send tokens back to caller if necessary. - if (callingAsEOA) { - IERC20 purchasedToken = IERC20(!sellLong ? longShortPair.shortToken() : longShortPair.longToken()); - TransferHelper.safeTransfer(address(purchasedToken), msg.sender, purchasedToken.balanceOf(address(this))); - } - } - - function getReserves( - address factory, - address tokenA, - address tokenB - ) public view returns (uint256 reserveA, uint256 reserveB) { - (address token0, ) = sortTokens(tokenA, tokenB); - (uint256 reserve0, uint256 reserve1, ) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves(); - (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); - } - - function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) { - require(tokenA != tokenB, "UniswapV2Library: IDENTICAL_ADDRESSES"); - (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); - require(token0 != address(0), "UniswapV2Library: ZERO_ADDRESS"); - } - - // calculates the CREATE2 address for a pair without making any external calls - function pairFor(address factory, address tokenA, address tokenB) internal pure returns (IUniswapV2Pair pair) { - (address token0, address token1) = sortTokens(tokenA, tokenB); - pair = IUniswapV2Pair( - address( - uint160( - uint256( - keccak256( - abi.encodePacked( - hex"ff", - factory, - keccak256(abi.encodePacked(token0, token1)), - hex"96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" // init code hash - ) - ) - ) - ) - ) - ); - } -} diff --git a/contracts/external/uma/core/contracts/proxy-scripts/uniswap-broker/UniswapV2Broker.sol b/contracts/external/uma/core/contracts/proxy-scripts/uniswap-broker/UniswapV2Broker.sol deleted file mode 100644 index 5c5dc1e6e..000000000 --- a/contracts/external/uma/core/contracts/proxy-scripts/uniswap-broker/UniswapV2Broker.sol +++ /dev/null @@ -1,278 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-v4/utils/math/SafeMath.sol"; - -import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; - -import "@uniswap/lib/contracts/libraries/Babylonian.sol"; -import "@uniswap/lib/contracts/libraries/TransferHelper.sol"; -import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol"; - -/** - * @title UniswapV2Broker - * @notice Trading contract used to arb uniswap pairs to a desired "true" price. Intended use is to arb UMA perpetual - * synthetics that trade off peg. This implementation can ber used in conjunction with a DSProxy contract to atomically - * swap and move a uniswap market. - */ - -contract UniswapV2Broker { - using SafeMath for uint256; - - /** - * @notice Swaps an amount of either token such that the trade results in the uniswap pair's price being as close as - * possible to the truePrice. - * @dev True price is expressed in the ratio of token A to token B. - * @dev The caller must approve this contract to spend whichever token is intended to be swapped. - * @param tradingAsEOA bool to indicate if the UniswapV2Broker is being called by a DSProxy or an EOA. - * @param uniswapRouter address of the uniswap router used to facilitate trades. - * @param uniswapFactory address of the uniswap factory used to fetch current pair reserves. - * @param swappedTokens array of addresses which are to be swapped. The order does not matter as the function will figure - * out which tokens need to be exchanged to move the market to the desired "true" price. - * @param truePriceTokens array of unit used to represent the true price. 0th value is the numerator of the true price - * and the 1st value is the the denominator of the true price. - * @param maxSpendTokens array of unit to represent the max to spend in the two tokens. - * @param to recipient of the trade proceeds. - * @param deadline to limit when the trade can execute. If the tx is mined after this timestamp then revert. - */ - function swapToPrice( - bool tradingAsEOA, - address uniswapRouter, - address uniswapFactory, - address[2] memory swappedTokens, - uint256[2] memory truePriceTokens, - uint256[2] memory maxSpendTokens, - address to, - uint256 deadline - ) public { - IUniswapV2Router01 router = IUniswapV2Router01(uniswapRouter); - - // true price is expressed as a ratio, so both values must be non-zero - require(truePriceTokens[0] != 0 && truePriceTokens[1] != 0, "SwapToPrice: ZERO_PRICE"); - // caller can specify 0 for either if they wish to swap in only one direction, but not both - require(maxSpendTokens[0] != 0 || maxSpendTokens[1] != 0, "SwapToPrice: ZERO_SPEND"); - - bool aToB; - uint256 amountIn; - { - (uint256 reserveA, uint256 reserveB) = getReserves(uniswapFactory, swappedTokens[0], swappedTokens[1]); - (aToB, amountIn) = computeTradeToMoveMarket(truePriceTokens[0], truePriceTokens[1], reserveA, reserveB); - } - - require(amountIn > 0, "SwapToPrice: ZERO_AMOUNT_IN"); - - // spend up to the allowance of the token in - uint256 maxSpend = aToB ? maxSpendTokens[0] : maxSpendTokens[1]; - if (amountIn > maxSpend) { - amountIn = maxSpend; - } - - address tokenIn = aToB ? swappedTokens[0] : swappedTokens[1]; - address tokenOut = aToB ? swappedTokens[1] : swappedTokens[0]; - - TransferHelper.safeApprove(tokenIn, address(router), amountIn); - - if (tradingAsEOA) TransferHelper.safeTransferFrom(tokenIn, msg.sender, address(this), amountIn); - - address[] memory path = new address[](2); - path[0] = tokenIn; - path[1] = tokenOut; - - router.swapExactTokensForTokens( - amountIn, - 0, // amountOutMin: we can skip computing this number because the math is tested within the uniswap tests. - path, - to, - deadline - ); - } - - /** - * @notice Given the "true" price a token (represented by truePriceTokenA/truePriceTokenB) and the reservers in the - * uniswap pair, calculate: a) the direction of trade (aToB) and b) the amount needed to trade (amountIn) to move - * the pool price to be equal to the true price. - * @dev Note that this method uses the Babylonian square root method which has a small margin of error which will - * result in a small over or under estimation on the size of the trade needed. - * @param truePriceTokenA the nominator of the true price. - * @param truePriceTokenB the denominator of the true price. - * @param reserveA number of token A in the pair reserves - * @param reserveB number of token B in the pair reserves - */ - // - function computeTradeToMoveMarket( - uint256 truePriceTokenA, - uint256 truePriceTokenB, - uint256 reserveA, - uint256 reserveB - ) public pure returns (bool aToB, uint256 amountIn) { - aToB = FullMath.mulDiv(reserveA, truePriceTokenB, reserveB) < truePriceTokenA; - - uint256 invariant = reserveA.mul(reserveB); - - // The trade ∆a of token a required to move the market to some desired price P' from the current price P can be - // found with ∆a=(kP')^1/2-Ra. - uint256 leftSide = Babylonian.sqrt( - FullMath.mulDiv( - invariant, - aToB ? truePriceTokenA : truePriceTokenB, - aToB ? truePriceTokenB : truePriceTokenA - ) - ); - uint256 rightSide = (aToB ? reserveA : reserveB); - - if (leftSide < rightSide) return (false, 0); - - // compute the amount that must be sent to move the price back to the true price. - amountIn = leftSide.sub(rightSide); - } - - // The methods below are taken from https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/libraries/UniswapV2Library.sol - // We could import this library into this contract but this library is dependent Uniswap's SafeMath, which is bound - // to solidity 6.6.6. UMA uses 0.8.0 and so a modified version is needed to accomidate this solidity version. - function getReserves( - address factory, - address tokenA, - address tokenB - ) public view returns (uint256 reserveA, uint256 reserveB) { - (address token0, ) = sortTokens(tokenA, tokenB); - (uint256 reserve0, uint256 reserve1, ) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves(); - (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); - } - - function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) { - require(tokenA != tokenB, "UniswapV2Library: IDENTICAL_ADDRESSES"); - (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); - require(token0 != address(0), "UniswapV2Library: ZERO_ADDRESS"); - } - - // calculates the CREATE2 address for a pair without making any external calls - function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) { - (address token0, address token1) = sortTokens(tokenA, tokenB); - pair = address( - uint160( - uint256( - keccak256( - abi.encodePacked( - hex"ff", - factory, - keccak256(abi.encodePacked(token0, token1)), - hex"96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" // init code hash - ) - ) - ) - ) - ); - } -} - -// The library below is taken from @uniswap/lib/contracts/libraries/FullMath.sol. It has been modified to work with solidity 0.8 -library FullMath { - /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 - /// @param a The multiplicand - /// @param b The multiplier - /// @param denominator The divisor - /// @return result The 256-bit result - /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv - function mulDiv(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) { - // 512-bit multiply [prod1 prod0] = a * b - // Compute the product mod 2**256 and mod 2**256 - 1 - // then use the Chinese Remainder Theorem to reconstruct - // the 512 bit result. The result is stored in two 256 - // variables such that product = prod1 * 2**256 + prod0 - uint256 prod0; // Least significant 256 bits of the product - uint256 prod1; // Most significant 256 bits of the product - assembly { - let mm := mulmod(a, b, not(0)) - prod0 := mul(a, b) - prod1 := sub(sub(mm, prod0), lt(mm, prod0)) - } - - // Handle non-overflow cases, 256 by 256 division - if (prod1 == 0) { - require(denominator > 0); - assembly { - result := div(prod0, denominator) - } - return result; - } - - // Make sure the result is less than 2**256. - // Also prevents denominator == 0 - require(denominator > prod1); - - /////////////////////////////////////////////// - // 512 by 256 division. - /////////////////////////////////////////////// - - // Make division exact by subtracting the remainder from [prod1 prod0] - // Compute remainder using mulmod - uint256 remainder; - assembly { - remainder := mulmod(a, b, denominator) - } - // Subtract 256 bit number from 512 bit number - assembly { - prod1 := sub(prod1, gt(remainder, prod0)) - prod0 := sub(prod0, remainder) - } - - // Factor powers of two out of denominator - // Compute largest power of two divisor of denominator. - // Always >= 1. - uint256 twos = denominator & (~denominator + 1); - // Divide denominator by power of two - assembly { - denominator := div(denominator, twos) - } - - // Divide [prod1 prod0] by the factors of two - assembly { - prod0 := div(prod0, twos) - } - // Shift in bits from prod1 into prod0. For this we need - // to flip `twos` such that it is 2**256 / twos. - // If twos is zero, then it becomes one - assembly { - twos := add(div(sub(0, twos), twos), 1) - } - prod0 |= prod1 * twos; - - // Invert denominator mod 2**256 - // Now that denominator is an odd number, it has an inverse - // modulo 2**256 such that denominator * inv = 1 mod 2**256. - // Compute the inverse by starting with a seed that is correct - // correct for four bits. That is, denominator * inv = 1 mod 2**4 - uint256 inv = (3 * denominator) ^ 2; - // Now use Newton-Raphson iteration to improve the precision. - // Thanks to Hensel's lifting lemma, this also works in modular - // arithmetic, doubling the correct bits in each step. - inv *= 2 - denominator * inv; // inverse mod 2**8 - inv *= 2 - denominator * inv; // inverse mod 2**16 - inv *= 2 - denominator * inv; // inverse mod 2**32 - inv *= 2 - denominator * inv; // inverse mod 2**64 - inv *= 2 - denominator * inv; // inverse mod 2**128 - inv *= 2 - denominator * inv; // inverse mod 2**256 - - // Because the division is now exact we can divide by multiplying - // with the modular inverse of denominator. This will give us the - // correct result modulo 2**256. Since the precoditions guarantee - // that the outcome is less than 2**256, this is the final result. - // We don't need to compute the high bits of the result and prod1 - // is no longer required. - result = prod0 * inv; - return result; - } - - /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 - /// @param a The multiplicand - /// @param b The multiplier - /// @param denominator The divisor - /// @return result The 256-bit result - function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) { - result = mulDiv(a, b, denominator); - if (mulmod(a, b, denominator) > 0) { - require(result < type(uint256).max); - result++; - } - } -} diff --git a/contracts/external/uma/core/contracts/proxy-scripts/uniswap-broker/UniswapV3Broker.sol b/contracts/external/uma/core/contracts/proxy-scripts/uniswap-broker/UniswapV3Broker.sol deleted file mode 100644 index 634deb1ea..000000000 --- a/contracts/external/uma/core/contracts/proxy-scripts/uniswap-broker/UniswapV3Broker.sol +++ /dev/null @@ -1,623 +0,0 @@ -pragma solidity ^0.8.0; - -import "@uniswap/lib/contracts/libraries/TransferHelper.sol"; - -import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; - -import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; - -import "@uniswap/v3-core/contracts/libraries/LowGasSafeMath.sol"; -import "@uniswap/v3-core/contracts/libraries/LiquidityMath.sol"; -import "@uniswap/v3-core/contracts/libraries/BitMath.sol"; -import "@uniswap/v3-core/contracts/libraries/UnsafeMath.sol"; -import "@uniswap/v3-core/contracts/libraries/SafeCast.sol"; - -/** - * @title UniswapV3Broker - * @notice Trading contract used to arb uniswapV3 pairs to a desired "true" price. Intended use is to arb UMA perpetual - * synthetics that trade off peg. This implementation can ber used in conjunction with a DSProxy contract to atomically - * swap and move a uniswap market. - */ - -contract UniswapV3Broker { - using SafeCast for uint256; - using LowGasSafeMath for uint256; - using LowGasSafeMath for int256; - - struct SwapState { - uint160 sqrtPriceX96; - int24 tick; - uint128 liquidity; - uint256 requiredInputAmount; - } - - struct StepComputations { - uint160 sqrtPriceStartX96; - int24 tickNext; - bool initialized; - uint160 sqrtPriceNextX96; - } - - /** - * @notice Swaps an amount of either pool tokens such that the trade results in the uniswap pair's price equaling a - * desired price. - * @dev The desired price is represented as sqrtRatioTargetX96. This is the Price^(1/2) * 96^2. - * @dev The caller must approve this contract to spend whichever token is intended to be swapped. - * @param tradingAsEOA bool to indicate if the UniswapBroker is being called by a DSProxy or an EOA. - * @param uniswapPool address of the pool to uniswap v3 trade against. - * @param uniswapRouter address of the uniswap v3 router to route the trade. - * @param sqrtRatioTargetX96 target, encoded price. - * @param recipient address that the output tokens should be sent to. - * @param deadline to limit when the trade can execute. If the tx is mined after this timestamp then revert. - */ - function swapToPrice( - bool tradingAsEOA, - address uniswapPool, - address uniswapRouter, - uint160 sqrtRatioTargetX96, - address recipient, - uint256 deadline - ) external returns (uint256) { - // Create an instance of the pool and load in the current token price and the active tick. - IUniswapV3Pool pool = IUniswapV3Pool(uniswapPool); - (uint160 sqrtPriceX96, int24 tick, , , , , ) = pool.slot0(); - - // Work out the direction we need to trade. If the current price is more than the target price then we are - // trading token0 for token1. Else, we are trading token1 for token0. - bool zeroForOne = sqrtPriceX96 >= sqrtRatioTargetX96; - - // Build a state object to store this information which can be re-used during. - SwapState memory state = SwapState({ - sqrtPriceX96: sqrtPriceX96, - tick: tick, - liquidity: pool.liquidity(), - requiredInputAmount: 0 - }); - - // Iterate in a while loop that breaks when we hit the target price. - while (true) { - // Compute the next initialized tick. We only need to traverse initialized ticks as uninitialized ticks - // have the same liquidity as the previous tick. - StepComputations memory step; - step.sqrtPriceStartX96 = state.sqrtPriceX96; - (step.tickNext, step.initialized) = TickBitmap.nextInitializedTickWithinOneWord( - pool, - state.tick, - pool.tickSpacing(), - zeroForOne - ); - - // Double check we are not over or underflow the ticks. - if (step.tickNext < TickMath.MIN_TICK) step.tickNext = TickMath.MIN_TICK; - else if (step.tickNext > TickMath.MAX_TICK) step.tickNext = TickMath.MAX_TICK; - - // Find the price at the next tick. Between the current state.sqrtPriceX96 and the nextTickPriceX96 we - // can find how much of the sold token is needed to sufficiently move the market over the interval. - - uint160 nextTickPriceX96 = TickMath.getSqrtRatioAtTick(step.tickNext); - uint256 inputAmountForStep; - - // If zeroForOne is true, then we are moving the price UP. In this case we need to ensure that if the next - // tick price is more than the target price, we set the set the the next step price to the target price. This - // ensures that the price does not undershoot when the next tick is the last tick. Else, traverse the whole tick. - if (zeroForOne) { - step.sqrtPriceNextX96 = nextTickPriceX96 > sqrtRatioTargetX96 ? sqrtRatioTargetX96 : nextTickPriceX96; - inputAmountForStep = SqrtPriceMath.getAmount0Delta( // As we are trading token0 for token1, calculate the token0 input. - step.sqrtPriceStartX96, - step.sqrtPriceNextX96, - state.liquidity, - false - ); - // Else, if zeroForOne is false, then we are moving the price DOWN. In this case we need to ensure that we - // don't overshoot the price on the next step. - } else { - step.sqrtPriceNextX96 = nextTickPriceX96 > sqrtRatioTargetX96 ? nextTickPriceX96 : sqrtRatioTargetX96; - inputAmountForStep = SqrtPriceMath.getAmount1Delta( // As we are trading token1 for token0, calculate the token1 input. - step.sqrtPriceStartX96, - step.sqrtPriceNextX96, - state.liquidity, - false - ); - } - - // Add amount for this step to the total required input. - state.requiredInputAmount = state.requiredInputAmount.add(inputAmountForStep); - - // If we have hit(or exceeded) our target price in the associate direction, then stop. - if (zeroForOne && state.sqrtPriceX96 <= sqrtRatioTargetX96) break; - if (!zeroForOne && state.sqrtPriceX96 >= sqrtRatioTargetX96) break; - - // If the next step is is initialized then we will need to update the liquidity for the current step. - if (step.initialized) { - // Fetch the net liquidity. this could be positive or negative depending on if a LP is turning on or off at this price. - (, int128 liquidityNet, , , , , , ) = pool.ticks(step.tickNext); - state.liquidity = LiquidityMath.addDelta(state.liquidity, zeroForOne ? liquidityNet : -liquidityNet); - } - - // Finally, set the state price to the next price for the next iteration. - state.sqrtPriceX96 = step.sqrtPriceNextX96; - state.tick = step.tickNext; - } - - // Based on the direction we are moving, set the input and output tokens. - (address tokenIn, address tokenOut) = zeroForOne - ? (pool.token0(), pool.token1()) - : (pool.token1(), pool.token0()); - - // If trading from an EOA pull tokens into this contract. If trading from a DSProxy this is redundant. - if (tradingAsEOA) - TransferHelper.safeTransferFrom(tokenIn, msg.sender, address(this), state.requiredInputAmount); - - // Approve the router and execute the swap. - TransferHelper.safeApprove(tokenIn, address(uniswapRouter), state.requiredInputAmount); - ISwapRouter(uniswapRouter).exactInputSingle( - ISwapRouter.ExactInputSingleParams({ - tokenIn: tokenIn, - tokenOut: tokenOut, - fee: pool.fee(), - recipient: recipient, - deadline: deadline, - amountIn: state.requiredInputAmount, - amountOutMinimum: 0, - sqrtPriceLimitX96: sqrtRatioTargetX96 - }) - ); - - return state.requiredInputAmount; - } -} - -// The code below are taken almost verbatim from https://github.com/Uniswap/uniswap-v3-core/blob/main/contracts/libraries/TickBitmap.sol. -// They was modified slightly to enable the them to be called on an external pool by passing in a pool address and -// to accommodate solidity 0.8. - -library TickBitmap { - function nextInitializedTickWithinOneWord( - IUniswapV3Pool pool, - int24 tick, - int24 tickSpacing, - bool lte - ) internal view returns (int24 next, bool initialized) { - int24 compressed = tick / tickSpacing; - if (tick < 0 && tick % tickSpacing != 0) compressed--; // round towards negative infinity - - if (lte) { - (int16 wordPos, uint8 bitPos) = position(compressed); - // all the 1s at or to the right of the current bitPos - uint256 mask = (1 << bitPos) - 1 + (1 << bitPos); - uint256 masked = pool.tickBitmap(wordPos) & mask; - - // if there are no initialized ticks to the right of or at the current tick, return rightmost in the word - initialized = masked != 0; - // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick - next = initialized - ? (compressed - int24(int24(uint24(bitPos)) - int24(uint24(BitMath.mostSignificantBit(masked))))) * - tickSpacing - : (compressed - int24(uint24(bitPos))) * tickSpacing; - } else { - // start from the word of the next tick, since the current tick state doesn't matter - (int16 wordPos, uint8 bitPos) = position(compressed + 1); - // all the 1s at or to the left of the bitPos - uint256 mask = ~((1 << bitPos) - 1); - uint256 masked = pool.tickBitmap(wordPos) & mask; - - // if there are no initialized ticks to the left of the current tick, return leftmost in the word - initialized = masked != 0; - // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick - next = initialized - ? (compressed + 1 + int24(BitMath.leastSignificantBit(masked) - uint24(bitPos))) * tickSpacing - : (compressed + 1 + int24(type(uint8).max - uint24(bitPos))) * tickSpacing; - } - } - - function position(int24 tick) private pure returns (int16 wordPos, uint8 bitPos) { - wordPos = int16(tick >> 8); - bitPos = uint8(int8(tick % 256)); - } - - function flipTick(mapping(int16 => uint256) storage self, int24 tick, int24 tickSpacing) internal { - require(tick % tickSpacing == 0); // ensure that the tick is spaced - (int16 wordPos, uint8 bitPos) = position(tick / tickSpacing); - uint256 mask = 1 << bitPos; - self[wordPos] ^= mask; - } -} - -// Taken from https://github.com/Uniswap/uniswap-v3-core/blob/main/contracts/libraries/TickMath.sol and update -// to work with solidity 0.8. -library TickMath { - /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128 - int24 internal constant MIN_TICK = -887272; - /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128 - int24 internal constant MAX_TICK = -MIN_TICK; - - /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK) - uint160 internal constant MIN_SQRT_RATIO = 4295128739; - /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK) - uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; - - /// @notice Calculates sqrt(1.0001^tick) * 2^96 - /// @dev Throws if |tick| > max tick - /// @param tick The input tick for the above formula - /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0) - /// at the given tick - function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) { - uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick)); - require(absTick <= uint256(int256(MAX_TICK)), "T"); - - uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000; - if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128; - if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128; - if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128; - if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128; - if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128; - if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128; - if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128; - if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128; - if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128; - if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128; - if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128; - if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128; - if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128; - if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128; - if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128; - if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128; - if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128; - if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128; - if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128; - - if (tick > 0) ratio = type(uint256).max / ratio; - - // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96. - // we then downcast because we know the result always fits within 160 bits due to our tick input constraint - // we round up in the division so getTickAtSqrtRatio of the output price is always consistent - sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1)); - } - - function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick) { - // second inequality must be < because the price can never reach the price at the max tick - require(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO, "R"); - uint256 ratio = uint256(sqrtPriceX96) << 32; - - uint256 r = ratio; - uint256 msb = 0; - - assembly { - let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) - msb := or(msb, f) - r := shr(f, r) - } - assembly { - let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF)) - msb := or(msb, f) - r := shr(f, r) - } - assembly { - let f := shl(5, gt(r, 0xFFFFFFFF)) - msb := or(msb, f) - r := shr(f, r) - } - assembly { - let f := shl(4, gt(r, 0xFFFF)) - msb := or(msb, f) - r := shr(f, r) - } - assembly { - let f := shl(3, gt(r, 0xFF)) - msb := or(msb, f) - r := shr(f, r) - } - assembly { - let f := shl(2, gt(r, 0xF)) - msb := or(msb, f) - r := shr(f, r) - } - assembly { - let f := shl(1, gt(r, 0x3)) - msb := or(msb, f) - r := shr(f, r) - } - assembly { - let f := gt(r, 0x1) - msb := or(msb, f) - } - - if (msb >= 128) r = ratio >> (msb - 127); - else r = ratio << (127 - msb); - - int256 log_2 = (int256(msb) - 128) << 64; - - assembly { - r := shr(127, mul(r, r)) - let f := shr(128, r) - log_2 := or(log_2, shl(63, f)) - r := shr(f, r) - } - assembly { - r := shr(127, mul(r, r)) - let f := shr(128, r) - log_2 := or(log_2, shl(62, f)) - r := shr(f, r) - } - assembly { - r := shr(127, mul(r, r)) - let f := shr(128, r) - log_2 := or(log_2, shl(61, f)) - r := shr(f, r) - } - assembly { - r := shr(127, mul(r, r)) - let f := shr(128, r) - log_2 := or(log_2, shl(60, f)) - r := shr(f, r) - } - assembly { - r := shr(127, mul(r, r)) - let f := shr(128, r) - log_2 := or(log_2, shl(59, f)) - r := shr(f, r) - } - assembly { - r := shr(127, mul(r, r)) - let f := shr(128, r) - log_2 := or(log_2, shl(58, f)) - r := shr(f, r) - } - assembly { - r := shr(127, mul(r, r)) - let f := shr(128, r) - log_2 := or(log_2, shl(57, f)) - r := shr(f, r) - } - assembly { - r := shr(127, mul(r, r)) - let f := shr(128, r) - log_2 := or(log_2, shl(56, f)) - r := shr(f, r) - } - assembly { - r := shr(127, mul(r, r)) - let f := shr(128, r) - log_2 := or(log_2, shl(55, f)) - r := shr(f, r) - } - assembly { - r := shr(127, mul(r, r)) - let f := shr(128, r) - log_2 := or(log_2, shl(54, f)) - r := shr(f, r) - } - assembly { - r := shr(127, mul(r, r)) - let f := shr(128, r) - log_2 := or(log_2, shl(53, f)) - r := shr(f, r) - } - assembly { - r := shr(127, mul(r, r)) - let f := shr(128, r) - log_2 := or(log_2, shl(52, f)) - r := shr(f, r) - } - assembly { - r := shr(127, mul(r, r)) - let f := shr(128, r) - log_2 := or(log_2, shl(51, f)) - r := shr(f, r) - } - assembly { - r := shr(127, mul(r, r)) - let f := shr(128, r) - log_2 := or(log_2, shl(50, f)) - } - - int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number - - int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128); - int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128); - - tick = tickLow == tickHi - ? tickLow - : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 - ? tickHi - : tickLow; - } -} - -library SqrtPriceMath { - using SafeCast for uint256; - - function getAmount0Delta( - uint160 sqrtRatioAX96, - uint160 sqrtRatioBX96, - uint128 liquidity, - bool roundUp - ) internal pure returns (uint256 amount0) { - if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); - - uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; - uint256 numerator2 = sqrtRatioBX96 - sqrtRatioAX96; - - require(sqrtRatioAX96 > 0); - - return - roundUp - ? UnsafeMath.divRoundingUp( - FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96), - sqrtRatioAX96 - ) - : FullMath.mulDiv(numerator1, numerator2, sqrtRatioBX96) / sqrtRatioAX96; - } - - /// @notice Gets the amount1 delta between two prices - /// @dev Calculates liquidity * (sqrt(upper) - sqrt(lower)) - /// @param sqrtRatioAX96 A sqrt price - /// @param sqrtRatioBX96 Another sqrt price - /// @param liquidity The amount of usable liquidity - /// @param roundUp Whether to round the amount up, or down - /// @return amount1 Amount of token1 required to cover a position of size liquidity between the two passed prices - function getAmount1Delta( - uint160 sqrtRatioAX96, - uint160 sqrtRatioBX96, - uint128 liquidity, - bool roundUp - ) internal pure returns (uint256 amount1) { - if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); - - return - roundUp - ? FullMath.mulDivRoundingUp(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96) - : FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96); - } - - /// @notice Helper that gets signed token0 delta - /// @param sqrtRatioAX96 A sqrt price - /// @param sqrtRatioBX96 Another sqrt price - /// @param liquidity The change in liquidity for which to compute the amount0 delta - /// @return amount0 Amount of token0 corresponding to the passed liquidityDelta between the two prices - function getAmount0Delta( - uint160 sqrtRatioAX96, - uint160 sqrtRatioBX96, - int128 liquidity - ) internal pure returns (int256 amount0) { - return - liquidity < 0 - ? -getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() - : getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); - } - - /// @notice Helper that gets signed token1 delta - /// @param sqrtRatioAX96 A sqrt price - /// @param sqrtRatioBX96 Another sqrt price - /// @param liquidity The change in liquidity for which to compute the amount1 delta - /// @return amount1 Amount of token1 corresponding to the passed liquidityDelta between the two prices - function getAmount1Delta( - uint160 sqrtRatioAX96, - uint160 sqrtRatioBX96, - int128 liquidity - ) internal pure returns (int256 amount1) { - return - liquidity < 0 - ? -getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() - : getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); - } -} - -library FullMath { - /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 - /// @param a The multiplicand - /// @param b The multiplier - /// @param denominator The divisor - /// @return result The 256-bit result - /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv - function mulDiv(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) { - // 512-bit multiply [prod1 prod0] = a * b - // Compute the product mod 2**256 and mod 2**256 - 1 - // then use the Chinese Remainder Theorem to reconstruct - // the 512 bit result. The result is stored in two 256 - // variables such that product = prod1 * 2**256 + prod0 - uint256 prod0; // Least significant 256 bits of the product - uint256 prod1; // Most significant 256 bits of the product - assembly { - let mm := mulmod(a, b, not(0)) - prod0 := mul(a, b) - prod1 := sub(sub(mm, prod0), lt(mm, prod0)) - } - - // Handle non-overflow cases, 256 by 256 division - if (prod1 == 0) { - require(denominator > 0); - assembly { - result := div(prod0, denominator) - } - return result; - } - - // Make sure the result is less than 2**256. - // Also prevents denominator == 0 - require(denominator > prod1); - - /////////////////////////////////////////////// - // 512 by 256 division. - /////////////////////////////////////////////// - - // Make division exact by subtracting the remainder from [prod1 prod0] - // Compute remainder using mulmod - uint256 remainder; - assembly { - remainder := mulmod(a, b, denominator) - } - // Subtract 256 bit number from 512 bit number - assembly { - prod1 := sub(prod1, gt(remainder, prod0)) - prod0 := sub(prod0, remainder) - } - - // Factor powers of two out of denominator - // Compute largest power of two divisor of denominator. - // Always >= 1. - // NOTE: this is modified from the original Full math implementation to work with solidity 8 - uint256 twos = (type(uint256).max - denominator + 1) & denominator; - // Divide denominator by power of two - assembly { - denominator := div(denominator, twos) - } - - // Divide [prod1 prod0] by the factors of two - assembly { - prod0 := div(prod0, twos) - } - // Shift in bits from prod1 into prod0. For this we need - // to flip `twos` such that it is 2**256 / twos. - // If twos is zero, then it becomes one - assembly { - twos := add(div(sub(0, twos), twos), 1) - } - unchecked { - prod0 |= prod1 * twos; - - // Invert denominator mod 2**256 - // Now that denominator is an odd number, it has an inverse - // modulo 2**256 such that denominator * inv = 1 mod 2**256. - // Compute the inverse by starting with a seed that is correct - // correct for four bits. That is, denominator * inv = 1 mod 2**4 - uint256 inv = (3 * denominator) ^ 2; - // Now use Newton-Raphson iteration to improve the precision. - // Thanks to Hensel's lifting lemma, this also works in modular - // arithmetic, doubling the correct bits in each step. - // NOTE: this is modified from the original Full math implementation to work with solidity 8 with the unchecked syntax. - inv *= 2 - denominator * inv; // inverse mod 2**8 - inv *= 2 - denominator * inv; // inverse mod 2**16 - inv *= 2 - denominator * inv; // inverse mod 2**32 - inv *= 2 - denominator * inv; // inverse mod 2**64 - inv *= 2 - denominator * inv; // inverse mod 2**128 - inv *= 2 - denominator * inv; // inverse mod 2**256 - // Because the division is now exact we can divide by multiplying - // with the modular inverse of denominator. This will give us the - // correct result modulo 2**256. Since the precoditions guarantee - // that the outcome is less than 2**256, this is the final result. - // We don't need to compute the high bits of the result and prod1 - // is no longer required. - result = prod0 * inv; - } - return result; - } - - /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 - /// @param a The multiplicand - /// @param b The multiplier - /// @param denominator The divisor - /// @return result The 256-bit result - function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) { - result = mulDiv(a, b, denominator); - if (mulmod(a, b, denominator) > 0) { - require(result < type(uint256).max); - result++; - } - } -} - -library FixedPoint96 { - uint8 internal constant RESOLUTION = 96; - uint256 internal constant Q96 = 0x1000000000000000000000000; -} diff --git a/contracts/external/uma/core/contracts/snapshot-helpers/SnapshotVotingPower.sol b/contracts/external/uma/core/contracts/snapshot-helpers/SnapshotVotingPower.sol deleted file mode 100644 index f00576fdd..000000000 --- a/contracts/external/uma/core/contracts/snapshot-helpers/SnapshotVotingPower.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -/** - * @title VotingInterface - * @notice Minimum interface required to interact with VotingV2 contract. - */ -interface VotingInterface { - struct VoterStake { - uint128 stake; - uint128 pendingUnstake; - uint128 rewardsPaidPerToken; - uint128 outstandingRewards; - int128 unappliedSlash; - uint64 nextIndexToProcess; - uint64 unstakeTime; - address delegate; - } - - function voterStakes(address) external view returns (VoterStake memory); - - function getVoterFromDelegate(address) external view returns (address); -} - -/** - * @title SnapshotVotingPower - * @notice Helper contract to support offchain voting with Snapshot. - */ -contract SnapshotVotingPower { - VotingInterface public immutable votingV2 = VotingInterface(0x004395edb43EFca9885CEdad51EC9fAf93Bd34ac); - - /** - * @notice This is only used by Snapshot to calculate voting power and does not represent transferable tokens. - * @param user address of the user for whom to calculate voting power. - * @return uint256 value of user's voting power based on staked UMA at DVM2.0. - **/ - function balanceOf(address user) external view returns (uint256) { - address voter = votingV2.getVoterFromDelegate(user); - VotingInterface.VoterStake memory voterStake = votingV2.voterStakes(voter); - - // Avoid double counting in case of stake delegation. - if (voterStake.delegate != address(0) && user != voterStake.delegate) return 0; - - return uint256(voterStake.stake); - } -} diff --git a/contracts/external/uma/core/contracts/umip-helpers/OriginValidator.sol b/contracts/external/uma/core/contracts/umip-helpers/OriginValidator.sol deleted file mode 100644 index 992c45415..000000000 --- a/contracts/external/uma/core/contracts/umip-helpers/OriginValidator.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -/** - * @title An auxiliary contract that checks if the tx origin is the upgrader. - * @dev Note: the validate function can be used as the first transaction in a governance proposals to block any other - * transactions from being executed if the proposal is not initiated by the upgrader. - */ -contract OriginValidator { - /** - * @notice Checks if the caller is the upgrader. - * @dev This is used as the first transaction in the upgrade process to block any other transactions from being - * executed if the upgrade is not initiated by the upgrader. - */ - function validate(address upgrader) public view { - require(tx.origin == upgrader); - } -} diff --git a/contracts/external/uma/core/contracts/umip-helpers/Umip3Upgrader.sol b/contracts/external/uma/core/contracts/umip-helpers/Umip3Upgrader.sol deleted file mode 100644 index 981f9d4b6..000000000 --- a/contracts/external/uma/core/contracts/umip-helpers/Umip3Upgrader.sol +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "../data-verification-mechanism/implementation/Finder.sol"; -import "../data-verification-mechanism/implementation/Constants.sol"; -import "../data-verification-mechanism/implementation/Voting.sol"; - -/** - * @title A contract to track a whitelist of addresses. - */ -contract Umip3Upgrader { - // Existing governor is the only one who can initiate the upgrade. - address public existingGovernor; - - // Existing Voting contract needs to be informed of the address of the new Voting contract. - Voting public existingVoting; - - // New governor will be the new owner of the finder. - address public newGovernor; - - // Finder contract to push upgrades to. - Finder public finder; - - // Addresses to upgrade. - address public voting; - address public identifierWhitelist; - address public store; - address public financialContractsAdmin; - address public registry; - - constructor( - address _existingGovernor, - address _existingVoting, - address _finder, - address _voting, - address _identifierWhitelist, - address _store, - address _financialContractsAdmin, - address _registry, - address _newGovernor - ) { - existingGovernor = _existingGovernor; - existingVoting = Voting(_existingVoting); - finder = Finder(_finder); - voting = _voting; - identifierWhitelist = _identifierWhitelist; - store = _store; - financialContractsAdmin = _financialContractsAdmin; - registry = _registry; - newGovernor = _newGovernor; - } - - function upgrade() external { - require(msg.sender == existingGovernor, "Upgrade can only be initiated by the existing governor."); - - // Change the addresses in the Finder. - finder.changeImplementationAddress(OracleInterfaces.Oracle, voting); - finder.changeImplementationAddress(OracleInterfaces.IdentifierWhitelist, identifierWhitelist); - finder.changeImplementationAddress(OracleInterfaces.Store, store); - finder.changeImplementationAddress(OracleInterfaces.FinancialContractsAdmin, financialContractsAdmin); - finder.changeImplementationAddress(OracleInterfaces.Registry, registry); - - // Transfer the ownership of the Finder to the new Governor now that all the addresses have been updated. - finder.transferOwnership(newGovernor); - - // Inform the existing Voting contract of the address of the new Voting contract and transfer its - // ownership to the new governor to allow for any future changes to the migrated contract. - existingVoting.setMigrated(voting); - existingVoting.transferOwnership(newGovernor); - } -} diff --git a/contracts/external/uma/core/contracts/umip-helpers/VotingUpgrader.sol b/contracts/external/uma/core/contracts/umip-helpers/VotingUpgrader.sol deleted file mode 100644 index cae70d937..000000000 --- a/contracts/external/uma/core/contracts/umip-helpers/VotingUpgrader.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "../data-verification-mechanism/implementation/Finder.sol"; -import "../data-verification-mechanism/implementation/Constants.sol"; -import "../data-verification-mechanism/implementation/Voting.sol"; - -/** - * @title A contract that executes a short series of upgrade calls that must be performed atomically as a part of the - * upgrade process for Voting.sol. - * @dev Note: the complete upgrade process requires more than just the transactions in this contract. These are only - * the ones that need to be performed atomically. - */ -contract VotingUpgrader { - // Existing governor is the only one who can initiate the upgrade. - address public governor; - - // Existing Voting contract needs to be informed of the address of the new Voting contract. - Voting public existingVoting; - - // New governor will be the new owner of the finder. - - // Finder contract to push upgrades to. - Finder public finder; - - // Addresses to upgrade. - address public newVoting; - - // Address to call setMigrated on the old voting contract. - address public setMigratedAddress; - - /** - * @notice Removes an address from the whitelist. - * @param _governor the Governor contract address. - * @param _existingVoting the current/existing Voting contract address. - * @param _newVoting the new Voting deployment address. - * @param _finder the Finder contract address. - * @param _setMigratedAddress the address to set migrated. This address will be able to continue making calls to - * old voting contract (used to claim rewards on others' behalf). Note: this address - * can always be changed by the voters. - */ - constructor( - address _governor, - address _existingVoting, - address _newVoting, - address _finder, - address _setMigratedAddress - ) { - governor = _governor; - existingVoting = Voting(_existingVoting); - newVoting = _newVoting; - finder = Finder(_finder); - setMigratedAddress = _setMigratedAddress; - } - - /** - * @notice Performs the atomic portion of the upgrade process. - * @dev This method updates the Voting address in the finder, sets the old voting contract to migrated state, and - * returns ownership of the existing Voting contract and Finder back to the Governor. - */ - function upgrade() external { - require(msg.sender == governor, "Upgrade can only be initiated by the existing governor."); - - // Change the addresses in the Finder. - finder.changeImplementationAddress(OracleInterfaces.Oracle, newVoting); - - // Set the preset "migrated" address to allow this address to claim rewards on voters' behalf. - // This also effectively shuts down the existing voting contract so new votes cannot be triggered. - existingVoting.setMigrated(setMigratedAddress); - - // Transfer back ownership of old voting contract and the finder to the governor. - existingVoting.transferOwnership(governor); - finder.transferOwnership(governor); - } -} diff --git a/contracts/external/uma/core/contracts/umip-helpers/VotingUpgraderV2.sol b/contracts/external/uma/core/contracts/umip-helpers/VotingUpgraderV2.sol deleted file mode 100644 index 6c59c666a..000000000 --- a/contracts/external/uma/core/contracts/umip-helpers/VotingUpgraderV2.sol +++ /dev/null @@ -1,153 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "../data-verification-mechanism/implementation/Finder.sol"; -import "../data-verification-mechanism/implementation/Constants.sol"; -import "../data-verification-mechanism/implementation/Voting.sol"; - -import "../common/implementation/MultiRole.sol"; -import "@openzeppelin/contracts-v4/access/Ownable.sol"; - -// Ownable contracts to transfer ownership of -struct OwnableContracts { - Ownable identifierWhitelist; - Ownable financialContractsAdmin; - Ownable addressWhitelist; - Ownable governorRootTunnel; - Ownable arbitrumParentMessenger; - Ownable oracleHub; - Ownable governorHub; - Ownable bobaParentMessenger; - Ownable optimismParentMessenger; - Ownable optimisticOracleV3; -} - -// Multirole contracts to transfer ownership of -struct MultiroleContracts { - MultiRole registry; - MultiRole store; -} - -/** - * @title A contract that executes a short series of upgrade calls that must be performed atomically as a part of the - * upgrade process for VotingV2.sol, GovernorV2.sol and ProposerV2.sol. - * @dev Note: the complete upgrade process requires more than just the transactions in this contract. These are only - * the ones that need to be performed atomically. - */ -contract VotingUpgraderV2 { - // The only one who can initiate the upgrade. - address public immutable upgrader; - - // Existing governor is the only one who can initiate the upgrade. - MultiRole public immutable existingGovernor; - - // New governor contract, set to be the UMA DVM owner post upgrade. - address public immutable newGovernor; - - // Existing Voting contract needs to be informed of the address of the new Voting contract. - Voting public immutable existingVoting; - - // New governor will be the new owner of the finder. - - // Finder contract to push upgrades to. - Finder public immutable finder; - - // Address to upgrade to. - address public immutable newVoting; - - // Proposer contract. - Ownable public immutable existingProposer; - - // Additional ownable contracts to transfer ownership of. - OwnableContracts public ownableContracts; - - // Additional multirole contracts to transfer ownership of. - MultiroleContracts public multiroleContracts; - - /** - * @notice Constructs the voting upgrader to upgrade to the DVM V2. This upgrades the voting, governor and proposer - * contracts. - * @param _existingGovernor the existing Governor contract address. - * @param _newGovernor the new Governor contract address. - * @param _existingVoting the current/existing Voting contract address. - * @param _newVoting the new Voting deployment address. - * @param _finder the Finder contract address. - * @param _ownableContracts additional ownable contracts to transfer ownership of. - * @param _multiroleContracts additional multirole contracts to transfer ownership of. - */ - constructor( - address _upgrader, - address _existingGovernor, - address _newGovernor, - address _existingVoting, - address _newVoting, - address _existingProposer, - address _finder, - OwnableContracts memory _ownableContracts, - MultiroleContracts memory _multiroleContracts - ) { - upgrader = _upgrader; - existingGovernor = MultiRole(_existingGovernor); - newGovernor = _newGovernor; - existingVoting = Voting(_existingVoting); - newVoting = _newVoting; - existingProposer = Ownable(_existingProposer); - finder = Finder(_finder); - ownableContracts = _ownableContracts; - multiroleContracts = _multiroleContracts; - } - - /** - * @notice Checks if the caller is the upgrader. - * @dev This is used as the first transaction in the upgrade process to block any other transactions from being - * executed if the upgrade is not initiated by the upgrader. - */ - function canRun() public view { - require(tx.origin == upgrader); - } - - /** - * @notice Performs the atomic portion of the upgrade process. - * @dev This method updates the Voting address in the finder, sets the old voting contract to migrated state, and - * transfers the required ownership of the contracts to GovernorV2. - */ - function upgrade() external { - require(msg.sender == address(existingGovernor), "Upgrade can only be initiated by the existing governor."); - - // Change the addresses in the Finder. - finder.changeImplementationAddress(OracleInterfaces.Oracle, newVoting); - - // Set the preset "migrated" address to allow this address to claim rewards on voters' behalf. - // This also effectively shuts down the existing voting contract so new votes cannot be triggered. - existingVoting.setMigrated(newVoting); - - // Transfer back ownership of old voting contract and the finder to the governor. - existingVoting.transferOwnership(newGovernor); - finder.transferOwnership(newGovernor); - - // Transfer ownership of existingProposer contract to the new governor. - existingProposer.transferOwnership(newGovernor); - - // Additional ownable contracts - ownableContracts.identifierWhitelist.transferOwnership(newGovernor); - ownableContracts.financialContractsAdmin.transferOwnership(newGovernor); - ownableContracts.addressWhitelist.transferOwnership(newGovernor); - ownableContracts.governorRootTunnel.transferOwnership(newGovernor); - ownableContracts.arbitrumParentMessenger.transferOwnership(newGovernor); - ownableContracts.oracleHub.transferOwnership(newGovernor); - ownableContracts.governorHub.transferOwnership(newGovernor); - ownableContracts.bobaParentMessenger.transferOwnership(newGovernor); - ownableContracts.optimismParentMessenger.transferOwnership(newGovernor); - ownableContracts.optimisticOracleV3.transferOwnership(newGovernor); - - // Set the new governor as the owner of the old governor - existingGovernor.resetMember(0, newGovernor); - - // Set governor as the owner of governor - MultiRole(newGovernor).resetMember(0, newGovernor); - - // Additional multirole contracts - multiroleContracts.registry.resetMember(0, newGovernor); - multiroleContracts.store.resetMember(0, newGovernor); - } -} diff --git a/contracts/merkle-distributor/AcrossMerkleDistributor.sol b/contracts/merkle-distributor/AcrossMerkleDistributor.sol index 3af0cab5d..86a91990d 100644 --- a/contracts/merkle-distributor/AcrossMerkleDistributor.sol +++ b/contracts/merkle-distributor/AcrossMerkleDistributor.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import "../external/uma/core/contracts/merkle-distributor/implementation/MerkleDistributor.sol"; +import "contracts/external/uma/core/contracts/merkle-distributor/implementation/MerkleDistributor.sol"; import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; diff --git a/foundry.toml b/foundry.toml index 8747790c4..5a9991845 100644 --- a/foundry.toml +++ b/foundry.toml @@ -29,11 +29,8 @@ remappings = [ via_ir = true optimizer_runs = 800 auto_detect_solc = true -# solc_version = "0.8.23" revert_strings = "strip" fs_permissions = [{ access = "read", path = "./"}] - -# solc = "0.8.23" evm_version = "prague" [profile.zksync.zksync] diff --git a/hardhat.config.ts b/hardhat.config.ts index 0a9abd137..36f7324d2 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -68,7 +68,7 @@ const isTest = process.env.IS_TEST === "true" || process.env.CI === "true"; // the following config is true. const compileZk = process.env.COMPILE_ZK === "true"; -const solcVersion = "0.8.30"; +const solcVersion = "0.8.25"; // Compilation settings are overridden for large contracts to allow them to compile without going over the bytecode // limit. @@ -114,14 +114,14 @@ const config: HardhatUserConfig = { ...LARGE_CONTRACT_COMPILER_SETTINGS, // NOTE: Linea only supports 0.8.19. // See https://docs.linea.build/build-on-linea/ethereum-differences#evm-opcodes - version: "0.8.19", - }, - "contracts/SpokePoolVerifier.sol": { - ...DEFAULT_CONTRACT_COMPILER_SETTINGS, - // NOTE: Linea only supports 0.8.19. - // See https://docs.linea.build/build-on-linea/ethereum-differences#evm-opcodes - version: "0.8.19", + // version: "0.8.19", }, + // "contracts/SpokePoolVerifier.sol": { + // ...DEFAULT_CONTRACT_COMPILER_SETTINGS, + // // NOTE: Linea only supports 0.8.19. + // // See https://docs.linea.build/build-on-linea/ethereum-differences#evm-opcodes + // version: "0.8.19", + // }, "contracts/Universal_SpokePool.sol": LARGE_CONTRACT_COMPILER_SETTINGS, "contracts/Arbitrum_SpokePool.sol": LARGE_CONTRACT_COMPILER_SETTINGS, "contracts/Scroll_SpokePool.sol": LARGE_CONTRACT_COMPILER_SETTINGS, From be05e4b0672c5ae9bcf0fa67434c1d7b32561136 Mon Sep 17 00:00:00 2001 From: Faisal Usmani Date: Sat, 1 Nov 2025 21:31:47 -0400 Subject: [PATCH 3/4] pinned foundry/hardhat versions Signed-off-by: Faisal Usmani --- contracts/SpokePool.sol | 2 +- foundry.toml | 5 +++-- hardhat.config.ts | 42 ++--------------------------------------- package.json | 1 - yarn.lock | 7 ------- 5 files changed, 6 insertions(+), 51 deletions(-) diff --git a/contracts/SpokePool.sol b/contracts/SpokePool.sol index fb4eb380a..4aa181a96 100644 --- a/contracts/SpokePool.sol +++ b/contracts/SpokePool.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.25; +pragma solidity ^0.8.18; import "./MerkleLib.sol"; import "./erc7683/ERC7683.sol"; diff --git a/foundry.toml b/foundry.toml index 5a9991845..21c51cfca 100644 --- a/foundry.toml +++ b/foundry.toml @@ -16,7 +16,6 @@ remappings = [ "@matterlabs/=node_modules/@matterlabs/", "@openzeppelin/=node_modules/@openzeppelin/", "@scroll-tech/=node_modules/@scroll-tech/", - "@uma/=contracts/external/uma/", "@uniswap/=node_modules/@uniswap/", "arb-bridge-eth/=node_modules/arb-bridge-eth/", "arb-bridge-peripherals/=node_modules/arb-bridge-peripherals/", @@ -28,9 +27,11 @@ remappings = [ ] via_ir = true optimizer_runs = 800 -auto_detect_solc = true +solc_version = "0.8.25" revert_strings = "strip" fs_permissions = [{ access = "read", path = "./"}] + +solc = "0.8.25" evm_version = "prague" [profile.zksync.zksync] diff --git a/hardhat.config.ts b/hardhat.config.ts index 36f7324d2..b6f96c798 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -14,7 +14,6 @@ import "hardhat-gas-reporter"; import "solidity-coverage"; import "hardhat-deploy"; import "@openzeppelin/hardhat-upgrades"; -import "hardhat-preprocessor"; const getMnemonic = () => { // Publicly-disclosed mnemonic. This is required for hre deployments in test. @@ -34,17 +33,6 @@ const getDefaultHardhatConfig = (chainId: number, isTestnet: boolean = false): a }; }; -// Hardhat already resolves node_modules paths, so we only need to remap specific contracts where we need to apply a custom remapping rule. -const customRemappings: [string, string][] = [["@uma", "contracts/external/uma/"]]; - -function applyRemappings(line: string): string { - // split/join works on plain-string patterns and supports old Node versions - for (const [find, rep] of customRemappings) { - if (line.includes(find)) line = line.split(find).join(rep); - } - return line; -} - // Custom tasks to add to HRE. const tasks = [ "enableL1TokenAcrossEcosystem", @@ -101,27 +89,10 @@ const LARGEST_CONTRACT_COMPILER_SETTINGS = { const config: HardhatUserConfig = { solidity: { - compilers: [ - DEFAULT_CONTRACT_COMPILER_SETTINGS, - { - ...DEFAULT_CONTRACT_COMPILER_SETTINGS, - version: "0.8.16", - }, - ], + compilers: [DEFAULT_CONTRACT_COMPILER_SETTINGS], overrides: { "contracts/HubPool.sol": LARGE_CONTRACT_COMPILER_SETTINGS, - "contracts/Linea_SpokePool.sol": { - ...LARGE_CONTRACT_COMPILER_SETTINGS, - // NOTE: Linea only supports 0.8.19. - // See https://docs.linea.build/build-on-linea/ethereum-differences#evm-opcodes - // version: "0.8.19", - }, - // "contracts/SpokePoolVerifier.sol": { - // ...DEFAULT_CONTRACT_COMPILER_SETTINGS, - // // NOTE: Linea only supports 0.8.19. - // // See https://docs.linea.build/build-on-linea/ethereum-differences#evm-opcodes - // version: "0.8.19", - // }, + "contracts/Linea_SpokePool.sol": LARGE_CONTRACT_COMPILER_SETTINGS, "contracts/Universal_SpokePool.sol": LARGE_CONTRACT_COMPILER_SETTINGS, "contracts/Arbitrum_SpokePool.sol": LARGE_CONTRACT_COMPILER_SETTINGS, "contracts/Scroll_SpokePool.sol": LARGE_CONTRACT_COMPILER_SETTINGS, @@ -133,10 +104,6 @@ const config: HardhatUserConfig = { "contracts/Cher_SpokePool.sol": LARGE_CONTRACT_COMPILER_SETTINGS, "contracts/Blast_SpokePool.sol": LARGEST_CONTRACT_COMPILER_SETTINGS, "contracts/Tatara_SpokePool.sol": LARGE_CONTRACT_COMPILER_SETTINGS, - "contracts/external/uma/**/*.sol": { - ...DEFAULT_CONTRACT_COMPILER_SETTINGS, - version: "0.8.16", - }, }, }, zksolc: { @@ -397,11 +364,6 @@ const config: HardhatUserConfig = { paths: { tests: "./test/evm/hardhat", }, - // preprocess: { - // eachLine: () => ({ - // transform: (line: string) => applyRemappings(line), - // }), - // }, }; export default config; diff --git a/package.json b/package.json index c426e4d6c..27971fd9c 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,6 @@ "@uma/contracts-node": "^0.4.17", "axios": "^1.7.4", "bs58": "^6.0.0", - "hardhat-preprocessor": "^0.1.5", "prettier-plugin-rust": "^0.1.9", "yargs": "^17.7.2", "zksync-web3": "^0.14.3" diff --git a/yarn.lock b/yarn.lock index 53993f28d..f08f387af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10358,13 +10358,6 @@ hardhat-gas-reporter@^1.0.4, hardhat-gas-reporter@^1.0.8: eth-gas-reporter "^0.2.24" sha1 "^1.1.1" -hardhat-preprocessor@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/hardhat-preprocessor/-/hardhat-preprocessor-0.1.5.tgz#75b22641fd6a680739c995d03bd5f7868eb72144" - integrity sha512-j8m44mmPxpxAAd0G8fPHRHOas/INZdzptSur0TNJvMEGcFdLDhbHHxBcqZVQ/bmiW42q4gC60AP4CXn9EF018g== - dependencies: - murmur-128 "^0.2.1" - hardhat-typechain@^0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/hardhat-typechain/-/hardhat-typechain-0.3.5.tgz#8e50616a9da348b33bd001168c8fda9c66b7b4af" From 651ae8277d17b0ccd77a6e8241aeb8d2aa9a7eca Mon Sep 17 00:00:00 2001 From: Faisal Usmani Date: Sun, 2 Nov 2025 20:26:43 -0500 Subject: [PATCH 4/4] downgraded to 0.8.24 Signed-off-by: Faisal Usmani --- foundry.toml | 4 ++-- hardhat.config.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/foundry.toml b/foundry.toml index 21c51cfca..68f1ad17d 100644 --- a/foundry.toml +++ b/foundry.toml @@ -27,11 +27,11 @@ remappings = [ ] via_ir = true optimizer_runs = 800 -solc_version = "0.8.25" +solc_version = "0.8.24" revert_strings = "strip" fs_permissions = [{ access = "read", path = "./"}] -solc = "0.8.25" +solc = "0.8.24" evm_version = "prague" [profile.zksync.zksync] diff --git a/hardhat.config.ts b/hardhat.config.ts index b6f96c798..115e76bf0 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -56,7 +56,7 @@ const isTest = process.env.IS_TEST === "true" || process.env.CI === "true"; // the following config is true. const compileZk = process.env.COMPILE_ZK === "true"; -const solcVersion = "0.8.25"; +const solcVersion = "0.8.24"; // Compilation settings are overridden for large contracts to allow them to compile without going over the bytecode // limit.