Skip to content

Commit fb6eab6

Browse files
brandonilesBrandon Iles
andauthored
Rebase Orchestrator (#150)
* Orchestrator and first set of tests * tests and origin protection * downstream revert protection * review feedback * Check for extra transactions * Remove configurability of transaction gwei amount Co-authored-by: Brandon Iles <[email protected]>
1 parent c72fe1f commit fb6eab6

10 files changed

+705
-72
lines changed

.eslintrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ module.exports = {
5050
// project-specific
5151
"rebase", "gons", "frg", "rng", "blockchain", "minlot",
5252
"redemptions", "rebased", "ganache", "ethclient",
53-
"bytecode", "Binance", "ethereum", "opcode", "cpi", "ampleforth",
53+
"bytecode", "Binance", "ethereum", "opcode", "cpi", "ampleforth", "orchestrator",
5454

5555
// names
5656
"nithin",

contracts/Orchestrator.sol

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
pragma solidity 0.4.24;
2+
3+
import "openzeppelin-eth/contracts/ownership/Ownable.sol";
4+
5+
import "./UFragmentsPolicy.sol";
6+
7+
8+
/**
9+
* @title Orchestrator
10+
* @notice The orchestrator is the main entry point for rebase operations. It coordinates the policy
11+
* actions with external consumers.
12+
*/
13+
contract Orchestrator is Ownable {
14+
15+
struct Transaction {
16+
address destination;
17+
bytes data;
18+
bool enabled;
19+
}
20+
21+
event TransactionFailed(address indexed destination, uint index, bytes data);
22+
23+
// Stable ordering is not guaranteed.
24+
Transaction[] public transactions;
25+
26+
UFragmentsPolicy public policy;
27+
28+
/**
29+
* @param policy_ Address of the UFragments policy.
30+
*/
31+
constructor(address policy_) public {
32+
Ownable.initialize(msg.sender);
33+
policy = UFragmentsPolicy(policy_);
34+
}
35+
36+
/**
37+
* @notice Main entry point to initiate a rebase operation.
38+
* The Orchestrator calls rebase on the policy and notifies downstream applications.
39+
* Contracts are guarded from calling, to avoid flash loan attacks on liquidity
40+
* providers.
41+
* If a transaction in the transaction list reverts, it is swallowed and the remaining
42+
* transactions are executed.
43+
*/
44+
function rebase()
45+
external
46+
{
47+
require(msg.sender == tx.origin); // solhint-disable-line avoid-tx-origin
48+
49+
policy.rebase();
50+
51+
for (uint i = 0; i < transactions.length; i++) {
52+
Transaction storage t = transactions[i];
53+
if (t.enabled) {
54+
bool result =
55+
externalCall(t.destination, 0, t.data.length, t.data);
56+
if (!result) {
57+
emit TransactionFailed(t.destination, i, t.data);
58+
}
59+
}
60+
}
61+
}
62+
63+
/**
64+
* @notice Adds a transaction that gets called for a downstream receiver of rebases
65+
* @param destination Address of contract destination
66+
* @param data Transaction data payload
67+
*/
68+
function addTransaction(address destination, bytes data)
69+
external
70+
onlyOwner
71+
{
72+
transactions.push(Transaction({
73+
destination: destination,
74+
data: data,
75+
enabled: true
76+
}));
77+
}
78+
79+
/**
80+
* @param index Index of transaction to remove.
81+
* Transaction ordering may have changed since adding.
82+
*/
83+
function removeTransaction(uint index)
84+
external
85+
onlyOwner
86+
{
87+
require(index < transactions.length, "index out of bounds");
88+
89+
if (index < transactions.length - 1) {
90+
transactions[index] = transactions[transactions.length - 1];
91+
}
92+
93+
delete transactions[transactions.length - 1];
94+
transactions.length--;
95+
}
96+
97+
/**
98+
* @param index Index of transaction. Transaction ordering may have changed since adding.
99+
* @param enabled True for enabled, false for disabled.
100+
*/
101+
function setTransactionEnabled(uint index, bool enabled)
102+
external
103+
onlyOwner
104+
{
105+
require(index < transactions.length, "index must be in range of stored tx list");
106+
transactions[index].enabled = enabled;
107+
}
108+
109+
/**
110+
* @return Number of transactions, both enabled and disabled, in transactions list.
111+
*/
112+
function transactionsLength()
113+
external
114+
view
115+
returns (uint256)
116+
{
117+
return transactions.length;
118+
}
119+
120+
/**
121+
* @dev wrapper to call the encoded transactions on downstream consumers.
122+
* @param destination Address of destination contract.
123+
* @param transferValueWei ETH value to send, in wei.
124+
* @param dataLength Size of data param.
125+
* @param data The encoded data payload.
126+
* @return True on success
127+
*/
128+
function externalCall(address destination, uint transferValueWei, uint dataLength, bytes data)
129+
internal
130+
returns (bool)
131+
{
132+
bool result;
133+
assembly { // solhint-disable-line no-inline-assembly
134+
// "Allocate" memory for output
135+
// (0x40 is where "free memory" pointer is stored by convention)
136+
let outputAddress := mload(0x40)
137+
138+
// First 32 bytes are the padded length of data, so exclude that
139+
let dataAddress := add(data, 32)
140+
141+
result := call(
142+
// 34710 is the value that solidity is currently emitting
143+
// It includes callGas (700) + callVeryLow (3, to pay for SUB)
144+
// + callValueTransferGas (9000) + callNewAccountGas
145+
// (25000, in case the destination address does not exist and needs creating)
146+
sub(gas, 34710),
147+
148+
149+
destination,
150+
transferValueWei,
151+
dataAddress,
152+
dataLength, // Size of the input (in bytes). This is what fixes the padding problem
153+
outputAddress,
154+
0 // Output is ignored, therefore the output size is zero
155+
)
156+
}
157+
return result;
158+
}
159+
}

contracts/UFragmentsPolicy.sol

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,22 @@ contract UFragmentsPolicy is Ownable {
8282
// MAX_SUPPLY = MAX_INT256 / MAX_RATE
8383
uint256 private constant MAX_SUPPLY = ~(uint256(1) << 255) / MAX_RATE;
8484

85+
// This module orchestrates the rebase execution and downstream notification.
86+
address public orchestrator = address(0x0);
87+
88+
modifier onlyOrchestrator() {
89+
require(msg.sender == orchestrator);
90+
_;
91+
}
92+
8593
/**
86-
* @notice Any EOA address can call this function to initiate a new rebase operation, provided
87-
* more than the minimum time period has elapsed.
88-
* Contracts are guarded from calling, to avoid flash loan attacks on liquidity
89-
* providers.
94+
* @notice Initiates a new rebase operation, provided the minimum time period has elapsed.
95+
*
9096
* @dev The supply adjustment equals (_totalSupply * DeviationFromTargetRate) / rebaseLag
9197
* Where DeviationFromTargetRate is (MarketOracleRate - targetRate) / targetRate
9298
* and targetRate is CpiOracleRate / baseCpi
9399
*/
94-
function rebase() external {
95-
require(msg.sender == tx.origin); // solhint-disable-line avoid-tx-origin
100+
function rebase() external onlyOrchestrator {
96101
require(inRebaseWindow());
97102

98103
// This comparison also ensures there is no reentrancy.
@@ -156,6 +161,17 @@ contract UFragmentsPolicy is Ownable {
156161
marketOracle = marketOracle_;
157162
}
158163

164+
/**
165+
* @notice Sets the reference to the orchestrator.
166+
* @param orchestrator_ The address of the orchestrator contract.
167+
*/
168+
function setOrchestrator(address orchestrator_)
169+
external
170+
onlyOwner
171+
{
172+
orchestrator = orchestrator_;
173+
}
174+
159175
/**
160176
* @notice Sets the deviation threshold fraction. If the exchange rate given by the market
161177
* oracle is within this fractional distance from the targetRate, then no supply
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
pragma solidity 0.4.24;
22

3-
import "../UFragmentsPolicy.sol";
3+
import "../Orchestrator.sol";
44

55

66
contract ConstructorRebaseCallerContract {
7-
constructor(address policy) public {
7+
constructor(address orchestrator) public {
88
// Take out a flash loan.
99
// Do something funky...
10-
UFragmentsPolicy(policy).rebase(); // should fail
10+
Orchestrator(orchestrator).rebase(); // should fail
1111
// pay back flash loan.
1212
}
1313
}

contracts/mocks/MockDownstream.sol

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
pragma solidity 0.4.24;
2+
3+
import "./Mock.sol";
4+
5+
6+
contract MockDownstream is Mock {
7+
8+
function updateNoArg() external returns (bool) {
9+
emit FunctionCalled("MockDownstream", "updateNoArg", msg.sender);
10+
uint256[] memory uintVals = new uint256[](0);
11+
int256[] memory intVals = new int256[](0);
12+
emit FunctionArguments(uintVals, intVals);
13+
return true;
14+
}
15+
16+
function updateOneArg(uint256 u) external {
17+
emit FunctionCalled("MockDownstream", "updateOneArg", msg.sender);
18+
19+
uint256[] memory uintVals = new uint256[](1);
20+
uintVals[0] = u;
21+
int256[] memory intVals = new int256[](0);
22+
emit FunctionArguments(uintVals, intVals);
23+
}
24+
25+
function updateTwoArgs(uint256 u, int256 i) external {
26+
emit FunctionCalled("MockDownstream", "updateTwoArgs", msg.sender);
27+
28+
uint256[] memory uintVals = new uint256[](1);
29+
uintVals[0] = u;
30+
int256[] memory intVals = new int256[](1);
31+
intVals[0] = i;
32+
emit FunctionArguments(uintVals, intVals);
33+
}
34+
35+
function reverts() external {
36+
emit FunctionCalled("MockDownstream", "reverts", msg.sender);
37+
38+
uint256[] memory uintVals = new uint256[](0);
39+
int256[] memory intVals = new int256[](0);
40+
emit FunctionArguments(uintVals, intVals);
41+
42+
require(false, "reverted");
43+
}
44+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
pragma solidity 0.4.24;
2+
3+
import "./Mock.sol";
4+
5+
6+
contract MockUFragmentsPolicy is Mock {
7+
8+
function rebase() external {
9+
emit FunctionCalled("UFragmentsPolicy", "rebase", msg.sender);
10+
}
11+
}

contracts/mocks/RebaseCallerContract.sol

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
pragma solidity 0.4.24;
22

3-
import "../UFragmentsPolicy.sol";
3+
import "../Orchestrator.sol";
44

55

66
contract RebaseCallerContract {
7-
function callRebase(address policy) public returns (bool) {
7+
8+
function callRebase(address orchestrator) public returns (bool) {
89
// Take out a flash loan.
910
// Do something funky...
10-
UFragmentsPolicy(policy).rebase(); // should fail
11+
Orchestrator(orchestrator).rebase(); // should fail
1112
// pay back flash loan.
1213
return true;
1314
}

0 commit comments

Comments
 (0)