diff --git a/.gitignore b/.gitignore index 3c3629e..b9f5f3b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ node_modules + +.idea/ diff --git a/README.md b/README.md index 8a66db5..ffc2ca9 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,9 @@ -### Deployment +## Deployment + +### Debt Token #### Parameters @@ -13,36 +15,57 @@ |* |_tokenSymbol: | Symbol for the token | |* |_initialAmount: | Actual amount of Ether requested in WEI | |* |_exchangeRate: | Amount of tokens per Ether | -|* |_decimalUnits: | Number of Decimal places | |* |_dayLength: | Number of seconds in a day | |* |_loanTerm: | Number of days for Loan maturity; before interest begins | |* |_loanCycle: | Number of days per loan cycle | |* |_interestRate: | Interest rate (Percentage) | -|* |_debtOwner: | Lender address | +|* |_lender: | Lender address | +|* |_borrower: | Borrower address | #### Deployed values * _tokenName: Performance Global Loan * _tokenSymbol: PGLOAN * _initialAmount: 500000000000000000000 * _exchangeRate: 1 - * _decimalUnits: 18 * _dayLength: 86400 * _loanTerm: 60 * _loanCycle: 30 * _interestRate: 2 - * _debtOwner: address + * _lender: address + * _borrower: address #### Ropsten Test deployment - Most recent version of the code is deployed at: - https://ropsten.etherscan.io/token/0x4dd86d50fa0ce7b25274406b985e509ed599d76b - https://ropsten.etherscan.io/address/0x4dd86d50fa0ce7b25274406b985e509ed599d76b +Most recent version of the code is deployed at: + +https://ropsten.etherscan.io/token/0x126c694e085517c257ecdad8f46455cf0403008c +https://ropsten.etherscan.io/address/0x126c694e085517c257ecdad8f46455cf0403008c +____ + +### Debt Token Deployer + +#### Parameters + + |--|--------|--------------| + |--|--------|--------------| + |* | _dayTokenAddress: | Address of DAY Tokens | + |* |_dayTokenFees: | Number of DAY tokens, required as fees | + + #### Deployed values + * _dayTokenAddress: 0x7941bc77e1d6bd4628467b6cd3650f20f745db06 + * _dayTokenFees: 100000000000000000000 + + #### Ropsten Test deployment + Most recent version of the code is deployed at: + + https://ropsten.etherscan.io/address/0x9d396156594b6a665fe28397e7bff3679dc24283 + -### Tests +## Tests * Test actual functionality of debt-smart-contract ~~~ - truffle test test/debtToken.js + truffle test test/1_debtToken.js ~~~ - * Test that resricted Token functions are restricted : + * Test that actual functionality of debt-smart-contract deployer : ~~~ - truffle test test/standardToken.js + truffle test test/2_debtTokenDeployer.js ~~~ diff --git a/contracts/DebtToken.sol b/contracts/DebtToken.sol index ea43fab..bec3cb6 100644 --- a/contracts/DebtToken.sol +++ b/contracts/DebtToken.sol @@ -1,61 +1,93 @@ -import 'zeppelin/token/ERC20Basic.sol'; -import 'zeppelin/token/MintableToken.sol'; +import '../installed_contracts/zeppelin/contracts/math/SafeMath.sol'; -pragma solidity ^0.4.15; +pragma solidity ^0.4.18; -contract DebtToken is ERC20Basic,MintableToken{ - +contract DebtToken { + using SafeMath for uint256; /** Recognition data */ string public name; string public symbol; string public version = 'DT0.1'; - + uint256 public decimals = 18; + + /** + ERC20 properties + */ + uint256 public totalSupply; + mapping(address => uint256) public balances; + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + Mintable Token properties + */ + bool public mintingFinished = true; + event Mint(address indexed to, uint256 amount); + event MintFinished(); + /** Actual logic data */ - uint256 public decimals; - uint256 public dayLength = 86400;//Number of seconds in a day + uint256 public dayLength;//Number of seconds in a day uint256 public loanTerm;//Loan term in days uint256 public exchangeRate; //Exchange rate for Ether to loan coins uint256 public initialSupply; //Keep record of Initial value of Loan uint256 public loanActivation; //Timestamp the loan was funded - uint256 public interestRate; //Interest rate per interest cycle - uint256 public interestCycleLength = 30; //Total number of days per interest cycle - uint256 public totalInterestCycle; //Total number of interest cycles completed - uint256 public lastinterestCycle; //Keep record of Initial value of Loan - address public debtOwner; //The address from which the loan will be funded, and to which the refund will be directed - uint256 public constant divisor = 100; - - - function DebtToken(string _tokenName, + + uint256 public interestRatePerCycle; //Interest rate per interest cycle + uint256 public interestCycleLength; //Total number of days per interest cycle + + uint256 public totalInterestCycles; //Total number of interest cycles completed + uint256 public lastInterestCycle; //Keep record of Initial value of Loan + + address public lender; //The address from which the loan will be funded, and to which the refund will be directed + address public borrower; + + uint256 public constant PERCENT_DIVISOR = 100; + + function DebtToken( + string _tokenName, string _tokenSymbol, uint256 _initialAmount, uint256 _exchangeRate, - uint256 _decimalUnits, uint256 _dayLength, uint256 _loanTerm, uint256 _loanCycle, - uint256 _interestRate, - address _debtOwner + uint256 _interestRatePerCycle, + address _lender, + address _borrower ) { + + require(_exchangeRate > 0); + require(_initialAmount > 0); + require(_dayLength > 0); + require(_loanCycle > 0); + + require(_lender != 0x0); + require(_borrower != 0x0); + exchangeRate = _exchangeRate; // Exchange rate for the coins - balances[msg.sender] = _initialAmount.mul(exchangeRate); // Give the creator all initial tokens initialSupply = _initialAmount.mul(exchangeRate); // Update initial supply totalSupply = initialSupply; //Update total supply - name = _tokenName; // Set the name for display purposes - decimals = _decimalUnits; // Amount of decimals for display purposes + balances[_borrower] = initialSupply; // Give the creator all initial tokens + + name = _tokenName; // Amount of decimals for display purposes symbol = _tokenSymbol; // Set the symbol for display purposes + dayLength = _dayLength; //Set the length of each day in seconds...For dev purposes loanTerm = _loanTerm; //Set the number of days, for loan maturity interestCycleLength = _loanCycle; //set the Interest cycle period - interestRate = _interestRate; //Set the Interest rate per cycle - debtOwner = _debtOwner; //set Debt owner - mintingFinished = true; //Disable minting - Transfer(0,msg.sender,totalSupply);//Allow funding be tracked + interestRatePerCycle = _interestRatePerCycle; //Set the Interest rate per cycle + lender = _lender; //set lender address + borrower = _borrower; + + Transfer(0,_borrower,totalSupply);//Allow funding be tracked } + /** + Debt token functionality + */ function actualTotalSupply() public constant returns(uint) { uint256 coins; uint256 cycle; @@ -69,10 +101,10 @@ contract DebtToken is ERC20Basic,MintableToken{ function getLoanValue(bool initial) public constant returns(uint){ //TODO get a more dynamic way to calculate if(initial == true) - return initialSupply.mul(exchangeRate); + return initialSupply.div(exchangeRate); else{ - uint totalTokens = actualTotalSupply().sub(balances[owner]); - return totalTokens.mul(exchangeRate); + uint totalTokens = actualTotalSupply().sub(balances[borrower]); + return totalTokens.div(exchangeRate); } } @@ -84,16 +116,27 @@ contract DebtToken is ERC20Basic,MintableToken{ } /** - Check that an address is the owner of the debt or the loan contract partner + Checks that caller's address is the lender + */ + function isLender() private constant returns(bool){ + return msg.sender == lender; + } + + /** + Check that caller's address is the borrower */ - function isDebtOwner(address addr) public constant returns(bool){ - return (addr == debtOwner); + function isBorrower() private constant returns (bool){ + return msg.sender == borrower; + } + + function isLoanFunded() public constant returns(bool) { + return balances[lender] > 0 && balances[borrower] == 0; } /** Check if the loan is mature for interest */ - function loanMature() public constant returns (bool){ + function isTermOver() public constant returns (bool){ if(loanActivation == 0) return false; else @@ -103,29 +146,23 @@ contract DebtToken is ERC20Basic,MintableToken{ /** Check if updateInterest() needs to be called before refundLoan() */ - function interestStatusUpdated() public constant returns(bool){ - if(!loanMature()) + function isInterestStatusUpdated() public constant returns(bool){ + if(!isTermOver()) return true; else - return !( now >= lastinterestCycle.add( interestCycleLength.mul(dayLength) ) ); + return !( now >= lastInterestCycle.add( interestCycleLength.mul(dayLength) ) ); } - /** - - */ - /** calculate the total number of passed interest cycles and coin value */ function calculateInterestDue() public constant returns(uint256 _coins,uint256 _cycle){ - if(!loanMature()) - return (0,0); - else if(balances[debtOwner] == 0) + if(!isTermOver() || !isLoanFunded()) return (0,0); else{ - uint timeDiff = now.sub(lastinterestCycle); + uint timeDiff = now.sub(lastInterestCycle); _cycle = timeDiff.div(dayLength.mul(interestCycleLength) ); - _coins = _cycle.mul( interestRate.mul(initialSupply) ).div(divisor);//Delayed division to avoid too early floor + _coins = _cycle.mul( interestRatePerCycle.mul(initialSupply) ).div(PERCENT_DIVISOR);//Delayed division to avoid too early floor } } @@ -133,99 +170,115 @@ contract DebtToken is ERC20Basic,MintableToken{ Update the interest of the contract */ function updateInterest() public { - require( loanMature() ); + require( isTermOver() ); uint interest_coins; uint256 interest_cycle; (interest_coins,interest_cycle) = calculateInterestDue(); assert(interest_coins > 0 && interest_cycle > 0); - totalInterestCycle = totalInterestCycle.add(interest_cycle); - lastinterestCycle = lastinterestCycle.add( interest_cycle.mul( interestCycleLength.mul(dayLength) ) ); - super.mint(debtOwner , interest_coins); + totalInterestCycles = totalInterestCycles.add(interest_cycle); + lastInterestCycle = lastInterestCycle.add( interest_cycle.mul( interestCycleLength.mul(dayLength) ) ); + mint(lender , interest_coins); } /** Make payment to inititate loan */ function fundLoan() public payable{ - require(isDebtOwner(msg.sender)); - require(msg.value > 0); //Ensure input available - - uint256 weiValue = getLoanValue(true); - require(msg.value == weiValue); - require( balances[msg.sender] == 0); //Avoid double payment + require(isLender()); + require(msg.value == getLoanValue(true)); //Ensure input available + require(!isLoanFunded()); //Avoid double payment - balances[owner] = balances[owner].sub(totalSupply); - balances[msg.sender] = balances[msg.sender].add(totalSupply); loanActivation = now; //store the time loan was activated - lastinterestCycle = now.add(dayLength.mul(loanTerm) ) ; //store the date interest matures - owner.transfer(msg.value); + lastInterestCycle = now.add(dayLength.mul(loanTerm) ) ; //store the date interest matures mintingFinished = false; //Enable minting - Transfer(owner,msg.sender,totalSupply);//Allow funding be tracked + transferFrom(borrower,lender,totalSupply); + + borrower.transfer(msg.value); } /** Make payment to refund loan */ - function refundLoan() onlyOwner public payable{ - if(! interestStatusUpdated() ) + function refundLoan() onlyBorrower public payable{ + if(! isInterestStatusUpdated() ) updateInterest(); //Ensure Interest is updated - require(msg.value > 0); require(msg.value == getLoanValue(false)); + require(isLoanFunded()); - require(balances[debtOwner] > 0); - super.finishMinting() ;//Prevent further Minting + finishMinting() ;//Prevent further Minting + transferFrom(lender,borrower,totalSupply); - balances[debtOwner] = balances[debtOwner].sub(totalSupply); - balances[owner] = balances[owner].add(totalSupply); - debtOwner.transfer(msg.value); - Transfer(debtOwner,owner,totalSupply);//Allow funding be tracked + lender.transfer(msg.value); } /** - Fallback function - */ - function() public payable{ - require(initialSupply > 0);//Stop the whole process if initialSupply not set - if(msg.sender == owner && balances[msg.sender] == 0) - refundLoan(); - else if(isDebtOwner(msg.sender) && balances[msg.sender] == 0) - fundLoan(); - else revert(); //Throw if neither of cases apply, ensure no free money + Partial ERC20 functionality + */ + + function balanceOf(address _owner) public constant returns (uint256 balance) { + return balances[_owner]; } - //Disable all unwanted Features + function transferFrom(address _from, address _to, uint256 _value) internal { + require(_to != address(0)); - function transferOwnership(address newOwner) onlyOwner public { - revert(); //Disable the transferOwnership feature: Loan non-transferrable + balances[_from] = balances[_from].sub(_value); + balances[_to] = balances[_to].add(_value); + Transfer(_from, _to, _value); } - function transfer(address to, uint256 value) public returns (bool){ - revert(); //Disable the transfer feature: Loan non-transferrable - } + /** + MintableToken functionality + */ - function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { - revert(); //Disable the transferFrom feature: Loan non-transferrable + modifier canMint() { + require(!mintingFinished); + _; } - function approve(address _spender, uint256 _value) public returns (bool) { - revert(); //Disable the approve feature: Loan non-transferrable + /** + * @dev Function to mint tokens + * @param _to The address that will receive the minted tokens. + * @param _amount The amount of tokens to mint. + * @return A boolean that indicates if the operation was successful. + */ + function mint(address _to, uint256 _amount) canMint internal returns (bool) { + totalSupply = totalSupply.add(_amount); + balances[_to] = balances[_to].add(_amount); + Mint(_to, _amount); + Transfer(0x0, _to, _amount); + return true; } - function allowance(address _owner, address _spender) public constant returns (uint256) { - revert(); //Disable the allowance feature: Loan non-transferrable + /** + * @dev Function to stop minting new tokens. + * @return True if the operation was successful. + */ + function finishMinting() onlyBorrower internal returns (bool) { + mintingFinished = true; + MintFinished(); + return true; } - function increaseApproval (address _spender, uint _addedValue) public returns (bool) { - revert(); //Disable the increaseApproval feature: Loan non-transferrable - } - function decreaseApproval (address _spender, uint _subtractedValue) public returns (bool) { - revert(); //Disable the decreaseApproval feature: Loan non-transferrable + /** + Fallback function + */ + function() public payable{ + require(initialSupply > 0);//Stop the whole process if initialSupply not set + if(isBorrower()) + refundLoan(); + else if(isLender()) + fundLoan(); + else revert(); //Throw if neither of cases apply, ensure no free money } - function finishMinting() onlyOwner public returns (bool) { - revert(); //Disable the external control of finishMinting + /** + Modifiers + */ + modifier onlyBorrower() { + require(isBorrower()); + _; } - } diff --git a/contracts/DebtTokenDeployer.sol b/contracts/DebtTokenDeployer.sol new file mode 100644 index 0000000..76b0a4b --- /dev/null +++ b/contracts/DebtTokenDeployer.sol @@ -0,0 +1,49 @@ +import './DebtToken.sol'; +import '../installed_contracts/zeppelin/contracts/token/ERC20.sol'; +import '../installed_contracts/zeppelin/contracts/ownership/Ownable.sol'; + +pragma solidity ^0.4.18; +contract DebtTokenDeployer is Ownable{ + + address public dayTokenAddress; + uint public dayTokenFees; //DAY tokens to be paid for deploying custom DAY contract + ERC20 dayToken; + + event FeeUpdated(uint _fee, uint _time); + event DebtTokenCreated(address _creator, address _debtTokenAddress, uint256 _time); + + function DebtTokenDeployer(address _dayTokenAddress, uint _dayTokenFees){ + dayTokenAddress = _dayTokenAddress; + dayTokenFees = _dayTokenFees; + dayToken = ERC20(dayTokenAddress); + } + + function updateDayTokenFees(uint _dayTokenFees) onlyOwner public { + dayTokenFees = _dayTokenFees; + FeeUpdated(dayTokenFees, now); + } + + function createDebtToken(string _tokenName, + string _tokenSymbol, + uint256 _initialAmount, + uint256 _exchangeRate, + uint256 _dayLength, + uint256 _loanTerm, + uint256 _loanCycle, + uint256 _intrestRatePerCycle, + address _lender) + public + { + if(dayToken.transferFrom(msg.sender, this, dayTokenFees)){ + DebtToken newDebtToken = new DebtToken(_tokenName, _tokenSymbol, _initialAmount, _exchangeRate, + _dayLength, _loanTerm, _loanCycle, + _intrestRatePerCycle, _lender, msg.sender); + DebtTokenCreated(msg.sender, address(newDebtToken), now); + } + } + + // to collect all fees paid till now + function fetchDayTokens() onlyOwner public { + dayToken.transfer(owner, dayToken.balanceOf(this)); + } +} \ No newline at end of file diff --git a/contracts/MasterCombinedContracts.sol b/contracts/MasterCombinedContracts.sol new file mode 100644 index 0000000..bf347fc --- /dev/null +++ b/contracts/MasterCombinedContracts.sol @@ -0,0 +1,426 @@ +pragma solidity ^0.4.18; + +/** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ +library SafeMath { + function mul(uint256 a, uint256 b) internal constant returns (uint256) { + uint256 c = a * b; + assert(a == 0 || c / a == b); + return c; + } + + function div(uint256 a, uint256 b) internal constant returns (uint256) { + // assert(b > 0); // Solidity automatically throws 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; + } + + function sub(uint256 a, uint256 b) internal constant returns (uint256) { + assert(b <= a); + return a - b; + } + + function add(uint256 a, uint256 b) internal constant returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } +} + +/** + * @title ERC20Basic + * @dev Simpler version of ERC20 interface + * @dev see https://github.com/ethereum/EIPs/issues/179 + */ +contract ERC20Basic { + uint256 public totalSupply; + function balanceOf(address who) public constant returns (uint256); + function transfer(address to, uint256 value) public returns (bool); + event Transfer(address indexed from, address indexed to, uint256 value); +} + +/** + * @title ERC20 interface + * @dev see https://github.com/ethereum/EIPs/issues/20 + */ +contract ERC20 is ERC20Basic { + function allowance(address owner, address spender) public constant returns (uint256); + function transferFrom(address from, address to, uint256 value) public returns (bool); + function approve(address spender, uint256 value) public returns (bool); + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +pragma solidity ^0.4.11; + + +/** + * @title Ownable + * @dev The Ownable contract has an owner address, and provides basic authorization control + * functions, this simplifies the implementation of "user permissions". + */ +contract Ownable { + address public owner; + + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + + /** + * @dev The Ownable constructor sets the original `owner` of the contract to the sender + * account. + */ + function Ownable() { + owner = msg.sender; + } + + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(msg.sender == owner); + _; + } + + + /** + * @dev Allows the current owner to transfer control of the contract to a newOwner. + * @param newOwner The address to transfer ownership to. + */ + function transferOwnership(address newOwner) onlyOwner public { + require(newOwner != address(0)); + OwnershipTransferred(owner, newOwner); + owner = newOwner; + } + +} + + +contract DebtToken { + using SafeMath for uint256; + /** + Recognition data + */ + string public name; + string public symbol; + string public version = 'DT0.1'; + uint256 public decimals = 18; + + /** + ERC20 properties + */ + uint256 public totalSupply; + mapping(address => uint256) public balances; + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + Mintable Token properties + */ + bool public mintingFinished = true; + event Mint(address indexed to, uint256 amount); + event MintFinished(); + + /** + Actual logic data + */ + uint256 public dayLength;//Number of seconds in a day + uint256 public loanTerm;//Loan term in days + uint256 public exchangeRate; //Exchange rate for Ether to loan coins + uint256 public initialSupply; //Keep record of Initial value of Loan + uint256 public loanActivation; //Timestamp the loan was funded + + uint256 public interestRatePerCycle; //Interest rate per interest cycle + uint256 public interestCycleLength; //Total number of days per interest cycle + + uint256 public totalInterestCycles; //Total number of interest cycles completed + uint256 public lastInterestCycle; //Keep record of Initial value of Loan + + address public lender; //The address from which the loan will be funded, and to which the refund will be directed + address public borrower; + + uint256 public constant PERCENT_DIVISOR = 100; + + function DebtToken( + string _tokenName, + string _tokenSymbol, + uint256 _initialAmount, + uint256 _exchangeRate, + uint256 _dayLength, + uint256 _loanTerm, + uint256 _loanCycle, + uint256 _interestRatePerCycle, + address _lender, + address _borrower + ) { + + require(_exchangeRate > 0); + require(_initialAmount > 0); + require(_dayLength > 0); + require(_loanCycle > 0); + + require(_lender != 0x0); + require(_borrower != 0x0); + + exchangeRate = _exchangeRate; // Exchange rate for the coins + initialSupply = _initialAmount.mul(exchangeRate); // Update initial supply + totalSupply = initialSupply; //Update total supply + balances[_borrower] = initialSupply; // Give the creator all initial tokens + + name = _tokenName; // Amount of decimals for display purposes + symbol = _tokenSymbol; // Set the symbol for display purposes + + dayLength = _dayLength; //Set the length of each day in seconds...For dev purposes + loanTerm = _loanTerm; //Set the number of days, for loan maturity + interestCycleLength = _loanCycle; //set the Interest cycle period + interestRatePerCycle = _interestRatePerCycle; //Set the Interest rate per cycle + lender = _lender; //set lender address + borrower = _borrower; + + Transfer(0,_borrower,totalSupply);//Allow funding be tracked + } + + /** + Debt token functionality + */ + function actualTotalSupply() public constant returns(uint) { + uint256 coins; + uint256 cycle; + (coins,cycle) = calculateInterestDue(); + return totalSupply.add(coins); + } + + /** + Fetch total value of loan in wei (Initial +interest) + */ + function getLoanValue(bool initial) public constant returns(uint){ + //TODO get a more dynamic way to calculate + if(initial == true) + return initialSupply.div(exchangeRate); + else{ + uint totalTokens = actualTotalSupply().sub(balances[borrower]); + return totalTokens.div(exchangeRate); + } + } + + /** + Fetch total coins gained from interest + */ + function getInterest() public constant returns (uint){ + return actualTotalSupply().sub(initialSupply); + } + + /** + Checks that caller's address is the lender + */ + function isLender() private constant returns(bool){ + return msg.sender == lender; + } + + /** + Check that caller's address is the borrower + */ + function isBorrower() private constant returns (bool){ + return msg.sender == borrower; + } + + function isLoanFunded() public constant returns(bool) { + return balances[lender] > 0 && balances[borrower] == 0; + } + + /** + Check if the loan is mature for interest + */ + function isTermOver() public constant returns (bool){ + if(loanActivation == 0) + return false; + else + return now >= loanActivation.add( dayLength.mul(loanTerm) ); + } + + /** + Check if updateInterest() needs to be called before refundLoan() + */ + function isInterestStatusUpdated() public constant returns(bool){ + if(!isTermOver()) + return true; + else + return !( now >= lastInterestCycle.add( interestCycleLength.mul(dayLength) ) ); + } + + /** + calculate the total number of passed interest cycles and coin value + */ + function calculateInterestDue() public constant returns(uint256 _coins,uint256 _cycle){ + if(!isTermOver() || !isLoanFunded()) + return (0,0); + else{ + uint timeDiff = now.sub(lastInterestCycle); + _cycle = timeDiff.div(dayLength.mul(interestCycleLength) ); + _coins = _cycle.mul( interestRatePerCycle.mul(initialSupply) ).div(PERCENT_DIVISOR);//Delayed division to avoid too early floor + } + } + + /** + Update the interest of the contract + */ + function updateInterest() public { + require( isTermOver() ); + uint interest_coins; + uint256 interest_cycle; + (interest_coins,interest_cycle) = calculateInterestDue(); + assert(interest_coins > 0 && interest_cycle > 0); + totalInterestCycles = totalInterestCycles.add(interest_cycle); + lastInterestCycle = lastInterestCycle.add( interest_cycle.mul( interestCycleLength.mul(dayLength) ) ); + mint(lender , interest_coins); + } + + /** + Make payment to inititate loan + */ + function fundLoan() public payable{ + require(isLender()); + require(msg.value == getLoanValue(true)); //Ensure input available + require(!isLoanFunded()); //Avoid double payment + + loanActivation = now; //store the time loan was activated + lastInterestCycle = now.add(dayLength.mul(loanTerm) ) ; //store the date interest matures + mintingFinished = false; //Enable minting + transferFrom(borrower,lender,totalSupply); + + borrower.transfer(msg.value); + } + + /** + Make payment to refund loan + */ + function refundLoan() onlyBorrower public payable{ + if(! isInterestStatusUpdated() ) + updateInterest(); //Ensure Interest is updated + + require(msg.value == getLoanValue(false)); + require(isLoanFunded()); + + finishMinting() ;//Prevent further Minting + transferFrom(lender,borrower,totalSupply); + + lender.transfer(msg.value); + } + + /** + Partial ERC20 functionality + */ + + function balanceOf(address _owner) public constant returns (uint256 balance) { + return balances[_owner]; + } + + function transferFrom(address _from, address _to, uint256 _value) internal { + require(_to != address(0)); + + balances[_from] = balances[_from].sub(_value); + balances[_to] = balances[_to].add(_value); + Transfer(_from, _to, _value); + } + + /** + MintableToken functionality + */ + + modifier canMint() { + require(!mintingFinished); + _; + } + + /** + * @dev Function to mint tokens + * @param _to The address that will receive the minted tokens. + * @param _amount The amount of tokens to mint. + * @return A boolean that indicates if the operation was successful. + */ + function mint(address _to, uint256 _amount) canMint internal returns (bool) { + totalSupply = totalSupply.add(_amount); + balances[_to] = balances[_to].add(_amount); + Mint(_to, _amount); + Transfer(0x0, _to, _amount); + return true; + } + + /** + * @dev Function to stop minting new tokens. + * @return True if the operation was successful. + */ + function finishMinting() onlyBorrower internal returns (bool) { + mintingFinished = true; + MintFinished(); + return true; + } + + + /** + Fallback function + */ + function() public payable{ + require(initialSupply > 0);//Stop the whole process if initialSupply not set + if(isBorrower()) + refundLoan(); + else if(isLender()) + fundLoan(); + else revert(); //Throw if neither of cases apply, ensure no free money + } + + /** + Modifiers + */ + modifier onlyBorrower() { + require(isBorrower()); + _; + } +} + +contract DebtTokenDeployer is Ownable{ + + address public dayTokenAddress; + uint public dayTokenFees; //DAY tokens to be paid for deploying custom DAY contract + ERC20 dayToken; + + event FeeUpdated(uint _fee, uint _time); + event DebtTokenCreated(address _creator, address _debtTokenAddress, uint256 _time); + + function DebtTokenDeployer(address _dayTokenAddress, uint _dayTokenFees){ + dayTokenAddress = _dayTokenAddress; + dayTokenFees = _dayTokenFees; + dayToken = ERC20(dayTokenAddress); + } + + function updateDayTokenFees(uint _dayTokenFees) onlyOwner public { + dayTokenFees = _dayTokenFees; + FeeUpdated(dayTokenFees, now); + } + + function createDebtToken(string _tokenName, + string _tokenSymbol, + uint256 _initialAmount, + uint256 _exchangeRate, + uint256 _dayLength, + uint256 _loanTerm, + uint256 _loanCycle, + uint256 _intrestRatePerCycle, + address _lender) + public + { + if(dayToken.transferFrom(msg.sender, this, dayTokenFees)){ + DebtToken newDebtToken = new DebtToken(_tokenName, _tokenSymbol, _initialAmount, _exchangeRate, + _dayLength, _loanTerm, _loanCycle, + _intrestRatePerCycle, _lender, msg.sender); + DebtTokenCreated(msg.sender, address(newDebtToken), now); + } + } + + // to collect all fees paid till now + function fetchDayTokens() onlyOwner public { + dayToken.transfer(owner, dayToken.balanceOf(this)); + } +} diff --git a/contracts/SimpleToken.sol b/contracts/SimpleToken.sol new file mode 100644 index 0000000..2b7d0d5 --- /dev/null +++ b/contracts/SimpleToken.sol @@ -0,0 +1,29 @@ +pragma solidity ^0.4.11; + + +import "../installed_contracts/zeppelin/contracts/token/StandardToken.sol"; + + +/** + * @title SimpleToken + * @dev Very simple ERC20 Token example, where all tokens are pre-assigned to the creator. + * Note they can later distribute these tokens as they wish using `transfer` and other + * `StandardToken` functions. + */ +contract SimpleToken is StandardToken { + + string public constant name = "SimpleToken"; + string public constant symbol = "SIM"; + uint8 public constant decimals = 18; + + uint256 public constant INITIAL_SUPPLY = 10000 * (10 ** uint256(decimals)); + + /** + * @dev Constructor that gives msg.sender all of existing tokens. + */ + function SimpleToken() { + totalSupply = INITIAL_SUPPLY; + balances[msg.sender] = INITIAL_SUPPLY; + } + +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a1e7225 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "debt-smart-contract", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "bignumber.js": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-5.0.0.tgz", + "integrity": "sha512-KWTu6ZMVk9sxlDJQh2YH1UOnfDP8O8TpxUxgQG/vKASoSnEjK9aVuOueFaPcQEYQ5fyNXNTOYwYw3099RYebWg==" + } + } +} diff --git a/package.json b/package.json index 856179b..9e114d8 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,6 @@ }, "homepage": "https://github.com/chronologic/debt-smart-contract#readme", "dependencies": { - "package-name": "^0.1.0" + "bignumber.js": "^5.0.0" } } diff --git a/test/debtToken.js b/test/1_debtToken.js similarity index 68% rename from test/debtToken.js rename to test/1_debtToken.js index cd6f511..7d06752 100644 --- a/test/debtToken.js +++ b/test/1_debtToken.js @@ -9,18 +9,29 @@ contract('DebtToken', function(accounts){ var deployment_config = { _tokenName: 'Performance Global Loan', _tokenSymbol: 'PGLOAN', - _initialAmount: 500000000000000000, - //_initialAmount: 500000000000000000000,//wei value of initial loan + _initialAmount: 0.5*_1ether, _exchangeRate: 1, - _decimalUnits: 18, - //_dayLength: 86400, _dayLength: 10, _loanTerm: 60, _loanCycle: 20, - _interestRate: 2, - _debtOwner: accounts[1] + _interestRatePerCycle: 2, + _lender: accounts[1], + _borrower: Me }, - unit = Math.pow(10,deployment_config._decimalUnits); + deployNewDebtContract = function(){ + return DebtToken.new( + deployment_config._tokenName, + deployment_config._tokenSymbol, + deployment_config._initialAmount, + deployment_config._exchangeRate, + deployment_config._dayLength, + deployment_config._loanTerm, + deployment_config._loanCycle, + deployment_config._interestRatePerCycle, + deployment_config._lender, + deployment_config._borrower + ); + }; function forceMine(time){ web3.currentProvider.send({jsonrpc: "2.0", method: "evm_increaseTime", params: [time], id: 123}); @@ -28,18 +39,7 @@ contract('DebtToken', function(accounts){ } it('should deploy the contract', function (done) { - DebtToken.new( - deployment_config._tokenName, - deployment_config._tokenSymbol, - deployment_config._initialAmount, - deployment_config._exchangeRate, - deployment_config._decimalUnits, - deployment_config._dayLength, - deployment_config._loanTerm, - deployment_config._loanCycle, - deployment_config._interestRate, - deployment_config._debtOwner - ) + deployNewDebtContract() .then(function(inst){ contract = inst.contract; web3 = inst.constructor.web3; @@ -53,7 +53,7 @@ contract('DebtToken', function(accounts){ console.log('Symbol:', r); }); contract.totalSupply(function(e,r){ - console.log('totalSupply:', r); + console.log('totalSupply:', Number(r)); }); assert.notEqual(contract.address, null, 'Contract not successfully deployed'); @@ -61,56 +61,82 @@ contract('DebtToken', function(accounts){ }); }); - describe('Loan Activation',function(){ + describe('Exchange Rate:: ',function(){ - it('Should fail to send wrong amount to the contract from non-debtOwner',function(done){ + it('Should test the Exchange Rate functionality',function(done){ + var exchange = Math.floor(Math.random()*10); + console.log(exchange); + DebtToken.new( + deployment_config._tokenName, + deployment_config._tokenSymbol, + deployment_config._initialAmount, + exchange, + deployment_config._dayLength, + deployment_config._loanTerm, + deployment_config._loanCycle, + deployment_config._interestRatePerCycle, + deployment_config._lender, + deployment_config._borrower + ) + .then(function(inst){ + var loanVal = inst.contract.getLoanValue.call(true); + assert.equal( Number(loanVal) , deployment_config._initialAmount, 'Exchange wrongly calculated'); + done(); + }) + }) + + }) + + describe('Loan Activation:: ',function(){ + + it('Should fail to send wrong amount to the contract from non-lender',function(done){ var _value = web3.toWei(2, 'ether'); web3.eth.sendTransaction({from:accounts[2],to:contract.address,value:_value},function(e,r){ - assert.notEqual(e,null,'Wrong amount used to fund loan by non-debtOwner'); + assert.notEqual(e,null,'Wrong amount used to fund loan by non-lender'); done(); }); }); - it('Should fail to send right amount to the contract from non-debtOwner',function(done){ + it('Should fail to send right amount to the contract from non-lender',function(done){ var _value = deployment_config._initialAmount; web3.eth.sendTransaction({from:accounts[2],to:contract.address,value:_value},function(e,r){ - assert.notEqual(e,null,'Loan funded by non-debtOwner'); + assert.notEqual(e,null,'Loan funded by non-lender'); done(); }); }); - it('Should fail to send wrong amount to the contract from debtOwner',function(done){ + it('Should fail to send wrong amount to the contract from lender',function(done){ var _value = web3.toWei(2, 'ether'), - debtOwner = deployment_config._debtOwner; - web3.eth.sendTransaction({from:debtOwner,to:contract.address,value:_value},function(e,r){ + lender = deployment_config._lender; + web3.eth.sendTransaction({from:lender,to:contract.address,value:_value},function(e,r){ assert.notEqual(e,null,'Wrong amount used to fund loan'); done(); }); }); - it('Should send right amount to the contract from debtOwner',function(done){ - var _debtOwner = contract.debtOwner.call(), + it('Should send right amount to the contract from lender',function(done){ + var _lender = contract.lender.call(), _value = contract.getLoanValue.call(true), _mybal = web3.eth.getBalance(Me), - txn = {from:_debtOwner,to:contract.address,value: _value, gas: 210000 }; + txn = {from:_lender,to:contract.address,value: _value, gas: 210000 }; web3.eth.sendTransaction(txn,function(e,r){ - var balance = contract.balanceOf.call(_debtOwner), + var balance = contract.balanceOf.call(_lender), totalSupply = contract.actualTotalSupply.call(); _mynewbal = web3.eth.getBalance(Me); - assert.equal(e,null,'Loan not successfully funded by debtOwner'); - assert.equal(Number(balance),Number(totalSupply),'Wrong number of tokens assigned to debtOwner'); + assert.equal(e,null,'Loan not successfully funded by lender'); + assert.equal(Number(balance),Number(totalSupply),'Wrong number of tokens assigned to lender'); assert.equal(Number(_mynewbal),Number(_mybal)+ deployment_config._initialAmount,'Wrong value of Ether sent to Owner'); done(); }); }); }) - describe('Interest Accruing ',function(){ - it('Should fetch interestUpdated satus',function(){ - var interestStatusUpdated = contract.interestStatusUpdated.call(); + describe('Interest Accruing:: ',function(){ + it('Should fetch isInterestStatusUpdated status',function(){ + var isInterestStatusUpdated = contract.isInterestStatusUpdated.call(); - assert.notEqual(interestStatusUpdated,null, 'Did not successfully fetch interestStatusUpdated value, instead "'+interestStatusUpdated+'"'); + assert.notEqual(isInterestStatusUpdated,null, 'Did not successfully fetch isInterestStatusUpdated value, instead "'+isInterestStatusUpdated+'"'); }) it('Should run updateInterest function from any address',function(done){ @@ -126,7 +152,7 @@ contract('DebtToken', function(accounts){ var time = deployment_config._loanTerm*deployment_config._dayLength*1000; forceMine(time); - assert.equal(contract.loanMature.call(),true,'Loan not mature in due time ( '+web3.eth.getBlock('latest').timestamp+' )'); + assert.equal(contract.isTermOver.call(),true,'Loan tern has not over ( '+web3.eth.getBlock('latest').timestamp+' )'); doUpdate(); }) @@ -160,15 +186,13 @@ contract('DebtToken', function(accounts){ }) it('Should fail to allow owner run finishMinting function',function(done){ - contract.finishMinting({from:Me},function(e,r){ - assert.notEqual(e,null,'Owner successfuly prevented minting without refunding Loan'); - done(); - }); + assert.isNotOk(contract.finishMinting, "finishMiting shall be available internally only"); + done(); }) }) - describe('Loan Refund',function(){ + describe('Loan Refund:: ',function(){ it('Should fail to refund amount diffferent from total due',function(done){ var _value = contract.getLoanValue.call(true);//fetch the initial loan value web3.eth.sendTransaction({from:Me,to:contract.address,value:_value},function(e,r){ @@ -187,88 +211,76 @@ contract('DebtToken', function(accounts){ it('Should successfully refund correct amount',function(done){ var _value = contract.getLoanValue.call(false),//fetch the initial loan value - _debtOwner = contract.debtOwner.call(), - _debtownerbal = web3.eth.getBalance(_debtOwner); + _lender = contract.lender.call(), + _lenderBalance = web3.eth.getBalance(_lender); - web3.eth.sendTransaction({from:Me,to:contract.address,value:_value},function(e,r){ + web3.eth.sendTransaction({from:Me,to:contract.address,value:_value,gas:4000000},function(e,r){ var balance = contract.balanceOf.call(Me), totalSupply = contract.actualTotalSupply.call(); - _debtownernewbal = web3.eth.getBalance(_debtOwner); + _debtownernewbal = web3.eth.getBalance(_lender); assert.equal(e,null,'Loan not successfully refunded by Owner'); assert.equal(Number(balance),Number(totalSupply),'Wrong number of tokens refunded to Owner'); - assert.equal(Number(_debtownernewbal),Number(_debtownerbal)+ Number(_value),'Wrong value of Ether sent to debtOwner'); + assert.equal(Number(_debtownernewbal),Number(_lenderBalance)+ Number(_value),'Wrong value of Ether sent to lender'); done(); }); }) it('Should successfully refund before contract maturation',function(done){ - - DebtToken.new( - deployment_config._tokenName, - deployment_config._tokenSymbol, - deployment_config._initialAmount, - deployment_config._exchangeRate, - deployment_config._decimalUnits, - deployment_config._dayLength, - deployment_config._loanTerm, - deployment_config._loanCycle, - deployment_config._interestRate, - deployment_config._debtOwner - ) + deployNewDebtContract() .then(function(inst){ newcontract = inst.contract; assert.notEqual(contract.address, null, 'Contract not successfully deployed'); - var _debtOwner = newcontract.debtOwner.call(), + var _lender = newcontract.lender.call(), _value = newcontract.getLoanValue.call(true), _mybal = web3.eth.getBalance(Me), - txn = {from:_debtOwner,to:newcontract.address,value: _value, gas: 210000 }; + txn = {from:_lender,to:newcontract.address,value: _value, gas: 210000 }; web3.eth.sendTransaction(txn,function(e,r){ - var _debtOwner = newcontract.debtOwner.call(), - balance = newcontract.balanceOf.call(_debtOwner), + var _lender = newcontract.lender.call(), + balance = newcontract.balanceOf.call(_lender), totalSupply = newcontract.actualTotalSupply.call(); _mynewbal = web3.eth.getBalance(Me); - assert.equal(e,null,'Loan not successfully funded by debtOwner'); - assert.equal(Number(balance),Number(totalSupply),'Wrong number of tokens assigned to debtOwner'); + assert.equal(e,null,'Loan not successfully funded by lender'); + assert.equal(Number(balance),Number(totalSupply),'Wrong number of tokens assigned to lender'); assert.equal(Number(_mynewbal),Number(_mybal)+ deployment_config._initialAmount,'Wrong value of Ether sent to Owner'); var _value = newcontract.getLoanValue.call(false),//fetch the initial loan value - _debtOwner = newcontract.debtOwner.call(), - _debtownerbal = web3.eth.getBalance(_debtOwner); - console.log('Loan Maturity:', newcontract.loanMature.call() ); + _lender = newcontract.lender.call(), + _lenderBalance = web3.eth.getBalance(_lender); + console.log('Loan term over:', newcontract.isTermOver.call() ); web3.eth.sendTransaction({from:Me,to:newcontract.address,value:_value},function(e,r){ var balance = newcontract.balanceOf.call(Me), totalSupply = newcontract.actualTotalSupply.call(); - _debtownernewbal = web3.eth.getBalance(_debtOwner); + _debtownernewbal = web3.eth.getBalance(_lender); assert.equal(e,null,'Loan not successfully refunded by Owner'); assert.equal(Number(balance),Number(totalSupply),'Wrong number of tokens refunded to Owner'); - assert.equal(Number(_debtownernewbal),Number(_debtownerbal)+ Number(_value),'Wrong value of Ether sent to debtOwner'); + assert.equal(Number(_debtownernewbal),Number(_lenderBalance)+ Number(_value),'Wrong value of Ether sent to lender'); done(); }); }); }); }); - + it('Should confirm loanValue does not increase after refundLoan',function(done){ var time = deployment_config._loanCycle*2*deployment_config._dayLength*1000; forceMine(time); - + totalSupply = contract.totalSupply.call(), actualTotalSupply = contract.actualTotalSupply.call(); - + newtotalSupply = newcontract.totalSupply.call(), newactualTotalSupply = newcontract.actualTotalSupply.call(); - - assert.equal( Number(totalSupply), Number(actualTotalSupply, 'Loan increased from '+totalSupply+' to '+actualTotalSupply+' after loan was repaid') ); - assert.equal( Number(newtotalSupply), Number(newactualTotalSupply, 'New Loan increased from '+newtotalSupply+' to '+newactualTotalSupply+' after loan was repaid') ); + + assert.equal( Number(totalSupply), Number(actualTotalSupply), 'Loan increased from '+totalSupply+' to '+actualTotalSupply+' after loan was repaid'); + assert.equal( Number(newtotalSupply), Number(newactualTotalSupply), 'New Loan increased from '+newtotalSupply+' to '+newactualTotalSupply+' after loan was repaid'); done(); }) - + }) }); diff --git a/test/2_debtTokenDeployer.js b/test/2_debtTokenDeployer.js new file mode 100644 index 0000000..a44ec10 --- /dev/null +++ b/test/2_debtTokenDeployer.js @@ -0,0 +1,175 @@ +var BigNumber = require('bignumber.js') + +const DebtTokenDeployer = artifacts.require('./DebtTokenDeployer.sol') +const SimpleToken = artifacts.require('./SimpleToken.sol') + +contract('DebtTokenDeployer', accounts => { + var deploymentConfig = { + _tokenName: 'Performance Global Loan', + _tokenSymbol: 'PGLOAN', + _initialAmount: web3.toWei('0.5'), + _exchangeRate: 1, + _decimalUnits: 18, + _dayLength: 10, + _loanTerm: 60, + _loanCycle: 20, + _interestRatePerCycle: 2, + _lender: accounts[1], + _borrower: accounts[3], + _deploymentFee: new BigNumber('10e18'), + } + + function deployDebtTokenDeployer(tokenAddress) { + return DebtTokenDeployer.new(tokenAddress, deploymentConfig._deploymentFee) + } + + async function grantFeeTokens(token, deployerAddress, amount) { + await token.transfer(deploymentConfig._borrower, deploymentConfig._deploymentFee.times(2), {from: accounts[0]}) + await token.approve(deployerAddress, amount, {from: deploymentConfig._borrower}) + } + + function createDebtToken(debtTokenDeployer) { + return debtTokenDeployer.createDebtToken( + deploymentConfig._tokenName, + deploymentConfig._tokenSymbol, + deploymentConfig._initialAmount, + deploymentConfig._exchangeRate, + ///deploymentConfig._decimalUnits, + deploymentConfig._dayLength, + deploymentConfig._loanTerm, + deploymentConfig._loanCycle, + deploymentConfig._interestRatePerCycle, + deploymentConfig._lender, + { from: deploymentConfig._borrower } + ) + } + + let simpleToken, debtTokenDeployer + + beforeEach(async function () { + simpleToken = await SimpleToken.new() + debtTokenDeployer = await deployDebtTokenDeployer(simpleToken.address) + }) + + it('should deploy the contract', async () => { + assert.notEqual(debtTokenDeployer.address, null, 'Contract not successfully deployed') + assert.isTrue(deploymentConfig._deploymentFee.equals(debtTokenDeployer.contract.dayTokenFees())) + assert.equal(simpleToken.address, debtTokenDeployer.contract.dayTokenAddress()) + }) + + describe('Deployer Ownership::',function(){ + var _owner = accounts[0]; + var _newOwner = accounts[1]; + + it('should fail to transfer Ownership from Rogue Address',async () => { + try{ + await debtTokenDeployer.transferOwnership(accounts[2], {from: _newOwner}); + assert.fail("Rogue address successfully transferred ownership"); + } + catch(e){ + assert.notEqual(e,null,"Rogue address unable to transfer ownership"); + } + }) + + it('should transfer Ownership',async () => { + try{ + await debtTokenDeployer.transferOwnership(_newOwner, {from: _owner}) + var newOwner = await debtTokenDeployer.owner.call(); + assert.equal(_newOwner,newOwner, 'Ownership transferred to wrong address'); + } + catch(e){ + assert.equal(e,null,"Owner unable to transfer ownership") + } + }) + + }) + + describe('Deployer Fees Update::',function(){ + it('should only allow owner to change the fee for deployment', async () => { + const newFee = deploymentConfig._deploymentFee.times(2) + const owner = await debtTokenDeployer.owner() + const caller = accounts[1] + + assert.notEqual(owner, caller) + + try { + await debtTokenDeployer.updateDayTokenFees(newFee, {from: caller}) + } catch(error) { + assert.isNotNull(error) + return + } + + assert.fail("Contract should only owner to change the fee") + }) + + it('should allow to change the fee for deployment', async () => { + const newFee = deploymentConfig._deploymentFee.times(2) + const tx = await debtTokenDeployer.updateDayTokenFees(newFee) + + const foundEvent = tx.logs.find(ev => ev.event == 'FeeUpdated'); + + assert.isTrue(foundEvent.args._fee.equals(newFee)); + }) + + it('should fail when no fee was sent before deployment', async () => { + try { + await createDebtToken(debtTokenDeployer) + } catch (error) { + assert.isNotNull(error) + return + } + + assert.fail("Contract deployment passed without the fee") + }) + }) + + describe('Create Debt Token::',function(){ + it('should fail when fee was to small', async () => { + const fee = deploymentConfig._deploymentFee.minus("1e18") + await grantFeeTokens(simpleToken, debtTokenDeployer.address, fee) + + try { + await createDebtToken(debtTokenDeployer) + } catch (error) { + assert.isNotNull(error) + return + } + + assert.fail("Contract deployment passed with smaller fee") + }) + + it('should pass when fee was to exactly as required', async () => { + const fee = deploymentConfig._deploymentFee + + await grantFeeTokens(simpleToken, debtTokenDeployer.address, fee) + const tx = await createDebtToken(debtTokenDeployer) + + const foundEvent = tx.logs.find(ev => ev.event == 'DebtTokenCreated') + + assert.isOk(foundEvent); + assert.equal(foundEvent.args._creator, deploymentConfig._borrower, "Borrower shall be set as contract creator") + assert.isOk(foundEvent.args._debtTokenAddress) + assert.isNumber(foundEvent.args._time.toNumber()) + }) + + it('should take no more than transaction fee even if was granted more', async () => { + const fee = deploymentConfig._deploymentFee.times(2) + + await grantFeeTokens(simpleToken, debtTokenDeployer.address, fee) // we always granting 2*deployment fee to borrower + const tx = await createDebtToken(debtTokenDeployer) + + const contractTokenBalance = await simpleToken.balanceOf(debtTokenDeployer.address) + const borrowerTokenBalanceAfterDeployment = await simpleToken.balanceOf(deploymentConfig._borrower) + + const foundEvent = tx.logs.find(ev => ev.event == 'DebtTokenCreated') + + assert.isOk(foundEvent); + assert.equal(foundEvent.args._creator, deploymentConfig._borrower, "Borrower shall be set as contract creator") + assert.isOk(foundEvent.args._debtTokenAddress); + assert.isNumber(foundEvent.args._time.toNumber()) + + assert.isTrue(deploymentConfig._deploymentFee.equals(contractTokenBalance)) + assert.isTrue(deploymentConfig._deploymentFee.equals(borrowerTokenBalanceAfterDeployment)) + }) + }); +}) diff --git a/test/standardToken.js b/test/standardToken.js deleted file mode 100644 index 832b083..0000000 --- a/test/standardToken.js +++ /dev/null @@ -1,204 +0,0 @@ -//implementation of the Base standard token test -//https://github.com/adibas03/Solidity-truffle-tests/blob/master/test/03.%20StandardToken.js - -var DebtToken = artifacts.require('./DebtToken.sol');// Import contract of StandarTOken type - -describe('3. StandardToken :: ', function () { - const _1ether = 1e+18; - var contract,web3,Me,accounts; - - it('should deploy the contract', function (done) { - DebtToken.new() - .then(function(inst){ - contract = inst.contract; - web3 = inst.constructor.web3; - accounts = inst.constructor.web3.eth.accounts; - Me = accounts[0]; - - //console.log('Contract',contract); - - assert.notEqual(contract.address, null, 'Contract not successfully deployed'); - done(); - }); - }); - - describe('Allocate Tokens ::',function(){ - - it('Should fail to allocate Negative value tokens (Underflow)', function(done){ - var oldallowance = Number(contract.allowance.call(Me,accounts[1]) ); - - contract.approve(accounts[1],-1,{from:Me}, - function(err,res){ - var newallowance = Number(contract.allowance.call(Me,accounts[1]) ); - assert.equal(err , null , 'Allocated Negative value tokens'); - assert.equal(newallowance , -1 , 'Allocated '+newallowance+' instead of -1'); - done(); - }) - }) - - it('Should Restore allocated value to 0', function(done){ - var oldallowance = Number(contract.allowance.call(Me,accounts[1]) ); - - contract.approve(accounts[1],0,{from:Me}, - function(err,res){ - var newallowance = Number(contract.allowance.call(Me,accounts[1]) ); - assert.equal(newallowance > oldallowance , false , 'Failed to allocate 0'); - assert.equal(newallowance , 0 , 'Allocated '+newallowance+' instead of 0'); - done(); - }) - }); - - it('Should allocate Non-existent tokens', function(done){ - var oldallowance = Number(contract.allowance.call(accounts[1],accounts[2]) ); - - contract.approve(accounts[2],10,{from:accounts[1]}, - function(err,res){ - var newallowance = Number(contract.allowance.call(accounts[1],accounts[2]) ); - assert.equal(newallowance > oldallowance , true , 'Failed to allocated tokens'); - assert.equal(newallowance , 10 , 'Allocated '+newallowance+' instead of 10'); - done(); - }) - }) - - it('Should Fail to allocate new value without updating allocation to 0', function(done){ - var oldallowance = Number(contract.allowance.call(accounts[1],accounts[2]) ); - - contract.approve(accounts[2],10+oldallowance,{from:accounts[1]}, - function(err,res){ - var newallowance = Number(contract.allowance.call(accounts[1],accounts[2]) ); - assert.notEqual(err , null, 'Allocated '+ newallowance+ ' from previous allocation of '+oldallowance); - done(); - }) - }) - - it('Should fail to spend allocated Non-existent tokens', function(done){ - var allowance = Number(contract.allowance.call(accounts[1],accounts[2]) ); - - contract.transferFrom(accounts[1],accounts[3],allowance,{from:accounts[2]}, - function(err,res){ - assert.notEqual(err , null, 'Transfer Non-existent coins via Proxy'); - done(); - }) - }) - - it('Should fail to spend Non-allocated tokens', function(done){ - var allowance = Number(contract.allowance.call(Me,accounts[2]) ); - var balance = Number(contract.balanceOf.call(Me) ); - - contract.transferFrom(Me,accounts[1],10,{from:accounts[2]}, - function(err,res){ - assert.notEqual(err , null, 'Transfer Non-allocated coins'); - done(); - }) - }) - - it('Should fail to raise allocation by Negative value (possibly underflow)', function(done){ - var allowance = Number(contract.allowance.call(accounts[2], accounts[3]) ); - - contract.increaseApproval(accounts[3],-1,{from:accounts[2]}, - function(err,res){ - var newallowance = Number(contract.allowance.call(accounts[2], accounts[3]) ); - assert.notEqual(err , null, 'Increased allocation by negative value: from '+allowance+' to '+newallowance); - done(); - }) - }) - - it('Should fail to reduce allocation by Negative value', function(done){ - var allowance = Number(contract.allowance.call(accounts[2], accounts[3]) ); - - contract.decreaseApproval(accounts[3] ,-1,{from:accounts[2]}, - function(err,res){ - var newallowance = Number(contract.allowance.call(accounts[2], accounts[3]) ); - assert.notEqual(err , null, 'Reduced allocation by negative value: from '+allowance+' to '+newallowance); - done(); - }) - }) - - it('Should successfully raise allocation', function(done){ - var allowance = Number(contract.allowance.call(accounts[2], accounts[3]) ); - - contract.increaseApproval(accounts[3],10,{from:accounts[2]}, - function(err,res){ - var newallowance = Number(contract.allowance.call(accounts[2], accounts[3]) ); - assert.equal(err , null, 'Failed to raise allowance'); - assert.equal(newallowance , allowance+10, 'Raised allocation by wrong value from '+allowance+' to '+newallowance); - done(); - }) - }) - - it('Should successfully reduce allocation', function(done){ - var allowance = Number(contract.allowance.call(accounts[2], accounts[3]) ); - - contract.decreaseApproval(accounts[3],5,{from:accounts[2]}, - function(err,res){ - var newallowance = Number(contract.allowance.call(accounts[2], accounts[3]) ); - assert.equal(err , null, 'Failed to rareduce allowance'); - assert.equal(newallowance , allowance-5, 'Reduced allocation by wrong value from '+allowance+' to '+newallowance); - done(); - }) - }) - - }) - - describe('Transfer Tokens ::',function(){ - - it('Should fail to spend Negative token value from one rogue address to another (Underflow)',function(done){ - - var oldbalance = Number(contract.balanceOf.call(accounts[3]) ), - oldroguebalance = Number(contract.balanceOf.call(accounts[2]) ); - - contract.transfer(accounts[3],-1,{from:accounts[2]}, - function(err,res){ - var newbalance = Number(contract.balanceOf.call(accounts[3]) ); - var roguebalance = Number(contract.balanceOf.call(accounts[2]) ); - assert.equal(oldroguebalance , roguebalance, 'Sending account was reduced by -1'); - assert.equal(oldbalance , newbalance, 'Receiving account was increased by -1'); - done(); - }) - }) - - it('Should fail to spend Negative token value from one rogue address to a real address (Underflow)',function(done){ - - var oldbalance = Number(contract.balanceOf.call(Me) ), - oldroguebalance = Number(contract.balanceOf.call(accounts[2]) ); - - contract.transfer(Me,-1,{from:accounts[2]}, - function(err,res){ - var newbalance = Number(contract.balanceOf.call(Me) ); - var roguebalance = Number(contract.balanceOf.call(accounts[2]) ); - assert.equal(oldroguebalance , roguebalance, 'Sending account was reduced by -1'); - assert.equal(oldbalance , newbalance, 'Receiving account was increased by -1'); - done(); - }) - }) - - it('Should fail to spend more token value than balance',function(done){ - - var oldbalance = Number(contract.balanceOf.call(accounts[3]) ), - oldroguebalance = Number(contract.balanceOf.call(accounts[2]) ); - - contract.transfer(accounts[2],1,{from:Me}, - function(err,res){ - var newbalance = Number(contract.balanceOf.call(accounts[3]) ); - var roguebalance = Number(contract.balanceOf.call(accounts[2]) ); - assert.equal(oldbalance , newbalance, 'Receiving account was credited 1 token from the cthulhu'); - done(); - }) - }) - - it('Should successfully spend tokens',function(done){ - - var oldbalance = Number(contract.balanceOf.call(accounts[2]) ); - - contract.transfer(accounts[2],10,{from:Me}, - function(err,res){ - var newbalance = Number(contract.balanceOf.call(accounts[2]) ); - assert.equal(newbalance > oldbalance ,true , 'Address not credited with token'); - assert.equal(newbalance , oldbalance+10 , 'Spent '+newbalance+ ' instead of 10'); - done(); - }) - }) - }) - - - }); \ No newline at end of file diff --git a/truffle.js b/truffle.js index 2491c7c..ee423ce 100644 --- a/truffle.js +++ b/truffle.js @@ -3,7 +3,8 @@ module.exports = { development: { host: "localhost", port: 8545, - network_id: "*" // Match any network id + network_id: "*", // Match any network id + gas: 4700000 } } };