Skip to content
This repository was archived by the owner on Jan 18, 2023. It is now read-only.
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
49 changes: 36 additions & 13 deletions contracts/managers/ETHTwentyDayMACOManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ contract ETHTwentyDayMACOManager {
uint256 constant ETH_DECIMALS = 18;

/* ============ State Variables ============ */
address public contractDeployer;
address public rebalancingSetTokenAddress;
address public coreAddress;
address public movingAveragePriceFeed;
address public setTokenFactory;
Expand Down Expand Up @@ -110,6 +112,7 @@ contract ETHTwentyDayMACOManager {
)
public
{
contractDeployer = msg.sender;
coreAddress = _coreAddress;
movingAveragePriceFeed = _movingAveragePriceFeed;
setTokenFactory = _setTokenFactory;
Expand All @@ -124,33 +127,56 @@ contract ETHTwentyDayMACOManager {
riskOn = _riskOn;
}

/* ============ External ============ */

/*
* When allowed on RebalancingSetToken, anyone can call for a new rebalance proposal. The Sets off a six
* hour period where the signal con be confirmed before moving ahead with rebalance.
* This function sets the Rebalancing Set Token address that the manager is associated with.
* Since, the rebalancing set token must first specify the address of the manager before deployment,
* we cannot know what the rebalancing set token is in advance. This function is only meant to be called
* once during initialization by the contract deployer.
*
* @param _rebalancingSetTokenAddress The address of Rebalancing Set Token to propose new allocation
* @param _rebalancingSetTokenAddress The address of the rebalancing Set token
*/
function initialPropose(
function initialize(
address _rebalancingSetTokenAddress
)
external
{
require(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note: I decided to restrict the caller of initializer to the deployer of the contract.

msg.sender == contractDeployer,
"ETHTwentyDayMACOManager.initialize: Only the contract deployer can initialize"
);

require(
rebalancingSetTokenAddress == address(0),
"ETHTwentyDayMACOManager.initialize: Rebalancing SetToken Address must be empty"
);

// Make sure the rebalancingSetToken is tracked by Core
require(
ICore(coreAddress).validSets(_rebalancingSetTokenAddress),
"ETHTwentyDayMACOManager.initialPropose: Invalid or disabled SetToken address"
"ETHTwentyDayMACOManager.initialize: Invalid or disabled SetToken address"
);

rebalancingSetTokenAddress = _rebalancingSetTokenAddress;
}

/* ============ External ============ */

/*
* When allowed on RebalancingSetToken, anyone can call for a new rebalance proposal. The Sets off a six
* hour period where the signal con be confirmed before moving ahead with rebalance.
*
*/
function initialPropose()
external
{
// Make sure propose in manager hasn't already been initiated
require(
block.timestamp > proposalTimestamp.add(TWELVE_HOURS_IN_SECONDS),
"ETHTwentyDayMACOManager.initialPropose: 12 hours must pass before new proposal initiated"
);

// Create interface to interact with RebalancingSetToken and check enough time has passed for proposal
FlexibleTimingManagerLibrary.validateManagerPropose(IRebalancingSetToken(_rebalancingSetTokenAddress));
FlexibleTimingManagerLibrary.validateManagerPropose(IRebalancingSetToken(rebalancingSetTokenAddress));

// Get price data from oracles
(
Expand All @@ -167,11 +193,8 @@ contract ETHTwentyDayMACOManager {
/*
* After initial propose is called, confirm the signal has been met and determine parameters for the rebalance
*
* @param _rebalancingSetTokenAddress The address of Rebalancing Set Token to propose new allocation
*/
function confirmPropose(
address _rebalancingSetTokenAddress
)
function confirmPropose()
external
{
// Make sure enough time has passed to initiate proposal on Rebalancing Set Token
Expand Down Expand Up @@ -214,7 +237,7 @@ contract ETHTwentyDayMACOManager {
);

// Propose new allocation to Rebalancing Set Token
IRebalancingSetToken(_rebalancingSetTokenAddress).propose(
IRebalancingSetToken(rebalancingSetTokenAddress).propose(
nextSetAddress,
auctionLibrary,
auctionTimeToPivot,
Expand Down
171 changes: 132 additions & 39 deletions test/contracts/managers/ethTwentyDayMACOManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const setTestUtils = new SetTestUtils(web3);
contract('ETHTwentyDayMACOManager', accounts => {
const [
deployerAccount,
notDeployerAccount,
] = accounts;

let rebalancingSetToken: RebalancingSetTokenContract;
Expand Down Expand Up @@ -265,8 +266,124 @@ contract('ETHTwentyDayMACOManager', accounts => {
});
});

describe('#initialPropose', async () => {
describe('#initialize', async () => {
let subjectRebalancingSetToken: Address;
let subjectCaller: Address;

let updatedValues: BigNumber[];
let proposalPeriod: BigNumber;
let auctionTimeToPivot: BigNumber;

before(async () => {
updatedValues = _.map(new Array(19), function(el, i) {return ether(150 + i); });
});

beforeEach(async () => {
await oracleWrapper.batchUpdateDailyPriceFeedAsync(
dailyPriceFeed,
ethMedianizer,
20,
updatedValues
);

auctionTimeToPivot = ONE_DAY_IN_SECONDS.div(4);
const [riskOn, initialAllocationAddress] = await managerWrapper.getMACOInitialAllocationAsync(
stableCollateral,
riskCollateral,
ethMedianizer,
movingAverageOracle,
new BigNumber(20)
);

ethTwentyDayMACOManager = await managerWrapper.deployETHTwentyDayMACOManagerAsync(
core.address,
movingAverageOracle.address,
daiMock.address,
wrappedETH.address,
stableCollateral.address,
riskCollateral.address,
factory.address,
linearAuctionPriceCurve.address,
auctionTimeToPivot,
riskOn,
);

proposalPeriod = ONE_DAY_IN_SECONDS;
rebalancingSetToken = await protocolWrapper.createDefaultRebalancingSetTokenAsync(
core,
rebalancingFactory.address,
ethTwentyDayMACOManager.address,
initialAllocationAddress,
proposalPeriod
);

subjectRebalancingSetToken = rebalancingSetToken.address;
subjectCaller = deployerAccount;
});

async function subject(): Promise<string> {
return ethTwentyDayMACOManager.initialize.sendTransactionAsync(
subjectRebalancingSetToken,
{ from: subjectCaller, gas: DEFAULT_GAS}
);
}

it('sets the rebalancing set token address', async () => {
await subject();

const rebalancingSetTokenAddress = await ethTwentyDayMACOManager.rebalancingSetTokenAddress.callAsync();

expect(rebalancingSetTokenAddress).to.equal(subjectRebalancingSetToken);
});

describe('when the rebalancing set address has already been set', async () => {
beforeEach(async () => {
await ethTwentyDayMACOManager.initialize.sendTransactionAsync(
subjectRebalancingSetToken,
{ from: subjectCaller, gas: DEFAULT_GAS}
);
});

it('should revert', async () => {
await expectRevertError(subject());
});
});

describe('but caller is not the contract deployer', async () => {
beforeEach(async () => {
subjectCaller = notDeployerAccount;
});

it('should revert', async () => {
await expectRevertError(subject());
});
});

describe('but the passed rebalancing set address was not created by Core', async () => {
beforeEach(async () => {
const unTrackedSetToken = await protocolWrapper.createDefaultRebalancingSetTokenAsync(
core,
rebalancingFactory.address,
ethTwentyDayMACOManager.address,
riskCollateral.address,
proposalPeriod,
);

await core.disableSet.sendTransactionAsync(
unTrackedSetToken.address,
{ from: deployerAccount, gas: DEFAULT_GAS },
);

subjectRebalancingSetToken = unTrackedSetToken.address;
});

it('should revert', async () => {
await expectRevertError(subject());
});
});
});

describe('#initialPropose', async () => {
let subjectTimeFastForward: BigNumber;
let subjectCaller: Address;

Expand Down Expand Up @@ -319,22 +436,25 @@ contract('ETHTwentyDayMACOManager', accounts => {
proposalPeriod
);

await ethTwentyDayMACOManager.initialize.sendTransactionAsync(
rebalancingSetToken.address,
{ from: subjectCaller, gas: DEFAULT_GAS}
);

const blockInfo = await web3.eth.getBlock('latest');
await oracleWrapper.updateMedianizerPriceAsync(
ethMedianizer,
lastPrice,
new BigNumber(blockInfo.timestamp + 1),
);

subjectRebalancingSetToken = rebalancingSetToken.address;
subjectTimeFastForward = ONE_DAY_IN_SECONDS.add(1);
subjectCaller = deployerAccount;
});

async function subject(): Promise<string> {
await blockchain.increaseTimeAsync(subjectTimeFastForward);
return ethTwentyDayMACOManager.initialPropose.sendTransactionAsync(
subjectRebalancingSetToken,
{ from: subjectCaller, gas: DEFAULT_GAS}
);
}
Expand Down Expand Up @@ -365,36 +485,11 @@ contract('ETHTwentyDayMACOManager', accounts => {
});
});

describe('but the passed rebalancing set address was not created by Core', async () => {
beforeEach(async () => {
const unTrackedSetToken = await protocolWrapper.createDefaultRebalancingSetTokenAsync(
core,
rebalancingFactory.address,
ethTwentyDayMACOManager.address,
riskCollateral.address,
proposalPeriod,
);

await core.disableSet.sendTransactionAsync(
unTrackedSetToken.address,
{ from: deployerAccount, gas: DEFAULT_GAS },
);

subjectRebalancingSetToken = unTrackedSetToken.address;
});

it('should revert', async () => {
await expectRevertError(subject());
});
});

describe('when 12 hours has not passed from an initial proposal', async () => {
beforeEach(async () => {
const timeFastForward = ONE_DAY_IN_SECONDS;
await blockchain.increaseTimeAsync(timeFastForward);
await ethTwentyDayMACOManager.initialPropose.sendTransactionAsync(
subjectRebalancingSetToken,
);
await ethTwentyDayMACOManager.initialPropose.sendTransactionAsync();
subjectTimeFastForward = ONE_DAY_IN_SECONDS.div(4);
});

Expand Down Expand Up @@ -449,14 +544,10 @@ contract('ETHTwentyDayMACOManager', accounts => {
describe('when propose is called and rebalancing set token is in Proposal state', async () => {
beforeEach(async () => {
await blockchain.increaseTimeAsync(subjectTimeFastForward);
await ethTwentyDayMACOManager.initialPropose.sendTransactionAsync(
subjectRebalancingSetToken,
);
await ethTwentyDayMACOManager.initialPropose.sendTransactionAsync();

await blockchain.increaseTimeAsync(ONE_DAY_IN_SECONDS.div(4));
await ethTwentyDayMACOManager.confirmPropose.sendTransactionAsync(
subjectRebalancingSetToken,
);
await ethTwentyDayMACOManager.confirmPropose.sendTransactionAsync();
});

it('should revert', async () => {
Expand All @@ -466,7 +557,6 @@ contract('ETHTwentyDayMACOManager', accounts => {
});

describe('#confirmPropose', async () => {
let subjectRebalancingSetToken: Address;
let subjectTimeFastForward: BigNumber;
let subjectCaller: Address;

Expand Down Expand Up @@ -521,16 +611,20 @@ contract('ETHTwentyDayMACOManager', accounts => {
proposalPeriod
);

await ethTwentyDayMACOManager.initialize.sendTransactionAsync(
rebalancingSetToken.address,
{ from: subjectCaller, gas: DEFAULT_GAS}
);

const triggerBlockInfo = await web3.eth.getBlock('latest');
await oracleWrapper.updateMedianizerPriceAsync(
ethMedianizer,
triggerPrice,
new BigNumber(triggerBlockInfo.timestamp + 1),
);

subjectRebalancingSetToken = rebalancingSetToken.address;
await blockchain.increaseTimeAsync(ONE_DAY_IN_SECONDS.add(1));
await ethTwentyDayMACOManager.initialPropose.sendTransactionAsync(subjectRebalancingSetToken);
await ethTwentyDayMACOManager.initialPropose.sendTransactionAsync();

const lastBlockInfo = await web3.eth.getBlock('latest');
await oracleWrapper.updateMedianizerPriceAsync(
Expand All @@ -546,7 +640,6 @@ contract('ETHTwentyDayMACOManager', accounts => {
async function subject(): Promise<string> {
await blockchain.increaseTimeAsync(subjectTimeFastForward);
return ethTwentyDayMACOManager.confirmPropose.sendTransactionAsync(
subjectRebalancingSetToken,
{ from: subjectCaller, gas: DEFAULT_GAS}
);
}
Expand Down