Skip to content

Commit

Permalink
Merge pull request aragon#9 from 1Hive/fees-updater
Browse files Browse the repository at this point in the history
Added FeesUpdater contract for updating fees using stable coin value.
  • Loading branch information
willjgriff committed Nov 5, 2020
2 parents 386f63e + c414bb7 commit e96e53a
Show file tree
Hide file tree
Showing 11 changed files with 325 additions and 14 deletions.
5 changes: 3 additions & 2 deletions contracts/court/AragonCourt.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ contract AragonCourt is Controller, IArbitrator {
* @param _governors Array containing:
* 0. _fundsGovernor Address of the funds governor
* 1. _configGovernor Address of the config governor
* 2. _modulesGovernor Address of the modules governor
* 2. _oracle Address of the price oracle
* 3. _modulesGovernor Address of the modules governor
* @param _feeToken Address of the token contract that is used to pay for fees
* @param _fees Array containing:
* 0. jurorFee Amount of fee tokens that is paid per juror per dispute
Expand Down Expand Up @@ -54,7 +55,7 @@ contract AragonCourt is Controller, IArbitrator {
*/
constructor(
uint64[2] memory _termParams,
address[3] memory _governors,
address[4] memory _governors,
ERC20 _feeToken,
uint256[3] memory _fees,
uint64[5] memory _roundStateDurations,
Expand Down
47 changes: 42 additions & 5 deletions contracts/court/controller/Controller.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ contract Controller is IsContract, CourtClock, CourtConfig {
struct Governor {
address funds; // This address can be unset at any time. It is allowed to recover funds from the ControlledRecoverable modules
address config; // This address is meant not to be unset. It is allowed to change the different configurations of the whole system
address feesUpdater;// This is a second address that can update the config. It is expected to be used with a price oracle for updating fees
address modules; // This address can be unset at any time. It is allowed to plug/unplug modules from the system
}

Expand All @@ -50,6 +51,7 @@ contract Controller is IsContract, CourtClock, CourtConfig {
event ModuleSet(bytes32 id, address addr);
event FundsGovernorChanged(address previousGovernor, address currentGovernor);
event ConfigGovernorChanged(address previousGovernor, address currentGovernor);
event FeesUpdaterChanged(address previousFeesUpdater, address currentFeesUpdater);
event ModulesGovernorChanged(address previousGovernor, address currentGovernor);

/**
Expand All @@ -61,13 +63,21 @@ contract Controller is IsContract, CourtClock, CourtConfig {
}

/**
* @dev Ensure the msg.sender is the modules governor
* @dev Ensure the msg.sender is the config governor
*/
modifier onlyConfigGovernor {
require(msg.sender == governor.config, ERROR_SENDER_NOT_GOVERNOR);
_;
}

/**
* @dev Ensure the msg.sender is the config governor or the fees updater
*/
modifier onlyConfigGovernorOrFeesUpdater {
require(msg.sender == governor.config || msg.sender == governor.feesUpdater, ERROR_SENDER_NOT_GOVERNOR);
_;
}

/**
* @dev Ensure the msg.sender is the modules governor
*/
Expand All @@ -84,7 +94,8 @@ contract Controller is IsContract, CourtClock, CourtConfig {
* @param _governors Array containing:
* 0. _fundsGovernor Address of the funds governor
* 1. _configGovernor Address of the config governor
* 2. _modulesGovernor Address of the modules governor
* 2. _feesUpdater Address of the price feesUpdater
* 3. _modulesGovernor Address of the modules governor
* @param _feeToken Address of the token contract that is used to pay for fees
* @param _fees Array containing:
* 0. jurorFee Amount of fee tokens that is paid per juror per dispute
Expand Down Expand Up @@ -114,7 +125,7 @@ contract Controller is IsContract, CourtClock, CourtConfig {
*/
constructor(
uint64[2] memory _termParams,
address[3] memory _governors,
address[4] memory _governors,
ERC20 _feeToken,
uint256[3] memory _fees,
uint64[5] memory _roundStateDurations,
Expand All @@ -129,7 +140,8 @@ contract Controller is IsContract, CourtClock, CourtConfig {
{
_setFundsGovernor(_governors[0]);
_setConfigGovernor(_governors[1]);
_setModulesGovernor(_governors[2]);
_setFeesUpdater(_governors[2]);
_setModulesGovernor(_governors[3]);
}

/**
Expand Down Expand Up @@ -173,7 +185,7 @@ contract Controller is IsContract, CourtClock, CourtConfig {
uint256[3] calldata _jurorsParams
)
external
onlyConfigGovernor
onlyConfigGovernorOrFeesUpdater
{
uint64 currentTermId = _ensureCurrentTerm();
_setConfig(
Expand Down Expand Up @@ -215,6 +227,14 @@ contract Controller is IsContract, CourtClock, CourtConfig {
_setConfigGovernor(_newConfigGovernor);
}

/**
* @notice Change fees updater to `_newFeesUpdater`
* @param _newFeesUpdater Address of the new fees updater to be set
*/
function changeFeesUpdater(address _newFeesUpdater) external onlyConfigGovernor {
_setFeesUpdater(_newFeesUpdater);
}

/**
* @notice Change modules governor address to `_newModulesGovernor`
* @param _newModulesGovernor Address of the new governor to be set
Expand Down Expand Up @@ -345,6 +365,14 @@ contract Controller is IsContract, CourtClock, CourtConfig {
return governor.config;
}

/**
* @dev Tell the address of the fees updater
* @return Address of the fees updater
*/
function getFeesUpdater() external view returns (address) {
return governor.feesUpdater;
}

/**
* @dev Tell the address of the modules governor
* @return Address of the modules governor
Expand Down Expand Up @@ -428,6 +456,15 @@ contract Controller is IsContract, CourtClock, CourtConfig {
governor.config = _newConfigGovernor;
}

/**
* @dev Internal function to set the address of the fees updater
* @param _newFeesUpdater Address of the new fees updater to be set
*/
function _setFeesUpdater(address _newFeesUpdater) internal {
emit FeesUpdaterChanged(governor.feesUpdater, _newFeesUpdater);
governor.feesUpdater = _newFeesUpdater;
}

/**
* @dev Internal function to set the address of the modules governor
* @param _newModulesGovernor Address of the new modules governor to be set
Expand Down
58 changes: 58 additions & 0 deletions contracts/feesUpdater/FeesUpdater.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
pragma solidity ^0.5.8;

import "../court/AragonCourt.sol";
import "./IPriceOracle.sol";
import "../lib/os/ERC20.sol";

contract FeesUpdater {

IPriceOracle public priceOracle;
AragonCourt public court;
address public courtStableToken;
uint256[3] public courtStableValueFees;

constructor(
IPriceOracle _priceOracle,
AragonCourt _court,
address _courtStableToken,
uint256[3] memory _courtStableValueFees
) public {
priceOracle = _priceOracle;
court = _court;
courtStableToken = _courtStableToken;
courtStableValueFees = _courtStableValueFees;
}

function getStableFees() external view returns (uint256[3] memory) {
return courtStableValueFees;
}

/**
* @notice Convert the court fees from their stable value to the fee token value and update the court config from the
* next term with them. This function can be called any number of times during a court term, the closer to the
* start of the following term the more accurate the configured fees will be.
*/
function updateCourtFees() external {
uint64 currentTerm = court.ensureCurrentTerm();

// We use the latest possible term to ensure that if the config has been updated by an account other
// than this oracle, the config fetched will be the updated one. However, this does mean that a config update
// that is scheduled for a future term will be scheduled for the next term instead.
uint64 latestPossibleTerm = uint64(-1);
(ERC20 feeToken,,
uint64[5] memory roundStateDurations,
uint16[2] memory pcts,
uint64[4] memory roundParams,
uint256[2] memory appealCollateralParams,
uint256[3] memory jurorsParams
) = court.getConfig(latestPossibleTerm);

uint256[3] memory convertedFees;
convertedFees[0] = priceOracle.consult(courtStableToken, courtStableValueFees[0], address(feeToken));
convertedFees[1] = priceOracle.consult(courtStableToken, courtStableValueFees[1], address(feeToken));
convertedFees[2] = priceOracle.consult(courtStableToken, courtStableValueFees[2], address(feeToken));

court.setConfig(currentTerm + 1, feeToken, convertedFees, roundStateDurations, pcts, roundParams,
appealCollateralParams, jurorsParams);
}
}
5 changes: 5 additions & 0 deletions contracts/feesUpdater/IPriceOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pragma solidity ^0.5.8;

contract IPriceOracle {
function consult(address tokenIn, uint256 amountIn, address tokenOut) external view returns (uint256 amountOut);
}
2 changes: 1 addition & 1 deletion contracts/test/court/AragonCourtMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ contract AragonCourtMock is AragonCourt, TimeHelpersMock {

constructor(
uint64[2] memory _termParams,
address[3] memory _governors,
address[4] memory _governors,
ERC20 _feeToken,
uint256[3] memory _fees,
uint64[5] memory _roundStateDurations,
Expand Down
16 changes: 16 additions & 0 deletions contracts/test/feesUpdater/MockPriceOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
pragma solidity ^0.5.8;

import "../../feesUpdater/IPriceOracle.sol";

contract MockPriceOracle is IPriceOracle {

uint256 public feeTokenPriceInStableToken;

constructor(uint256 _feeTokenPriceInStableToken) public {
feeTokenPriceInStableToken = _feeTokenPriceInStableToken;
}

function consult(address tokenIn, uint256 amountIn, address tokenOut) external view returns (uint256 amountOut) {
return amountIn / feeTokenPriceInStableToken;
}
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@1hive/celeste",
"version": "0.1.1",
"version": "0.1.2",
"description": "Celeste Oracle",
"author": "Aragon Association",
"license": "GPL-3.0",
Expand All @@ -16,6 +16,7 @@
"test:controller": "buidler test test/court/controller/*",
"test:subscriptions": "buidler test test/subscriptions/*",
"test:disputes": "buidler test test/disputes/*",
"test:feesupdater": "buidler test test/feesUpdater/*",
"test:registry": "buidler test test/registry/*",
"test:voting": "buidler test test/voting/*"
},
Expand Down
11 changes: 9 additions & 2 deletions test/court/controller/controller-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const { assertConfig, buildNewConfig } = require('../../helpers/utils/config')(a
const { assertEvent, assertAmountOfEvents } = require('../../helpers/asserts/assertEvent')
const { CLOCK_ERRORS, CONFIG_ERRORS, CONTROLLER_ERRORS } = require('../../helpers/utils/errors')

contract('Controller', ([_, configGovernor, someone, drafter, appealMaker, appealTaker, juror500, juror1000, juror3000]) => {
contract('Controller', ([_, configGovernor, feesUpdater, someone, drafter, appealMaker, appealTaker, juror500, juror1000, juror3000]) => {
let courtHelper, controller

let initialConfig, feeToken
Expand Down Expand Up @@ -154,7 +154,7 @@ contract('Controller', ([_, configGovernor, someone, drafter, appealMaker, appea
let newConfig

beforeEach('deploy controller and build new config', async () => {
controller = await courtHelper.deploy({ ...initialConfig, configGovernor })
controller = await courtHelper.deploy({ ...initialConfig, configGovernor, feesUpdater })
newConfig = await buildNewConfig(initialConfig)
})

Expand Down Expand Up @@ -444,6 +444,13 @@ contract('Controller', ([_, configGovernor, someone, drafter, appealMaker, appea
})
})

context('when the sender is the fees updater', () => {
it('updates the config', async () => {
const receipt = await courtHelper.setConfig(0, newConfig, { from: feesUpdater })
assertAmountOfEvents(receipt, CONFIG_EVENTS.CONFIG_CHANGED)
})
})

context('when the sender is not the governor', () => {
const from = someone

Expand Down
41 changes: 39 additions & 2 deletions test/court/controller/controller-modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ const Controlled = artifacts.require('Controlled')

const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'

contract('Controller', ([_, fundsGovernor, configGovernor, modulesGovernor, someone]) => {
contract('Controller', ([_, fundsGovernor, configGovernor, feesUpdater, modulesGovernor, someone]) => {
let controller

beforeEach('create controller', async () => {
controller = await buildHelper().deploy({ fundsGovernor, configGovernor, modulesGovernor })
controller = await buildHelper().deploy({ fundsGovernor, configGovernor, feesUpdater, modulesGovernor })
})

describe('getFundsGovernor', () => {
Expand All @@ -28,6 +28,12 @@ contract('Controller', ([_, fundsGovernor, configGovernor, modulesGovernor, some
})
})

describe('getFeesUpdater', () => {
it('tells the fees updater', async () => {
assert.equal(await controller.getFeesUpdater(), feesUpdater, 'fees updater does not match')
})
})

describe('getModulesGovernor', () => {
it('tells the expected governor', async () => {
assert.equal(await controller.getModulesGovernor(), modulesGovernor, 'modules governor does not match')
Expand Down Expand Up @@ -118,6 +124,37 @@ contract('Controller', ([_, fundsGovernor, configGovernor, modulesGovernor, some
})
})

describe('changeFeesUpdater', () => {
context('when the sender is the config governor', () => {
const from = configGovernor
const newFeesUpdater = someone

it('changes the fee updater', async () => {
await controller.changeFeesUpdater(newFeesUpdater, { from })

assert.equal(await controller.getFeesUpdater(), newFeesUpdater, 'fees updater does not match')
})

it('emits an event', async () => {
const receipt = await controller.changeFeesUpdater(newFeesUpdater, { from })

assertAmountOfEvents(receipt, 'FeesUpdaterChanged')
assertEvent(receipt, 'FeesUpdaterChanged', {
previousFeesUpdater: feesUpdater,
currentFeesUpdater: newFeesUpdater
})
})
})

context('when the sender is not the config governor', () => {
const from = feesUpdater

it('reverts', async () => {
await assertRevert(controller.changeFeesUpdater(someone, { from }), CONTROLLER_ERRORS.SENDER_NOT_GOVERNOR)
})
})
})

describe('changeModulesGovernor', () => {
context('when the sender is the modules governor', () => {
const from = modulesGovernor
Expand Down
Loading

0 comments on commit e96e53a

Please sign in to comment.