-
Notifications
You must be signed in to change notification settings - Fork 153
Rebase Orchestrator #150
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Rebase Orchestrator #150
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
57b7f28
draft
bb31d55
Orchestrator design
6dd32c0
Orchestrator and first set of tests
7add7f0
tests and origin protection
bae031f
downstream revert protection
439659a
review feedback
6fff902
Check for extra transactions
3185e60
Remove configurability of transaction gwei amount
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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
This file contains hidden or 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,159 @@ | ||
pragma solidity 0.4.24; | ||
|
||
import "openzeppelin-eth/contracts/ownership/Ownable.sol"; | ||
|
||
import "./UFragmentsPolicy.sol"; | ||
|
||
|
||
/** | ||
* @title Orchestrator | ||
* @notice The orchestrator is the main entry point for rebase operations. It coordinates the policy | ||
* actions with external consumers. | ||
*/ | ||
contract Orchestrator is Ownable { | ||
|
||
struct Transaction { | ||
address destination; | ||
bytes data; | ||
bool enabled; | ||
} | ||
|
||
event TransactionFailed(address indexed destination, uint index, bytes data); | ||
|
||
// Stable ordering is not guaranteed. | ||
Transaction[] public transactions; | ||
|
||
UFragmentsPolicy public policy; | ||
|
||
/** | ||
* @param policy_ Address of the UFragments policy. | ||
*/ | ||
constructor(address policy_) public { | ||
Ownable.initialize(msg.sender); | ||
policy = UFragmentsPolicy(policy_); | ||
} | ||
|
||
/** | ||
* @notice Main entry point to initiate a rebase operation. | ||
* The Orchestrator calls rebase on the policy and notifies downstream applications. | ||
* Contracts are guarded from calling, to avoid flash loan attacks on liquidity | ||
* providers. | ||
* If a transaction in the transaction list reverts, it is swallowed and the remaining | ||
* transactions are executed. | ||
*/ | ||
function rebase() | ||
external | ||
{ | ||
require(msg.sender == tx.origin); // solhint-disable-line avoid-tx-origin | ||
|
||
policy.rebase(); | ||
|
||
for (uint i = 0; i < transactions.length; i++) { | ||
Transaction storage t = transactions[i]; | ||
if (t.enabled) { | ||
bool result = | ||
externalCall(t.destination, 0, t.data.length, t.data); | ||
if (!result) { | ||
emit TransactionFailed(t.destination, i, t.data); | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* @notice Adds a transaction that gets called for a downstream receiver of rebases | ||
* @param destination Address of contract destination | ||
* @param data Transaction data payload | ||
*/ | ||
function addTransaction(address destination, bytes data) | ||
external | ||
onlyOwner | ||
{ | ||
transactions.push(Transaction({ | ||
destination: destination, | ||
data: data, | ||
enabled: true | ||
})); | ||
} | ||
|
||
/** | ||
* @param index Index of transaction to remove. | ||
* Transaction ordering may have changed since adding. | ||
*/ | ||
function removeTransaction(uint index) | ||
external | ||
onlyOwner | ||
{ | ||
require(index < transactions.length, "index out of bounds"); | ||
|
||
if (index < transactions.length - 1) { | ||
transactions[index] = transactions[transactions.length - 1]; | ||
} | ||
|
||
delete transactions[transactions.length - 1]; | ||
transactions.length--; | ||
} | ||
|
||
/** | ||
* @param index Index of transaction. Transaction ordering may have changed since adding. | ||
* @param enabled True for enabled, false for disabled. | ||
*/ | ||
function setTransactionEnabled(uint index, bool enabled) | ||
external | ||
onlyOwner | ||
{ | ||
require(index < transactions.length, "index must be in range of stored tx list"); | ||
transactions[index].enabled = enabled; | ||
} | ||
|
||
/** | ||
* @return Number of transactions, both enabled and disabled, in transactions list. | ||
*/ | ||
function transactionsLength() | ||
external | ||
view | ||
returns (uint256) | ||
{ | ||
return transactions.length; | ||
} | ||
|
||
/** | ||
* @dev wrapper to call the encoded transactions on downstream consumers. | ||
* @param destination Address of destination contract. | ||
* @param transferValueWei ETH value to send, in wei. | ||
* @param dataLength Size of data param. | ||
* @param data The encoded data payload. | ||
* @return True on success | ||
*/ | ||
function externalCall(address destination, uint transferValueWei, uint dataLength, bytes data) | ||
internal | ||
returns (bool) | ||
{ | ||
bool result; | ||
assembly { // solhint-disable-line no-inline-assembly | ||
// "Allocate" memory for output | ||
// (0x40 is where "free memory" pointer is stored by convention) | ||
let outputAddress := mload(0x40) | ||
|
||
// First 32 bytes are the padded length of data, so exclude that | ||
let dataAddress := add(data, 32) | ||
|
||
result := call( | ||
// 34710 is the value that solidity is currently emitting | ||
// It includes callGas (700) + callVeryLow (3, to pay for SUB) | ||
// + callValueTransferGas (9000) + callNewAccountGas | ||
// (25000, in case the destination address does not exist and needs creating) | ||
sub(gas, 34710), | ||
|
||
|
||
destination, | ||
transferValueWei, | ||
dataAddress, | ||
dataLength, // Size of the input (in bytes). This is what fixes the padding problem | ||
outputAddress, | ||
0 // Output is ignored, therefore the output size is zero | ||
) | ||
} | ||
return result; | ||
} | ||
} |
This file contains hidden or 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
This file contains hidden or 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 |
---|---|---|
@@ -1,13 +1,13 @@ | ||
pragma solidity 0.4.24; | ||
|
||
import "../UFragmentsPolicy.sol"; | ||
import "../Orchestrator.sol"; | ||
|
||
|
||
contract ConstructorRebaseCallerContract { | ||
constructor(address policy) public { | ||
constructor(address orchestrator) public { | ||
// Take out a flash loan. | ||
// Do something funky... | ||
UFragmentsPolicy(policy).rebase(); // should fail | ||
Orchestrator(orchestrator).rebase(); // should fail | ||
// pay back flash loan. | ||
} | ||
} |
This file contains hidden or 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,44 @@ | ||
pragma solidity 0.4.24; | ||
|
||
import "./Mock.sol"; | ||
|
||
|
||
contract MockDownstream is Mock { | ||
|
||
function updateNoArg() external returns (bool) { | ||
emit FunctionCalled("MockDownstream", "updateNoArg", msg.sender); | ||
uint256[] memory uintVals = new uint256[](0); | ||
int256[] memory intVals = new int256[](0); | ||
emit FunctionArguments(uintVals, intVals); | ||
return true; | ||
} | ||
|
||
function updateOneArg(uint256 u) external { | ||
emit FunctionCalled("MockDownstream", "updateOneArg", msg.sender); | ||
|
||
uint256[] memory uintVals = new uint256[](1); | ||
uintVals[0] = u; | ||
int256[] memory intVals = new int256[](0); | ||
emit FunctionArguments(uintVals, intVals); | ||
} | ||
|
||
function updateTwoArgs(uint256 u, int256 i) external { | ||
emit FunctionCalled("MockDownstream", "updateTwoArgs", msg.sender); | ||
|
||
uint256[] memory uintVals = new uint256[](1); | ||
uintVals[0] = u; | ||
int256[] memory intVals = new int256[](1); | ||
intVals[0] = i; | ||
emit FunctionArguments(uintVals, intVals); | ||
} | ||
|
||
function reverts() external { | ||
emit FunctionCalled("MockDownstream", "reverts", msg.sender); | ||
|
||
uint256[] memory uintVals = new uint256[](0); | ||
int256[] memory intVals = new int256[](0); | ||
emit FunctionArguments(uintVals, intVals); | ||
|
||
require(false, "reverted"); | ||
} | ||
} |
This file contains hidden or 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,11 @@ | ||
pragma solidity 0.4.24; | ||
|
||
import "./Mock.sol"; | ||
|
||
|
||
contract MockUFragmentsPolicy is Mock { | ||
|
||
function rebase() external { | ||
emit FunctionCalled("UFragmentsPolicy", "rebase", msg.sender); | ||
} | ||
} |
This file contains hidden or 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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.