Contract Name:
Participation
Contract Source Code:
pragma solidity 0.6.1;
import "../vault/Vault.sol";
import "../shares/Shares.sol";
import "../policies/PolicyManager.sol";
import "../hub/Spoke.sol";
import "../accounting/Accounting.sol";
import "../../prices/IPriceSource.sol";
import "../../factory/Factory.sol";
import "../../engine/AmguConsumer.sol";
import "../../dependencies/token/IERC20.sol";
import "../../dependencies/DSMath.sol";
import "../../dependencies/TokenUser.sol";
/// @notice Entry and exit point for investors
contract Participation is TokenUser, AmguConsumer, Spoke {
event EnableInvestment (address[] asset);
event DisableInvestment (address[] assets);
event InvestmentRequest (
address indexed requestOwner,
address indexed investmentAsset,
uint requestedShares,
uint investmentAmount
);
event RequestExecution (
address indexed requestOwner,
address indexed executor,
address indexed investmentAsset,
uint investmentAmount,
uint requestedShares
);
event CancelRequest (
address indexed requestOwner
);
event Redemption (
address indexed redeemer,
address[] assets,
uint[] assetQuantities,
uint redeemedShares
);
struct Request {
address investmentAsset;
uint investmentAmount;
uint requestedShares;
uint timestamp;
}
uint constant public SHARES_DECIMALS = 18;
uint constant public REQUEST_LIFESPAN = 1 days;
mapping (address => Request) public requests;
mapping (address => bool) public investAllowed;
mapping (address => bool) public hasInvested; // for information purposes only (read)
address[] public historicalInvestors; // for information purposes only (read)
constructor(address _hub, address[] memory _defaultAssets, address _registry)
public
Spoke(_hub)
{
routes.registry = _registry;
_enableInvestment(_defaultAssets);
}
receive() external payable {}
function _enableInvestment(address[] memory _assets) internal {
for (uint i = 0; i < _assets.length; i++) {
require(
Registry(routes.registry).assetIsRegistered(_assets[i]),
"Asset not registered"
);
investAllowed[_assets[i]] = true;
}
emit EnableInvestment(_assets);
}
function enableInvestment(address[] calldata _assets) external auth {
_enableInvestment(_assets);
}
function disableInvestment(address[] calldata _assets) external auth {
for (uint i = 0; i < _assets.length; i++) {
investAllowed[_assets[i]] = false;
}
emit DisableInvestment(_assets);
}
function hasRequest(address _who) public view returns (bool) {
return requests[_who].timestamp > 0;
}
function hasExpiredRequest(address _who) public view returns (bool) {
return block.timestamp > add(requests[_who].timestamp, REQUEST_LIFESPAN);
}
/// @notice Whether request is OK and invest delay is being respected
/// @dev Request valid if price update happened since request and not expired
/// @dev If no shares exist and not expired, request can be executed immediately
function hasValidRequest(address _who) public view returns (bool) {
IPriceSource priceSource = IPriceSource(priceSource());
bool delayRespectedOrNoShares = requests[_who].timestamp < priceSource.getLastUpdate() ||
Shares(routes.shares).totalSupply() == 0;
return hasRequest(_who) &&
delayRespectedOrNoShares &&
!hasExpiredRequest(_who) &&
requests[_who].investmentAmount > 0 &&
requests[_who].requestedShares > 0;
}
function requestInvestment(
uint requestedShares,
uint investmentAmount,
address investmentAsset
)
external
notShutDown
payable
amguPayable(true)
onlyInitialized
{
PolicyManager(routes.policyManager).preValidate(
msg.sig,
[msg.sender, address(0), address(0), investmentAsset, address(0)],
[uint(0), uint(0), uint(0)],
bytes32(0)
);
require(
investAllowed[investmentAsset],
"Investment not allowed in this asset"
);
safeTransferFrom(
investmentAsset, msg.sender, address(this), investmentAmount
);
require(
requests[msg.sender].timestamp == 0,
"Only one request can exist at a time"
);
requests[msg.sender] = Request({
investmentAsset: investmentAsset,
investmentAmount: investmentAmount,
requestedShares: requestedShares,
timestamp: block.timestamp
});
PolicyManager(routes.policyManager).postValidate(
msg.sig,
[msg.sender, address(0), address(0), investmentAsset, address(0)],
[uint(0), uint(0), uint(0)],
bytes32(0)
);
emit InvestmentRequest(
msg.sender,
investmentAsset,
requestedShares,
investmentAmount
);
}
function _cancelRequestFor(address requestOwner) internal {
require(hasRequest(requestOwner), "No request to cancel");
IPriceSource priceSource = IPriceSource(priceSource());
Request memory request = requests[requestOwner];
require(
!priceSource.hasValidPrice(request.investmentAsset) ||
hasExpiredRequest(requestOwner) ||
hub.isShutDown(),
"No cancellation condition was met"
);
IERC20 investmentAsset = IERC20(request.investmentAsset);
uint investmentAmount = request.investmentAmount;
delete requests[requestOwner];
msg.sender.transfer(Registry(routes.registry).incentive());
safeTransfer(address(investmentAsset), requestOwner, investmentAmount);
emit CancelRequest(requestOwner);
}
/// @notice Can only cancel when no price, request expired or fund shut down
/// @dev Only request owner can cancel their request
function cancelRequest() external payable amguPayable(false) {
_cancelRequestFor(msg.sender);
}
function cancelRequestFor(address requestOwner)
external
payable
amguPayable(false)
{
_cancelRequestFor(requestOwner);
}
function executeRequestFor(address requestOwner)
external
notShutDown
amguPayable(false)
payable
{
Request memory request = requests[requestOwner];
require(
hasValidRequest(requestOwner),
"No valid request for this address"
);
FeeManager(routes.feeManager).rewardManagementFee();
uint totalShareCostInInvestmentAsset = Accounting(routes.accounting)
.getShareCostInAsset(
request.requestedShares,
request.investmentAsset
);
require(
totalShareCostInInvestmentAsset <= request.investmentAmount,
"Invested amount too low"
);
// send necessary amount of investmentAsset to vault
safeTransfer(
request.investmentAsset,
routes.vault,
totalShareCostInInvestmentAsset
);
uint investmentAssetChange = sub(
request.investmentAmount,
totalShareCostInInvestmentAsset
);
// return investmentAsset change to request owner
if (investmentAssetChange > 0) {
safeTransfer(
request.investmentAsset,
requestOwner,
investmentAssetChange
);
}
msg.sender.transfer(Registry(routes.registry).incentive());
Shares(routes.shares).createFor(requestOwner, request.requestedShares);
Accounting(routes.accounting).addAssetToOwnedAssets(request.investmentAsset);
if (!hasInvested[requestOwner]) {
hasInvested[requestOwner] = true;
historicalInvestors.push(requestOwner);
}
emit RequestExecution(
requestOwner,
msg.sender,
request.investmentAsset,
request.investmentAmount,
request.requestedShares
);
delete requests[requestOwner];
}
function getOwedPerformanceFees(uint shareQuantity)
public
returns (uint remainingShareQuantity)
{
Shares shares = Shares(routes.shares);
uint totalPerformanceFee = FeeManager(routes.feeManager).performanceFeeAmount();
// The denominator is augmented because performanceFeeAmount() accounts for inflation
// Since shares are directly transferred, we don't need to account for inflation in this case
uint performanceFeePortion = mul(
totalPerformanceFee,
shareQuantity
) / add(shares.totalSupply(), totalPerformanceFee);
return performanceFeePortion;
}
/// @dev "Happy path" (no asset throws & quantity available)
/// @notice Redeem all shares and across all assets
function redeem() external {
uint ownedShares = Shares(routes.shares).balanceOf(msg.sender);
redeemQuantity(ownedShares);
}
/// @notice Redeem shareQuantity across all assets
function redeemQuantity(uint shareQuantity) public {
address[] memory assetList;
assetList = Accounting(routes.accounting).getOwnedAssets();
redeemWithConstraints(shareQuantity, assetList);
}
// TODO: reconsider the scenario where the user has enough funds to force shutdown on a large trade (any way around this?)
/// @dev Redeem only selected assets (used only when an asset throws)
function redeemWithConstraints(uint shareQuantity, address[] memory requestedAssets) public {
Shares shares = Shares(routes.shares);
require(
shares.balanceOf(msg.sender) >= shareQuantity &&
shares.balanceOf(msg.sender) > 0,
"Sender does not have enough shares to fulfill request"
);
uint owedPerformanceFees = 0;
if (
IPriceSource(priceSource()).hasValidPrices(requestedAssets) &&
msg.sender != hub.manager()
) {
FeeManager(routes.feeManager).rewardManagementFee();
owedPerformanceFees = getOwedPerformanceFees(shareQuantity);
shares.destroyFor(msg.sender, owedPerformanceFees);
shares.createFor(hub.manager(), owedPerformanceFees);
}
uint remainingShareQuantity = sub(shareQuantity, owedPerformanceFees);
address ofAsset;
uint[] memory ownershipQuantities = new uint[](requestedAssets.length);
address[] memory redeemedAssets = new address[](requestedAssets.length);
// Check whether enough assets held by fund
Accounting accounting = Accounting(routes.accounting);
for (uint i = 0; i < requestedAssets.length; ++i) {
ofAsset = requestedAssets[i];
require(
accounting.isInAssetList(ofAsset),
"Requested asset not in asset list"
);
for (uint j = 0; j < redeemedAssets.length; j++) {
require(
ofAsset != redeemedAssets[j],
"Asset can only be redeemed once"
);
}
redeemedAssets[i] = ofAsset;
uint quantityHeld = accounting.assetHoldings(ofAsset);
if (quantityHeld == 0) continue;
// participant's ownership percentage of asset holdings
ownershipQuantities[i] = mul(quantityHeld, remainingShareQuantity) / shares.totalSupply();
}
shares.destroyFor(msg.sender, remainingShareQuantity);
// Transfer owned assets
for (uint k = 0; k < requestedAssets.length; ++k) {
ofAsset = requestedAssets[k];
if (ownershipQuantities[k] == 0) {
continue;
} else {
Vault(routes.vault).withdraw(ofAsset, ownershipQuantities[k]);
safeTransfer(ofAsset, msg.sender, ownershipQuantities[k]);
}
}
emit Redemption(
msg.sender,
requestedAssets,
ownershipQuantities,
remainingShareQuantity
);
}
function getHistoricalInvestors() external view returns (address[] memory) {
return historicalInvestors;
}
function engine() public view override(AmguConsumer, Spoke) returns (address) { return Spoke.engine(); }
function mlnToken() public view override(AmguConsumer, Spoke) returns (address) { return Spoke.mlnToken(); }
function priceSource() public view override(AmguConsumer, Spoke) returns (address) { return Spoke.priceSource(); }
function registry() public view override(AmguConsumer, Spoke) returns (address) { return Spoke.registry(); }
}
contract ParticipationFactory is Factory {
event NewInstance(
address indexed hub,
address indexed instance,
address[] defaultAssets,
address registry
);
function createInstance(address _hub, address[] calldata _defaultAssets, address _registry)
external
returns (address)
{
address participation = address(
new Participation(_hub, _defaultAssets, _registry)
);
childExists[participation] = true;
emit NewInstance(_hub, participation, _defaultAssets, _registry);
return participation;
}
}
pragma solidity 0.6.1;
import "../hub/Spoke.sol";
import "../../factory/Factory.sol";
import "../../dependencies/TokenUser.sol";
/// @notice Dumb custody component
contract Vault is TokenUser, Spoke {
constructor(address _hub) public Spoke(_hub) {}
function withdraw(address token, uint amount) external auth {
safeTransfer(token, msg.sender, amount);
}
}
contract VaultFactory is Factory {
function createInstance(address _hub) external returns (address) {
address vault = address(new Vault(_hub));
childExists[vault] = true;
emit NewInstance(_hub, vault);
return vault;
}
}
pragma solidity 0.6.1;
import "../hub/Spoke.sol";
import "../../dependencies/token/StandardToken.sol";
import "../../factory/Factory.sol";
contract Shares is Spoke, StandardToken {
string public symbol;
string public name;
uint8 public decimals;
constructor(address _hub) public Spoke(_hub) {
name = hub.name();
symbol = "MLNF";
decimals = 18;
}
function createFor(address who, uint amount) public auth {
_mint(who, amount);
}
function destroyFor(address who, uint amount) public auth {
_burn(who, amount);
}
function transfer(address to, uint amount) public override returns (bool) {
revert("Unimplemented");
}
function transferFrom(
address from,
address to,
uint amount
)
public
override
returns (bool)
{
revert("Unimplemented");
}
function approve(address spender, uint amount) public override returns (bool) {
revert("Unimplemented");
}
function increaseApproval(
address spender,
uint amount
)
public
override
returns (bool)
{
revert("Unimplemented");
}
function decreaseApproval(
address spender,
uint amount
)
public
override
returns (bool)
{
revert("Unimplemented");
}
}
contract SharesFactory is Factory {
function createInstance(address _hub) external returns (address) {
address shares = address(new Shares(_hub));
childExists[shares] = true;
emit NewInstance(_hub, shares);
return shares;
}
}
pragma solidity 0.6.1;
import "../../factory/Factory.sol";
import "../hub/Spoke.sol";
import "./IPolicy.sol";
contract PolicyManager is Spoke {
event Registration(
bytes4 indexed sig,
IPolicy.Applied position,
address indexed policy
);
struct Entry {
IPolicy[] pre;
IPolicy[] post;
}
mapping(bytes4 => Entry) policies;
constructor (address _hub) public Spoke(_hub) {}
function register(bytes4 sig, address _policy) public auth {
IPolicy.Applied position = IPolicy(_policy).position();
if (position == IPolicy.Applied.pre) {
policies[sig].pre.push(IPolicy(_policy));
} else if (position == IPolicy.Applied.post) {
policies[sig].post.push(IPolicy(_policy));
} else {
revert("Only pre and post allowed");
}
emit Registration(sig, position, _policy);
}
function batchRegister(bytes4[] memory sig, address[] memory _policies) public auth {
require(sig.length == _policies.length, "Arrays lengths unequal");
for (uint i = 0; i < sig.length; i++) {
register(sig[i], _policies[i]);
}
}
function PoliciesToAddresses(IPolicy[] storage _policies) internal view returns (address[] memory) {
address[] memory res = new address[](_policies.length);
for(uint i = 0; i < _policies.length; i++) {
res[i] = address(_policies[i]);
}
return res;
}
function getPoliciesBySig(bytes4 sig) public view returns (address[] memory, address[] memory) {
return (PoliciesToAddresses(policies[sig].pre), PoliciesToAddresses(policies[sig].post));
}
modifier isValidPolicyBySig(bytes4 sig, address[5] memory addresses, uint[3] memory values, bytes32 identifier) {
preValidate(sig, addresses, values, identifier);
_;
postValidate(sig, addresses, values, identifier);
}
modifier isValidPolicy(address[5] memory addresses, uint[3] memory values, bytes32 identifier) {
preValidate(msg.sig, addresses, values, identifier);
_;
postValidate(msg.sig, addresses, values, identifier);
}
function preValidate(bytes4 sig, address[5] memory addresses, uint[3] memory values, bytes32 identifier) public {
validate(policies[sig].pre, sig, addresses, values, identifier);
}
function postValidate(bytes4 sig, address[5] memory addresses, uint[3] memory values, bytes32 identifier) public {
validate(policies[sig].post, sig, addresses, values, identifier);
}
function validate(IPolicy[] storage aux, bytes4 sig, address[5] memory addresses, uint[3] memory values, bytes32 identifier) internal {
for(uint i = 0; i < aux.length; i++) {
require(
aux[i].rule(sig, addresses, values, identifier),
string(abi.encodePacked("Rule evaluated to false: ", aux[i].identifier()))
);
}
}
}
contract PolicyManagerFactory is Factory {
function createInstance(address _hub) external returns (address) {
address policyManager = address(new PolicyManager(_hub));
childExists[policyManager] = true;
emit NewInstance(_hub, policyManager);
return policyManager;
}
}
pragma solidity 0.6.1;
import "./Hub.sol";
import "../../dependencies/DSAuth.sol";
/// @notice Has one Hub
contract Spoke is DSAuth {
Hub public hub;
Hub.Routes public routes;
bool public initialized;
modifier onlyInitialized() {
require(initialized, "Component not yet initialized");
_;
}
modifier notShutDown() {
require(!hub.isShutDown(), "Hub is shut down");
_;
}
constructor(address _hub) public {
hub = Hub(_hub);
setAuthority(hub);
setOwner(address(hub)); // temporary, to allow initialization
}
function initialize(address[11] calldata _spokes) external auth {
require(msg.sender == address(hub));
require(!initialized, "Already initialized");
routes = Hub.Routes(
_spokes[0],
_spokes[1],
_spokes[2],
_spokes[3],
_spokes[4],
_spokes[5],
_spokes[6],
_spokes[7],
_spokes[8],
_spokes[9],
_spokes[10]
);
initialized = true;
setOwner(address(0));
}
function engine() public view virtual returns (address) { return routes.engine; }
function mlnToken() public view virtual returns (address) { return routes.mlnToken; }
function priceSource() public view virtual returns (address) { return hub.priceSource(); }
function version() public view virtual returns (address) { return routes.version; }
function registry() public view virtual returns (address) { return routes.registry; }
}
pragma solidity 0.6.1;
import "../../dependencies/token/StandardToken.sol";
import "../../factory/Factory.sol";
import "../../prices/IPriceSource.sol";
import "../fees/FeeManager.sol";
import "../hub/Spoke.sol";
import "../shares/Shares.sol";
import "../trading/ITrading.sol";
import "../vault/Vault.sol";
import "../../engine/AmguConsumer.sol";
contract Accounting is AmguConsumer, Spoke {
event AssetAddition(address indexed asset);
event AssetRemoval(address indexed asset);
struct Calculations {
uint gav;
uint nav;
uint allocatedFees;
uint totalSupply;
uint timestamp;
}
uint constant public MAX_OWNED_ASSETS = 20;
address[] public ownedAssets;
mapping (address => bool) public isInAssetList;
uint public constant SHARES_DECIMALS = 18;
address public NATIVE_ASSET;
address public DENOMINATION_ASSET;
uint public DENOMINATION_ASSET_DECIMALS;
uint public DEFAULT_SHARE_PRICE;
Calculations public atLastAllocation;
constructor(address _hub, address _denominationAsset, address _nativeAsset)
public
Spoke(_hub)
{
DENOMINATION_ASSET = _denominationAsset;
NATIVE_ASSET = _nativeAsset;
DENOMINATION_ASSET_DECIMALS = ERC20WithFields(DENOMINATION_ASSET).decimals();
DEFAULT_SHARE_PRICE = 10 ** uint(DENOMINATION_ASSET_DECIMALS);
}
function getOwnedAssetsLength() external view returns (uint256) {
return ownedAssets.length;
}
function getOwnedAssets() external view returns (address[] memory) {
return ownedAssets;
}
function assetHoldings(address _asset) public returns (uint256) {
return add(
uint256(ERC20WithFields(_asset).balanceOf(routes.vault)),
ITrading(routes.trading).updateAndGetQuantityBeingTraded(_asset)
);
}
/// @dev Returns sparse array
function getFundHoldings() external returns (uint[] memory, address[] memory) {
uint[] memory _quantities = new uint[](ownedAssets.length);
address[] memory _assets = new address[](ownedAssets.length);
for (uint i = 0; i < ownedAssets.length; i++) {
address ofAsset = ownedAssets[i];
// assetHoldings formatting: mul(exchangeHoldings, 10 ** assetDecimal)
uint quantityHeld = assetHoldings(ofAsset);
_assets[i] = ofAsset;
_quantities[i] = quantityHeld;
}
return (_quantities, _assets);
}
function calcAssetGAV(address _queryAsset) external returns (uint) {
uint queryAssetQuantityHeld = assetHoldings(_queryAsset);
return IPriceSource(priceSource()).convertQuantity(
queryAssetQuantityHeld, _queryAsset, DENOMINATION_ASSET
);
}
// prices are quoted in DENOMINATION_ASSET so they use denominationDecimals
function calcGav() public returns (uint gav) {
for (uint i = 0; i < ownedAssets.length; ++i) {
address asset = ownedAssets[i];
// assetHoldings formatting: mul(exchangeHoldings, 10 ** assetDecimals)
uint quantityHeld = assetHoldings(asset);
// Dont bother with the calculations if the balance of the asset is 0
if (quantityHeld == 0) {
continue;
}
// gav as sum of mul(assetHoldings, assetPrice) with formatting: mul(mul(exchangeHoldings, exchangePrice), 10 ** shareDecimals)
gav = add(
gav,
IPriceSource(priceSource()).convertQuantity(
quantityHeld, asset, DENOMINATION_ASSET
)
);
}
return gav;
}
function calcNav(uint gav, uint unclaimedFeesInDenominationAsset) public pure returns (uint) {
return sub(gav, unclaimedFeesInDenominationAsset);
}
function valuePerShare(uint totalValue, uint numShares) public pure returns (uint) {
require(numShares > 0, "No shares to calculate value for");
return (totalValue * 10 ** uint(SHARES_DECIMALS)) / numShares;
}
function performCalculations()
public
returns (
uint gav,
uint feesInDenominationAsset, // unclaimed amount
uint feesInShares, // unclaimed amount
uint nav,
uint sharePrice,
uint gavPerShareNetManagementFee
)
{
gav = calcGav();
uint totalSupply = Shares(routes.shares).totalSupply();
feesInShares = FeeManager(routes.feeManager).totalFeeAmount();
feesInDenominationAsset = (totalSupply == 0) ?
0 :
mul(feesInShares, gav) / add(totalSupply, feesInShares);
nav = calcNav(gav, feesInDenominationAsset);
// The total share supply including the value of feesInDenominationAsset, measured in shares of this fund
uint totalSupplyAccountingForFees = add(totalSupply, feesInShares);
sharePrice = (totalSupply > 0) ?
valuePerShare(gav, totalSupplyAccountingForFees) :
DEFAULT_SHARE_PRICE;
gavPerShareNetManagementFee = (totalSupply > 0) ?
valuePerShare(gav, add(totalSupply, FeeManager(routes.feeManager).managementFeeAmount())) :
DEFAULT_SHARE_PRICE;
return (gav, feesInDenominationAsset, feesInShares, nav, sharePrice, gavPerShareNetManagementFee);
}
function calcGavPerShareNetManagementFee()
public
returns (uint gavPerShareNetManagementFee)
{
(,,,,,gavPerShareNetManagementFee) = performCalculations();
return gavPerShareNetManagementFee;
}
function getShareCostInAsset(uint _numShares, address _altAsset)
external
returns (uint)
{
uint denominationAssetQuantity = mul(
_numShares,
calcGavPerShareNetManagementFee()
) / 10 ** uint(SHARES_DECIMALS);
return IPriceSource(priceSource()).convertQuantity(
denominationAssetQuantity, DENOMINATION_ASSET, _altAsset
);
}
/// @notice Reward all fees and perform some updates
/// @dev Anyone can call this
function triggerRewardAllFees()
external
amguPayable(false)
payable
{
updateOwnedAssets();
uint256 gav;
uint256 feesInDenomination;
uint256 feesInShares;
uint256 nav;
(gav, feesInDenomination, feesInShares, nav,,) = performCalculations();
uint256 totalSupply = Shares(routes.shares).totalSupply();
FeeManager(routes.feeManager).rewardAllFees();
atLastAllocation = Calculations({
gav: gav,
nav: nav,
allocatedFees: feesInDenomination,
totalSupply: totalSupply,
timestamp: block.timestamp
});
}
/// @dev Check holdings for all assets, and adjust list
function updateOwnedAssets() public {
for (uint i = 0; i < ownedAssets.length; i++) {
address asset = ownedAssets[i];
if (
assetHoldings(asset) == 0 &&
!(asset == address(DENOMINATION_ASSET)) &&
ITrading(routes.trading).getOpenMakeOrdersAgainstAsset(asset) == 0
) {
_removeFromOwnedAssets(asset);
}
}
}
function addAssetToOwnedAssets(address _asset) external auth {
_addAssetToOwnedAssets(_asset);
}
function removeFromOwnedAssets(address _asset) external auth {
_removeFromOwnedAssets(_asset);
}
/// @dev Just pass if asset already in list
function _addAssetToOwnedAssets(address _asset) internal {
if (isInAssetList[_asset]) { return; }
require(
ownedAssets.length < MAX_OWNED_ASSETS,
"Max owned asset limit reached"
);
isInAssetList[_asset] = true;
ownedAssets.push(_asset);
emit AssetAddition(_asset);
}
/// @dev Just pass if asset not in list
function _removeFromOwnedAssets(address _asset) internal {
if (!isInAssetList[_asset]) { return; }
isInAssetList[_asset] = false;
for (uint i; i < ownedAssets.length; i++) {
if (ownedAssets[i] == _asset) {
ownedAssets[i] = ownedAssets[ownedAssets.length - 1];
ownedAssets.pop();
break;
}
}
emit AssetRemoval(_asset);
}
function engine() public view override(AmguConsumer, Spoke) returns (address) { return Spoke.engine(); }
function mlnToken() public view override(AmguConsumer, Spoke) returns (address) { return Spoke.mlnToken(); }
function priceSource() public view override(AmguConsumer, Spoke) returns (address) { return Spoke.priceSource(); }
function registry() public view override(AmguConsumer, Spoke) returns (address) { return Spoke.registry(); }
}
contract AccountingFactory is Factory {
event NewInstance(
address indexed hub,
address indexed instance,
address denominationAsset,
address nativeAsset
);
function createInstance(address _hub, address _denominationAsset, address _nativeAsset) external returns (address) {
address accounting = address(new Accounting(_hub, _denominationAsset, _nativeAsset));
childExists[accounting] = true;
emit NewInstance(_hub, accounting, _denominationAsset, _nativeAsset);
return accounting;
}
}
pragma solidity 0.6.1;
/// @notice Must return a value for an asset
interface IPriceSource {
function getQuoteAsset() external view returns (address);
function getLastUpdate() external view returns (uint);
/// @notice Returns false if asset not applicable, or price not recent
function hasValidPrice(address) external view returns (bool);
function hasValidPrices(address[] calldata) external view returns (bool);
/// @notice Return the last known price, and when it was issued
function getPrice(address _asset) external view returns (uint price, uint timestamp);
function getPrices(address[] calldata _assets) external view returns (uint[] memory prices, uint[] memory timestamps);
/// @notice Get price info, and revert if not valid
function getPriceInfo(address _asset) external view returns (uint price, uint decimals);
function getInvertedPriceInfo(address ofAsset) external view returns (uint price, uint decimals);
function getReferencePriceInfo(address _base, address _quote) external view returns (uint referencePrice, uint decimal);
function getOrderPriceInfo(address sellAsset, uint sellQuantity, uint buyQuantity) external view returns (uint orderPrice);
function existsPriceOnAssetPair(address sellAsset, address buyAsset) external view returns (bool isExistent);
function convertQuantity(
uint fromAssetQuantity,
address fromAsset,
address toAsset
) external view returns (uint);
}
pragma solidity 0.6.1;
contract Factory {
mapping (address => bool) public childExists;
event NewInstance(
address indexed hub,
address indexed instance
);
function isInstance(address _child) public view returns (bool) {
return childExists[_child];
}
}
pragma solidity 0.6.1;
import "../dependencies/DSMath.sol";
import "../dependencies/token/IERC20.sol";
import "../prices/IPriceSource.sol";
import "../version/IVersion.sol";
import "./IEngine.sol";
import "../version/Registry.sol";
/// @notice Abstract contracts
/// @notice inherit this to pay AMGU on a function call
abstract contract AmguConsumer is DSMath {
/// @dev each of these must be implemented by the inheriting contract
function engine() public view virtual returns (address);
function mlnToken() public view virtual returns (address);
function priceSource() public view virtual returns (address);
function registry() public view virtual returns (address);
event AmguPaid(address indexed payer, uint256 totalAmguPaidInEth, uint256 amguChargableGas, uint256 incentivePaid);
/// bool deductIncentive is used when sending extra eth beyond amgu
modifier amguPayable(bool deductIncentive) {
uint preGas = gasleft();
_;
uint postGas = gasleft();
uint mlnPerAmgu = IEngine(engine()).getAmguPrice();
uint mlnQuantity = mul(
mlnPerAmgu,
sub(preGas, postGas)
);
address nativeAsset = Registry(registry()).nativeAsset();
uint ethToPay = IPriceSource(priceSource()).convertQuantity(
mlnQuantity,
mlnToken(),
nativeAsset
);
uint incentiveAmount;
if (deductIncentive) {
incentiveAmount = Registry(registry()).incentive();
} else {
incentiveAmount = 0;
}
require(
msg.value >= add(ethToPay, incentiveAmount),
"Insufficent AMGU and/or incentive"
);
IEngine(engine()).payAmguInEther.value(ethToPay)();
require(
msg.sender.send(
sub(
sub(msg.value, ethToPay),
incentiveAmount
)
),
"Refund failed"
);
emit AmguPaid(msg.sender, ethToPay, sub(preGas, postGas), incentiveAmount);
}
}
pragma solidity 0.6.1;
/**
* @title ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
* Altered from https://github.com/OpenZeppelin/openzeppelin-solidity/blob/a466e76d26c394b1faa6e2797aefe34668566392/contracts/token/ERC20/ERC20.sol
*/
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address _who) external view returns (uint256);
function allowance(address _owner, address _spender)
external view returns (uint256);
function transfer(address _to, uint256 _value) external returns (bool);
function approve(address _spender, uint256 _value) external returns (bool);
function transferFrom(address _from, address _to, uint256 _value) external returns (bool);
event Transfer(
address indexed from,
address indexed to,
uint256 value
);
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
}
/// @dev Just adds extra functions that we use elsewhere
abstract contract ERC20WithFields is IERC20 {
string public symbol;
string public name;
uint8 public decimals;
}
/// DSMath.sol -- mixin for inline numerical wizardry
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
pragma solidity >0.4.13;
contract DSMath {
function add(uint x, uint y) internal pure returns (uint z) {
require((z = x + y) >= x, "ds-math-add-overflow");
}
function sub(uint x, uint y) internal pure returns (uint z) {
require((z = x - y) <= x, "ds-math-sub-underflow");
}
function mul(uint x, uint y) internal pure returns (uint z) {
require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow");
}
function min(uint x, uint y) internal pure returns (uint z) {
return x <= y ? x : y;
}
function max(uint x, uint y) internal pure returns (uint z) {
return x >= y ? x : y;
}
function imin(int x, int y) internal pure returns (int z) {
return x <= y ? x : y;
}
function imax(int x, int y) internal pure returns (int z) {
return x >= y ? x : y;
}
uint constant WAD = 10 ** 18;
uint constant RAY = 10 ** 27;
function wmul(uint x, uint y) internal pure returns (uint z) {
z = add(mul(x, y), WAD / 2) / WAD;
}
function rmul(uint x, uint y) internal pure returns (uint z) {
z = add(mul(x, y), RAY / 2) / RAY;
}
function wdiv(uint x, uint y) internal pure returns (uint z) {
z = add(mul(x, WAD), y / 2) / y;
}
function rdiv(uint x, uint y) internal pure returns (uint z) {
z = add(mul(x, RAY), y / 2) / y;
}
// This famous algorithm is called "exponentiation by squaring"
// and calculates x^n with x as fixed-point and n as regular unsigned.
//
// It's O(log n), instead of O(n) for naive repeated multiplication.
//
// These facts are why it works:
//
// If n is even, then x^n = (x^2)^(n/2).
// If n is odd, then x^n = x * x^(n-1),
// and applying the equation for even x gives
// x^n = x * (x^2)^((n-1) / 2).
//
// Also, EVM division is flooring and
// floor[(n-1) / 2] = floor[n / 2].
//
function rpow(uint x, uint n) internal pure returns (uint z) {
z = n % 2 != 0 ? x : RAY;
for (n /= 2; n != 0; n /= 2) {
x = rmul(x, x);
if (n % 2 != 0) {
z = rmul(z, x);
}
}
}
}
pragma solidity 0.6.1;
import "./token/IERC20.sol";
import "./DSMath.sol";
/// @notice Wrapper to ensure tokens are received
contract TokenUser is DSMath {
function safeTransfer(
address _token,
address _to,
uint _value
) internal {
uint receiverPreBalance = IERC20(_token).balanceOf(_to);
IERC20(_token).transfer(_to, _value);
uint receiverPostBalance = IERC20(_token).balanceOf(_to);
require(
add(receiverPreBalance, _value) == receiverPostBalance,
"Receiver did not receive tokens in transfer"
);
}
function safeTransferFrom(
address _token,
address _from,
address _to,
uint _value
) internal {
uint receiverPreBalance = IERC20(_token).balanceOf(_to);
IERC20(_token).transferFrom(_from, _to, _value);
uint receiverPostBalance = IERC20(_token).balanceOf(_to);
require(
add(receiverPreBalance, _value) == receiverPostBalance,
"Receiver did not receive tokens in transferFrom"
);
}
}
pragma solidity 0.6.1;
import "./IERC20.sol";
import "../SafeMath.sol";
/**
* @title Standard ERC20 token
*
* @dev Implementation of the basic standard token.
* https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md
* Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
* Modified from https://github.com/OpenZeppelin/openzeppelin-solidity/blob/a466e76d26c394b1faa6e2797aefe34668566392/contracts/token/ERC20/StandardToken.sol
*/
contract StandardToken is IERC20 {
using SafeMath for uint256;
mapping (address => uint256) balances;
mapping (address => mapping (address => uint256)) allowed;
uint256 totalSupply_;
/**
* @dev Total number of tokens in existence
*/
function totalSupply() public view override returns (uint256) {
return totalSupply_;
}
/**
* @dev Gets the balance of the specified address.
* @param _owner The address to query the the balance of.
* @return An uint256 representing the amount owned by the passed address.
*/
function balanceOf(address _owner) public view override returns (uint256) {
return balances[_owner];
}
/**
* @dev Function to check the amount of tokens that an owner allowed to a spender.
* @param _owner address The address which owns the funds.
* @param _spender address The address which will spend the funds.
* @return A uint256 specifying the amount of tokens still available for the spender.
*/
function allowance(
address _owner,
address _spender
)
public
view
override
returns (uint256)
{
return allowed[_owner][_spender];
}
/**
* @dev Transfer token for a specified address
* @param _to The address to transfer to.
* @param _value The amount to be transferred.
*/
function transfer(address _to, uint256 _value) public virtual override returns (bool) {
require(_value <= balances[msg.sender]);
require(_to != address(0));
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
emit Transfer(msg.sender, _to, _value);
return true;
}
/**
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
* Beware that changing an allowance with this method brings the risk that someone may use both the old
* and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
* race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
* @param _spender The address which will spend the funds.
* @param _value The amount of tokens to be spent.
*/
function approve(address _spender, uint256 _value) public virtual override returns (bool) {
allowed[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
/**
* @dev Transfer tokens from one address to another
* @param _from address The address which you want to send tokens from
* @param _to address The address which you want to transfer to
* @param _value uint256 the amount of tokens to be transferred
*/
function transferFrom(
address _from,
address _to,
uint256 _value
)
public
virtual
override
returns (bool)
{
require(_value <= balances[_from]);
require(_value <= allowed[_from][msg.sender]);
require(_to != address(0));
balances[_from] = balances[_from].sub(_value);
balances[_to] = balances[_to].add(_value);
allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
emit Approval(_from, msg.sender, allowed[_from][msg.sender]);
emit Transfer(_from, _to, _value);
return true;
}
/**
* @dev Increase the amount of tokens that an owner allowed to a spender.
* approve should be called when allowed[_spender] == 0. To increment
* allowed value is better to use this function to avoid 2 calls (and wait until
* the first transaction is mined)
* From MonolithDAO Token.sol
* @param _spender The address which will spend the funds.
* @param _addedValue The amount of tokens to increase the allowance by.
*/
function increaseApproval(
address _spender,
uint256 _addedValue
)
public
virtual
returns (bool)
{
allowed[msg.sender][_spender] = (allowed[msg.sender][_spender].add(_addedValue));
emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
return true;
}
/**
* @dev Decrease the amount of tokens that an owner allowed to a spender.
* approve should be called when allowed[_spender] == 0. To decrement
* allowed value is better to use this function to avoid 2 calls (and wait until
* the first transaction is mined)
* From MonolithDAO Token.sol
* @param _spender The address which will spend the funds.
* @param _subtractedValue The amount of tokens to decrease the allowance by.
*/
function decreaseApproval(
address _spender,
uint256 _subtractedValue
)
public
virtual
returns (bool)
{
uint256 oldValue = allowed[msg.sender][_spender];
if (_subtractedValue >= oldValue) {
allowed[msg.sender][_spender] = 0;
} else {
allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
}
emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
return true;
}
/**
* @dev Internal function that mints an amount of the token and assigns it to
* an account. This encapsulates the modification of balances such that the
* proper events are emitted.
* @param _account The account that will receive the created tokens.
* @param _amount The amount that will be created.
*/
function _mint(address _account, uint256 _amount) internal {
require(_account != address(0));
totalSupply_ = totalSupply_.add(_amount);
balances[_account] = balances[_account].add(_amount);
emit Transfer(address(0), _account, _amount);
}
/**
* @dev Internal function that burns an amount of the token of a given
* account.
* @param _account The account whose tokens will be burnt.
* @param _amount The amount that will be burnt.
*/
function _burn(address _account, uint256 _amount) internal {
require(_account != address(0));
require(_amount <= balances[_account]);
totalSupply_ = totalSupply_.sub(_amount);
balances[_account] = balances[_account].sub(_amount);
emit Transfer(_account, address(0), _amount);
}
/**
* @dev Internal function that burns an amount of the token of a given
* account, deducting from the sender's allowance for said account. Uses the
* internal _burn function.
* @param _account The account whose tokens will be burnt.
* @param _amount The amount that will be burnt.
*/
function _burnFrom(address _account, uint256 _amount) internal {
require(_amount <= allowed[_account][msg.sender]);
allowed[_account][msg.sender] = allowed[_account][msg.sender].sub(_amount);
emit Approval(_account, msg.sender, allowed[_account][msg.sender]);
_burn(_account, _amount);
}
}
pragma solidity 0.6.1;
interface IPolicy {
enum Applied { pre, post }
// In Trading context:
// addresses: Order maker, Order taker, Order maker asset, Order taker asset, Exchange address
// values: Maker token quantity, Taker token quantity, Fill Taker Quantity
// In Participation context:
// address[0]: Investor address, address[3]: Investment asset
function rule(bytes4 sig, address[5] calldata addresses, uint[3] calldata values, bytes32 identifier) external returns (bool);
function position() external view returns (Applied);
function identifier() external view returns (string memory);
}
pragma solidity 0.6.1;
import "../../dependencies/DSGuard.sol";
import "./Spoke.sol";
import "../../version/Registry.sol";
/// @notice Router for communication between components
/// @notice Has one or more Spokes
contract Hub is DSGuard {
event FundShutDown();
struct Routes {
address accounting;
address feeManager;
address participation;
address policyManager;
address shares;
address trading;
address vault;
address registry;
address version;
address engine;
address mlnToken;
}
Routes public routes;
address public manager;
address public creator;
string public name;
bool public isShutDown;
bool public fundInitialized;
uint public creationTime;
mapping (address => bool) public isSpoke;
constructor(address _manager, string memory _name) public {
creator = msg.sender;
manager = _manager;
name = _name;
creationTime = block.timestamp;
}
modifier onlyCreator() {
require(msg.sender == creator, "Only creator can do this");
_;
}
function shutDownFund() external {
require(msg.sender == routes.version);
isShutDown = true;
emit FundShutDown();
}
function initializeAndSetPermissions(address[11] calldata _spokes) external onlyCreator {
require(!fundInitialized, "Fund is already initialized");
for (uint i = 0; i < _spokes.length; i++) {
isSpoke[_spokes[i]] = true;
}
routes.accounting = _spokes[0];
routes.feeManager = _spokes[1];
routes.participation = _spokes[2];
routes.policyManager = _spokes[3];
routes.shares = _spokes[4];
routes.trading = _spokes[5];
routes.vault = _spokes[6];
routes.registry = _spokes[7];
routes.version = _spokes[8];
routes.engine = _spokes[9];
routes.mlnToken = _spokes[10];
Spoke(routes.accounting).initialize(_spokes);
Spoke(routes.feeManager).initialize(_spokes);
Spoke(routes.participation).initialize(_spokes);
Spoke(routes.policyManager).initialize(_spokes);
Spoke(routes.shares).initialize(_spokes);
Spoke(routes.trading).initialize(_spokes);
Spoke(routes.vault).initialize(_spokes);
permit(routes.participation, routes.vault, bytes4(keccak256('withdraw(address,uint256)')));
permit(routes.trading, routes.vault, bytes4(keccak256('withdraw(address,uint256)')));
permit(routes.participation, routes.shares, bytes4(keccak256('createFor(address,uint256)')));
permit(routes.participation, routes.shares, bytes4(keccak256('destroyFor(address,uint256)')));
permit(routes.feeManager, routes.shares, bytes4(keccak256('createFor(address,uint256)')));
permit(routes.participation, routes.accounting, bytes4(keccak256('addAssetToOwnedAssets(address)')));
permit(routes.trading, routes.accounting, bytes4(keccak256('addAssetToOwnedAssets(address)')));
permit(routes.trading, routes.accounting, bytes4(keccak256('removeFromOwnedAssets(address)')));
permit(routes.accounting, routes.feeManager, bytes4(keccak256('rewardAllFees()')));
permit(manager, routes.policyManager, bytes4(keccak256('register(bytes4,address)')));
permit(manager, routes.policyManager, bytes4(keccak256('batchRegister(bytes4[],address[])')));
permit(manager, routes.participation, bytes4(keccak256('enableInvestment(address[])')));
permit(manager, routes.participation, bytes4(keccak256('disableInvestment(address[])')));
permit(manager, routes.trading, bytes4(keccak256('addExchange(address,address)')));
fundInitialized = true;
}
function vault() external view returns (address) { return routes.vault; }
function accounting() external view returns (address) { return routes.accounting; }
function priceSource() external view returns (address) { return Registry(routes.registry).priceSource(); }
function participation() external view returns (address) { return routes.participation; }
function trading() external view returns (address) { return routes.trading; }
function shares() external view returns (address) { return routes.shares; }
function registry() external view returns (address) { return routes.registry; }
function version() external view returns (address) { return routes.version; }
function policyManager() external view returns (address) { return routes.policyManager; }
}
/// @notice Modified from DappHub (https://git.io/fpwrq)
pragma solidity 0.6.1;
abstract contract DSAuthority {
function canCall(
address src, address dst, bytes4 sig
) public view virtual returns (bool);
}
contract DSAuthEvents {
event LogSetAuthority (address indexed authority);
event LogSetOwner (address indexed owner);
}
contract DSAuth is DSAuthEvents {
DSAuthority public authority;
address public owner;
constructor() public {
owner = msg.sender;
emit LogSetOwner(msg.sender);
}
function setOwner(address owner_)
public
auth
{
owner = owner_;
emit LogSetOwner(owner);
}
function setAuthority(DSAuthority authority_)
public
auth
{
authority = authority_;
emit LogSetAuthority(address(authority));
}
modifier auth {
require(isAuthorized(msg.sender, msg.sig), "ds-auth-unauthorized");
_;
}
function isAuthorized(address src, bytes4 sig) internal view returns (bool) {
if (src == address(this)) {
return true;
} else if (src == owner) {
return true;
} else if (authority == DSAuthority(0)) {
return false;
} else {
return authority.canCall(src, address(this), sig);
}
}
}
pragma solidity 0.6.1;
pragma experimental ABIEncoderV2;
import "./IFee.sol";
import "../hub/Spoke.sol";
import "../shares/Shares.sol";
import "../../factory/Factory.sol";
import "../../version/Registry.sol";
import "../../dependencies/DSMath.sol";
import "./IFeeManager.sol";
/// @notice Manages and allocates fees for a particular fund
contract FeeManager is DSMath, Spoke {
event FeeReward(uint shareQuantity);
event FeeRegistration(address fee);
struct FeeInfo {
address feeAddress;
uint feeRate;
uint feePeriod;
}
IFee[] public fees;
mapping (address => bool) public feeIsRegistered;
constructor(address _hub, address _denominationAsset, address[] memory _fees, uint[] memory _rates, uint[] memory _periods, address _registry) Spoke(_hub) public {
for (uint i = 0; i < _fees.length; i++) {
require(
Registry(_registry).isFeeRegistered(_fees[i]),
"Fee must be known to Registry"
);
register(_fees[i], _rates[i], _periods[i], _denominationAsset);
}
if (fees.length > 0) {
require(
fees[0].identifier() == 0,
"Management fee must be at 0 index"
);
}
if (fees.length > 1) {
require(
fees[1].identifier() == 1,
"Performance fee must be at 1 index"
);
}
}
function register(address feeAddress, uint feeRate, uint feePeriod, address denominationAsset) internal {
require(!feeIsRegistered[feeAddress], "Fee already registered");
feeIsRegistered[feeAddress] = true;
fees.push(IFee(feeAddress));
IFee(feeAddress).initializeForUser(feeRate, feePeriod, denominationAsset); // initialize state
emit FeeRegistration(feeAddress);
}
function totalFeeAmount() external returns (uint total) {
for (uint i = 0; i < fees.length; i++) {
total = add(total, fees[i].feeAmount());
}
return total;
}
/// @dev Shares to be inflated after update state
function _rewardFee(IFee fee) internal {
require(feeIsRegistered[address(fee)], "Fee is not registered");
uint rewardShares = fee.feeAmount();
fee.updateState();
Shares(routes.shares).createFor(hub.manager(), rewardShares);
emit FeeReward(rewardShares);
}
function _rewardAllFees() internal {
for (uint i = 0; i < fees.length; i++) {
_rewardFee(fees[i]);
}
}
/// @dev Used when calling from other components
function rewardAllFees() public auth { _rewardAllFees(); }
/// @dev Convenience function; anyone can reward management fee any time
/// @dev Convention that management fee is 0
function rewardManagementFee() public {
if (fees.length >= 1) _rewardFee(fees[0]);
}
/// @dev Convenience function
/// @dev Convention that management fee is 0
function managementFeeAmount() external returns (uint) {
if (fees.length < 1) return 0;
return fees[0].feeAmount();
}
/// @dev Convenience function
/// @dev Convention that performace fee is 1
function performanceFeeAmount() external returns (uint) {
if (fees.length < 2) return 0;
return fees[1].feeAmount();
}
}
contract FeeManagerFactory is Factory {
function createInstance(
address _hub,
address _denominationAsset,
address[] memory _fees,
uint[] memory _feeRates,
uint[] memory _feePeriods,
address _registry
) public returns (address) {
address feeManager = address(
new FeeManager(_hub, _denominationAsset, _fees, _feeRates, _feePeriods, _registry)
);
childExists[feeManager] = true;
emit NewInstance(_hub, feeManager);
return feeManager;
}
}
pragma solidity 0.6.1;
pragma experimental ABIEncoderV2;
// TODO: Restore indexed params
/// @notice Mediation between a Fund and exchanges
interface ITrading {
function callOnExchange(
uint exchangeIndex,
string calldata methodSignature,
address[8] calldata orderAddresses,
uint[8] calldata orderValues,
bytes[4] calldata orderData,
bytes32 identifier,
bytes calldata signature
) external;
function addOpenMakeOrder(
address ofExchange,
address ofSellAsset,
address ofBuyAsset,
address ofFeeAsset,
uint orderId,
uint expiryTime
) external;
function removeOpenMakeOrder(
address ofExchange,
address ofSellAsset
) external;
function updateAndGetQuantityBeingTraded(address _asset) external returns (uint256);
function getOpenMakeOrdersAgainstAsset(address _asset) external view returns (uint256);
}
interface ITradingFactory {
function createInstance(
address _hub,
address[] calldata _exchanges,
address[] calldata _adapters,
address _registry
) external returns (address);
}
pragma solidity 0.6.1;
interface IVersion {
function shutDownFund(address) external;
}
pragma solidity 0.6.1;
interface IEngine {
function payAmguInEther() external payable;
function getAmguPrice() external view returns (uint256);
}
pragma solidity 0.6.1;
import "../dependencies/DSAuth.sol";
import "../fund/hub/Hub.sol";
import "../dependencies/token/IERC20.sol";
contract Registry is DSAuth {
// EVENTS
event AssetUpsert (
address indexed asset,
string name,
string symbol,
uint decimals,
string url,
uint reserveMin,
uint[] standards,
bytes4[] sigs
);
event ExchangeAdapterUpsert (
address indexed exchange,
address indexed adapter,
bool takesCustody,
bytes4[] sigs
);
event AssetRemoval (address indexed asset);
event EfxWrapperRegistryChange(address indexed registry);
event EngineChange(address indexed engine);
event ExchangeAdapterRemoval (address indexed exchange);
event IncentiveChange(uint incentiveAmount);
event MGMChange(address indexed MGM);
event MlnTokenChange(address indexed mlnToken);
event NativeAssetChange(address indexed nativeAsset);
event PriceSourceChange(address indexed priceSource);
event VersionRegistration(address indexed version);
// TYPES
struct Asset {
bool exists;
string name;
string symbol;
uint decimals;
string url;
uint reserveMin;
uint[] standards;
bytes4[] sigs;
}
struct Exchange {
bool exists;
address exchangeAddress;
bool takesCustody;
bytes4[] sigs;
}
struct Version {
bool exists;
bytes32 name;
}
// CONSTANTS
uint public constant MAX_REGISTERED_ENTITIES = 20;
uint public constant MAX_FUND_NAME_BYTES = 66;
// FIELDS
mapping (address => Asset) public assetInformation;
address[] public registeredAssets;
// Mapping from adapter address to exchange Information (Adapters are unique)
mapping (address => Exchange) public exchangeInformation;
address[] public registeredExchangeAdapters;
mapping (address => Version) public versionInformation;
address[] public registeredVersions;
mapping (address => bool) public isFeeRegistered;
mapping (address => address) public fundsToVersions;
mapping (bytes32 => bool) public versionNameExists;
mapping (bytes32 => address) public fundNameHashToOwner;
uint public incentive = 10 finney;
address public priceSource;
address public mlnToken;
address public nativeAsset;
address public engine;
address public ethfinexWrapperRegistry;
address public MGM;
modifier onlyVersion() {
require(
versionInformation[msg.sender].exists,
"Only a Version can do this"
);
_;
}
// METHODS
constructor(address _postDeployOwner) public {
setOwner(_postDeployOwner);
}
// PUBLIC METHODS
/// @notice Whether _name has only valid characters
function isValidFundName(string memory _name) public pure returns (bool) {
bytes memory b = bytes(_name);
if (b.length > MAX_FUND_NAME_BYTES) return false;
for (uint i; i < b.length; i++){
bytes1 char = b[i];
if(
!(char >= 0x30 && char <= 0x39) && // 9-0
!(char >= 0x41 && char <= 0x5A) && // A-Z
!(char >= 0x61 && char <= 0x7A) && // a-z
!(char == 0x20 || char == 0x2D) && // space, dash
!(char == 0x2E || char == 0x5F) && // period, underscore
!(char == 0x2A) // *
) {
return false;
}
}
return true;
}
/// @notice Whether _user can use _name for their fund
function canUseFundName(address _user, string memory _name) public view returns (bool) {
bytes32 nameHash = keccak256(bytes(_name));
return (
isValidFundName(_name) &&
(
fundNameHashToOwner[nameHash] == address(0) ||
fundNameHashToOwner[nameHash] == _user
)
);
}
function reserveFundName(address _owner, string calldata _name)
external
onlyVersion
{
require(canUseFundName(_owner, _name), "Fund name cannot be used");
fundNameHashToOwner[keccak256(bytes(_name))] = _owner;
}
function registerFund(address _fund, address _owner, string calldata _name)
external
onlyVersion
{
require(canUseFundName(_owner, _name), "Fund name cannot be used");
fundsToVersions[_fund] = msg.sender;
}
/// @notice Registers an Asset information entry
/// @dev Pre: Only registrar owner should be able to register
/// @dev Post: Address _asset is registered
/// @param _asset Address of asset to be registered
/// @param _name Human-readable name of the Asset
/// @param _symbol Human-readable symbol of the Asset
/// @param _url Url for extended information of the asset
/// @param _standards Integers of EIP standards this asset adheres to
/// @param _sigs Function signatures for whitelisted asset functions
function registerAsset(
address _asset,
string calldata _name,
string calldata _symbol,
string calldata _url,
uint _reserveMin,
uint[] calldata _standards,
bytes4[] calldata _sigs
) external auth {
require(registeredAssets.length < MAX_REGISTERED_ENTITIES);
require(!assetInformation[_asset].exists);
assetInformation[_asset].exists = true;
registeredAssets.push(_asset);
updateAsset(
_asset,
_name,
_symbol,
_url,
_reserveMin,
_standards,
_sigs
);
}
/// @notice Register an exchange information entry (A mapping from exchange adapter -> Exchange information)
/// @dev Adapters are unique so are used as the mapping key. There may be different adapters for same exchange (0x / Ethfinex)
/// @dev Pre: Only registrar owner should be able to register
/// @dev Post: Address _exchange is registered
/// @param _exchange Address of the exchange for the adapter
/// @param _adapter Address of exchange adapter
/// @param _takesCustody Whether this exchange takes custody of tokens before trading
/// @param _sigs Function signatures for whitelisted exchange functions
function registerExchangeAdapter(
address _exchange,
address _adapter,
bool _takesCustody,
bytes4[] calldata _sigs
) external auth {
require(!exchangeInformation[_adapter].exists, "Adapter already exists");
exchangeInformation[_adapter].exists = true;
require(registeredExchangeAdapters.length < MAX_REGISTERED_ENTITIES, "Exchange limit reached");
registeredExchangeAdapters.push(_adapter);
updateExchangeAdapter(
_exchange,
_adapter,
_takesCustody,
_sigs
);
}
/// @notice Versions cannot be removed from registry
/// @param _version Address of the version contract
/// @param _name Name of the version
function registerVersion(
address _version,
bytes32 _name
) external auth {
require(!versionInformation[_version].exists, "Version already exists");
require(!versionNameExists[_name], "Version name already exists");
versionInformation[_version].exists = true;
versionNameExists[_name] = true;
versionInformation[_version].name = _name;
registeredVersions.push(_version);
emit VersionRegistration(_version);
}
function setIncentive(uint _weiAmount) external auth {
incentive = _weiAmount;
emit IncentiveChange(_weiAmount);
}
function setPriceSource(address _priceSource) external auth {
priceSource = _priceSource;
emit PriceSourceChange(_priceSource);
}
function setMlnToken(address _mlnToken) external auth {
mlnToken = _mlnToken;
emit MlnTokenChange(_mlnToken);
}
function setNativeAsset(address _nativeAsset) external auth {
nativeAsset = _nativeAsset;
emit NativeAssetChange(_nativeAsset);
}
function setEngine(address _engine) external auth {
engine = _engine;
emit EngineChange(_engine);
}
function setMGM(address _MGM) external auth {
MGM = _MGM;
emit MGMChange(_MGM);
}
function setEthfinexWrapperRegistry(address _registry) external auth {
ethfinexWrapperRegistry = _registry;
emit EfxWrapperRegistryChange(_registry);
}
/// @notice Updates description information of a registered Asset
/// @dev Pre: Owner can change an existing entry
/// @dev Post: Changed Name, Symbol, URL and/or IPFSHash
/// @param _asset Address of the asset to be updated
/// @param _name Human-readable name of the Asset
/// @param _symbol Human-readable symbol of the Asset
/// @param _url Url for extended information of the asset
function updateAsset(
address _asset,
string memory _name,
string memory _symbol,
string memory _url,
uint _reserveMin,
uint[] memory _standards,
bytes4[] memory _sigs
) public auth {
require(assetInformation[_asset].exists);
Asset storage asset = assetInformation[_asset];
asset.name = _name;
asset.symbol = _symbol;
asset.decimals = ERC20WithFields(_asset).decimals();
asset.url = _url;
asset.reserveMin = _reserveMin;
asset.standards = _standards;
asset.sigs = _sigs;
emit AssetUpsert(
_asset,
_name,
_symbol,
asset.decimals,
_url,
_reserveMin,
_standards,
_sigs
);
}
function updateExchangeAdapter(
address _exchange,
address _adapter,
bool _takesCustody,
bytes4[] memory _sigs
) public auth {
require(exchangeInformation[_adapter].exists, "Exchange with adapter doesn't exist");
Exchange storage exchange = exchangeInformation[_adapter];
exchange.exchangeAddress = _exchange;
exchange.takesCustody = _takesCustody;
exchange.sigs = _sigs;
emit ExchangeAdapterUpsert(
_exchange,
_adapter,
_takesCustody,
_sigs
);
}
/// @notice Deletes an existing entry
/// @dev Owner can delete an existing entry
/// @param _asset address for which specific information is requested
function removeAsset(
address _asset,
uint _assetIndex
) external auth {
require(assetInformation[_asset].exists);
require(registeredAssets[_assetIndex] == _asset);
delete assetInformation[_asset];
delete registeredAssets[_assetIndex];
for (uint i = _assetIndex; i < registeredAssets.length-1; i++) {
registeredAssets[i] = registeredAssets[i+1];
}
registeredAssets.pop();
emit AssetRemoval(_asset);
}
/// @notice Deletes an existing entry
/// @dev Owner can delete an existing entry
/// @param _adapter address of the adapter of the exchange that is to be removed
/// @param _adapterIndex index of the exchange in array
function removeExchangeAdapter(
address _adapter,
uint _adapterIndex
) external auth {
require(exchangeInformation[_adapter].exists, "Exchange with adapter doesn't exist");
require(registeredExchangeAdapters[_adapterIndex] == _adapter, "Incorrect adapter index");
delete exchangeInformation[_adapter];
delete registeredExchangeAdapters[_adapterIndex];
for (uint i = _adapterIndex; i < registeredExchangeAdapters.length-1; i++) {
registeredExchangeAdapters[i] = registeredExchangeAdapters[i+1];
}
registeredExchangeAdapters.pop();
emit ExchangeAdapterRemoval(_adapter);
}
function registerFees(address[] calldata _fees) external auth {
for (uint i; i < _fees.length; i++) {
isFeeRegistered[_fees[i]] = true;
}
}
function deregisterFees(address[] calldata _fees) external auth {
for (uint i; i < _fees.length; i++) {
delete isFeeRegistered[_fees[i]];
}
}
// PUBLIC VIEW METHODS
// get asset specific information
function getName(address _asset) external view returns (string memory) {
return assetInformation[_asset].name;
}
function getSymbol(address _asset) external view returns (string memory) {
return assetInformation[_asset].symbol;
}
function getDecimals(address _asset) external view returns (uint) {
return assetInformation[_asset].decimals;
}
function getReserveMin(address _asset) external view returns (uint) {
return assetInformation[_asset].reserveMin;
}
function assetIsRegistered(address _asset) external view returns (bool) {
return assetInformation[_asset].exists;
}
function getRegisteredAssets() external view returns (address[] memory) {
return registeredAssets;
}
function assetMethodIsAllowed(address _asset, bytes4 _sig)
external
view
returns (bool)
{
bytes4[] memory signatures = assetInformation[_asset].sigs;
for (uint i = 0; i < signatures.length; i++) {
if (signatures[i] == _sig) {
return true;
}
}
return false;
}
// get exchange-specific information
function exchangeAdapterIsRegistered(address _adapter) external view returns (bool) {
return exchangeInformation[_adapter].exists;
}
function getRegisteredExchangeAdapters() external view returns (address[] memory) {
return registeredExchangeAdapters;
}
function getExchangeInformation(address _adapter)
public
view
returns (address, bool)
{
Exchange memory exchange = exchangeInformation[_adapter];
return (
exchange.exchangeAddress,
exchange.takesCustody
);
}
function exchangeForAdapter(address _adapter) external view returns (address) {
Exchange memory exchange = exchangeInformation[_adapter];
return exchange.exchangeAddress;
}
function getAdapterFunctionSignatures(address _adapter)
public
view
returns (bytes4[] memory)
{
return exchangeInformation[_adapter].sigs;
}
function adapterMethodIsAllowed(
address _adapter, bytes4 _sig
)
external
view
returns (bool)
{
bytes4[] memory signatures = exchangeInformation[_adapter].sigs;
for (uint i = 0; i < signatures.length; i++) {
if (signatures[i] == _sig) {
return true;
}
}
return false;
}
// get version and fund information
function getRegisteredVersions() external view returns (address[] memory) {
return registeredVersions;
}
function isFund(address _who) external view returns (bool) {
if (fundsToVersions[_who] != address(0)) {
return true; // directly from a hub
} else {
Hub hub = Hub(Spoke(_who).hub());
require(
hub.isSpoke(_who),
"Call from either a spoke or hub"
);
return fundsToVersions[address(hub)] != address(0);
}
}
function isFundFactory(address _who) external view returns (bool) {
return versionInformation[_who].exists;
}
}
pragma solidity 0.6.1;
/**
* @title SafeMath
* @dev Math operations with safety checks that revert on error
*/
library SafeMath {
/**
* @dev Multiplies two numbers, reverts on overflow.
*/
function mul(uint256 _a, uint256 _b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (_a == 0) {
return 0;
}
uint256 c = _a * _b;
require(c / _a == _b);
return c;
}
/**
* @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
*/
function div(uint256 _a, uint256 _b) internal pure returns (uint256) {
require(_b > 0); // Solidity only automatically asserts when dividing by 0
uint256 c = _a / _b;
// assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 _a, uint256 _b) internal pure returns (uint256) {
require(_b <= _a);
uint256 c = _a - _b;
return c;
}
/**
* @dev Adds two numbers, reverts on overflow.
*/
function add(uint256 _a, uint256 _b) internal pure returns (uint256) {
uint256 c = _a + _b;
require(c >= _a);
return c;
}
/**
* @dev Divides two numbers and returns the remainder (unsigned integer modulo),
* reverts when dividing by zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0);
return a % b;
}
}
/// @notice Retrieved from DappHub (https://git.io/fpwMi)
pragma solidity 0.6.1;
import "./DSAuth.sol";
contract DSGuardEvents {
event LogPermit(
bytes32 indexed src,
bytes32 indexed dst,
bytes32 indexed sig
);
event LogForbid(
bytes32 indexed src,
bytes32 indexed dst,
bytes32 indexed sig
);
}
contract DSGuard is DSAuth, DSAuthority, DSGuardEvents {
bytes32 constant public ANY = bytes32(uint(-1));
mapping (bytes32 => mapping (bytes32 => mapping (bytes32 => bool))) acl;
function canCall(
address src_, address dst_, bytes4 sig
) public view override returns (bool) {
bytes32 src = bytes32(bytes20(src_));
bytes32 dst = bytes32(bytes20(dst_));
return acl[src][dst][sig]
|| acl[src][dst][ANY]
|| acl[src][ANY][sig]
|| acl[src][ANY][ANY]
|| acl[ANY][dst][sig]
|| acl[ANY][dst][ANY]
|| acl[ANY][ANY][sig]
|| acl[ANY][ANY][ANY];
}
function permit(bytes32 src, bytes32 dst, bytes32 sig) public auth {
acl[src][dst][sig] = true;
emit LogPermit(src, dst, sig);
}
function forbid(bytes32 src, bytes32 dst, bytes32 sig) public auth {
acl[src][dst][sig] = false;
emit LogForbid(src, dst, sig);
}
function permit(address src, address dst, bytes32 sig) public {
permit(bytes32(bytes20(src)), bytes32(bytes20(dst)), sig);
}
function forbid(address src, address dst, bytes32 sig) public {
forbid(bytes32(bytes20(src)), bytes32(bytes20(dst)), sig);
}
}
contract DSGuardFactory {
mapping (address => bool) public isGuard;
function newGuard() public returns (DSGuard guard) {
guard = new DSGuard();
guard.setOwner(msg.sender);
isGuard[address(guard)] = true;
}
}
pragma solidity 0.6.1;
/// @dev Exposes "feeAmount", which maps fund state and fee state to uint
/// @dev Notice that "feeAmount" *may* change contract state
/// @dev Also exposes "updateState", which changes fee's internal state
interface IFee {
function initializeForUser(uint feeRate, uint feePeriod, address denominationAsset) external;
function feeAmount() external returns (uint);
function updateState() external;
/// @notice Used to enforce a convention
function identifier() external view returns (uint);
}
pragma solidity 0.6.1;
interface IFeeManagerFactory {
function createInstance(
address _hub,
address _denominationAsset,
address[] calldata _fees,
uint[] calldata _feeRates,
uint[] calldata _feePeriods,
address _registry
) external returns (address);
}