-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Global optimistic auction rebalance extension (#154)
* feat: GlobalAuctionRebalanceExtension contract, tests & utils * test: adds cases that increase coverage to 100% * feat: prevent intialization when not ready. * chore(deps): add @uma/core as devDep * ref(AuctionRebalance): startRebalance overridable * ref(OptimisticAuction): adds Initial contract. * feat: setProductSettings, proposeRebalance, override startRebalance. * chore(deps): remove @uma/core dev dep. * test: derive unit test template for GlobalOptimisticAuctionRebalanceExtension from GlobalAuctionRebalanceExtension. * test: update deployment helper and barrel. * refactor: remove unused param from constructor. * chore(deps): add base58 encode/decode lib. * feat: add OOV3 mock. * test: extends to cover propose rebalance path. * refactor: add events, update docstrings. fix assertedProducts update. * feat: emit events, simplify tracking assertion id relationships, refactor out bonds. * docs: update contract natspec description. * fix: add virtual keyword back and removed extra imports in utils. * ref: refactor and doc updates.
- Loading branch information
1 parent
ec7ce83
commit d5ab030
Showing
11 changed files
with
1,483 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
360 changes: 360 additions & 0 deletions
360
contracts/global-extensions/GlobalOptimisticAuctionRebalanceExtension.sol
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
// SPDX-License-Identifier: AGPL-3.0-only | ||
pragma solidity 0.6.10; | ||
pragma experimental "ABIEncoderV2"; | ||
|
||
import {IERC20} from "@openzeppelin/contracts/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 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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
// SPDX-License-Identifier: AGPL-3.0-only | ||
pragma solidity ^0.6.10; | ||
|
||
/** | ||
* @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) { | ||
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, ":"); | ||
} | ||
} | ||
} |
Oops, something went wrong.