Skip to content
Open
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
18 changes: 14 additions & 4 deletions contracts/Orchestrator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,25 @@ contract Orchestrator is Ownable {

IUFragmentsPolicy public policy;

address public rebaseCaller;

/**
* @param policy_ Address of the UFragments policy.
*/
constructor(address policy_) public {
constructor(address policy_) {
Ownable.initialize(msg.sender);
policy = IUFragmentsPolicy(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.
* Access is restricted to the authorized rebase caller to prevent unauthorized rebases.
* 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
require(msg.sender == rebaseCaller, "Unauthorized rebase caller");

policy.rebase();

Expand All @@ -56,6 +57,15 @@ contract Orchestrator is Ownable {
}
}

/**
* @notice Sets the authorized rebase caller address.
* @param rebaseCaller_ Address authorized to call rebase.
*/
function setRebaseCaller(address rebaseCaller_) external onlyOwner {
require(rebaseCaller_ != address(0), "Invalid rebase caller");
rebaseCaller = rebaseCaller_;
}

/**
* @notice Adds a transaction that gets called for a downstream receiver of rebases
* @param destination Address of contract destination
Expand Down
8 changes: 8 additions & 0 deletions scripts/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ task('deploy:amplforce:testnet', 'Deploy ampleforth contract suite for testnet')
const RATE_REPORT_EXPIRATION_SEC = 86400 // 1 day
const RATE_REPORT_DELAY_SEC = 0
const RATE_MIN_PROVIDERS = 1
const RATE_ORACLE_SCALAR = utils.parseUnits('1', 18)

// CPI oracle
const CPI_REPORT_EXPIRATION_SEC = 7776000 // 90 days
const CPI_REPORT_DELAY_SEC = 0
const CPI_MIN_PROVIDERS = 1
const CPI_ORACLE_SCALAR = utils.parseUnits('1', 18)

// Policy
const DEVIATION_TRESHOLD = utils.parseUnits('0.002', 18) // 0.002% (ie) 0.05/24)
Expand Down Expand Up @@ -70,6 +72,7 @@ task('deploy:amplforce:testnet', 'Deploy ampleforth contract suite for testnet')
RATE_REPORT_EXPIRATION_SEC,
RATE_REPORT_DELAY_SEC,
RATE_MIN_PROVIDERS,
RATE_ORACLE_SCALAR,
)
console.log('Market oracle to:', marketOracle.address)

Expand All @@ -79,6 +82,7 @@ task('deploy:amplforce:testnet', 'Deploy ampleforth contract suite for testnet')
CPI_REPORT_EXPIRATION_SEC,
CPI_REPORT_DELAY_SEC,
CPI_MIN_PROVIDERS,
CPI_ORACLE_SCALAR,
)
console.log('CPI oracle to:', cpiOracle.address)

Expand Down Expand Up @@ -107,6 +111,10 @@ task('deploy:amplforce:testnet', 'Deploy ampleforth contract suite for testnet')
)
console.log('Orchestrator deployed to:', orchestrator.address)

// Set rebase caller
await waitFor(orchestrator.connect(deployer).setRebaseCaller(owner))
console.log('Rebase caller set to:', owner)

// Set references
await waitFor(ampl.connect(deployer).setMonetaryPolicy(policy.address))
await waitFor(
Expand Down
37 changes: 34 additions & 3 deletions test/unit/Orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ async function mockedOrchestrator() {
)
.connect(deployer)
.deploy()
await orchestrator.connect(deployer).setRebaseCaller(await deployer.getAddress())
return {
deployer,
user,
Expand All @@ -37,7 +38,7 @@ async function mockedOrchestrator() {

describe('Orchestrator', function () {
before('setup Orchestrator contract', async () => {
;({ deployer, user, orchestrator, mockPolicy, mockDownstream } =
; ({ deployer, user, orchestrator, mockPolicy, mockDownstream } =
await waffle.loadFixture(mockedOrchestrator))
})

Expand All @@ -48,8 +49,8 @@ describe('Orchestrator', function () {
})
})

describe('when rebase called by a contract', function () {
it('should fail', async function () {
describe('when rebase called by unauthorized address', function () {
it('should fail when called by a contract', async function () {
const rebaseCallerContract = await (
await ethers.getContractFactory('RebaseCallerContract')
)
Expand All @@ -58,6 +59,12 @@ describe('Orchestrator', function () {
await expect(rebaseCallerContract.callRebase(orchestrator.address)).to.be
.reverted
})

it('should fail when called by unauthorized user', async function () {
await expect(orchestrator.connect(user).rebase()).to.be.revertedWith(
'Unauthorized rebase caller',
)
})
})

describe('when rebase called by a contract which is being constructed', function () {
Expand Down Expand Up @@ -344,6 +351,30 @@ describe('Orchestrator', function () {
})
})

describe('setRebaseCaller', async function () {
it('should be callable by owner', async function () {
const newCaller = await user.getAddress()
await expect(
orchestrator.connect(deployer).setRebaseCaller(newCaller),
).to.not.be.reverted
expect(await orchestrator.rebaseCaller()).to.eq(newCaller)
})

it('should not be callable by others', async function () {
const newCaller = await user.getAddress()
await expect(orchestrator.connect(user).setRebaseCaller(newCaller)).to
.be.reverted
})

it('should reject zero address', async function () {
await expect(
orchestrator
.connect(deployer)
.setRebaseCaller('0x0000000000000000000000000000000000000000'),
).to.be.revertedWith('Invalid rebase caller')
})
})

describe('transferOwnership', async function () {
it('should transfer ownership', async function () {
expect(await orchestrator.owner()).to.eq(await deployer.getAddress())
Expand Down