// Copyright (C) 2017 MixBytes, LLC // Licensed under the Apache License, Version 2.0 (the "License"). // You may not use this file except in compliance with the License. // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND (express or implied). pragma solidity ^0.4.24; import 'openzeppelin-solidity/contracts/math/SafeMath.sol'; /* * @title This is proxy for analytics. Target contract can be found at field m_analytics (see "read contract"). * @author Eenae * FIXME after fix of truffle issue #560: refactor to a separate contract file which uses InvestmentAnalytics interface */ contract AnalyticProxy { constructor() public { m_analytics = InvestmentAnalytics(msg.sender); } /// @notice forward payment to analytics-capable contract function() external payable { m_analytics.iaInvestedBy.value(msg.value)(msg.sender); } InvestmentAnalytics public m_analytics; } /* * @title Mixin contract which supports different payment channels and provides analytical per-channel data. * @author Eenae */ contract InvestmentAnalytics { using SafeMath for uint256; constructor() public { } /// @dev creates more payment channels, up to the limit but not exceeding gas stipend function createMorePaymentChannelsInternal(uint256 limit) internal returns (uint256) { uint256 paymentChannelsCreated; for (uint256 i = 0; i < limit; i++) { uint256 startingGas = gasleft(); /* * ~170k of gas per paymentChannel, * using gas price = 4Gwei 2k paymentChannels will cost ~1.4 ETH. */ address paymentChannel = new AnalyticProxy(); m_validPaymentChannels[paymentChannel] = true; m_paymentChannels.push(paymentChannel); paymentChannelsCreated++; // cost of creating one channel uint256 gasPerChannel = startingGas.sub(gasleft()); if (gasPerChannel.add(50000) > gasleft()) break; // enough proxies for this call } return paymentChannelsCreated; } /// @dev process payments - record analytics and pass control to iaOnInvested callback function iaInvestedBy(address investor) external payable { address paymentChannel = msg.sender; if (m_validPaymentChannels[paymentChannel]) { // payment received by one of our channels uint256 value = msg.value; m_investmentsByPaymentChannel[paymentChannel] = m_investmentsByPaymentChannel[paymentChannel].add(value); // We know for sure that investment came from specified investor (see AnalyticProxy). iaOnInvested(investor, value, true); } else { // Looks like some user has paid to this method, this payment is not included in the analytics, // but, of course, processed. iaOnInvested(msg.sender, msg.value, false); } } /// @dev callback which must be overridden function iaOnInvested(address /*investor*/, uint256 /*payment*/, bool /*usingPaymentChannel*/) internal { assert(false); // must be overridden m_paymentChannels[0] = address(0); // useles code to suppress solc warning } function paymentChannelsCount() external view returns (uint256) { return m_paymentChannels.length; } function readAnalyticsMap() external view returns (address[], uint256[]) { address[] memory keys = new address[](m_paymentChannels.length); uint256[] memory values = new uint256[](m_paymentChannels.length); for (uint256 i = 0; i < m_paymentChannels.length; i++) { address key = m_paymentChannels[i]; keys[i] = key; values[i] = m_investmentsByPaymentChannel[key]; } return (keys, values); } function readPaymentChannels() external view returns (address[]) { return m_paymentChannels; } mapping(address => uint256) public m_investmentsByPaymentChannel; mapping(address => bool) m_validPaymentChannels; address[] public m_paymentChannels; }