Skip to content
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

Orchestrator Updates #207

Closed
wants to merge 10 commits into from
Closed
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
62 changes: 43 additions & 19 deletions contracts/Orchestrator.sol
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
pragma solidity 0.7.6;
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.2;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0.8 is really tempting but... the latest version that slither recommends is 0.7.6:
https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity
(changed just a few days ago from 0.6.11)

Are there any features from 0.8 that you need?

Copy link
Member Author

@aalavandhan aalavandhan Apr 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like keeping up with the latest version that openzeppelin contracts use is a good practice. They are up to 0.8x now

@thegostep thoughts?


import "./_external/Ownable.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {StringUtils} from "./_external/StringUtils.sol";

import "./UFragmentsPolicy.sol";
interface IUFragmentsPolicy {
function rebase() external;
}

/**
* @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 {
using StringUtils for uint16;

// Reference to the Ampleforth Policy
address public policy;

struct Transaction {
bool enabled;
address destination;
Expand All @@ -19,35 +28,32 @@ contract Orchestrator is Ownable {
// 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_);
constructor(address policy_) Ownable() {
policy = 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 fails, Orchestrator will stop execution
* and revert to prevent a gas underprice attack.
*/
function rebase() external {
require(msg.sender == tx.origin); // solhint-disable-line avoid-tx-origin

policy.rebase();
IUFragmentsPolicy(policy).rebase();

for (uint256 i = 0; i < transactions.length; i++) {
for (uint16 i = 0; i < transactions.length; i++) {
Transaction storage t = transactions[i];
if (t.enabled) {
(bool result, ) = t.destination.call(t.data);
if (!result) {
revert("Transaction Failed");
(bool success, ) = t.destination.call(t.data);

// Transaction failed, revert with message
if (!success) {
revert(buildRevertReason(i));
}
}
}
Expand All @@ -59,15 +65,16 @@ contract Orchestrator is Ownable {
* @param data Transaction data payload
*/
function addTransaction(address destination, bytes memory data) external onlyOwner {
// require(transactions.length < type(uint16).max);
transactions.push(Transaction({enabled: true, destination: destination, data: data}));
}

/**
* @param index Index of transaction to remove.
* Transaction ordering may have changed since adding.
*/
function removeTransaction(uint256 index) external onlyOwner {
require(index < transactions.length, "index out of bounds");
function removeTransaction(uint16 index) external onlyOwner {
require(index < transactions.length, "Orchestrator: index out of bounds");

if (index < transactions.length - 1) {
transactions[index] = transactions[transactions.length - 1];
Expand All @@ -80,8 +87,11 @@ contract Orchestrator is Ownable {
* @param index Index of transaction. Transaction ordering may have changed since adding.
* @param enabled True for enabled, false for disabled.
*/
function setTransactionEnabled(uint256 index, bool enabled) external onlyOwner {
require(index < transactions.length, "index must be in range of stored tx list");
function setTransactionEnabled(uint16 index, bool enabled) external onlyOwner {
require(
index < transactions.length,
"Orchestrator: index must be in range of stored tx list"
);
transactions[index].enabled = enabled;
}

Expand All @@ -91,4 +101,18 @@ contract Orchestrator is Ownable {
function transactionsSize() external view returns (uint256) {
return transactions.length;
}

/**
* @param txIndex The index of the failing transaction in the transaction array.
* @return The revert reason.
*/
function buildRevertReason(uint16 txIndex) internal pure returns (string memory) {
return
string(
abi.encodePacked(
"Orchestrator: transaction:{index} reverted:",
txIndex.uintToBytes()
)
);
}
}
1 change: 1 addition & 0 deletions contracts/UFragments.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.7.6;

import "./_external/SafeMath.sol";
Expand Down
1 change: 1 addition & 0 deletions contracts/UFragmentsPolicy.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.7.6;

import "./_external/SafeMath.sol";
Expand Down
23 changes: 23 additions & 0 deletions contracts/_external/StringUtils.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.0;

// https://github.com/pipermerriam/ethereum-string-utils/blob/master/contracts/StringLib.sol
// String Utils v0.1
/// @title String Utils - String utility functions
/// @author Piper Merriam - <[email protected]>
library StringUtils {
/// @dev Converts an unsigned integert to its string representation.
/// @param v The number to be converted.
function uintToBytes(uint256 v) internal pure returns (bytes32 ret) {
if (v == 0) {
ret = "0";
} else {
while (v > 0) {
ret = bytes32(uint256(ret) / (2**8));
ret |= bytes32(((v % 10) + 48) * 2**(8 * 31));
v /= 10;
}
}
return ret;
}
}
6 changes: 4 additions & 2 deletions contracts/mocks/ConstructorRebaseCallerContract.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
pragma solidity 0.7.6;

import "../Orchestrator.sol";
interface IOrchestrator {
function rebase() external;
}

contract ConstructorRebaseCallerContract {
constructor(address orchestrator) public {
// Take out a flash loan.
// Do something funky...
Orchestrator(orchestrator).rebase(); // should fail
IOrchestrator(orchestrator).rebase(); // should fail
// pay back flash loan.
}
}
10 changes: 10 additions & 0 deletions contracts/mocks/MockDownstream.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,14 @@ contract MockDownstream is Mock {

require(false, "reverted");
}

function revertsWithoutMessage() external {
emit FunctionCalled("MockDownstream", "reverts", msg.sender);

uint256[] memory uintVals = new uint256[](0);
int256[] memory intVals = new int256[](0);
emit FunctionArguments(uintVals, intVals);

revert();
}
}
6 changes: 4 additions & 2 deletions contracts/mocks/RebaseCallerContract.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
pragma solidity 0.7.6;

import "../Orchestrator.sol";
interface IOrchestrator {
function rebase() external;
}

contract RebaseCallerContract {
function callRebase(address orchestrator) public returns (bool) {
// Take out a flash loan.
// Do something funky...
Orchestrator(orchestrator).rebase(); // should fail
IOrchestrator(orchestrator).rebase(); // should fail
// pay back flash loan.
return true;
}
Expand Down
20 changes: 13 additions & 7 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,26 @@ import { HardhatUserConfig } from 'hardhat/config'
import '@nomiclabs/hardhat-ethers'
import '@nomiclabs/hardhat-waffle'
import '@openzeppelin/hardhat-upgrades'
import "@nomiclabs/hardhat-etherscan";
import '@nomiclabs/hardhat-etherscan'
import 'solidity-coverage'
import 'hardhat-gas-reporter'

require('./scripts/deploy')

export default {
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY
apiKey: process.env.ETHERSCAN_API_KEY,
},
networks: {
rinkeby: {
url: `https://rinkeby.infura.io/v3/${process.env.INFURA_SECRET}`
url: `https://rinkeby.infura.io/v3/${process.env.INFURA_SECRET}`,
},
kovan: {
url: `https://kovan.infura.io/v3/${process.env.INFURA_SECRET}`
url: `https://kovan.infura.io/v3/${process.env.INFURA_SECRET}`,
},
mainnet: {
url: `https://mainnet.infura.io/v3/${process.env.INFURA_SECRET}`
}
url: `https://mainnet.infura.io/v3/${process.env.INFURA_SECRET}`,
},
},
solidity: {
compilers: [
Expand All @@ -36,7 +36,13 @@ export default {
},
},
{
version: '0.4.24',
version: '0.8.2',
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
],
},
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"typescript": "^4.0.2"
},
"dependencies": {
"@openzeppelin/contracts": "^4.0.0",
"hardhat-gas-reporter": "^1.0.4"
}
}
75 changes: 69 additions & 6 deletions test/unit/Orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ describe('Orchestrator', function () {
})
})

describe('when a transaction reverts', async function () {
describe('when a transaction reverts with message', async function () {
before('adding 3 transactions', async function () {
const updateOneArgEncoded = await mockDownstream.populateTransaction.updateOneArg(
123,
Expand All @@ -282,12 +282,75 @@ describe('Orchestrator', function () {
await orchestrator
.connect(deployer)
.addTransaction(mockDownstream.address, updateTwoArgsEncoded.data)
await expect(orchestrator.connect(deployer).rebase()).to.be.reverted

let exp
try {
await orchestrator.connect(deployer).rebase()
} catch (e) {
exp = e
}
expect(!exp).to.be.false
expect(exp.message.replace(/\0/g, '')).to.eq(
'VM Exception while processing transaction: revert Orchestrator: transaction:{index} reverted:1',
)
})

it('should have 3 transactions', async function () {
expect(await orchestrator.transactionsSize()).to.eq(3)
})

after('removing 3 transactions', async function () {
await orchestrator.connect(deployer).removeTransaction(2)
await orchestrator.connect(deployer).removeTransaction(1)
await orchestrator.connect(deployer).removeTransaction(0)
expect(await orchestrator.transactionsSize()).to.eq(0)
})
})

describe('when a transaction reverts without message', async function () {
before('adding 3 transactions', async function () {
const updateOneArgEncoded = await mockDownstream.populateTransaction.updateOneArg(
555,
)
await orchestrator
.connect(deployer)
.addTransaction(mockDownstream.address, updateOneArgEncoded.data)

const updateTwoArgsEncoded = await mockDownstream.populateTransaction.updateTwoArgs(
72,
33,
)
await orchestrator
.connect(deployer)
.addTransaction(mockDownstream.address, updateTwoArgsEncoded.data)

const revertsEncoded = await mockDownstream.populateTransaction.revertsWithoutMessage()
await orchestrator
.connect(deployer)
.addTransaction(mockDownstream.address, revertsEncoded.data)

let exp
try {
await orchestrator.connect(deployer).rebase()
} catch (e) {
exp = e
}
expect(!exp).to.be.false
expect(exp.message.replace(/\0/g, '')).to.eq(
'VM Exception while processing transaction: revert Orchestrator: transaction:{index} reverted:2',
)
})

it('should have 3 transactions', async function () {
expect(await orchestrator.transactionsSize()).to.eq(3)
})

after('removing 3 transactions', async function () {
await orchestrator.connect(deployer).removeTransaction(2)
await orchestrator.connect(deployer).removeTransaction(1)
await orchestrator.connect(deployer).removeTransaction(0)
expect(await orchestrator.transactionsSize()).to.eq(0)
})
})

describe('Access Control', function () {
Expand Down Expand Up @@ -320,9 +383,9 @@ describe('Orchestrator', function () {
})

it('should revert if index out of bounds', async function () {
expect(await orchestrator.transactionsSize()).to.lt(5)
expect(await orchestrator.transactionsSize()).to.lt(2)
await expect(
orchestrator.connect(deployer).setTransactionEnabled(5, true),
orchestrator.connect(deployer).setTransactionEnabled(2, true),
).to.be.reverted
})

Expand All @@ -341,8 +404,8 @@ describe('Orchestrator', function () {
})

it('should revert if index out of bounds', async function () {
expect(await orchestrator.transactionsSize()).to.lt(5)
await expect(orchestrator.connect(deployer).removeTransaction(5)).to.be
expect(await orchestrator.transactionsSize()).to.lt(2)
await expect(orchestrator.connect(deployer).removeTransaction(2)).to.be
.reverted
})

Expand Down
Loading