Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ module.exports = {
// project-specific
"rebase", "gons", "frg", "rng", "blockchain", "minlot",
"redemptions", "rebased", "ganache", "ethclient",
"bytecode", "Binance", "ethereum", "opcode", "cpi", "ampleforth",
"bytecode", "Binance", "ethereum", "opcode", "cpi", "ampleforth", "orchestrator",

// names
"nithin",
Expand Down
159 changes: 159 additions & 0 deletions contracts/Orchestrator.sol
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;
}
}
28 changes: 22 additions & 6 deletions contracts/UFragmentsPolicy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,22 @@ contract UFragmentsPolicy is Ownable {
// MAX_SUPPLY = MAX_INT256 / MAX_RATE
uint256 private constant MAX_SUPPLY = ~(uint256(1) << 255) / MAX_RATE;

// This module orchestrates the rebase execution and downstream notification.
address public orchestrator = address(0x0);

modifier onlyOrchestrator() {
require(msg.sender == orchestrator);
_;
}

/**
* @notice Any EOA address can call this function to initiate a new rebase operation, provided
* more than the minimum time period has elapsed.
* Contracts are guarded from calling, to avoid flash loan attacks on liquidity
* providers.
* @notice Initiates a new rebase operation, provided the minimum time period has elapsed.
*
* @dev The supply adjustment equals (_totalSupply * DeviationFromTargetRate) / rebaseLag
* Where DeviationFromTargetRate is (MarketOracleRate - targetRate) / targetRate
* and targetRate is CpiOracleRate / baseCpi
*/
function rebase() external {
require(msg.sender == tx.origin); // solhint-disable-line avoid-tx-origin
function rebase() external onlyOrchestrator {
require(inRebaseWindow());

// This comparison also ensures there is no reentrancy.
Expand Down Expand Up @@ -156,6 +161,17 @@ contract UFragmentsPolicy is Ownable {
marketOracle = marketOracle_;
}

/**
* @notice Sets the reference to the orchestrator.
* @param orchestrator_ The address of the orchestrator contract.
*/
function setOrchestrator(address orchestrator_)
external
onlyOwner
{
orchestrator = orchestrator_;
}

/**
* @notice Sets the deviation threshold fraction. If the exchange rate given by the market
* oracle is within this fractional distance from the targetRate, then no supply
Expand Down
6 changes: 3 additions & 3 deletions contracts/mocks/ConstructorRebaseCallerContract.sol
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.
}
}
44 changes: 44 additions & 0 deletions contracts/mocks/MockDownstream.sol
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");
}
}
11 changes: 11 additions & 0 deletions contracts/mocks/MockUFragmentsPolicy.sol
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);
}
}
7 changes: 4 additions & 3 deletions contracts/mocks/RebaseCallerContract.sol
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
pragma solidity 0.4.24;

import "../UFragmentsPolicy.sol";
import "../Orchestrator.sol";


contract RebaseCallerContract {
function callRebase(address policy) public returns (bool) {

function callRebase(address orchestrator) public returns (bool) {
// Take out a flash loan.
// Do something funky...
UFragmentsPolicy(policy).rebase(); // should fail
Orchestrator(orchestrator).rebase(); // should fail
// pay back flash loan.
return true;
}
Expand Down
Loading