// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// SPDX-License-Identifier: gpl-3.0
pragma solidity 0.8.3;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "../storage/IEthicHubStorage.sol";
contract EthicHubLending is Pausable, Ownable {
using SafeMath for uint256;
enum LendingState {
Uninitialized,
AcceptingContributions,
Funded,
AwaitingReturn,
ProjectNotFunded,
ContributionReturned,
Default
}
uint8 public version;
IEthicHubStorage public ethicHubStorage;
mapping(address => Investor) public investors;
uint256 public investorCount;
uint256 public reclaimedContributions;
uint256 public fundingStartTime; // Start time of contribution period in UNIX time
uint256 public fundingEndTime; // End time of contribution period in UNIX time
uint256 public totalContributed;
bool public capReached;
LendingState public state;
uint256 public annualInterest;
uint256 public totalLendingAmount;
uint256 public lendingDays;
uint256 public borrowerReturnDays;
uint256 public maxDelayDays;
address payable public borrower;
address public localNode;
address payable public ethicHubTeam;
address payable public systemFeesCollector;
uint256 public ethicHubFee;
uint256 public systemFees;
// Interest rate is using base uint 100 and 100% 10000, this means 1% is 100
// this guarantee we can have a 2 decimal presicion in our calculation
uint256 public constant interestBaseUint = 100;
uint256 public constant interestBasePercent = 10000;
bool public systemFeesReclaimed;
bool public ethicHubTeamFeeReclaimed;
uint256 public returnedAmount;
struct Investor {
uint256 amount;
bool isCompensated;
}
struct LoanParams {
uint256 fundingStartTime;
uint256 fundingEndTime;
uint256 annualInterest;
uint256 totalLendingAmount;
uint256 lendingDays;
uint256 ethicHubFee;
uint256 systemFees;
uint256 maxDelayDays;
}
struct Actors {
address payable borrower;
address localNode;
address payable ethicHubTeam;
address payable systemFeesCollector;
}
// Events
event CapReached(uint endTime);
event Contribution(uint totalContributed, address indexed investor, uint amount, uint investorsCount);
event Compensated(address indexed contributor, uint amount);
event StateChange(uint state);
event ReturnAmount(address indexed borrower, uint amount);
event BorrowerChanged(address indexed newBorrower);
event InvestorChanged(address indexed oldInvestor, address indexed newInvestor);
event Reclaim(address indexed target, uint256 amount);
modifier checkIfArbiter() {
address arbiter = ethicHubStorage.getAddress(keccak256(abi.encodePacked("arbiter", this)));
require(arbiter == msg.sender, "Sender not authorized");
_;
}
modifier onlyOwnerOrLocalNode() {
require(localNode == msg.sender || owner() == msg.sender,"Sender not authorized");
_;
}
constructor(
address _ethicHubStorage,
LoanParams memory _loanParams,
Actors memory _actors
) Ownable () Pausable () public {
require(address(_ethicHubStorage) != address(0), "Storage address cannot be zero address");
ethicHubStorage = IEthicHubStorage(_ethicHubStorage);
require(_loanParams.fundingEndTime > _loanParams.fundingStartTime, "fundingEndTime should be later than fundingStartTime");
require(_loanParams.totalLendingAmount > 0, "totalLendingAmount must be > 0");
require(_loanParams.lendingDays > 0, "lendingDays must be > 0");
require(_loanParams.annualInterest > 0 && _loanParams.annualInterest < 100, "_annualInterest must be between 0 and 100");
require(_actors.borrower != address(0), "No borrower set");
require(_actors.localNode != address(0), "No Local Node set");
require(_actors.ethicHubTeam != address(0), "No EthicHub Team set");
require(_actors.systemFeesCollector != address(0), "No System Fees Collector set");
require(ethicHubStorage.getBool(keccak256(abi.encodePacked("user", "representative", _actors.borrower))), "Borrower not registered representative");
require(ethicHubStorage.getBool(keccak256(abi.encodePacked("user", "localNode", _actors.localNode))), "Local Node is not registered");
version = 11;
reclaimedContributions = 0;
borrowerReturnDays = 0;
maxDelayDays = _loanParams.maxDelayDays;
annualInterest = _loanParams.annualInterest;
totalLendingAmount = _loanParams.totalLendingAmount;
lendingDays = _loanParams.lendingDays;
ethicHubFee = _loanParams.ethicHubFee;
systemFees = _loanParams.systemFees;
fundingStartTime = _loanParams.fundingStartTime;
fundingEndTime = _loanParams.fundingEndTime;
borrower = _actors.borrower;
localNode = _actors.localNode;
systemFeesCollector = _actors.systemFeesCollector;
ethicHubTeam = _actors.ethicHubTeam;
state = LendingState.AcceptingContributions;
}
function setBorrower(address payable _borrower) external checkIfArbiter {
require(_borrower != address(0), "No borrower set");
require(ethicHubStorage.getBool(keccak256(abi.encodePacked("user", "representative", _borrower))), "Borrower not registered representative");
borrower = _borrower;
emit BorrowerChanged(borrower);
}
function changeInvestorAddress(address _oldInvestor, address payable _newInvestor) external checkIfArbiter {
require(_newInvestor != address(0));
require(ethicHubStorage.getBool(keccak256(abi.encodePacked("user", "investor", _newInvestor))));
require(investors[_oldInvestor].amount != 0, "OldInvestor should have invested in this project");
require(
investors[_newInvestor].amount == 0,
"newInvestor should not have invested anything"
);
investors[_newInvestor].amount = investors[_oldInvestor].amount;
investors[_newInvestor].isCompensated = investors[_oldInvestor].isCompensated;
delete investors[_oldInvestor];
emit InvestorChanged(_oldInvestor, _newInvestor);
}
function returnBorrowed() external payable {
require(msg.sender == borrower, "In state AwaitingReturn only borrower can contribute");
require(state == LendingState.AwaitingReturn, "State is not AwaitingReturn");
bool projectRepayed = false;
uint excessRepayment = 0;
uint newReturnedAmount = 0;
emit ReturnAmount(borrower, msg.value);
(newReturnedAmount, projectRepayed, excessRepayment) = calculatePaymentGoal(borrowerReturnAmount(), returnedAmount, msg.value);
returnedAmount = newReturnedAmount;
if (projectRepayed == true) {
borrowerReturnDays = getDaysPassedBetweenDates(fundingEndTime, block.timestamp);
changeState(LendingState.ContributionReturned);
}
if (excessRepayment > 0) {
payable(borrower).call{value: excessRepayment}('');
}
}
// @notice Function to participate in contribution period
// Amounts from the same address should be added up
// If cap is reached, end time should be modified
// Funds should be transferred into multisig wallet
// @param contributor Address
function deposit(address payable contributor) external payable whenNotPaused {
require(
ethicHubStorage.getBool(keccak256(abi.encodePacked("user", "investor", contributor))),
"Contributor is not registered lender"
);
require(state == LendingState.AcceptingContributions, "state is not AcceptingContributions");
require(isContribPeriodRunning(), "can't contribute outside contribution period");
uint oldTotalContributed = totalContributed;
uint newTotalContributed = 0;
uint excessContribAmount = 0;
(newTotalContributed, capReached, excessContribAmount) = calculatePaymentGoal(totalLendingAmount, oldTotalContributed, msg.value);
totalContributed = newTotalContributed;
if (capReached) {
fundingEndTime = block.timestamp;
emit CapReached(fundingEndTime);
changeState(LendingState.Funded);
}
if (investors[contributor].amount == 0) {
investorCount = investorCount.add(1);
}
if (excessContribAmount > 0) {
payable(contributor).call{value:excessContribAmount}('');
investors[contributor].amount = investors[contributor].amount.add(msg.value).sub(excessContribAmount);
emit Contribution(newTotalContributed, contributor, msg.value.sub(excessContribAmount), investorCount);
} else {
investors[contributor].amount = investors[contributor].amount.add(msg.value);
emit Contribution(newTotalContributed, contributor, msg.value, investorCount);
}
}
/**
* After the contribution period ends unsuccesfully, this method enables the contributor
* to retrieve their contribution
*/
function declareProjectNotFunded() external onlyOwnerOrLocalNode {
require(totalContributed < totalLendingAmount);
require(state == LendingState.AcceptingContributions);
require(block.timestamp > fundingEndTime);
changeState(LendingState.ProjectNotFunded);
}
function declareProjectDefault() external onlyOwnerOrLocalNode {
require(state == LendingState.AwaitingReturn);
require(getDelayDays(block.timestamp) >= maxDelayDays);
changeState(LendingState.Default);
}
/**
* Method to reclaim contribution after project is declared default (% of partial funds)
* @param beneficiary the contributor
*
*/
function reclaimContributionDefault(address payable beneficiary) external {
require(state == LendingState.Default);
require(!investors[beneficiary].isCompensated);
// contribution = contribution * partial_funds / total_funds
uint256 contribution = checkInvestorReturns(beneficiary);
require(contribution > 0);
investors[beneficiary].isCompensated = true;
reclaimedContributions = reclaimedContributions.add(1);
doReclaim(beneficiary, contribution);
}
/**
* Method to reclaim contribution after a project is declared as not funded
* @param beneficiary the contributor
*
*/
function reclaimContribution(address payable beneficiary) external {
require(state == LendingState.ProjectNotFunded, "State is not ProjectNotFunded");
require(!investors[beneficiary].isCompensated, "Contribution already reclaimed");
uint256 contribution = investors[beneficiary].amount;
require(contribution > 0, "Contribution is 0");
investors[beneficiary].isCompensated = true;
reclaimedContributions = reclaimedContributions.add(1);
doReclaim(beneficiary, contribution);
}
function reclaimContributionWithInterest(address payable beneficiary) external {
require(state == LendingState.ContributionReturned, "State is not ContributionReturned");
require(!investors[beneficiary].isCompensated, "Lender already compensated");
uint256 contribution = checkInvestorReturns(beneficiary);
require(contribution > 0, "Contribution is 0");
investors[beneficiary].isCompensated = true;
reclaimedContributions = reclaimedContributions.add(1);
doReclaim(beneficiary, contribution);
}
function reclaimSystemFees() external {
require(state == LendingState.AwaitingReturn || state == LendingState.ContributionReturned, "State is not AwaitingReturn or ContributionReturned");
require(systemFeesReclaimed == false, "Local Node's fee already reclaimed");
uint256 fee = totalLendingAmount.mul(systemFees).mul(interestBaseUint).div(interestBasePercent);
require(fee > 0, "Local Node's team fee is 0");
systemFeesReclaimed = true;
doReclaim(systemFeesCollector, fee);
}
function reclaimEthicHubTeamFee() external {
require(state == LendingState.AwaitingReturn || state == LendingState.ContributionReturned, "State is not AwaitingReturn or ContributionReturned");
require(ethicHubTeamFeeReclaimed == false, "EthicHub team's fee already reclaimed");
uint256 fee = totalLendingAmount.mul(ethicHubFee).mul(interestBaseUint).div(interestBasePercent);
require(fee > 0, "EthicHub's team fee is 0");
ethicHubTeamFeeReclaimed = true;
doReclaim(ethicHubTeam, fee);
}
function reclaimLeftover() external checkIfArbiter {
require(state == LendingState.ContributionReturned || state == LendingState.Default, "State is not ContributionReturned or Default");
require(systemFeesReclaimed, "Local Node fee is not reclaimed");
require(ethicHubTeamFeeReclaimed, "Team fee is not reclaimed");
require(investorCount == reclaimedContributions, "Not all investors have reclaimed their share");
doReclaim(ethicHubTeam, address(this).balance);
}
function doReclaim(address payable target, uint256 amount) internal {
uint256 contractBalance = address(this).balance;
uint256 reclaimAmount = (contractBalance < amount) ? contractBalance : amount;
payable(target).call{value:reclaimAmount}('');
emit Reclaim(target, reclaimAmount);
}
/**
* Calculates if a target value is reached after increment, and by how much it was surpassed.
* @param goal the target to achieve
* @param oldTotal the total so far after the increment
* @param contribValue the increment
* @return (the incremented count, not bigger than max), (goal has been reached), (excess to return)
*/
function calculatePaymentGoal(uint goal, uint oldTotal, uint contribValue) internal pure returns(uint, bool, uint) {
uint newTotal = oldTotal.add(contribValue);
bool goalReached = false;
uint excess = 0;
if (newTotal >= goal && oldTotal < goal) {
goalReached = true;
excess = newTotal.sub(goal);
contribValue = contribValue.sub(excess);
newTotal = goal;
}
return (newTotal, goalReached, excess);
}
function sendFundsToBorrower() external onlyOwnerOrLocalNode {
// Waiting for Exchange
require(state == LendingState.Funded, "State has to be AcceptingContributions");
uint256 systemFee = totalLendingAmount.mul(systemFees).mul(interestBaseUint).div(interestBasePercent);
uint256 teamFee = totalLendingAmount.mul(ethicHubFee).mul(interestBaseUint).div(interestBasePercent);
changeState(LendingState.AwaitingReturn);
payable(borrower).call{value: totalContributed.sub(systemFee).sub(teamFee)}('');
}
/**
* Calculates days passed after defaulting
* @param date timestamp to calculate days
* @return day number
*/
function getDelayDays(uint date) public view returns(uint) {
uint lendingDaysSeconds = lendingDays * 1 days;
uint defaultTime = fundingEndTime.add(lendingDaysSeconds);
if (date < defaultTime) {
return 0;
} else {
return getDaysPassedBetweenDates(defaultTime, date);
}
}
/**
* Calculates days passed between two dates in seconds
* @param firstDate timestamp
* @param lastDate timestamp
* @return days passed
*/
function getDaysPassedBetweenDates(uint firstDate, uint lastDate) public pure returns(uint) {
require(firstDate <= lastDate, "lastDate must be bigger than firstDate");
return lastDate.sub(firstDate).div(60).div(60).div(24);
}
// lendingInterestRate with 2 decimal
// 15 * (lending days)/ 365 + 4% system fee + 3% LendingDev fee
function lendingInterestRatePercentage() public view returns(uint256){
return annualInterest.mul(interestBaseUint)
// current days
.mul(getDaysPassedBetweenDates(fundingEndTime, block.timestamp)).div(365)
.add(systemFees.mul(interestBaseUint))
.add(ethicHubFee.mul(interestBaseUint))
.add(interestBasePercent);
}
// lendingInterestRate with 2 decimal
function investorInterest() public view returns(uint256){
return annualInterest.mul(interestBaseUint).mul(borrowerReturnDays).div(365).add(interestBasePercent);
}
function borrowerReturnAmount() public view returns(uint256) {
uint interestGenerated = annualInterest.mul(interestBaseUint).mul(getDaysPassedBetweenDates(fundingEndTime, block.timestamp)).div(365).add(interestBasePercent);
return totalLendingAmount.mul(interestGenerated).div(interestBasePercent);
}
function isContribPeriodRunning() public view returns(bool) {
return fundingStartTime <= block.timestamp && fundingEndTime > block.timestamp && !capReached;
}
function checkInvestorContribution(address investor) public view returns(uint256){
return investors[investor].amount;
}
function checkInvestorReturns(address investor) public view returns(uint256) {
uint256 investorAmount = 0;
if (state == LendingState.ContributionReturned) {
investorAmount = investors[investor].amount;
return investorAmount.mul(investorInterest()).div(interestBasePercent);
} else if (state == LendingState.Default){
investorAmount = investors[investor].amount;
// contribution = contribution * partial_funds / total_funds
return investorAmount.mul(returnedAmount).div(totalLendingAmount);
} else {
return 0;
}
}
function getUserContributionReclaimStatus(address userAddress) public view returns(bool isCompensated){
return investors[userAddress].isCompensated;
}
function changeState(LendingState newState) internal {
state = newState;
emit StateChange(uint(newState));
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: 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
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// SPDX-License-Identifier: gpl-3.0
pragma solidity 0.8.3;
/**
* Interface for the eternal storage.
* Thanks RocketPool!
* https://github.com/rocket-pool/rocketpool/blob/master/contracts/interface/RocketStorageInterface.sol
*/
interface IEthicHubStorage {
//modifier for access in sets and deletes
modifier onlyEthicHubContracts() {_;}
// Setters
function setAddress(bytes32 _key, address _value) external;
function setUint(bytes32 _key, uint _value) external;
function setString(bytes32 _key, string calldata _value) external;
function setBytes(bytes32 _key, bytes calldata _value) external;
function setBool(bytes32 _key, bool _value) external;
function setInt(bytes32 _key, int _value) external;
// Deleters
function deleteAddress(bytes32 _key) external;
function deleteUint(bytes32 _key) external;
function deleteString(bytes32 _key) external;
function deleteBytes(bytes32 _key) external;
function deleteBool(bytes32 _key) external;
function deleteInt(bytes32 _key) external;
// Getters
function getAddress(bytes32 _key) external view returns (address);
function getUint(bytes32 _key) external view returns (uint);
function getString(bytes32 _key) external view returns (string memory);
function getBytes(bytes32 _key) external view returns (bytes memory);
function getBool(bytes32 _key) external view returns (bool);
function getInt(bytes32 _key) external view returns (int);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_setOwner(_msgSender());
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_setOwner(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_setOwner(newOwner);
}
function _setOwner(address newOwner) private {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
bool private _paused;
/**
* @dev Initializes the contract in unpaused state.
*/
constructor() {
_paused = false;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
require(!paused(), "Pausable: paused");
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
require(paused(), "Pausable: not paused");
_;
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.
/**
* @dev Wrappers over Solidity's arithmetic operations.
*
* NOTE: `SafeMath` is no longer needed starting with Solidity 0.8. The compiler
* now has built in overflow checking.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the substraction of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
// 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-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return a - b;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
return a * b;
}
/**
* @dev Returns the integer division of two unsigned integers, reverting on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator.
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return a / b;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return a % b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {trySub}.
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
unchecked {
require(b <= a, errorMessage);
return a - b;
}
}
/**
* @dev Returns the integer division of two unsigned integers, reverting with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
unchecked {
require(b > 0, errorMessage);
return a / b;
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting with custom message when dividing by zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryMod}.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
unchecked {
require(b > 0, errorMessage);
return a % b;
}
}
}
{
"compilationTarget": {
"contracts/lending/EthicHubLending.sol": "EthicHubLending"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_ethicHubStorage","type":"address"},{"components":[{"internalType":"uint256","name":"fundingStartTime","type":"uint256"},{"internalType":"uint256","name":"fundingEndTime","type":"uint256"},{"internalType":"uint256","name":"annualInterest","type":"uint256"},{"internalType":"uint256","name":"totalLendingAmount","type":"uint256"},{"internalType":"uint256","name":"lendingDays","type":"uint256"},{"internalType":"uint256","name":"ethicHubFee","type":"uint256"},{"internalType":"uint256","name":"systemFees","type":"uint256"},{"internalType":"uint256","name":"maxDelayDays","type":"uint256"}],"internalType":"struct EthicHubLending.LoanParams","name":"_loanParams","type":"tuple"},{"components":[{"internalType":"address payable","name":"borrower","type":"address"},{"internalType":"address","name":"localNode","type":"address"},{"internalType":"address payable","name":"ethicHubTeam","type":"address"},{"internalType":"address payable","name":"systemFeesCollector","type":"address"}],"internalType":"struct EthicHubLending.Actors","name":"_actors","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newBorrower","type":"address"}],"name":"BorrowerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"endTime","type":"uint256"}],"name":"CapReached","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"contributor","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Compensated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"totalContributed","type":"uint256"},{"indexed":true,"internalType":"address","name":"investor","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"investorsCount","type":"uint256"}],"name":"Contribution","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldInvestor","type":"address"},{"indexed":true,"internalType":"address","name":"newInvestor","type":"address"}],"name":"InvestorChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Reclaim","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"borrower","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ReturnAmount","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"state","type":"uint256"}],"name":"StateChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"annualInterest","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"borrower","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"borrowerReturnAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"borrowerReturnDays","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"capReached","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_oldInvestor","type":"address"},{"internalType":"address payable","name":"_newInvestor","type":"address"}],"name":"changeInvestorAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"investor","type":"address"}],"name":"checkInvestorContribution","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"investor","type":"address"}],"name":"checkInvestorReturns","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"declareProjectDefault","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"declareProjectNotFunded","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"contributor","type":"address"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"ethicHubFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ethicHubStorage","outputs":[{"internalType":"contract IEthicHubStorage","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ethicHubTeam","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ethicHubTeamFeeReclaimed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fundingEndTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fundingStartTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"firstDate","type":"uint256"},{"internalType":"uint256","name":"lastDate","type":"uint256"}],"name":"getDaysPassedBetweenDates","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"date","type":"uint256"}],"name":"getDelayDays","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"userAddress","type":"address"}],"name":"getUserContributionReclaimStatus","outputs":[{"internalType":"bool","name":"isCompensated","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"interestBasePercent","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"interestBaseUint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"investorCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"investorInterest","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"investors","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bool","name":"isCompensated","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isContribPeriodRunning","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lendingDays","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lendingInterestRatePercentage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"localNode","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxDelayDays","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address payable","name":"beneficiary","type":"address"}],"name":"reclaimContribution","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"beneficiary","type":"address"}],"name":"reclaimContributionDefault","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"beneficiary","type":"address"}],"name":"reclaimContributionWithInterest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reclaimEthicHubTeamFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reclaimLeftover","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reclaimSystemFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reclaimedContributions","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"returnBorrowed","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"returnedAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"sendFundsToBorrower","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"_borrower","type":"address"}],"name":"setBorrower","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"state","outputs":[{"internalType":"enum EthicHubLending.LendingState","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"systemFees","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"systemFeesCollector","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"systemFeesReclaimed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalContributed","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalLendingAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"}]