/** * Source Code first verified at https://etherscan.io on Monday, April 29, 2019 (UTC) */ pragma solidity ^0.5.7; contract ProxyStorage { address public powner; address public pimplementation; } interface IERC165 { /// @notice Query if a contract implements an interface /// @param interfaceID The interface identifier, as specified in ERC-165 /// @dev Interface identification is specified in ERC-165. This function /// uses less than 30,000 gas. /// @return `true` if the contract implements `interfaceID` and /// `interfaceID` is not 0xffffffff, `false` otherwise function supportsInterface(bytes4 interfaceID) external view returns (bool); } // File: contracts/core/diaspore/interfaces/Model.sol pragma solidity ^0.5.7; /** The abstract contract Model defines the whole lifecycle of a debt on the DebtEngine. Models can be used without previous approbation, this is meant to avoid centralization on the development of RCN; this implies that not all models are secure. Models can have back-doors, bugs and they have not guarantee of being autonomous. The DebtEngine is meant to be the User of this model, so all the methods with the ability to perform state changes should only be callable by the DebtEngine. All models should implement the 0xaf498c35 interface. @author Agustin Aguilar */ contract Model is IERC165 { // /// // Events // /// /** @dev This emits when create a new debt. */ event Created(bytes32 indexed _id); /** @dev This emits when the status of debt change. @param _timestamp Timestamp of the registry @param _status New status of the registry */ event ChangedStatus(bytes32 indexed _id, uint256 _timestamp, uint256 _status); /** @dev This emits when the obligation of debt change. @param _timestamp Timestamp of the registry @param _debt New debt of the registry */ event ChangedObligation(bytes32 indexed _id, uint256 _timestamp, uint256 _debt); /** @dev This emits when the frequency of debt change. @param _timestamp Timestamp of the registry @param _frequency New frequency of each installment */ event ChangedFrequency(bytes32 indexed _id, uint256 _timestamp, uint256 _frequency); /** @param _timestamp Timestamp of the registry */ event ChangedDueTime(bytes32 indexed _id, uint256 _timestamp, uint256 _status); /** @param _timestamp Timestamp of the registry @param _dueTime New dueTime of each installment */ event ChangedFinalTime(bytes32 indexed _id, uint256 _timestamp, uint64 _dueTime); /** @dev This emits when the call addDebt function. @param _amount New amount of the debt, old amount plus added */ event AddedDebt(bytes32 indexed _id, uint256 _amount); /** @dev This emits when the call addPaid function. If the registry is fully paid on the call and the amount parameter exceeds the required payment amount, the event emits the real amount paid on the payment. @param _paid Real amount paid */ event AddedPaid(bytes32 indexed _id, uint256 _paid); // Model interface selector bytes4 internal constant MODEL_INTERFACE = 0xaf498c35; uint256 public constant STATUS_ONGOING = 1; uint256 public constant STATUS_PAID = 2; uint256 public constant STATUS_ERROR = 4; // /// // Meta // /// /** @return Identifier of the model */ function modelId() external view returns (bytes32); /** Returns the address of the contract used as Descriptor of the model @dev The descriptor contract should follow the ModelDescriptor.sol scheme @return Address of the descriptor */ function descriptor() external view returns (address); /** If called for any address with the ability to modify the state of the model registries, this method should return True. @dev Some contracts may check if the DebtEngine is an operator to know if the model is operative or not. @param operator Address of the target request operator @return True if operator is able to modify the state of the model */ function isOperator(address operator) external view returns (bool canOperate); /** Validates the data for the creation of a new registry, if returns True the same data should be compatible with the create method. @dev This method can revert the call or return false, and both meant an invalid data. @param data Data to validate @return True if the data can be used to create a new registry */ function validate(bytes calldata data) external view returns (bool isValid); // /// // Getters // /// /** Exposes the current status of the registry. The possible values are: 1: Ongoing - The debt is still ongoing and waiting to be paid 2: Paid - The debt is already paid and 4: Error - There was an Error with the registry @dev This method should always be called by the DebtEngine @param id Id of the registry @return The current status value */ function getStatus(bytes32 id) external view returns (uint256 status); /** Returns the total paid amount on the registry. @dev it should equal to the sum of all real addPaid @param id Id of the registry @return Total paid amount */ function getPaid(bytes32 id) external view returns (uint256 paid); /** If the returned amount does not depend on any interactions and only on the model logic, the defined flag will be True; if the amount is an estimation of the future debt, the flag will be set to False. If timestamp equals the current moment, the defined flag should always be True. @dev This can be a gas-intensive method to call, consider calling the run method before. @param id Id of the registry @param timestamp Timestamp of the obligation query @return amount Amount pending to pay on the given timestamp @return defined True If the amount returned is fixed and can't change */ function getObligation(bytes32 id, uint64 timestamp) external view returns (uint256 amount, bool defined); /** The amount required to fully paid a registry. All registries should be payable in a single time, even when it has multiple installments. If the registry discounts interest for early payment, those discounts should be taken into account in the returned amount. @dev This can be a gas-intensive method to call, consider calling the run method before. @param id Id of the registry @return amount Amount required to fully paid the loan on the current timestamp */ function getClosingObligation(bytes32 id) external view returns (uint256 amount); /** The timestamp of the next required payment. After this moment, if the payment goal is not met the debt will be considered overdue. The getObligation method can be used to know the required payment on the future timestamp. @param id Id of the registry @return timestamp The timestamp of the next due time */ function getDueTime(bytes32 id) external view returns (uint256 timestamp); // /// // Metadata // /// /** If the loan has multiple installments returns the duration of each installment in seconds, if the loan has not installments it should return 1. @param id Id of the registry @return frequency Frequency of each installment */ function getFrequency(bytes32 id) external view returns (uint256 frequency); /** If the loan has multiple installments returns the total of installments, if the loan has not installments it should return 1. @param id Id of the registry @return installments Total of installments */ function getInstallments(bytes32 id) external view returns (uint256 installments); /** The registry could be paid before or after the date, but the debt will always be considered overdue if paid after this timestamp. This is the estimated final payment date of the debt if it's always paid on each exact dueTime. @param id Id of the registry @return timestamp Timestamp of the final due time */ function getFinalTime(bytes32 id) external view returns (uint256 timestamp); /** Similar to getFinalTime returns the expected payment remaining if paid always on the exact dueTime. If the model has no interest discounts for early payments, this method should return the same value as getClosingObligation. @param id Id of the registry @return amount Expected payment amount */ function getEstimateObligation(bytes32 id) external view returns (uint256 amount); // /// // State interface // /// /** Creates a new registry using the provided data and id, it should fail if the id already exists or if calling validate(data) returns false or throws. @dev This method should only be callable by an operator @param id Id of the registry to create @param data Data to construct the new registry @return success True if the registry was created */ function create(bytes32 id, bytes calldata data) external returns (bool success); /** If the registry is fully paid on the call and the amount parameter exceeds the required payment amount, the method returns the real amount used on the payment. The payment taken should always be the same as the requested unless the registry is fully paid on the process. @dev This method should only be callable by an operator @param id If of the registry @param amount Amount to pay @return real Real amount paid */ function addPaid(bytes32 id, uint256 amount) external returns (uint256 real); /** Adds a new amount to be paid on the debt model, each model can handle the addition of more debt freely. @dev This method should only be callable by an operator @param id Id of the registry @param amount Debt amount to add to the registry @return added True if the debt was added */ function addDebt(bytes32 id, uint256 amount) external returns (bool added); // /// // Utils // /// /** Runs the internal clock of a registry, this is used to compute the last changes on the state. It can make transactions cheaper by avoiding multiple calculations when calling views. Not all models have internal clocks, a model without an internal clock should always return false. Calls to this method should be possible from any address, multiple calls to run shouldn't affect the internal calculations of the model. @dev If the call had no effect the method would return False, that is no sign of things going wrong, and the call shouldn't be wrapped on a require @param id If of the registry @return effect True if the run performed a change on the state */ function run(bytes32 id) external returns (bool effect); } // File: contracts/core/diaspore/interfaces/ModelDescriptor.sol pragma solidity ^0.5.7; contract ModelDescriptor { bytes4 internal constant MODEL_DESCRIPTOR_INTERFACE = 0x02735375; function simFirstObligation(bytes calldata data) external view returns (uint256 amount, uint256 time); function simTotalObligation(bytes calldata data) external view returns (uint256 amount); function simDuration(bytes calldata data) external view returns (uint256 duration); function simPunitiveInterestRate(bytes calldata data) external view returns (uint256 punitiveInterestRate); function simFrequency(bytes calldata data) external view returns (uint256 frequency); function simInstallments(bytes calldata data) external view returns (uint256 installments); } // File: contracts/interfaces/IERC173.sol pragma solidity ^0.5.7; /// @title ERC-173 Contract Ownership Standard /// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-173.md /// Note: the ERC-165 identifier for this interface is 0x7f5828d0 contract IERC173 { /// @dev This emits when ownership of a contract changes. event OwnershipTransferred(address indexed _previousOwner, address indexed _newOwner); /// @notice Get the address of the owner /// @return The address of the owner. //// function owner() external view returns (address); /// @notice Set the address of the new owner of the contract /// @param _newOwner The address of the new owner of the contract function transferOwnership2(address _newOwner) external; } pragma solidity ^0.5.7; contract Initable { bool public inited; modifier initer() { require(inited == false, "Already inited"); _; inited = true; } function init() public initer { } } // File: contracts/commons/Ownable.sol pragma solidity ^0.5.7; contract Ownable is Initable, IERC173 { address internal _owner; modifier onlyOwner() { require(msg.sender == _owner, "The owner should be the sender"); _; } function init() public initer { _owner = msg.sender; emit OwnershipTransferred(address(0x0), msg.sender); super.init(); } function owner2() external view returns (address) { return _owner; } /** @dev Transfers the ownership of the contract. @param _newOwner Address of the new owner */ function transferOwnership2(address _newOwner) external onlyOwner { require(_newOwner != address(0), "0x0 Is not a valid owner"); emit OwnershipTransferred(_owner, _newOwner); _owner = _newOwner; } } // File: contracts/commons/ERC165.sol pragma solidity ^0.5.7; /** * @title ERC165 * @author Matt Condon (@shrugs) * @dev Implements ERC165 using a lookup table. */ contract ERC165 is Initable, IERC165 { bytes4 private constant _InterfaceId_ERC165 = 0x01ffc9a7; /** * 0x01ffc9a7 === * bytes4(keccak256('supportsInterface(bytes4)')) */ /** * @dev a mapping of interface id to whether or not it's supported */ mapping(bytes4 => bool) private _supportedInterfaces; /** * @dev A contract implementing SupportsInterfaceWithLookup * implement ERC165 itself */ function init() public initer { _registerInterface(_InterfaceId_ERC165); super.init(); } /** * @dev implement supportsInterface(bytes4) using a lookup table */ function supportsInterface(bytes4 interfaceId) external view returns (bool) { return _supportedInterfaces[interfaceId]; } /** * @dev internal method for registering an interface */ function _registerInterface(bytes4 interfaceId) internal { require(interfaceId != 0xffffffff, "Can't register 0xffffffff"); _supportedInterfaces[interfaceId] = true; } } // File: contracts/utils/BytesUtils.sol pragma solidity ^0.5.7; contract BytesUtils { function readBytes32(bytes memory data, uint256 index) internal pure returns (bytes32 o) { require(data.length / 32 > index, "Reading bytes out of bounds"); assembly { o := mload(add(data, add(32, mul(32, index)))) } } function read(bytes memory data, uint256 offset, uint256 length) internal pure returns (bytes32 o) { require(data.length >= offset + length, "Reading bytes out of bounds"); assembly { o := mload(add(data, add(32, offset))) let lb := sub(32, length) if lb { o := div(o, exp(2, mul(lb, 8))) } } } function decode( bytes memory _data, uint256 _la ) internal pure returns (bytes32 _a) { require(_data.length >= _la, "Reading bytes out of bounds"); assembly { _a := mload(add(_data, 32)) let l := sub(32, _la) if l { _a := div(_a, exp(2, mul(l, 8))) } } } function decode( bytes memory _data, uint256 _la, uint256 _lb ) internal pure returns (bytes32 _a, bytes32 _b) { uint256 o; assembly { let s := add(_data, 32) _a := mload(s) let l := sub(32, _la) if l { _a := div(_a, exp(2, mul(l, 8))) } o := add(s, _la) _b := mload(o) l := sub(32, _lb) if l { _b := div(_b, exp(2, mul(l, 8))) } o := sub(o, s) } require(_data.length >= o, "Reading bytes out of bounds"); } function decode( bytes memory _data, uint256 _la, uint256 _lb, uint256 _lc ) internal pure returns (bytes32 _a, bytes32 _b, bytes32 _c) { uint256 o; assembly { let s := add(_data, 32) _a := mload(s) let l := sub(32, _la) if l { _a := div(_a, exp(2, mul(l, 8))) } o := add(s, _la) _b := mload(o) l := sub(32, _lb) if l { _b := div(_b, exp(2, mul(l, 8))) } o := add(o, _lb) _c := mload(o) l := sub(32, _lc) if l { _c := div(_c, exp(2, mul(l, 8))) } o := sub(o, s) } require(_data.length >= o, "Reading bytes out of bounds"); } function decode( bytes memory _data, uint256 _la, uint256 _lb, uint256 _lc, uint256 _ld ) internal pure returns (bytes32 _a, bytes32 _b, bytes32 _c, bytes32 _d) { uint256 o; assembly { let s := add(_data, 32) _a := mload(s) let l := sub(32, _la) if l { _a := div(_a, exp(2, mul(l, 8))) } o := add(s, _la) _b := mload(o) l := sub(32, _lb) if l { _b := div(_b, exp(2, mul(l, 8))) } o := add(o, _lb) _c := mload(o) l := sub(32, _lc) if l { _c := div(_c, exp(2, mul(l, 8))) } o := add(o, _lc) _d := mload(o) l := sub(32, _ld) if l { _d := div(_d, exp(2, mul(l, 8))) } o := sub(o, s) } require(_data.length >= o, "Reading bytes out of bounds"); } function decode( bytes memory _data, uint256 _la, uint256 _lb, uint256 _lc, uint256 _ld, uint256 _le ) internal pure returns (bytes32 _a, bytes32 _b, bytes32 _c, bytes32 _d, bytes32 _e) { uint256 o; assembly { let s := add(_data, 32) _a := mload(s) let l := sub(32, _la) if l { _a := div(_a, exp(2, mul(l, 8))) } o := add(s, _la) _b := mload(o) l := sub(32, _lb) if l { _b := div(_b, exp(2, mul(l, 8))) } o := add(o, _lb) _c := mload(o) l := sub(32, _lc) if l { _c := div(_c, exp(2, mul(l, 8))) } o := add(o, _lc) _d := mload(o) l := sub(32, _ld) if l { _d := div(_d, exp(2, mul(l, 8))) } o := add(o, _ld) _e := mload(o) l := sub(32, _le) if l { _e := div(_e, exp(2, mul(l, 8))) } o := sub(o, s) } require(_data.length >= o, "Reading bytes out of bounds"); } function decode( bytes memory _data, uint256 _la, uint256 _lb, uint256 _lc, uint256 _ld, uint256 _le, uint256 _lf ) internal pure returns ( bytes32 _a, bytes32 _b, bytes32 _c, bytes32 _d, bytes32 _e, bytes32 _f ) { uint256 o; assembly { let s := add(_data, 32) _a := mload(s) let l := sub(32, _la) if l { _a := div(_a, exp(2, mul(l, 8))) } o := add(s, _la) _b := mload(o) l := sub(32, _lb) if l { _b := div(_b, exp(2, mul(l, 8))) } o := add(o, _lb) _c := mload(o) l := sub(32, _lc) if l { _c := div(_c, exp(2, mul(l, 8))) } o := add(o, _lc) _d := mload(o) l := sub(32, _ld) if l { _d := div(_d, exp(2, mul(l, 8))) } o := add(o, _ld) _e := mload(o) l := sub(32, _le) if l { _e := div(_e, exp(2, mul(l, 8))) } o := add(o, _le) _f := mload(o) l := sub(32, _lf) if l { _f := div(_f, exp(2, mul(l, 8))) } o := sub(o, s) } require(_data.length >= o, "Reading bytes out of bounds"); } } // File: contracts/core/diaspore/model/InstallmentsModel.sol pragma solidity ^0.5.7; contract InstallmentsModel is ProxyStorage, Initable, ERC165, BytesUtils, Ownable, Model, ModelDescriptor { mapping(bytes4 => bool) private _supportedInterface; function init() public initer { _registerInterface(MODEL_INTERFACE); _registerInterface(MODEL_DESCRIPTOR_INTERFACE); super.init(); } address public engine; address private altDescriptor; mapping(bytes32 => Config) public configs; mapping(bytes32 => State) public states; uint256 public constant L_DATA = 16 + 32 + 3 + 5 + 4; uint256 private constant U_128_OVERFLOW = 2 ** 128; uint256 private constant U_64_OVERFLOW = 2 ** 64; uint256 private constant U_40_OVERFLOW = 2 ** 40; uint256 private constant U_24_OVERFLOW = 2 ** 24; event _setEngine(address _engine); event _setDescriptor(address _descriptor); event _setClock(bytes32 _id, uint64 _to); event _setPaidBase(bytes32 _id, uint128 _paidBase); event _setInterest(bytes32 _id, uint128 _interest); struct Config { uint24 installments; uint32 timeUnit; uint40 duration; uint64 lentTime; uint128 cuota; uint256 interestRate; } struct State { uint8 status; uint64 clock; uint64 lastPayment; uint128 paid; uint128 paidBase; uint128 interest; } modifier onlyEngine { require(msg.sender == engine, "Only engine allowed"); _; } function modelId() external view returns (bytes32) { // InstallmentsModel A 0.0.2 return bytes32(0x00000000000000496e7374616c6c6d656e74734d6f64656c204120302e302e32); } function descriptor() external view returns (address) { address _descriptor = altDescriptor; return _descriptor == address(0) ? address(this) : _descriptor; } function setEngine(address _engine) external onlyOwner returns (bool) { engine = _engine; emit _setEngine(_engine); return true; } function setDescriptor(address _descriptor) external onlyOwner returns (bool) { altDescriptor = _descriptor; emit _setDescriptor(_descriptor); return true; } function encodeData( uint128 _cuota, uint256 _interestRate, uint24 _installments, uint40 _duration, uint32 _timeUnit ) external pure returns (bytes memory) { return abi.encodePacked(_cuota, _interestRate, _installments, _duration, _timeUnit); } function create(bytes32 id, bytes calldata data) external onlyEngine returns (bool) { require(configs[id].cuota == 0, "Entry already exist"); (uint128 cuota, uint256 interestRate, uint24 installments, uint40 duration, uint32 timeUnit) = _decodeData(data); _validate(cuota, interestRate, installments, duration, timeUnit); configs[id] = Config({ installments: installments, duration: duration, lentTime: uint64(now), cuota: cuota, interestRate: interestRate, timeUnit: timeUnit }); states[id].clock = duration; emit Created(id); emit _setClock(id, duration); return true; } function addPaid(bytes32 id, uint256 amount) external onlyEngine returns (uint256 real) { Config storage config = configs[id]; State storage state = states[id]; _advanceClock(id, uint64(now) - config.lentTime); if (state.status != STATUS_PAID) { // State & config memory load uint256 paid = state.paid; uint256 duration = config.duration; uint256 interest = state.interest; // Payment aux uint256 available = amount; require(available < U_128_OVERFLOW, "Amount overflow"); // Aux variables uint256 unpaidInterest; uint256 pending; uint256 target; uint256 baseDebt; uint256 clock; do { clock = state.clock; baseDebt = _baseDebt(clock, duration, config.installments, config.cuota); pending = baseDebt + interest - paid; // min(pending, available) target = pending < available ? pending : available; // Calc paid base unpaidInterest = interest - (paid - state.paidBase); // max(target - unpaidInterest, 0) state.paidBase += uint128(target > unpaidInterest ? target - unpaidInterest : 0); emit _setPaidBase(id, state.paidBase); paid += target; available -= target; // Check fully paid // All installments paid + interest if (clock / duration >= config.installments && baseDebt + interest <= paid) { // Registry paid! state.status = uint8(STATUS_PAID); emit ChangedStatus(id, now, STATUS_PAID); break; } // If installment fully paid, advance to next one if (pending == target) { _advanceClock(id, clock + duration - (clock % duration)); } } while (available != 0); require(paid < U_128_OVERFLOW, "Paid overflow"); state.paid = uint128(paid); state.lastPayment = state.clock; real = amount - available; emit AddedPaid(id, real); } } function addDebt(bytes32 id, uint256 amount) external onlyEngine returns (bool) { revert("Not implemented!"); } function fixClock(bytes32 id, uint64 target) external returns (bool) { require(target <= now, "Forbidden advance clock into the future"); Config storage config = configs[id]; State storage state = states[id]; uint64 lentTime = config.lentTime; require(lentTime < target, "Clock can't go negative"); uint64 targetClock = target - lentTime; require(targetClock > state.clock, "Clock is ahead of target"); return _advanceClock(id, targetClock); } function isOperator(address _target) external view returns (bool) { return engine == _target; } function getStatus(bytes32 id) external view returns (uint256) { Config storage config = configs[id]; State storage state = states[id]; require(config.lentTime != 0, "The registry does not exist"); return state.status == STATUS_PAID ? STATUS_PAID : STATUS_ONGOING; } function getPaid(bytes32 id) external view returns (uint256) { return states[id].paid; } function getObligation(bytes32 id, uint64 timestamp) external view returns (uint256, bool) { State storage state = states[id]; Config storage config = configs[id]; // Can't be before creation if (timestamp < config.lentTime) { return (0, true); } // Static storage loads uint256 currentClock = timestamp - config.lentTime; uint256 base = _baseDebt( currentClock, config.duration, config.installments, config.cuota ); uint256 interest; uint256 prevInterest = state.interest; uint256 clock = state.clock; bool defined; if (clock >= currentClock) { interest = prevInterest; defined = true; } else { // We need to calculate the new interest, on a view! (interest, currentClock) = _simRunClock( clock, currentClock, prevInterest, config, state ); defined = prevInterest == interest; } uint256 debt = base + interest; uint256 paid = state.paid; return (debt > paid ? debt - paid : 0, defined); } function _simRunClock( uint256 _clock, uint256 _targetClock, uint256 _prevInterest, Config memory _config, State memory _state ) internal pure returns (uint256 interest, uint256 clock) { (interest, clock) = _runAdvanceClock({ _clock: _clock, _timeUnit: _config.timeUnit, _interest: _prevInterest, _duration: _config.duration, _cuota: _config.cuota, _installments: _config.installments, _paidBase: _state.paidBase, _interestRate: _config.interestRate, _targetClock: _targetClock }); } function run(bytes32 id) external returns (bool) { Config storage config = configs[id]; return _advanceClock(id, uint64(now) - config.lentTime); } function validate(bytes calldata data) external view returns (bool) { (uint128 cuota, uint256 interestRate, uint24 installments, uint40 duration, uint32 timeUnit) = _decodeData(data); _validate(cuota, interestRate, installments, duration, timeUnit); return true; } function getClosingObligation(bytes32 id) external view returns (uint256) { return _getClosingObligation(id); } function getDueTime(bytes32 id) external view returns (uint256) { Config storage config = configs[id]; if (config.cuota == 0) return 0; uint256 last = states[id].lastPayment; uint256 duration = config.duration; last = last != 0 ? last : duration; return last - (last % duration) + config.lentTime; } function getFinalTime(bytes32 id) external view returns (uint256) { Config storage config = configs[id]; return config.lentTime + (uint256(config.duration) * (uint256(config.installments))); } function getFrequency(bytes32 id) external view returns (uint256) { return configs[id].duration; } function getInstallments(bytes32 id) external view returns (uint256) { return configs[id].installments; } function getEstimateObligation(bytes32 id) external view returns (uint256) { return _getClosingObligation(id); } function simFirstObligation(bytes calldata _data) external view returns (uint256 amount, uint256 time) { (amount,,, time,) = _decodeData(_data); } function simTotalObligation(bytes calldata _data) external view returns (uint256 amount) { (uint256 cuota,, uint256 installments,,) = _decodeData(_data); amount = cuota * installments; } function simDuration(bytes calldata _data) external view returns (uint256 duration) { (,,uint256 installments, uint256 installmentDuration,) = _decodeData(_data); duration = installmentDuration * installments; } function simPunitiveInterestRate(bytes calldata _data) external view returns (uint256 punitiveInterestRate) { (,punitiveInterestRate,,,) = _decodeData(_data); } function simFrequency(bytes calldata _data) external view returns (uint256 frequency) { (,,, frequency,) = _decodeData(_data); } function simInstallments(bytes calldata _data) external view returns (uint256 installments) { (,, installments,,) = _decodeData(_data); } function _advanceClock(bytes32 id, uint256 _target) internal returns (bool) { Config storage config = configs[id]; State storage state = states[id]; uint256 clock = state.clock; if (clock < _target) { (uint256 newInterest, uint256 newClock) = _runAdvanceClock({ _clock: state.clock, _timeUnit: config.timeUnit, _interest: state.interest, _duration: config.duration, _cuota: config.cuota, _installments: config.installments, _paidBase: state.paidBase, _interestRate: config.interestRate, _targetClock: _target }); require(newClock < U_64_OVERFLOW, "Clock overflow"); require(newInterest < U_128_OVERFLOW, "Interest overflow"); emit _setClock(id, uint64(newClock)); if (newInterest != 0) { emit _setInterest(id, uint128(newInterest)); } state.clock = uint64(newClock); state.interest = uint128(newInterest); return true; } } function _getClosingObligation(bytes32 id) internal view returns (uint256) { State storage state = states[id]; Config storage config = configs[id]; // Static storage loads uint256 installments = config.installments; uint256 cuota = config.cuota; uint256 currentClock = uint64(now) - config.lentTime; uint256 interest; uint256 clock = state.clock; if (clock >= currentClock) { interest = state.interest; } else { (interest,) = _runAdvanceClock({ _clock: clock, _timeUnit: config.timeUnit, _interest: state.interest, _duration: config.duration, _cuota: cuota, _installments: installments, _paidBase: state.paidBase, _interestRate: config.interestRate, _targetClock: currentClock }); } uint256 debt = cuota * installments + interest; uint256 paid = state.paid; return debt > paid ? debt - paid : 0; } function _runAdvanceClock( uint256 _clock, uint256 _timeUnit, uint256 _interest, uint256 _duration, uint256 _cuota, uint256 _installments, uint256 _paidBase, uint256 _interestRate, uint256 _targetClock ) internal pure returns (uint256 interest, uint256 clock) { // Advance clock to lentTime if never advanced before clock = _clock; interest = _interest; // Aux variables uint256 delta; bool installmentCompleted; do { // Delta to next installment and absolute delta (no exceeding 1 installment) (delta, installmentCompleted) = _calcDelta({ _targetDelta: _targetClock - clock, _clock: clock, _duration: _duration, _installments: _installments }); // Running debt uint256 newInterest = _newInterest({ _clock: clock, _timeUnit: _timeUnit, _duration: _duration, _installments: _installments, _cuota: _cuota, _paidBase: _paidBase, _delta: delta, _interestRate: _interestRate }); // Don't change clock unless we have a change if (installmentCompleted || newInterest > 0) { clock += delta; interest += newInterest; } else { break; } } while (clock < _targetClock); } function _calcDelta( uint256 _targetDelta, uint256 _clock, uint256 _duration, uint256 _installments ) internal pure returns (uint256 delta, bool installmentCompleted) { uint256 nextInstallmentDelta = _duration - _clock % _duration; if (nextInstallmentDelta <= _targetDelta && _clock / _duration < _installments) { delta = nextInstallmentDelta; installmentCompleted = true; } else { delta = _targetDelta; installmentCompleted = false; } } function _newInterest( uint256 _clock, uint256 _timeUnit, uint256 _duration, uint256 _installments, uint256 _cuota, uint256 _paidBase, uint256 _delta, uint256 _interestRate ) internal pure returns (uint256) { uint256 runningDebt = _baseDebt(_clock, _duration, _installments, _cuota) - _paidBase; uint256 newInterest = (100000 * (_delta / _timeUnit) * runningDebt) / (_interestRate / _timeUnit); require(newInterest < U_128_OVERFLOW, "New interest overflow"); return newInterest; } function _baseDebt( uint256 clock, uint256 duration, uint256 installments, uint256 cuota ) internal pure returns (uint256 base) { uint256 installment = clock / duration; return uint128(installment < installments ? installment * cuota : installments * cuota); } function _validate( uint256 _cuota, uint256 _interestRate, uint256 _installments, uint256 _installmentDuration, uint256 _timeUnit ) internal pure { require(_cuota > 0, "Cuota can't be 0"); require(_interestRate > 0, "Interest rate can't be 0"); require(_installments > 0, "Installments can't be 0"); require(_installmentDuration > 0, "Installment duration can't be 0"); require(_timeUnit <= _installmentDuration, "Time unit can't be lower than installment duration"); require(_interestRate > _timeUnit, "Interest rate by time unit is too low"); require(_timeUnit > 0, "Time unit can'be 0"); } function _decodeData( bytes memory _data ) internal pure returns (uint128, uint256, uint24, uint40, uint32) { require(_data.length == L_DATA, "Invalid data length"); ( bytes32 cuota, bytes32 interestRate, bytes32 installments, bytes32 duration, bytes32 timeUnit ) = decode(_data, 16, 32, 3, 5, 4); return (uint128(uint256(cuota)), uint256(interestRate), uint24(uint256(installments)), uint40(uint256(duration)), uint32(uint256(timeUnit))); } }