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
164 changes: 101 additions & 63 deletions contracts/managers/AssetPairManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,79 +33,103 @@ import { ITrigger } from "./triggers/ITrigger.sol";
* @title AssetPairManager
* @author Set Protocol
*
* Rebalancing Manager contract for implementing any trading pair strategy. Allocation determinations are made
* base on output of Trigger contract. Max base asset allocation amount is passed in and used when bullish,
* allocationPrecision - bullishBaseAssetAllocation used when bearish. Additionally, all allocations are priced
* using the base contract's Allocator contract.
* Manager contract for implementing any trading pair and strategy. Allocation determinations are made
* based on output of Trigger contract. bullishBaseAssetAllocation amount is passed in and used when bullish,
* allocationDenominator - bullishBaseAssetAllocation used when bearish.
*/
contract AssetPairManager {
using SafeMath for uint256;

/* ============ Events ============ */

event InitialProposeCalled(
address indexed rebalancingSetToken
);

/* ============ Constants ============ */
uint256 constant private HUNDRED = 100;

/* ============ State Variables ============ */
ICore public coreInstance;
IAllocator public allocatorInstance;
ITrigger public triggerInstance;
IAuctionPriceCurve public auctionLibraryInstance;
IRebalancingSetToken public rebalancingSetTokenInstance;
ICore public core;
IAllocator public allocator;
ITrigger public trigger;
IAuctionPriceCurve public auctionLibrary;
IRebalancingSetToken public rebalancingSetToken;
uint256 public baseAssetAllocation; // Percent of base asset currently allocated in strategy
uint256 public allocationPrecision;
uint256 public allocationDenominator;
uint256 public bullishBaseAssetAllocation;
uint256 public auctionStartPercentage;
uint256 public auctionEndPercentage;
uint256 public bearishBaseAssetAllocation;
uint256 public auctionStartPercentage; // Percent below fair value to start linear auction
uint256 public auctionPivotPercentage; // Percent above fair value to end linear auction
uint256 public auctionTimeToPivot;

// Time to start of confirmation period in seconds
// Time until start of confirmation period after initialPropse called, in seconds
uint256 public signalConfirmationMinTime;
// Time to end of confirmation period in seconds
// Time until end of confirmation period after intialPropse called, in seconds
uint256 public signalConfirmationMaxTime;
uint256 public lastInitialTriggerTimestamp;
// Timestamp of last successful initialPropose call
uint256 public recentInitialProposeTimestamp;

address public initializerAddress;

/*
* AssetPairManager constructor.
*
* @param _coreInstance The address of the Core contract
* @param _allocatorInstance The address of the Allocator to be used in the strategy
* @param _triggerInstance The address of the PriceTrigger to be used in the strategy
* @param _auctionLibraryInstance The address of auction price curve to use in rebalance
* @param _core The address of the Core contract
* @param _allocator The address of the Allocator to be used in the strategy
* @param _trigger The address of the PriceTrigger to be used in the strategy
* @param _auctionLibrary The address of auction price curve to use in rebalance
* @param _baseAssetAllocation Starting allocation of the Rebalancing Set in baseAsset amount
* @param _allocationPrecision Precision of allocation percentage
* @param _allocationDenominator Precision of allocation (i.e. 100 = percent, 10000 = basis point)
* @param _bullishBaseAssetAllocation Base asset allocation when trigger is bullish
* @param _auctionTimeToPivot Time, in seconds, spent between start and pivot price
* @param _auctionPriceBounds The price bounds, in percent below and above fair value, of linear auction
* @param _signalConfirmationBounds The lower and upper bounds of time, in seconds, from initialTrigger to confirm signal
*/
constructor(
ICore _coreInstance,
IAllocator _allocatorInstance,
ITrigger _triggerInstance,
IAuctionPriceCurve _auctionLibraryInstance,
ICore _core,
IAllocator _allocator,
ITrigger _trigger,
IAuctionPriceCurve _auctionLibrary,
uint256 _baseAssetAllocation,
uint256 _allocationPrecision,
uint256 _allocationDenominator,
uint256 _bullishBaseAssetAllocation,
uint256 _auctionTimeToPivot,
uint256[2] memory _auctionPriceBounds,
uint256[2] memory _signalConfirmationBounds
)
public
{
// Passed bullish allocation must be less than or equal to allocationDenominator
require(
_bullishBaseAssetAllocation <= _allocationDenominator,
"AssetPairManager.constructor: Passed bullishBaseAssetAllocation must be less than allocationDenominator."
);

bullishBaseAssetAllocation = _bullishBaseAssetAllocation;
bearishBaseAssetAllocation = _allocationDenominator.sub(_bullishBaseAssetAllocation);

// Passed baseAssetAllocation must equal bullish or bearish allocations
require(
bullishBaseAssetAllocation == _baseAssetAllocation || bearishBaseAssetAllocation == _baseAssetAllocation,
"AssetPairManager.constructor: Passed baseAssetAllocation must equal bullish or bearish allocations."
);

// Make sure confirmation max time is greater than confirmation min time
require(
_signalConfirmationBounds[1] >= _signalConfirmationBounds[0],
"AssetPairManager.constructor: Confirmation max time must be greater than min time."
);

coreInstance = _coreInstance;
allocatorInstance = _allocatorInstance;
triggerInstance = _triggerInstance;
auctionLibraryInstance = _auctionLibraryInstance;
core = _core;
allocator = _allocator;
trigger = _trigger;
auctionLibrary = _auctionLibrary;
baseAssetAllocation = _baseAssetAllocation;
allocationPrecision = _allocationPrecision;
bullishBaseAssetAllocation = _bullishBaseAssetAllocation;
allocationDenominator = _allocationDenominator;
auctionTimeToPivot = _auctionTimeToPivot;
auctionStartPercentage = _auctionPriceBounds[0];
auctionEndPercentage = _auctionPriceBounds[1];
auctionPivotPercentage = _auctionPriceBounds[1];
signalConfirmationMinTime = _signalConfirmationBounds[0];
signalConfirmationMaxTime = _signalConfirmationBounds[1];
initializerAddress = msg.sender;
Expand All @@ -117,10 +141,10 @@ contract AssetPairManager {
* This function sets the Rebalancing Set Token address that the manager is associated with.
* This function is only meant to be called once during initialization by the contract deployer.
*
* @param _rebalancingSetTokenInstance The address of the rebalancing Set token
* @param _rebalancingSetToken The address of the rebalancing Set token
*/
function initialize(
IRebalancingSetToken _rebalancingSetTokenInstance
IRebalancingSetToken _rebalancingSetToken
)
external
{
Expand All @@ -132,12 +156,12 @@ contract AssetPairManager {

// Make sure the rebalancingSetToken is tracked by Core
require( // coverage-disable-line
coreInstance.validSets(address(_rebalancingSetTokenInstance)),
core.validSets(address(_rebalancingSetToken)),
"AssetPairManager.initialize: Invalid or disabled RebalancingSetToken address"
);

rebalancingSetTokenInstance = _rebalancingSetTokenInstance;
// Set initializer address to 0 so that no one can update RebalancingSetTokenInstance state
rebalancingSetToken = _rebalancingSetToken;
// Set initializer address to 0 so that no one can update RebalancingSetToken state
initializerAddress = address(0);
}

Expand All @@ -149,8 +173,14 @@ contract AssetPairManager {
function initialPropose()
external
{
// Make sure Manager has been initialized with RebalancingSetToken
require(
address(rebalancingSetToken) != address(0),
"AssetPairManager.confirmPropose: Manager must be initialized with RebalancingSetToken."
);

// Check enough time has passed for proposal and RebalancingSetToken in Default state
FlexibleTimingManagerLibrary.validateManagerPropose(rebalancingSetTokenInstance);
FlexibleTimingManagerLibrary.validateManagerPropose(rebalancingSetToken);

// Make sure there is not an existing initial proposal underway
require(
Expand All @@ -168,7 +198,9 @@ contract AssetPairManager {
);

// Set initial trigger timestamp
lastInitialTriggerTimestamp = block.timestamp;
recentInitialProposeTimestamp = block.timestamp;

emit InitialProposeCalled(address(rebalancingSetToken));
}

/*
Expand All @@ -178,8 +210,14 @@ contract AssetPairManager {
function confirmPropose()
external
{
// Make sure Manager has been initialized with RebalancingSetToken
require(
address(rebalancingSetToken) != address(0),
"AssetPairManager.confirmPropose: Manager must be initialized with RebalancingSetToken."
);

// Check that enough time has passed for the proposal and RebalancingSetToken is in Default state
FlexibleTimingManagerLibrary.validateManagerPropose(rebalancingSetTokenInstance);
FlexibleTimingManagerLibrary.validateManagerPropose(rebalancingSetToken);

// Make sure in confirmation window
require(
Expand All @@ -197,27 +235,27 @@ contract AssetPairManager {
);

// Get current collateral Set
address currentCollateralSetAddress = rebalancingSetTokenInstance.currentSet();
ISetToken currentCollateralSet = ISetToken(rebalancingSetToken.currentSet());

// If price trigger has been met, get next Set allocation. Create new set if price difference is too
// great to run good auction. Return nextSet address.
address nextSetAddress = allocatorInstance.determineNewAllocation(
ISetToken nextSet = allocator.determineNewAllocation(
newBaseAssetAllocation,
allocationPrecision,
ISetToken(currentCollateralSetAddress)
allocationDenominator,
currentCollateralSet
);

// Get current and next Set dollar values
uint256 currentSetDollarValue = allocatorInstance.calculateCollateralSetValue(
ISetToken(currentCollateralSetAddress)
uint256 currentSetDollarValue = allocator.calculateCollateralSetValue(
currentCollateralSet
);

uint256 nextSetDollarValue = allocatorInstance.calculateCollateralSetValue(
ISetToken(nextSetAddress)
uint256 nextSetDollarValue = allocator.calculateCollateralSetValue(
nextSet
);

// Get auction price divisor
uint256 auctionPriceDivisor = auctionLibraryInstance.priceDivisor();
uint256 auctionPriceDivisor = auctionLibrary.priceDivisor();

// Calculate the price parameters for auction
(
Expand All @@ -230,9 +268,9 @@ contract AssetPairManager {
);

// Propose new allocation to Rebalancing Set Token
rebalancingSetTokenInstance.propose(
nextSetAddress,
address(auctionLibraryInstance),
rebalancingSetToken.propose(
address(nextSet),
address(auctionLibrary),
auctionTimeToPivot,
auctionStartPrice,
auctionPivotPrice
Expand Down Expand Up @@ -288,13 +326,13 @@ contract AssetPairManager {
view
returns (uint256)
{
return triggerInstance.isBullish() ? bullishBaseAssetAllocation : allocationPrecision.sub(bullishBaseAssetAllocation);
return trigger.isBullish() ? bullishBaseAssetAllocation : bearishBaseAssetAllocation;
}

/*
* Calculates the auction price parameters, targetting 1% slippage every 10 minutes. Range is
* defined by subtracting auctionStartPercentage * onePercentSlippage from fairValue and adding
* auctionEndPercentage * onePercentSlippage to fairValue
* auctionPivotPercentage * onePercentSlippage to fairValue
*
* @param _currentSetDollarAmount The 18 decimal value of one currenSet
* @param _nextSetDollarAmount The 18 decimal value of one nextSet
Expand All @@ -314,15 +352,15 @@ contract AssetPairManager {
// Determine fair value of nextSet/currentSet and put in terms of auction library price divisor
uint256 fairValue = _nextSetDollarAmount.mul(_auctionLibraryPriceDivisor).div(_currentSetDollarAmount);
// Calculate how much one percent slippage from fair value is
uint256 onePercentSlippage = fairValue.div(100);
uint256 onePercentSlippage = fairValue.div(HUNDRED);

// Auction start price is fair value minus half price range to center the auction at fair value
uint256 auctionStartPrice = fairValue.sub(
auctionStartPercentage.mul(onePercentSlippage)
);
// Auction pivot price is fair value plus half price range to center the auction at fair value
uint256 auctionPivotPrice = fairValue.add(
auctionEndPercentage.mul(onePercentSlippage)
auctionPivotPercentage.mul(onePercentSlippage)
);

return (auctionStartPrice, auctionPivotPrice);
Expand All @@ -340,12 +378,12 @@ contract AssetPairManager {
returns (bool)
{
// Get RebalancingSetToken timing info
uint256 lastRebalanceTimestamp = rebalancingSetTokenInstance.lastRebalanceTimestamp();
uint256 rebalanceInterval = rebalancingSetTokenInstance.rebalanceInterval();
uint256 lastRebalanceTimestamp = rebalancingSetToken.lastRebalanceTimestamp();
uint256 rebalanceInterval = rebalancingSetToken.rebalanceInterval();

// Require that Rebalancing Set Token is in Default state and rebalanceInterval elapsed
return block.timestamp >= lastRebalanceTimestamp.add(rebalanceInterval) &&
rebalancingSetTokenInstance.rebalanceState() == RebalancingLibrary.State.Default;
return block.timestamp.sub(lastRebalanceTimestamp) >= rebalanceInterval &&
rebalancingSetToken.rebalanceState() == RebalancingLibrary.State.Default;
}

/*
Expand All @@ -358,7 +396,7 @@ contract AssetPairManager {
view
returns (bool)
{
return block.timestamp > lastInitialTriggerTimestamp.add(signalConfirmationMaxTime);
return block.timestamp.sub(recentInitialProposeTimestamp) > signalConfirmationMaxTime;
}

/*
Expand All @@ -371,7 +409,7 @@ contract AssetPairManager {
view
returns (bool)
{
return block.timestamp >= lastInitialTriggerTimestamp.add(signalConfirmationMinTime) &&
block.timestamp <= lastInitialTriggerTimestamp.add(signalConfirmationMaxTime);
uint256 timeSinceInitialPropose = block.timestamp.sub(recentInitialProposeTimestamp);
return timeSinceInitialPropose >= signalConfirmationMinTime && timeSinceInitialPropose <= signalConfirmationMaxTime;
}
}
Loading