Skip to content

Commit d5ab030

Browse files
authored
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.
1 parent ec7ce83 commit d5ab030

11 files changed

+1483
-6
lines changed

contracts/global-extensions/GlobalAuctionRebalanceExtension.sol

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,11 @@ contract GlobalAuctionRebalanceExtension is BaseGlobalExtension {
6363

6464

6565
/* ============ Constructor ============ */
66-
/*
67-
* Instantiate with ManagerCore address and WrapModuleV2 address.
68-
*
69-
* @param _managerCore Address of ManagerCore contract
70-
* @param _auctionModule Address of AuctionRebalanceModuleV1 contract
66+
/**
67+
* @dev Instantiate with ManagerCore address and WrapModuleV2 address.
68+
*
69+
* @param _managerCore Address of ManagerCore contract
70+
* @param _auctionModule Address of AuctionRebalanceModuleV1 contract
7171
*/
7272
constructor(IManagerCore _managerCore, IAuctionRebalanceModuleV1 _auctionModule) public BaseGlobalExtension(_managerCore) {
7373
auctionModule = _auctionModule;
@@ -155,6 +155,7 @@ contract GlobalAuctionRebalanceExtension is BaseGlobalExtension {
155155
uint256 _positionMultiplier
156156
)
157157
external
158+
virtual
158159
onlyOperator(_setToken)
159160
{
160161
address[] memory currentComponents = _setToken.getComponents();

contracts/global-extensions/GlobalOptimisticAuctionRebalanceExtension.sol

Lines changed: 360 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
// SPDX-License-Identifier: AGPL-3.0-only
2+
pragma solidity 0.6.10;
3+
pragma experimental "ABIEncoderV2";
4+
5+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6+
7+
/**
8+
* @title Optimistic Oracle V3 Interface that callers must use to assert truths about the world.
9+
*/
10+
interface OptimisticOracleV3Interface {
11+
// Struct grouping together the settings related to the escalation manager stored in the assertion.
12+
struct EscalationManagerSettings {
13+
bool arbitrateViaEscalationManager; // False if the DVM is used as an oracle (EscalationManager on True).
14+
bool discardOracle; // False if Oracle result is used for resolving assertion after dispute.
15+
bool validateDisputers; // True if the EM isDisputeAllowed should be checked on disputes.
16+
address assertingCaller; // Stores msg.sender when assertion was made.
17+
address escalationManager; // Address of the escalation manager (zero address if not configured).
18+
}
19+
20+
// Struct for storing properties and lifecycle of an assertion.
21+
struct Assertion {
22+
EscalationManagerSettings escalationManagerSettings; // Settings related to the escalation manager.
23+
address asserter; // Address of the asserter.
24+
uint64 assertionTime; // Time of the assertion.
25+
bool settled; // True if the request is settled.
26+
IERC20 currency; // ERC20 token used to pay rewards and fees.
27+
uint64 expirationTime; // Unix timestamp marking threshold when the assertion can no longer be disputed.
28+
bool settlementResolution; // Resolution of the assertion (false till resolved).
29+
bytes32 domainId; // Optional domain that can be used to relate the assertion to others in the escalationManager.
30+
bytes32 identifier; // UMA DVM identifier to use for price requests in the event of a dispute.
31+
uint256 bond; // Amount of currency that the asserter has bonded.
32+
address callbackRecipient; // Address that receives the callback.
33+
address disputer; // Address of the disputer.
34+
}
35+
36+
// Struct for storing cached currency whitelist.
37+
struct WhitelistedCurrency {
38+
bool isWhitelisted; // True if the currency is whitelisted.
39+
uint256 finalFee; // Final fee of the currency.
40+
}
41+
42+
/**
43+
* @notice Returns the default identifier used by the Optimistic Oracle V3.
44+
* @return The default identifier.
45+
*/
46+
function defaultIdentifier() external view returns (bytes32);
47+
48+
/**
49+
* @notice Fetches information about a specific assertion and returns it.
50+
* @param assertionId unique identifier for the assertion to fetch information for.
51+
* @return assertion information about the assertion.
52+
*/
53+
function getAssertion(bytes32 assertionId) external view returns (Assertion memory);
54+
55+
/**
56+
* @notice Asserts a truth about the world, using the default currency and liveness. No callback recipient or
57+
* escalation manager is enabled. The caller is expected to provide a bond of finalFee/burnedBondPercentage
58+
* (with burnedBondPercentage set to 50%, the bond is 2x final fee) of the default currency.
59+
* @dev The caller must approve this contract to spend at least the result of getMinimumBond(defaultCurrency).
60+
* @param claim the truth claim being asserted. This is an assertion about the world, and is verified by disputers.
61+
* @param asserter receives bonds back at settlement. This could be msg.sender or
62+
* any other account that the caller wants to receive the bond at settlement time.
63+
* @return assertionId unique identifier for this assertion.
64+
*/
65+
function assertTruthWithDefaults(bytes memory claim, address asserter) external returns (bytes32);
66+
67+
/**
68+
* @notice Asserts a truth about the world, using a fully custom configuration.
69+
* @dev The caller must approve this contract to spend at least bond amount of currency.
70+
* @param claim the truth claim being asserted. This is an assertion about the world, and is verified by disputers.
71+
* @param asserter receives bonds back at settlement. This could be msg.sender or
72+
* any other account that the caller wants to receive the bond at settlement time.
73+
* @param callbackRecipient if configured, this address will receive a function call assertionResolvedCallback and
74+
* assertionDisputedCallback at resolution or dispute respectively. Enables dynamic responses to these events. The
75+
* recipient _must_ implement these callbacks and not revert or the assertion resolution will be blocked.
76+
* @param escalationManager if configured, this address will control escalation properties of the assertion. This
77+
* means a) choosing to arbitrate via the UMA DVM, b) choosing to discard assertions on dispute, or choosing to
78+
* validate disputes. Combining these, the asserter can define their own security properties for the assertion.
79+
* escalationManager also _must_ implement the same callbacks as callbackRecipient.
80+
* @param liveness time to wait before the assertion can be resolved. Assertion can be disputed in this time.
81+
* @param currency bond currency pulled from the caller and held in escrow until the assertion is resolved.
82+
* @param bond amount of currency to pull from the caller and hold in escrow until the assertion is resolved. This
83+
* must be >= getMinimumBond(address(currency)).
84+
* @param identifier UMA DVM identifier to use for price requests in the event of a dispute. Must be pre-approved.
85+
* @param domainId optional domain that can be used to relate this assertion to others in the escalationManager and
86+
* can be used by the configured escalationManager to define custom behavior for groups of assertions. This is
87+
* typically used for "escalation games" by changing bonds or other assertion properties based on the other
88+
* assertions that have come before. If not needed this value should be 0 to save gas.
89+
* @return assertionId unique identifier for this assertion.
90+
*/
91+
function assertTruth(
92+
bytes memory claim,
93+
address asserter,
94+
address callbackRecipient,
95+
address escalationManager,
96+
uint64 liveness,
97+
IERC20 currency,
98+
uint256 bond,
99+
bytes32 identifier,
100+
bytes32 domainId
101+
) external returns (bytes32);
102+
103+
/**
104+
* @notice Fetches information about a specific identifier & currency from the UMA contracts and stores a local copy
105+
* of the information within this contract. This is used to save gas when making assertions as we can avoid an
106+
* external call to the UMA contracts to fetch this.
107+
* @param identifier identifier to fetch information for and store locally.
108+
* @param currency currency to fetch information for and store locally.
109+
*/
110+
function syncUmaParams(bytes32 identifier, address currency) external;
111+
112+
/**
113+
* @notice Resolves an assertion. If the assertion has not been disputed, the assertion is resolved as true and the
114+
* asserter receives the bond. If the assertion has been disputed, the assertion is resolved depending on the oracle
115+
* result. Based on the result, the asserter or disputer receives the bond. If the assertion was disputed then an
116+
* amount of the bond is sent to the UMA Store as an oracle fee based on the burnedBondPercentage. The remainder of
117+
* the bond is returned to the asserter or disputer.
118+
* @param assertionId unique identifier for the assertion to resolve.
119+
*/
120+
function settleAssertion(bytes32 assertionId) external;
121+
122+
/**
123+
* @notice Settles an assertion and returns the resolution.
124+
* @param assertionId unique identifier for the assertion to resolve and return the resolution for.
125+
* @return resolution of the assertion.
126+
*/
127+
function settleAndGetAssertionResult(bytes32 assertionId) external returns (bool);
128+
129+
/**
130+
* @notice Fetches the resolution of a specific assertion and returns it. If the assertion has not been settled then
131+
* this will revert. If the assertion was disputed and configured to discard the oracle resolution return false.
132+
* @param assertionId unique identifier for the assertion to fetch the resolution for.
133+
* @return resolution of the assertion.
134+
*/
135+
function getAssertionResult(bytes32 assertionId) external view returns (bool);
136+
137+
/**
138+
* @notice Returns the minimum bond amount required to make an assertion. This is calculated as the final fee of the
139+
* currency divided by the burnedBondPercentage. If burn percentage is 50% then the min bond is 2x the final fee.
140+
* @param currency currency to calculate the minimum bond for.
141+
* @return minimum bond amount.
142+
*/
143+
function getMinimumBond(address currency) external view returns (uint256);
144+
145+
event AssertionMade(
146+
bytes32 indexed assertionId,
147+
bytes32 domainId,
148+
bytes claim,
149+
address indexed asserter,
150+
address callbackRecipient,
151+
address escalationManager,
152+
address caller,
153+
uint64 expirationTime,
154+
IERC20 currency,
155+
uint256 bond,
156+
bytes32 indexed identifier
157+
);
158+
159+
event AssertionDisputed(bytes32 indexed assertionId, address indexed caller, address indexed disputer);
160+
161+
event AssertionSettled(
162+
bytes32 indexed assertionId,
163+
address indexed bondRecipient,
164+
bool disputed,
165+
bool settlementResolution,
166+
address settleCaller
167+
);
168+
169+
event AdminPropertiesSet(IERC20 defaultCurrency, uint64 defaultLiveness, uint256 burnedBondPercentage);
170+
}

contracts/lib/AncillaryData.sol

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// SPDX-License-Identifier: AGPL-3.0-only
2+
pragma solidity ^0.6.10;
3+
4+
/**
5+
* @title Library for encoding and decoding ancillary data for DVM price requests.
6+
* @notice We assume that on-chain ancillary data can be formatted directly from bytes to utf8 encoding via
7+
* web3.utils.hexToUtf8, and that clients will parse the utf8-encoded ancillary data as a comma-delimitted key-value
8+
* dictionary. Therefore, this library provides internal methods that aid appending to ancillary data from Solidity
9+
* smart contracts. More details on UMA's ancillary data guidelines below:
10+
* https://docs.google.com/document/d/1zhKKjgY1BupBGPPrY_WOJvui0B6DMcd-xDR8-9-SPDw/edit
11+
*/
12+
library AncillaryData {
13+
// This converts the bottom half of a bytes32 input to hex in a highly gas-optimized way.
14+
// Source: the brilliant implementation at https://gitter.im/ethereum/solidity?at=5840d23416207f7b0ed08c9b.
15+
function toUtf8Bytes32Bottom(bytes32 bytesIn) private pure returns (bytes32) {
16+
uint256 x = uint256(bytesIn);
17+
18+
// Nibble interleave
19+
x = x & 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff;
20+
x = (x | (x * 2**64)) & 0x0000000000000000ffffffffffffffff0000000000000000ffffffffffffffff;
21+
x = (x | (x * 2**32)) & 0x00000000ffffffff00000000ffffffff00000000ffffffff00000000ffffffff;
22+
x = (x | (x * 2**16)) & 0x0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff;
23+
x = (x | (x * 2**8)) & 0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff;
24+
x = (x | (x * 2**4)) & 0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f;
25+
26+
// Hex encode
27+
uint256 h = (x & 0x0808080808080808080808080808080808080808080808080808080808080808) / 8;
28+
uint256 i = (x & 0x0404040404040404040404040404040404040404040404040404040404040404) / 4;
29+
uint256 j = (x & 0x0202020202020202020202020202020202020202020202020202020202020202) / 2;
30+
x = x + (h & (i | j)) * 0x27 + 0x3030303030303030303030303030303030303030303030303030303030303030;
31+
32+
// Return the result.
33+
return bytes32(x);
34+
}
35+
36+
/**
37+
* @notice Returns utf8-encoded bytes32 string that can be read via web3.utils.hexToUtf8.
38+
* @dev Will return bytes32 in all lower case hex characters and without the leading 0x.
39+
* This has minor changes from the toUtf8BytesAddress to control for the size of the input.
40+
* @param bytesIn bytes32 to encode.
41+
* @return utf8 encoded bytes32.
42+
*/
43+
function toUtf8Bytes(bytes32 bytesIn) internal pure returns (bytes memory) {
44+
return abi.encodePacked(toUtf8Bytes32Bottom(bytesIn >> 128), toUtf8Bytes32Bottom(bytesIn));
45+
}
46+
47+
/**
48+
* @notice Returns utf8-encoded address that can be read via web3.utils.hexToUtf8.
49+
* Source: https://ethereum.stackexchange.com/questions/8346/convert-address-to-string/8447#8447
50+
* @dev Will return address in all lower case characters and without the leading 0x.
51+
* @param x address to encode.
52+
* @return utf8 encoded address bytes.
53+
*/
54+
function toUtf8BytesAddress(address x) internal pure returns (bytes memory) {
55+
return
56+
abi.encodePacked(toUtf8Bytes32Bottom(bytes32(bytes20(x)) >> 128), bytes8(toUtf8Bytes32Bottom(bytes20(x))));
57+
}
58+
59+
/**
60+
* @notice Converts a uint into a base-10, UTF-8 representation stored in a `string` type.
61+
* @dev This method is based off of this code: https://stackoverflow.com/a/65707309.
62+
*/
63+
function toUtf8BytesUint(uint256 x) internal pure returns (bytes memory) {
64+
if (x == 0) {
65+
return "0";
66+
}
67+
uint256 j = x;
68+
uint256 len;
69+
while (j != 0) {
70+
len++;
71+
j /= 10;
72+
}
73+
bytes memory bstr = new bytes(len);
74+
uint256 k = len;
75+
while (x != 0) {
76+
k = k - 1;
77+
uint8 temp = (48 + uint8(x - (x / 10) * 10));
78+
bytes1 b1 = bytes1(temp);
79+
bstr[k] = b1;
80+
x /= 10;
81+
}
82+
return bstr;
83+
}
84+
85+
function appendKeyValueBytes32(
86+
bytes memory currentAncillaryData,
87+
bytes memory key,
88+
bytes32 value
89+
) internal pure returns (bytes memory) {
90+
bytes memory prefix = constructPrefix(currentAncillaryData, key);
91+
return abi.encodePacked(currentAncillaryData, prefix, toUtf8Bytes(value));
92+
}
93+
94+
/**
95+
* @notice Adds "key:value" to `currentAncillaryData` where `value` is an address that first needs to be converted
96+
* to utf8 bytes. For example, if `utf8(currentAncillaryData)="k1:v1"`, then this function will return
97+
* `utf8(k1:v1,key:value)`, and if `currentAncillaryData` is blank, then this will return `utf8(key:value)`.
98+
* @param currentAncillaryData This bytes data should ideally be able to be utf8-decoded, but its OK if not.
99+
* @param key Again, this bytes data should ideally be able to be utf8-decoded, but its OK if not.
100+
* @param value An address to set as the value in the key:value pair to append to `currentAncillaryData`.
101+
* @return Newly appended ancillary data.
102+
*/
103+
function appendKeyValueAddress(
104+
bytes memory currentAncillaryData,
105+
bytes memory key,
106+
address value
107+
) internal pure returns (bytes memory) {
108+
bytes memory prefix = constructPrefix(currentAncillaryData, key);
109+
return abi.encodePacked(currentAncillaryData, prefix, toUtf8BytesAddress(value));
110+
}
111+
112+
/**
113+
* @notice Adds "key:value" to `currentAncillaryData` where `value` is a uint that first needs to be converted
114+
* to utf8 bytes. For example, if `utf8(currentAncillaryData)="k1:v1"`, then this function will return
115+
* `utf8(k1:v1,key:value)`, and if `currentAncillaryData` is blank, then this will return `utf8(key:value)`.
116+
* @param currentAncillaryData This bytes data should ideally be able to be utf8-decoded, but its OK if not.
117+
* @param key Again, this bytes data should ideally be able to be utf8-decoded, but its OK if not.
118+
* @param value A uint to set as the value in the key:value pair to append to `currentAncillaryData`.
119+
* @return Newly appended ancillary data.
120+
*/
121+
function appendKeyValueUint(
122+
bytes memory currentAncillaryData,
123+
bytes memory key,
124+
uint256 value
125+
) internal pure returns (bytes memory) {
126+
bytes memory prefix = constructPrefix(currentAncillaryData, key);
127+
return abi.encodePacked(currentAncillaryData, prefix, toUtf8BytesUint(value));
128+
}
129+
130+
/**
131+
* @notice Helper method that returns the left hand side of a "key:value" pair plus the colon ":" and a leading
132+
* comma "," if the `currentAncillaryData` is not empty. The return value is intended to be prepended as a prefix to
133+
* some utf8 value that is ultimately added to a comma-delimited, key-value dictionary.
134+
*/
135+
function constructPrefix(bytes memory currentAncillaryData, bytes memory key) internal pure returns (bytes memory) {
136+
if (currentAncillaryData.length > 0) {
137+
return abi.encodePacked(",", key, ":");
138+
} else {
139+
return abi.encodePacked(key, ":");
140+
}
141+
}
142+
}

0 commit comments

Comments
 (0)