Permalink
Switch branches/tags
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
710 lines (644 sloc) 31.9 KB
pragma solidity ^0.4.21;
import "./assets/Shares.sol";
import "./dependencies/DBC.sol";
import "./dependencies/Owned.sol";
import "./compliance/ComplianceInterface.sol";
import "./pricefeeds/CanonicalPriceFeed.sol";
import "./riskmgmt/RiskMgmtInterface.sol";
import "./exchange/GenericExchangeInterface.sol";
import "./FundInterface.sol";
import "./dependencies/math.sol";
/// @title Melon Fund Contract
/// @author Melonport AG <team@melonport.com>
/// @notice Simple Melon Fund
contract Fund is DSMath, DBC, Owned, Shares, FundInterface {
event OrderUpdated(address exchange, bytes32 orderId, UpdateType updateType);
// TYPES
struct Modules { // Describes all modular parts, standardised through an interface
CanonicalPriceFeed pricefeed; // Provides all external data
ComplianceInterface compliance; // Boolean functions regarding invest/redeem
RiskMgmtInterface riskmgmt; // Boolean functions regarding make/take orders
}
struct Calculations { // List of internal calculations
uint gav; // Gross asset value
uint managementFee; // Time based fee
uint performanceFee; // Performance based fee measured against QUOTE_ASSET
uint unclaimedFees; // Fees not yet allocated to the fund manager
uint nav; // Net asset value
uint highWaterMark; // A record of best all-time fund performance
uint totalSupply; // Total supply of shares
uint timestamp; // Time when calculations are performed in seconds
}
enum UpdateType { make, take, cancel, swap }
enum RequestStatus { active, cancelled, executed }
struct Request { // Describes and logs whenever asset enter and leave fund due to Participants
address participant; // Participant in Melon fund requesting investment or redemption
RequestStatus status; // Enum: active, cancelled, executed; Status of request
address requestAsset; // Address of the asset being requested
uint shareQuantity; // Quantity of Melon fund shares
uint giveQuantity; // Quantity in Melon asset to give to Melon fund to receive shareQuantity
uint receiveQuantity; // Quantity in Melon asset to receive from Melon fund for given shareQuantity
uint timestamp; // Time of request creation in seconds
uint atUpdateId; // Pricefeed updateId when this request was created
}
struct Exchange {
address exchange;
address exchangeAdapter;
bool takesCustody; // exchange takes custody before making order
}
struct OpenMakeOrder {
uint id; // Order Id from exchange
uint expiresAt; // Timestamp when the order expires
}
struct Order { // Describes an order event (make or take order)
address exchangeAddress; // address of the exchange this order is on
bytes32 orderId; // Id as returned from exchange
UpdateType updateType; // Enum: make, take, swap (cancel should be ignored)
address makerAsset; // Order maker's asset
address takerAsset; // Order taker's asset
uint makerQuantity; // Quantity of makerAsset to be traded
uint takerQuantity; // Quantity of takerAsset to be traded
uint timestamp; // Time of order creation in seconds
uint fillTakerQuantity; // Quantity of takerAsset to be filled
}
// FIELDS
// Constant fields
uint public constant MAX_FUND_ASSETS = 20; // Max ownable assets by the fund supported by gas limits
uint public constant ORDER_EXPIRATION_TIME = 86400; // Make order expiration time (1 day)
// Constructor fields
uint public MANAGEMENT_FEE_RATE; // Fee rate in QUOTE_ASSET per managed seconds in WAD
uint public PERFORMANCE_FEE_RATE; // Fee rate in QUOTE_ASSET per delta improvement in WAD
address public VERSION; // Address of Version contract
Asset public QUOTE_ASSET; // QUOTE asset as ERC20 contract
Asset public NATIVE_ASSET; // NATIVE asset as ERC20 contract
// Methods fields
Modules public modules; // Struct which holds all the initialised module instances
Exchange[] public exchanges; // Array containing exchanges this fund supports
Calculations public atLastUnclaimedFeeAllocation; // Calculation results at last allocateUnclaimedFees() call
Order[] public orders; // append-only list of makes/takes from this fund
mapping (address => mapping(address => OpenMakeOrder)) public exchangesToOpenMakeOrders; // exchangeIndex to: asset to open make orders
bool public isShutDown; // Security feature, if yes than investing, managing, allocateUnclaimedFees gets blocked
Request[] public requests; // All the requests this fund received from participants
mapping (address => bool) public isInvestAllowed; // If false, fund rejects investments from the key asset
address[] public ownedAssets; // List of all assets owned by the fund or for which the fund has open make orders
mapping (address => bool) public isInAssetList; // Mapping from asset to whether the asset exists in ownedAssets
mapping (address => bool) public isInOpenMakeOrder; // Mapping from asset to whether the asset is in a open make order as buy asset
// METHODS
// CONSTRUCTOR
/// @dev Should only be called via Version.setupFund(..)
/// @param withName human-readable descriptive name (not necessarily unique)
/// @param ofQuoteAsset Asset against which mgmt and performance fee is measured against and which can be used to invest using this single asset
/// @param ofManagementFee A time based fee expressed, given in a number which is divided by 1 WAD
/// @param ofPerformanceFee A time performance based fee, performance relative to ofQuoteAsset, given in a number which is divided by 1 WAD
/// @param ofCompliance Address of compliance module
/// @param ofRiskMgmt Address of risk management module
/// @param ofPriceFeed Address of price feed module
/// @param ofExchanges Addresses of exchange on which this fund can trade
/// @param ofDefaultAssets Addresses of assets to enable invest for (quote asset is already enabled)
/// @return Deployed Fund with manager set as ofManager
function Fund(
address ofManager,
bytes32 withName,
address ofQuoteAsset,
address ofNativeAsset,
uint ofManagementFee,
uint ofPerformanceFee,
address ofCompliance,
address ofRiskMgmt,
address ofPriceFeed,
address[] ofExchanges,
address[] ofDefaultAssets
)
Shares(withName, "MLNF", 18, now)
{
require(ofManagementFee < 10 ** 18); // Require management fee to be less than 100 percent
require(ofPerformanceFee < 10 ** 18); // Require performance fee to be less than 100 percent
isInvestAllowed[ofQuoteAsset] = true;
owner = ofManager;
MANAGEMENT_FEE_RATE = ofManagementFee; // 1 percent is expressed as 0.01 * 10 ** 18
PERFORMANCE_FEE_RATE = ofPerformanceFee; // 1 percent is expressed as 0.01 * 10 ** 18
VERSION = msg.sender;
modules.compliance = ComplianceInterface(ofCompliance);
modules.riskmgmt = RiskMgmtInterface(ofRiskMgmt);
modules.pricefeed = CanonicalPriceFeed(ofPriceFeed);
// Bridged to Melon exchange interface by exchangeAdapter library
for (uint i = 0; i < ofExchanges.length; ++i) {
require(modules.pricefeed.exchangeIsRegistered(ofExchanges[i]));
var (ofExchangeAdapter, takesCustody, ) = modules.pricefeed.getExchangeInformation(ofExchanges[i]);
exchanges.push(Exchange({
exchange: ofExchanges[i],
exchangeAdapter: ofExchangeAdapter,
takesCustody: takesCustody
}));
}
QUOTE_ASSET = Asset(ofQuoteAsset);
NATIVE_ASSET = Asset(ofNativeAsset);
// Quote Asset always in owned assets list
ownedAssets.push(ofQuoteAsset);
isInAssetList[ofQuoteAsset] = true;
require(address(QUOTE_ASSET) == modules.pricefeed.getQuoteAsset()); // Sanity check
for (uint j = 0; j < ofDefaultAssets.length; j++) {
require(modules.pricefeed.assetIsRegistered(ofDefaultAssets[j]));
isInvestAllowed[ofDefaultAssets[j]] = true;
}
atLastUnclaimedFeeAllocation = Calculations({
gav: 0,
managementFee: 0,
performanceFee: 0,
unclaimedFees: 0,
nav: 0,
highWaterMark: 10 ** getDecimals(),
totalSupply: _totalSupply,
timestamp: now
});
}
// EXTERNAL METHODS
// EXTERNAL : ADMINISTRATION
/// @notice Enable investment in specified assets
/// @param ofAssets Array of assets to enable investment in
function enableInvestment(address[] ofAssets)
external
pre_cond(isOwner())
{
for (uint i = 0; i < ofAssets.length; ++i) {
require(modules.pricefeed.assetIsRegistered(ofAssets[i]));
isInvestAllowed[ofAssets[i]] = true;
}
}
/// @notice Disable investment in specified assets
/// @param ofAssets Array of assets to disable investment in
function disableInvestment(address[] ofAssets)
external
pre_cond(isOwner())
{
for (uint i = 0; i < ofAssets.length; ++i) {
isInvestAllowed[ofAssets[i]] = false;
}
}
function shutDown() external pre_cond(msg.sender == VERSION) { isShutDown = true; }
// EXTERNAL : PARTICIPATION
/// @notice Give melon tokens to receive shares of this fund
/// @dev Recommended to give some leeway in prices to account for possibly slightly changing prices
/// @param giveQuantity Quantity of Melon token times 10 ** 18 offered to receive shareQuantity
/// @param shareQuantity Quantity of shares times 10 ** 18 requested to be received
/// @param investmentAsset Address of asset to invest in
function requestInvestment(
uint giveQuantity,
uint shareQuantity,
address investmentAsset
)
external
pre_cond(!isShutDown)
pre_cond(isInvestAllowed[investmentAsset]) // investment using investmentAsset has not been deactivated by the Manager
pre_cond(modules.compliance.isInvestmentPermitted(msg.sender, giveQuantity, shareQuantity)) // Compliance Module: Investment permitted
{
requests.push(Request({
participant: msg.sender,
status: RequestStatus.active,
requestAsset: investmentAsset,
shareQuantity: shareQuantity,
giveQuantity: giveQuantity,
receiveQuantity: shareQuantity,
timestamp: now,
atUpdateId: modules.pricefeed.getLastUpdateId()
}));
emit RequestUpdated(getLastRequestId());
}
/// @notice Executes active investment and redemption requests, in a way that minimises information advantages of investor
/// @dev Distributes melon and shares according to the request
/// @param id Index of request to be executed
/// @dev Active investment or redemption request executed
function executeRequest(uint id)
external
pre_cond(!isShutDown)
pre_cond(requests[id].status == RequestStatus.active)
pre_cond(
_totalSupply == 0 ||
(
now >= add(requests[id].timestamp, modules.pricefeed.getInterval()) &&
modules.pricefeed.getLastUpdateId() >= add(requests[id].atUpdateId, 2)
)
) // PriceFeed Module: Wait at least one interval time and two updates before continuing (unless it is the first investment)
{
Request request = requests[id];
var (isRecent, , ) =
modules.pricefeed.getPriceInfo(address(request.requestAsset));
require(isRecent);
// sharePrice quoted in QUOTE_ASSET and multiplied by 10 ** fundDecimals
uint costQuantity = toWholeShareUnit(mul(request.shareQuantity, calcSharePriceAndAllocateFees())); // By definition quoteDecimals == fundDecimals
if (request.requestAsset != address(QUOTE_ASSET)) {
var (isPriceRecent, invertedRequestAssetPrice, requestAssetDecimal) = modules.pricefeed.getInvertedPriceInfo(request.requestAsset);
if (!isPriceRecent) {
revert();
}
costQuantity = mul(costQuantity, invertedRequestAssetPrice) / 10 ** requestAssetDecimal;
}
if (
isInvestAllowed[request.requestAsset] &&
costQuantity <= request.giveQuantity
) {
request.status = RequestStatus.executed;
require(AssetInterface(request.requestAsset).transferFrom(request.participant, address(this), costQuantity)); // Allocate Value
createShares(request.participant, request.shareQuantity); // Accounting
if (!isInAssetList[request.requestAsset]) {
ownedAssets.push(request.requestAsset);
isInAssetList[request.requestAsset] = true;
}
} else {
revert(); // Invalid Request or invalid giveQuantity / receiveQuantity
}
}
/// @notice Cancels active investment and redemption requests
/// @param id Index of request to be executed
function cancelRequest(uint id)
external
pre_cond(requests[id].status == RequestStatus.active) // Request is active
pre_cond(requests[id].participant == msg.sender || isShutDown) // Either request creator or fund is shut down
{
requests[id].status = RequestStatus.cancelled;
}
/// @notice Redeems by allocating an ownership percentage of each asset to the participant
/// @dev Independent of running price feed!
/// @param shareQuantity Number of shares owned by the participant, which the participant would like to redeem for individual assets
/// @return Whether all assets sent to shareholder or not
function redeemAllOwnedAssets(uint shareQuantity)
external
returns (bool success)
{
return emergencyRedeem(shareQuantity, ownedAssets);
}
// EXTERNAL : MANAGING
/// @notice Universal method for calling exchange functions through adapters
/// @notice See adapter contracts for parameters needed for each exchange
/// @param exchangeIndex Index of the exchange in the "exchanges" array
/// @param method Signature of the adapter method to call (as per ABI spec)
/// @param orderAddresses [0] Order maker
/// @param orderAddresses [1] Order taker
/// @param orderAddresses [2] Order maker asset
/// @param orderAddresses [3] Order taker asset
/// @param orderAddresses [4] Fee recipient
/// @param orderValues [0] Maker token quantity
/// @param orderValues [1] Taker token quantity
/// @param orderValues [2] Maker fee
/// @param orderValues [3] Taker fee
/// @param orderValues [4] Timestamp (seconds)
/// @param orderValues [5] Salt/nonce
/// @param orderValues [6] Fill amount: amount of taker token to be traded
/// @param orderValues [7] Dexy signature mode
/// @param identifier Order identifier
/// @param v ECDSA recovery id
/// @param r ECDSA signature output r
/// @param s ECDSA signature output s
function callOnExchange(
uint exchangeIndex,
bytes4 method,
address[5] orderAddresses,
uint[8] orderValues,
bytes32 identifier,
uint8 v,
bytes32 r,
bytes32 s
)
external
{
require(
modules.pricefeed.exchangeMethodIsAllowed(
exchanges[exchangeIndex].exchange, method
)
);
require(
exchanges[exchangeIndex].exchangeAdapter.delegatecall(
method, exchanges[exchangeIndex].exchange,
orderAddresses, orderValues, identifier, v, r, s
)
);
}
function addOpenMakeOrder(
address ofExchange,
address ofSellAsset,
uint orderId
)
pre_cond(msg.sender == address(this))
{
isInOpenMakeOrder[ofSellAsset] = true;
exchangesToOpenMakeOrders[ofExchange][ofSellAsset].id = orderId;
exchangesToOpenMakeOrders[ofExchange][ofSellAsset].expiresAt = add(now, ORDER_EXPIRATION_TIME);
}
function removeOpenMakeOrder(
address ofExchange,
address ofSellAsset
)
pre_cond(msg.sender == address(this))
{
isInOpenMakeOrder[ofSellAsset] = false;
delete exchangesToOpenMakeOrders[ofExchange][ofSellAsset];
}
function orderUpdateHook(
address ofExchange,
bytes32 orderId,
UpdateType updateType,
address[2] orderAddresses, // makerAsset, takerAsset
uint[3] orderValues // makerQuantity, takerQuantity, fillTakerQuantity (take only)
)
pre_cond(msg.sender == address(this))
{
// only save make/take
if (updateType == UpdateType.make || updateType == UpdateType.take || updateType == UpdateType.swap) {
orders.push(Order({
exchangeAddress: ofExchange,
orderId: orderId,
updateType: updateType,
makerAsset: orderAddresses[0],
takerAsset: orderAddresses[1],
makerQuantity: orderValues[0],
takerQuantity: orderValues[1],
timestamp: block.timestamp,
fillTakerQuantity: orderValues[2]
}));
}
emit OrderUpdated(ofExchange, orderId, updateType);
}
// PUBLIC METHODS
// PUBLIC METHODS : ACCOUNTING
/// @notice Calculates gross asset value of the fund
/// @dev Decimals in assets must be equal to decimals in PriceFeed for all entries in AssetRegistrar
/// @dev Assumes that module.pricefeed.getPriceInfo(..) returns recent prices
/// @return gav Gross asset value quoted in QUOTE_ASSET and multiplied by 10 ** shareDecimals
function calcGav() returns (uint gav) {
// prices quoted in QUOTE_ASSET and multiplied by 10 ** assetDecimal
uint[] memory allAssetHoldings = new uint[](ownedAssets.length);
uint[] memory allAssetPrices = new uint[](ownedAssets.length);
address[] memory tempOwnedAssets;
tempOwnedAssets = ownedAssets;
delete ownedAssets;
for (uint i = 0; i < tempOwnedAssets.length; ++i) {
address ofAsset = tempOwnedAssets[i];
// assetHoldings formatting: mul(exchangeHoldings, 10 ** assetDecimal)
uint assetHoldings = add(
uint(AssetInterface(ofAsset).balanceOf(address(this))), // asset base units held by fund
quantityHeldInCustodyOfExchange(ofAsset)
);
// assetPrice formatting: mul(exchangePrice, 10 ** assetDecimal)
var (isRecent, assetPrice, assetDecimals) = modules.pricefeed.getPriceInfo(ofAsset);
if (!isRecent) {
revert();
}
allAssetHoldings[i] = assetHoldings;
allAssetPrices[i] = assetPrice;
// gav as sum of mul(assetHoldings, assetPrice) with formatting: mul(mul(exchangeHoldings, exchangePrice), 10 ** shareDecimals)
gav = add(gav, mul(assetHoldings, assetPrice) / (10 ** uint256(assetDecimals))); // Sum up product of asset holdings of this vault and asset prices
if (assetHoldings != 0 || ofAsset == address(QUOTE_ASSET) || isInOpenMakeOrder[ofAsset]) { // Check if asset holdings is not zero or is address(QUOTE_ASSET) or in open make order
ownedAssets.push(ofAsset);
} else {
isInAssetList[ofAsset] = false; // Remove from ownedAssets if asset holdings are zero
}
}
emit PortfolioContent(tempOwnedAssets, allAssetHoldings, allAssetPrices);
}
/// @notice Add an asset to the list that this fund owns
function addAssetToOwnedAssets (address ofAsset)
public
pre_cond(isOwner() || msg.sender == address(this))
{
isInOpenMakeOrder[ofAsset] = true;
if (!isInAssetList[ofAsset]) {
ownedAssets.push(ofAsset);
isInAssetList[ofAsset] = true;
}
}
/**
@notice Calculates unclaimed fees of the fund manager
@param gav Gross asset value in QUOTE_ASSET and multiplied by 10 ** shareDecimals
@return {
"managementFees": "A time (seconds) based fee in QUOTE_ASSET and multiplied by 10 ** shareDecimals",
"performanceFees": "A performance (rise of sharePrice measured in QUOTE_ASSET) based fee in QUOTE_ASSET and multiplied by 10 ** shareDecimals",
"unclaimedfees": "The sum of both managementfee and performancefee in QUOTE_ASSET and multiplied by 10 ** shareDecimals"
}
*/
function calcUnclaimedFees(uint gav)
view
returns (
uint managementFee,
uint performanceFee,
uint unclaimedFees)
{
// Management fee calculation
uint timePassed = sub(now, atLastUnclaimedFeeAllocation.timestamp);
uint gavPercentage = mul(timePassed, gav) / (1 years);
managementFee = wmul(gavPercentage, MANAGEMENT_FEE_RATE);
// Performance fee calculation
// Handle potential division through zero by defining a default value
uint valuePerShareExclMgmtFees = _totalSupply > 0 ? calcValuePerShare(sub(gav, managementFee), _totalSupply) : toSmallestShareUnit(1);
if (valuePerShareExclMgmtFees > atLastUnclaimedFeeAllocation.highWaterMark) {
uint gainInSharePrice = sub(valuePerShareExclMgmtFees, atLastUnclaimedFeeAllocation.highWaterMark);
uint investmentProfits = wmul(gainInSharePrice, _totalSupply);
performanceFee = wmul(investmentProfits, PERFORMANCE_FEE_RATE);
}
// Sum of all FEES
unclaimedFees = add(managementFee, performanceFee);
}
/// @notice Calculates the Net asset value of this fund
/// @param gav Gross asset value of this fund in QUOTE_ASSET and multiplied by 10 ** shareDecimals
/// @param unclaimedFees The sum of both managementFee and performanceFee in QUOTE_ASSET and multiplied by 10 ** shareDecimals
/// @return nav Net asset value in QUOTE_ASSET and multiplied by 10 ** shareDecimals
function calcNav(uint gav, uint unclaimedFees)
view
returns (uint nav)
{
nav = sub(gav, unclaimedFees);
}
/// @notice Calculates the share price of the fund
/// @dev Convention for valuePerShare (== sharePrice) formatting: mul(totalValue / numShares, 10 ** decimal), to avoid floating numbers
/// @dev Non-zero share supply; value denominated in [base unit of melonAsset]
/// @param totalValue the total value in QUOTE_ASSET and multiplied by 10 ** shareDecimals
/// @param numShares the number of shares multiplied by 10 ** shareDecimals
/// @return valuePerShare Share price denominated in QUOTE_ASSET and multiplied by 10 ** shareDecimals
function calcValuePerShare(uint totalValue, uint numShares)
view
pre_cond(numShares > 0)
returns (uint valuePerShare)
{
valuePerShare = toSmallestShareUnit(totalValue) / numShares;
}
/**
@notice Calculates essential fund metrics
@return {
"gav": "Gross asset value of this fund denominated in [base unit of melonAsset]",
"managementFee": "A time (seconds) based fee",
"performanceFee": "A performance (rise of sharePrice measured in QUOTE_ASSET) based fee",
"unclaimedFees": "The sum of both managementFee and performanceFee denominated in [base unit of melonAsset]",
"feesShareQuantity": "The number of shares to be given as fees to the manager",
"nav": "Net asset value denominated in [base unit of melonAsset]",
"sharePrice": "Share price denominated in [base unit of melonAsset]"
}
*/
function performCalculations()
view
returns (
uint gav,
uint managementFee,
uint performanceFee,
uint unclaimedFees,
uint feesShareQuantity,
uint nav,
uint sharePrice
)
{
gav = calcGav(); // Reflects value independent of fees
(managementFee, performanceFee, unclaimedFees) = calcUnclaimedFees(gav);
nav = calcNav(gav, unclaimedFees);
// The value of unclaimedFees measured in shares of this fund at current value
feesShareQuantity = (gav == 0) ? 0 : mul(_totalSupply, unclaimedFees) / gav;
// The total share supply including the value of unclaimedFees, measured in shares of this fund
uint totalSupplyAccountingForFees = add(_totalSupply, feesShareQuantity);
sharePrice = _totalSupply > 0 ? calcValuePerShare(gav, totalSupplyAccountingForFees) : toSmallestShareUnit(1); // Handle potential division through zero by defining a default value
}
/// @notice Converts unclaimed fees of the manager into fund shares
/// @return sharePrice Share price denominated in [base unit of melonAsset]
function calcSharePriceAndAllocateFees() public returns (uint)
{
var (
gav,
managementFee,
performanceFee,
unclaimedFees,
feesShareQuantity,
nav,
sharePrice
) = performCalculations();
createShares(owner, feesShareQuantity); // Updates _totalSupply by creating shares allocated to manager
// Update Calculations
uint highWaterMark = atLastUnclaimedFeeAllocation.highWaterMark >= sharePrice ? atLastUnclaimedFeeAllocation.highWaterMark : sharePrice;
atLastUnclaimedFeeAllocation = Calculations({
gav: gav,
managementFee: managementFee,
performanceFee: performanceFee,
unclaimedFees: unclaimedFees,
nav: nav,
highWaterMark: highWaterMark,
totalSupply: _totalSupply,
timestamp: now
});
emit FeesConverted(now, feesShareQuantity, unclaimedFees);
emit CalculationUpdate(now, managementFee, performanceFee, nav, sharePrice, _totalSupply);
return sharePrice;
}
// PUBLIC : REDEEMING
/// @notice Redeems by allocating an ownership percentage only of requestedAssets to the participant
/// @dev This works, but with loops, so only up to a certain number of assets (right now the max is 4)
/// @dev Independent of running price feed! Note: if requestedAssets != ownedAssets then participant misses out on some owned value
/// @param shareQuantity Number of shares owned by the participant, which the participant would like to redeem for a slice of assets
/// @param requestedAssets List of addresses that consitute a subset of ownedAssets.
/// @return Whether all assets sent to shareholder or not
function emergencyRedeem(uint shareQuantity, address[] requestedAssets)
public
pre_cond(balances[msg.sender] >= shareQuantity) // sender owns enough shares
returns (bool)
{
address ofAsset;
uint[] memory ownershipQuantities = new uint[](requestedAssets.length);
address[] memory redeemedAssets = new address[](requestedAssets.length);
// Check whether enough assets held by fund
for (uint i = 0; i < requestedAssets.length; ++i) {
ofAsset = requestedAssets[i];
require(isInAssetList[ofAsset]);
for (uint j = 0; j < redeemedAssets.length; j++) {
if (ofAsset == redeemedAssets[j]) {
revert();
}
}
redeemedAssets[i] = ofAsset;
uint assetHoldings = add(
uint(AssetInterface(ofAsset).balanceOf(address(this))),
quantityHeldInCustodyOfExchange(ofAsset)
);
if (assetHoldings == 0) continue;
// participant's ownership percentage of asset holdings
ownershipQuantities[i] = mul(assetHoldings, shareQuantity) / _totalSupply;
// CRITICAL ERR: Not enough fund asset balance for owed ownershipQuantitiy, eg in case of unreturned asset quantity at address(exchanges[i].exchange) address
if (uint(AssetInterface(ofAsset).balanceOf(address(this))) < ownershipQuantities[i]) {
isShutDown = true;
emit ErrorMessage("CRITICAL ERR: Not enough assetHoldings for owed ownershipQuantitiy");
return false;
}
}
// Annihilate shares before external calls to prevent reentrancy
annihilateShares(msg.sender, shareQuantity);
// Transfer ownershipQuantity of Assets
for (uint k = 0; k < requestedAssets.length; ++k) {
// Failed to send owed ownershipQuantity from fund to participant
ofAsset = requestedAssets[k];
if (ownershipQuantities[k] == 0) {
continue;
} else if (!AssetInterface(ofAsset).transfer(msg.sender, ownershipQuantities[k])) {
revert();
}
}
emit Redeemed(msg.sender, now, shareQuantity);
return true;
}
function() public payable { }
// PUBLIC : FEES
/// @dev Quantity of asset held in exchange according to associated order id
/// @param ofAsset Address of asset
/// @return Quantity of input asset held in exchange
function quantityHeldInCustodyOfExchange(address ofAsset) returns (uint) {
uint totalSellQuantity; // quantity in custody across exchanges
uint totalSellQuantityInApprove; // quantity of asset in approve (allowance) but not custody of exchange
for (uint i; i < exchanges.length; i++) {
if (exchangesToOpenMakeOrders[exchanges[i].exchange][ofAsset].id == 0) {
continue;
}
var (, , sellQuantity, ) = GenericExchangeInterface(exchanges[i].exchangeAdapter).getOrder(exchanges[i].exchange, exchangesToOpenMakeOrders[exchanges[i].exchange][ofAsset].id);
if (sellQuantity == 0) { // remove id if remaining sell quantity zero (closed)
delete exchangesToOpenMakeOrders[exchanges[i].exchange][ofAsset];
}
totalSellQuantity = add(totalSellQuantity, sellQuantity);
if (!exchanges[i].takesCustody) {
totalSellQuantityInApprove += sellQuantity;
}
}
if (totalSellQuantity == 0) {
isInOpenMakeOrder[ofAsset] = false;
}
return sub(totalSellQuantity, totalSellQuantityInApprove); // Since quantity in approve is not actually in custody
}
// PUBLIC VIEW METHODS
/// @notice Calculates sharePrice denominated in [base unit of melonAsset]
/// @return sharePrice Share price denominated in [base unit of melonAsset]
function calcSharePrice() view returns (uint sharePrice) {
(, , , , , sharePrice) = performCalculations();
return sharePrice;
}
function getModules() view returns (address, address, address) {
return (
address(modules.pricefeed),
address(modules.compliance),
address(modules.riskmgmt)
);
}
function getLastRequestId() view returns (uint) { return requests.length - 1; }
function getLastOrderIndex() view returns (uint) { return orders.length - 1; }
function getManager() view returns (address) { return owner; }
function getOwnedAssetsLength() view returns (uint) { return ownedAssets.length; }
function getExchangeInfo() view returns (address[], address[], bool[]) {
address[] memory ofExchanges = new address[](exchanges.length);
address[] memory ofAdapters = new address[](exchanges.length);
bool[] memory takesCustody = new bool[](exchanges.length);
for (uint i = 0; i < exchanges.length; i++) {
ofExchanges[i] = exchanges[i].exchange;
ofAdapters[i] = exchanges[i].exchangeAdapter;
takesCustody[i] = exchanges[i].takesCustody;
}
return (ofExchanges, ofAdapters, takesCustody);
}
function orderExpired(address ofExchange, address ofAsset) view returns (bool) {
uint expiryTime = exchangesToOpenMakeOrders[ofExchange][ofAsset].expiresAt;
require(expiryTime > 0);
return block.timestamp >= expiryTime;
}
function getOpenOrderInfo(address ofExchange, address ofAsset) view returns (uint, uint) {
OpenMakeOrder order = exchangesToOpenMakeOrders[ofExchange][ofAsset];
return (order.id, order.expiresAt);
}
}