diff --git a/.travis.yml b/.travis.yml index 84b21382..e4a0a5e1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,7 +32,7 @@ after_success: - echo after_success TRAVIS_BRANCH=$TRAVIS_BRANCH TRAVIS_PULL_REQUEST=$TRAVIS_PULL_REQUEST TRAVIS_TAG=$TRAVIS_TAG TRAVIS_PULL_REQUEST_BRANCH=$TRAVIS_PULL_REQUEST_BRANCH TRAVIS_BUILD_NUMBER=$TRAVIS_BUILD_NUMBER TRAVIS_COMMIT=$TRAVIS_COMMIT ; - | if [ $TRAVIS_PULL_REQUEST == "false" ]; then - if [ $TRAVIS_BRANCH == "master" ] || [[ $TRAVIS_TAG =~ v([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then + if [ $TRAVIS_BRANCH == "master" ] || [[ $TRAVIS_TAG =~ v([0-9]+)\.([0-9]+)\.([0-9]+)((-|_)\S*)? ]]; then echo $DOCKER_PASSWORD | docker login --username $DOCKER_USERNAME --password-stdin ; yarn docker:tag:build ; if [ ! -z $TRAVIS_TAG ]; then diff --git a/abiniser.json b/abiniser.json index e8e83d3a..44a2f761 100644 --- a/abiniser.json +++ b/abiniser.json @@ -1,6 +1,5 @@ { "truffleContractFiles": [ - "SafeMath.json", "Migrations.json", "Rates.json", "AugmintReserves.json", diff --git a/abiniser/abis/LoanManager_ABI_1a18d349e2ea55585c57e2125d99dad9.json b/abiniser/abis/LoanManager_ABI_753a73f4b2140507197a8f80bff47b40.json similarity index 91% rename from abiniser/abis/LoanManager_ABI_1a18d349e2ea55585c57e2125d99dad9.json rename to abiniser/abis/LoanManager_ABI_753a73f4b2140507197a8f80bff47b40.json index 1d16054d..6c9b34f6 100644 --- a/abiniser/abis/LoanManager_ABI_1a18d349e2ea55585c57e2125d99dad9.json +++ b/abiniser/abis/LoanManager_ABI_753a73f4b2140507197a8f80bff47b40.json @@ -1,7 +1,7 @@ { "contractName": "LoanManager", - "abiHash": "1a18d349e2ea55585c57e2125d99dad9", - "generatedAt": "2019-06-06T11:06:21.107Z", + "abiHash": "753a73f4b2140507197a8f80bff47b40", + "generatedAt": "2019-07-02T09:59:47.884Z", "abi": [ { "constant": true, @@ -118,7 +118,7 @@ "type": "uint32" }, { - "name": "collateralRatio", + "name": "initialCollateralRatio", "type": "uint32" }, { @@ -305,11 +305,48 @@ "indexed": false, "name": "maturity", "type": "uint40" + }, + { + "indexed": false, + "name": "currentRate", + "type": "uint256" } ], "name": "NewLoan", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "loanId", + "type": "uint256" + }, + { + "indexed": true, + "name": "borrower", + "type": "address" + }, + { + "indexed": false, + "name": "collateralAmount", + "type": "uint256" + }, + { + "indexed": false, + "name": "repaymentAmount", + "type": "uint256" + }, + { + "indexed": false, + "name": "currentRate", + "type": "uint256" + } + ], + "name": "LoanChanged", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -348,12 +385,17 @@ "type": "uint256" }, { - "indexed": false, + "indexed": true, "name": "borrower", "type": "address" + }, + { + "indexed": false, + "name": "currentRate", + "type": "uint256" } ], - "name": "LoanRepayed", + "name": "LoanRepaid", "type": "event" }, { @@ -383,6 +425,11 @@ "indexed": false, "name": "defaultingFee", "type": "uint256" + }, + { + "indexed": false, + "name": "currentRate", + "type": "uint256" } ], "name": "LoanCollected", @@ -451,7 +498,7 @@ "type": "uint32" }, { - "name": "collateralRatio", + "name": "initialCollateralRatio", "type": "uint32" }, { @@ -501,6 +548,10 @@ { "name": "productId", "type": "uint32" + }, + { + "name": "minRate", + "type": "uint256" } ], "name": "newEthBackedLoan", diff --git a/abiniser/abis/PreTokenProxy_ABI_875b3d71bd113c550827112698f839cd.json b/abiniser/abis/PreTokenProxy_ABI_875b3d71bd113c550827112698f839cd.json new file mode 100644 index 00000000..0aacec87 --- /dev/null +++ b/abiniser/abis/PreTokenProxy_ABI_875b3d71bd113c550827112698f839cd.json @@ -0,0 +1,346 @@ +{ + "contractName": "PreTokenProxy", + "abiHash": "875b3d71bd113c550827112698f839cd", + "generatedAt": "2019-07-02T09:59:47.919Z", + "abi": [ + { + "constant": true, + "inputs": [ + { + "name": "offset", + "type": "uint256" + }, + { + "name": "chunkSize", + "type": "uint16" + } + ], + "name": "getSigners", + "outputs": [ + { + "name": "", + "type": "uint256[3][]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "offset", + "type": "uint256" + }, + { + "name": "chunkSize", + "type": "uint16" + } + ], + "name": "getScripts", + "outputs": [ + { + "name": "", + "type": "uint256[4][]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "activeSignersCount", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "scriptAddress", + "type": "address" + } + ], + "name": "cancelScript", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "scriptAddress", + "type": "address" + } + ], + "name": "execute", + "outputs": [ + { + "name": "result", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "isSigner", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "signers", + "type": "address[]" + } + ], + "name": "removeSigners", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getScriptsCount", + "outputs": [ + { + "name": "scriptsCount", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "scriptAddresses", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "scripts", + "outputs": [ + { + "name": "state", + "type": "uint8" + }, + { + "name": "signCount", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getAllSignersCount", + "outputs": [ + { + "name": "allSignersCount", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "allSigners", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "scriptAddress", + "type": "address" + } + ], + "name": "dryExecute", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "signers", + "type": "address[]" + } + ], + "name": "addSigners", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "scriptAddress", + "type": "address" + } + ], + "name": "sign", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "signer", + "type": "address" + } + ], + "name": "SignerAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "signer", + "type": "address" + } + ], + "name": "SignerRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "scriptAddress", + "type": "address" + }, + { + "indexed": false, + "name": "signer", + "type": "address" + } + ], + "name": "ScriptSigned", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "scriptAddress", + "type": "address" + } + ], + "name": "ScriptApproved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "scriptAddress", + "type": "address" + } + ], + "name": "ScriptCancelled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "scriptAddress", + "type": "address" + }, + { + "indexed": false, + "name": "result", + "type": "bool" + } + ], + "name": "ScriptExecuted", + "type": "event" + } + ] +} \ No newline at end of file diff --git a/abiniser/abis/StabilityBoardProxy_ABI_875b3d71bd113c550827112698f839cd.json b/abiniser/abis/StabilityBoardProxy_ABI_875b3d71bd113c550827112698f839cd.json new file mode 100644 index 00000000..fd7c6837 --- /dev/null +++ b/abiniser/abis/StabilityBoardProxy_ABI_875b3d71bd113c550827112698f839cd.json @@ -0,0 +1,346 @@ +{ + "contractName": "StabilityBoardProxy", + "abiHash": "875b3d71bd113c550827112698f839cd", + "generatedAt": "2019-07-02T09:59:47.916Z", + "abi": [ + { + "constant": true, + "inputs": [ + { + "name": "offset", + "type": "uint256" + }, + { + "name": "chunkSize", + "type": "uint16" + } + ], + "name": "getSigners", + "outputs": [ + { + "name": "", + "type": "uint256[3][]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "offset", + "type": "uint256" + }, + { + "name": "chunkSize", + "type": "uint16" + } + ], + "name": "getScripts", + "outputs": [ + { + "name": "", + "type": "uint256[4][]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "activeSignersCount", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "scriptAddress", + "type": "address" + } + ], + "name": "cancelScript", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "scriptAddress", + "type": "address" + } + ], + "name": "execute", + "outputs": [ + { + "name": "result", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "isSigner", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "signers", + "type": "address[]" + } + ], + "name": "removeSigners", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getScriptsCount", + "outputs": [ + { + "name": "scriptsCount", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "scriptAddresses", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "scripts", + "outputs": [ + { + "name": "state", + "type": "uint8" + }, + { + "name": "signCount", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getAllSignersCount", + "outputs": [ + { + "name": "allSignersCount", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "allSigners", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "scriptAddress", + "type": "address" + } + ], + "name": "dryExecute", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "signers", + "type": "address[]" + } + ], + "name": "addSigners", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "scriptAddress", + "type": "address" + } + ], + "name": "sign", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "signer", + "type": "address" + } + ], + "name": "SignerAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "signer", + "type": "address" + } + ], + "name": "SignerRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "scriptAddress", + "type": "address" + }, + { + "indexed": false, + "name": "signer", + "type": "address" + } + ], + "name": "ScriptSigned", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "scriptAddress", + "type": "address" + } + ], + "name": "ScriptApproved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "scriptAddress", + "type": "address" + } + ], + "name": "ScriptCancelled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "scriptAddress", + "type": "address" + }, + { + "indexed": false, + "name": "result", + "type": "bool" + } + ], + "name": "ScriptExecuted", + "type": "event" + } + ] +} \ No newline at end of file diff --git a/abiniser/deployments/4/LoanManager_DEPLOYS.json b/abiniser/deployments/4/LoanManager_DEPLOYS.json index 0d668238..e72ab683 100644 --- a/abiniser/deployments/4/LoanManager_DEPLOYS.json +++ b/abiniser/deployments/4/LoanManager_DEPLOYS.json @@ -1,6 +1,6 @@ { "contractName": "LoanManager", - "latestAbiHash": "fdf5fde95aa940c6dbfb8353c572c5fb", + "latestAbiHash": "753a73f4b2140507197a8f80bff47b40", "deployedAbis": { "fdf5fde95aa940c6dbfb8353c572c5fb": { "latestDeployedAddress": "0x3792c5a5077dacfe331b81837ef73bc0ea721d90", @@ -30,6 +30,23 @@ "sourceHash": "234fd5848af85ca2444c888fd6ba61aa" } } + }, + "753a73f4b2140507197a8f80bff47b40": { + "latestDeployedAddress": "0x99928c5121de38ca6d23c7645cc9697a7263e859", + "deployments": { + "0x99928c5121de38ca6d23c7645cc9697a7263e859": { + "generatedAt": "2019-07-02T11:05:08.432Z", + "truffleContractFileUpdatedAt": "2019-07-02T10:59:24.934Z", + "deployTransactionHash": "0x17ffd93ff3d05cac7334a2549a7e38a701aee5d19c3de2c80a3a4a18c10d7786", + "compiler": { + "name": "solc", + "version": "0.4.24+commit.e67f0147.Emscripten.clang" + }, + "bytecodeHash": "4408d1ad57d708e0d0664838e6f10136", + "deployedBytecodeHash": "05ee93b22e275d2ea639c2fcb1284dfa", + "sourceHash": "78a783ccd39197b8ac7ef2cb49cc243c" + } + } } } } \ No newline at end of file diff --git a/abiniser/deployments/4/StabilityBoardProxy_DEPLOYS.json b/abiniser/deployments/4/StabilityBoardProxy_DEPLOYS.json index 3a156b43..5c4de123 100644 --- a/abiniser/deployments/4/StabilityBoardProxy_DEPLOYS.json +++ b/abiniser/deployments/4/StabilityBoardProxy_DEPLOYS.json @@ -1,6 +1,6 @@ { "contractName": "StabilityBoardProxy", - "latestAbiHash": "dd40c0d39ea8bad8a388522667a84687", + "latestAbiHash": "875b3d71bd113c550827112698f839cd", "deployedAbis": { "dd40c0d39ea8bad8a388522667a84687": { "latestDeployedAddress": "0xa612de13b629a1ff790c1f4e41d0422d2bb50a30", @@ -30,6 +30,23 @@ "sourceHash": "4d60c55b8e4009873db939b37558d9dc" } } + }, + "875b3d71bd113c550827112698f839cd": { + "latestDeployedAddress": "0x9bb8f0855b8bbaea064bce9b4ef88bc22e649af5", + "deployments": { + "0x9bb8f0855b8bbaea064bce9b4ef88bc22e649af5": { + "generatedAt": "2019-07-02T11:05:08.462Z", + "truffleContractFileUpdatedAt": "2019-07-02T10:59:24.915Z", + "deployTransactionHash": "0xb4d8550e24607c2c5969d9230a0b7471ace76f1133c4712f264562fe910eb8ae", + "compiler": { + "name": "solc", + "version": "0.4.24+commit.e67f0147.Emscripten.clang" + }, + "bytecodeHash": "9d8ba85809293da3b9a84c628de8d2c7", + "deployedBytecodeHash": "c3fe17ffca9ede5d2fc500da0865c1c7", + "sourceHash": "4d60c55b8e4009873db939b37558d9dc" + } + } } } } \ No newline at end of file diff --git a/abiniser/deployments/999/LoanManager_DEPLOYS.json b/abiniser/deployments/999/LoanManager_DEPLOYS.json index 44b66e2d..c849e35e 100644 --- a/abiniser/deployments/999/LoanManager_DEPLOYS.json +++ b/abiniser/deployments/999/LoanManager_DEPLOYS.json @@ -1,23 +1,11 @@ { "contractName": "LoanManager", - "latestAbiHash": "1a18d349e2ea55585c57e2125d99dad9", + "latestAbiHash": "753a73f4b2140507197a8f80bff47b40", "deployedAbis": { "fdf5fde95aa940c6dbfb8353c572c5fb": { - "latestDeployedAddress": "0x213135c85437c23bc529a2ee9c2980646c332fcb", + "latestDeployedAddress": "0xF7B8384c392fc333d3858a506c4F1506af44D53c", "deployments": { - "0x213135c85437c23bc529a2ee9c2980646c332fcb": { - "generatedAt": "2019-04-04T18:31:39.018Z", - "truffleContractFileUpdatedAt": "2019-04-04T18:20:16.642Z", - "deployTransactionHash": "0x04c167ad48f178b4a316b0d6bbfa885ca96dcd70ed9edf1d4923aeb29fa7fa4c", - "compiler": { - "name": "solc", - "version": "0.4.24+commit.e67f0147.Emscripten.clang" - }, - "bytecodeHash": "4f80cc451be48e4d62a17dbc3895a91d", - "deployedBytecodeHash": "bc0eea72e35030fc44549a2a0064bf73", - "sourceHash": "234fd5848af85ca2444c888fd6ba61aa" - }, - "0xf7b8384c392fc333d3858a506c4f1506af44d53c": { + "0xF7B8384c392fc333d3858a506c4F1506af44D53c": { "generatedAt": "2019-04-04T18:31:39.018Z", "truffleContractFileUpdatedAt": "2019-04-04T18:20:16.642Z", "deployTransactionHash": "0x23d5906de7ecfe9595bb8cfd7080a0e06eccee34ef92f017a3b4b657855e3619", @@ -31,20 +19,20 @@ } } }, - "1a18d349e2ea55585c57e2125d99dad9": { - "latestDeployedAddress": "0x213135c85437c23bc529a2ee9c2980646c332fcb", + "753a73f4b2140507197a8f80bff47b40": { + "latestDeployedAddress": "0x213135c85437C23bC529A2eE9c2980646c332fCB", "deployments": { - "0x213135c85437c23bc529a2ee9c2980646c332fcb": { - "generatedAt": "2019-06-06T11:06:21.108Z", - "truffleContractFileUpdatedAt": "2019-06-06T10:59:23.781Z", - "deployTransactionHash": "0x65c11d08cd492ddc3b84e63a0bfd68cc05053537e0ce14757f70f9340465424d", + "0x213135c85437C23bC529A2eE9c2980646c332fCB": { + "generatedAt": "2019-07-02T09:59:47.885Z", + "truffleContractFileUpdatedAt": "2019-07-02T09:59:42.024Z", + "deployTransactionHash": "0x6e09b864f8d42ac28128e3c1af6b8f2aa43f5007bbc91f5f655dc6840883224e", "compiler": { "name": "solc", "version": "0.4.24+commit.e67f0147.Emscripten.clang" }, - "bytecodeHash": "6f74cede259b4e896f5faa772de789f4", - "deployedBytecodeHash": "ab71949ed51debdd86e3241773c06862", - "sourceHash": "f5c13c265210321f169220a20110aa5e" + "bytecodeHash": "4408d1ad57d708e0d0664838e6f10136", + "deployedBytecodeHash": "05ee93b22e275d2ea639c2fcb1284dfa", + "sourceHash": "78a783ccd39197b8ac7ef2cb49cc243c" } } } diff --git a/abiniser/deployments/999/PreTokenProxy_DEPLOYS.json b/abiniser/deployments/999/PreTokenProxy_DEPLOYS.json index 2f1635f1..5a606d7a 100644 --- a/abiniser/deployments/999/PreTokenProxy_DEPLOYS.json +++ b/abiniser/deployments/999/PreTokenProxy_DEPLOYS.json @@ -1,6 +1,6 @@ { "contractName": "PreTokenProxy", - "latestAbiHash": "dd40c0d39ea8bad8a388522667a84687", + "latestAbiHash": "875b3d71bd113c550827112698f839cd", "deployedAbis": { "dd40c0d39ea8bad8a388522667a84687": { "latestDeployedAddress": "0x8b639dc72f3e640c0d6bc19497fbc7b5160d0463", @@ -18,6 +18,23 @@ "sourceHash": "2d172fe41d2b97c827d6dca816138047" } } + }, + "875b3d71bd113c550827112698f839cd": { + "latestDeployedAddress": "0x8b639dc72f3e640c0d6bc19497fbc7b5160d0463", + "deployments": { + "0x8b639dc72f3e640c0d6bc19497fbc7b5160d0463": { + "generatedAt": "2019-07-02T09:59:47.921Z", + "truffleContractFileUpdatedAt": "2019-07-02T09:59:32.036Z", + "deployTransactionHash": "0xfd73a567cd08abd21c7024b7ff4a084f13c73a4ecb2c0047ae9b143d3037885f", + "compiler": { + "name": "solc", + "version": "0.4.24+commit.e67f0147.Emscripten.clang" + }, + "bytecodeHash": "755b42465949a31e279cdc861575f4f4", + "deployedBytecodeHash": "10882a748215bfda390bb5b1cbbae842", + "sourceHash": "2d172fe41d2b97c827d6dca816138047" + } + } } } } \ No newline at end of file diff --git a/abiniser/deployments/999/StabilityBoardProxy_DEPLOYS.json b/abiniser/deployments/999/StabilityBoardProxy_DEPLOYS.json index a451fd1a..20107b19 100644 --- a/abiniser/deployments/999/StabilityBoardProxy_DEPLOYS.json +++ b/abiniser/deployments/999/StabilityBoardProxy_DEPLOYS.json @@ -1,6 +1,6 @@ { "contractName": "StabilityBoardProxy", - "latestAbiHash": "dd40c0d39ea8bad8a388522667a84687", + "latestAbiHash": "875b3d71bd113c550827112698f839cd", "deployedAbis": { "dd40c0d39ea8bad8a388522667a84687": { "latestDeployedAddress": "0xd3ef19679c2dbbf3b8e2077c61b88f5e9c6178eb", @@ -18,6 +18,23 @@ "sourceHash": "4d60c55b8e4009873db939b37558d9dc" } } + }, + "875b3d71bd113c550827112698f839cd": { + "latestDeployedAddress": "0xd3ef19679c2dbbf3b8e2077c61b88f5e9c6178eb", + "deployments": { + "0xd3ef19679c2dbbf3b8e2077c61b88f5e9c6178eb": { + "generatedAt": "2019-07-02T09:59:47.917Z", + "truffleContractFileUpdatedAt": "2019-07-02T09:59:38.204Z", + "deployTransactionHash": "0xb7abdfaafb74748c3aedc212708ec6148ce0c8d07684af8983a169493b55f4a4", + "compiler": { + "name": "solc", + "version": "0.4.24+commit.e67f0147.Emscripten.clang" + }, + "bytecodeHash": "9d8ba85809293da3b9a84c628de8d2c7", + "deployedBytecodeHash": "c3fe17ffca9ede5d2fc500da0865c1c7", + "sourceHash": "4d60c55b8e4009873db939b37558d9dc" + } + } } } } \ No newline at end of file diff --git a/contracts/LoanManager.sol b/contracts/LoanManager.sol index 07d96e2f..23459dae 100644 --- a/contracts/LoanManager.sol +++ b/contracts/LoanManager.sol @@ -26,13 +26,13 @@ contract LoanManager is Restricted, TokenReceiver { enum LoanState { Open, Repaid, DoNotUse, Collected } // NB: DoNotUse state is kept for backwards compatibility only (so the ordinal of 'Collected' does not shift), as the name states: do not use it. struct LoanProduct { - uint minDisbursedAmount; // 0: minimum loanAmount, with decimals set in AugmintToken.decimals (i.e. token amount) - uint32 term; // 1: term length (in seconds) - uint32 discountRate; // 2: discountRate (in parts per million, i.e. 10,000 = 1%) - uint32 collateralRatio; // 3: inverse of collateral ratio: [repayment value (in token) / collateral value (in token)] (in ppm). Note: this is actually the inverse of the commonly used "collateral ratio"! TODO: fix it - uint32 defaultingFeePt; // 4: % of repaymentAmount (in parts per million, i.e. 50,000 = 5%) - bool isActive; // 5: flag to enable/disable product - uint32 minCollateralRatio; // 6: minimum collateral ratio: [collateral value (in token) / repayment value (in token)] (in ppm), defines the margin, zero means no margin. Note: this is _not_ an inverse like the above "collateralRatio", it is already stored properly! + uint minDisbursedAmount; // 0: minimum loanAmount, with decimals set in AugmintToken.decimals (i.e. token amount) + uint32 term; // 1: term length (in seconds) + uint32 discountRate; // 2: discountRate (in parts per million, i.e. 10,000 = 1%) + uint32 initialCollateralRatio; // 3: initial collateral ratio: [collateral value (in token) / repayment value (in token)] (in ppm). + uint32 defaultingFeePt; // 4: % of repaymentAmount (in parts per million, i.e. 50,000 = 5%) + bool isActive; // 5: flag to enable/disable product + uint32 minCollateralRatio; // 6: minimum collateral ratio: [collateral value (in token) / repayment value (in token)] (in ppm), defines the margin, zero means no margin. } /* NB: we don't need to store loan parameters because loan products can't be altered (only disabled/enabled) */ @@ -55,16 +55,19 @@ contract LoanManager is Restricted, TokenReceiver { MonetarySupervisor public monetarySupervisor; event NewLoan(uint32 productId, uint loanId, address indexed borrower, uint collateralAmount, uint loanAmount, - uint repaymentAmount, uint40 maturity); + uint repaymentAmount, uint40 maturity, uint currentRate); + + event LoanChanged(uint loanId, address indexed borrower, uint collateralAmount, + uint repaymentAmount, uint currentRate); event LoanProductActiveStateChanged(uint32 productId, bool newState); event LoanProductAdded(uint32 productId); - event LoanRepayed(uint loanId, address borrower); + event LoanRepaid(uint loanId, address indexed borrower, uint currentRate); event LoanCollected(uint loanId, address indexed borrower, uint collectedCollateral, - uint releasedCollateral, uint defaultingFee); + uint releasedCollateral, uint defaultingFee, uint currentRate); event SystemContractsChanged(Rates newRatesContract, MonetarySupervisor newMonetarySupervisor); @@ -76,11 +79,11 @@ contract LoanManager is Restricted, TokenReceiver { rates = _rates; } - function addLoanProduct(uint32 term, uint32 discountRate, uint32 collateralRatio, uint minDisbursedAmount, + function addLoanProduct(uint32 term, uint32 discountRate, uint32 initialCollateralRatio, uint minDisbursedAmount, uint32 defaultingFeePt, bool isActive, uint32 minCollateralRatio) external restrict("StabilityBoard") { uint _newProductId = products.push( - LoanProduct(minDisbursedAmount, term, discountRate, collateralRatio, defaultingFeePt, isActive, minCollateralRatio) + LoanProduct(minDisbursedAmount, term, discountRate, initialCollateralRatio, defaultingFeePt, isActive, minCollateralRatio) ) - 1; uint32 newProductId = uint32(_newProductId); @@ -96,15 +99,17 @@ contract LoanManager is Restricted, TokenReceiver { emit LoanProductActiveStateChanged(productId, newState); } - function newEthBackedLoan(uint32 productId) external payable { + function newEthBackedLoan(uint32 productId, uint minRate) external payable { require(productId < products.length, "invalid productId"); // next line would revert but require to emit reason LoanProduct storage product = products[productId]; require(product.isActive, "product must be in active state"); // valid product + uint currentRate = getCurrentRate(); + require(currentRate >= minRate, "current rate is below minimum"); // calculate loan values based on ETH sent in with Tx - uint collateralValueInToken = rates.convertFromWei(augmintToken.peggedSymbol(), msg.value); - uint repaymentAmount = collateralValueInToken.mul(product.collateralRatio).div(PPM_FACTOR); + uint collateralValueInToken = _convertFromWei(currentRate, msg.value); + uint repaymentAmount = collateralValueInToken.mul(PPM_FACTOR).div(product.initialCollateralRatio); uint loanAmount; (loanAmount, ) = calculateLoanValues(product, repaymentAmount); @@ -126,7 +131,7 @@ contract LoanManager is Restricted, TokenReceiver { // Issue tokens and send to borrower monetarySupervisor.issueLoan(msg.sender, loanAmount); - emit NewLoan(productId, loanId, msg.sender, msg.value, loanAmount, repaymentAmount, maturity); + emit NewLoan(productId, loanId, msg.sender, msg.value, loanAmount, repaymentAmount, maturity, currentRate); } function addExtraCollateral(uint loanId) external payable { @@ -137,6 +142,8 @@ contract LoanManager is Restricted, TokenReceiver { require(product.minCollateralRatio > 0, "not a margin type loan"); loan.collateralAmount = loan.collateralAmount.add(msg.value); + + emit LoanChanged(loanId, loan.borrower, loan.collateralAmount, loan.repaymentAmount, getCurrentRate()); } /* repay loan, called from AugmintToken's transferAndNotify @@ -195,7 +202,7 @@ contract LoanManager is Restricted, TokenReceiver { } // returns loan products starting from some : - // [ productId, minDisbursedAmount, term, discountRate, collateralRatio, defaultingFeePt, maxLoanAmount, isActive, minCollateralRatio ] + // [ productId, minDisbursedAmount, term, discountRate, initialCollateralRatio, defaultingFeePt, maxLoanAmount, isActive, minCollateralRatio ] function getProducts(uint offset, uint16 chunkSize) external view returns (uint[9][]) { uint limit = SafeMath.min(offset.add(chunkSize), products.length); @@ -204,7 +211,7 @@ contract LoanManager is Restricted, TokenReceiver { for (uint i = offset; i < limit; i++) { LoanProduct storage product = products[i]; response[i - offset] = [i, product.minDisbursedAmount, product.term, product.discountRate, - product.collateralRatio, product.defaultingFeePt, + product.initialCollateralRatio, product.defaultingFeePt, monetarySupervisor.getMaxLoanAmount(product.minDisbursedAmount), product.isActive ? 1 : 0, product.minCollateralRatio]; } @@ -332,7 +339,7 @@ contract LoanManager is Restricted, TokenReceiver { loan.borrower.transfer(loan.collateralAmount); // send back ETH collateral - emit LoanRepayed(loanId, loan.borrower); + emit LoanRepaid(loanId, loan.borrower, getCurrentRate()); } function _collectLoan(uint loanId, uint currentRate) private returns(uint loanAmount, uint defaultingFee, uint collateralToCollect) { @@ -363,11 +370,14 @@ contract LoanManager is Restricted, TokenReceiver { } emit LoanCollected(loanId, loan.borrower, collateralToCollect.add(defaultingFee), - releasedCollateral, defaultingFee); + releasedCollateral, defaultingFee, currentRate); } - function _convertToWei(uint rate, uint value) private pure returns(uint weiValue) { - return value.mul(WEI_FACTOR).roundedDiv(rate); + function _convertToWei(uint rate, uint tokenAmount) private pure returns(uint weiAmount) { + return tokenAmount.mul(WEI_FACTOR).roundedDiv(rate); } + function _convertFromWei(uint rate, uint weiAmount) private pure returns(uint tokenAmount) { + return weiAmount.mul(rate).roundedDiv(WEI_FACTOR); + } } diff --git a/contracts/SB_scripts/localTest/localTest_initialSetup.sol b/contracts/SB_scripts/localTest/localTest_initialSetup.sol index c5137212..b2098d1d 100644 --- a/contracts/SB_scripts/localTest/localTest_initialSetup.sol +++ b/contracts/SB_scripts/localTest/localTest_initialSetup.sol @@ -104,18 +104,18 @@ contract localTest_initialSetup { _monetarySupervisor.grantPermission(_locker, "Locker"); // add test loan Products - // term (in sec), discountRate, loanCoverageRatio, minDisbursedAmount (w/ 4 decimals), defaultingFeePt, isActive - _loanManager.addLoanProduct(365 days, 854701, 550000, 1000, 50000, true, 1500000); // 17% p.a., (collateral ratio: initial = ~181%, minimum = 150%) - _loanManager.addLoanProduct(180 days, 924753, 550000, 1000, 50000, true, 1500000); // 16.5% p.a., (collateral ratio: initial = ~181%, minimum = 150%) - - _loanManager.addLoanProduct(90 days, 962046, 600000, 1000, 50000, true, 1200000); // 16%. p.a., (collateral ratio: initial = ~166%, minimum = 120%) - _loanManager.addLoanProduct(60 days, 975154, 600000, 1000, 50000, true, 1200000); // 15.5% p.a., (collateral ratio: initial = ~166%, minimum = 120%) - _loanManager.addLoanProduct(30 days, 987822, 600000, 1000, 50000, true, 1200000); // 15% p.a., (collateral ratio: initial = ~166%, minimum = 120%) - _loanManager.addLoanProduct(14 days, 994280, 600000, 1000, 50000, true, 1200000); // 15% p.a., (collateral ratio: initial = ~166%, minimum = 120%) - _loanManager.addLoanProduct(7 days, 997132, 600000, 1000, 50000, true, 1200000); // 15% p.a., (collateral ratio: initial = ~166%, minimum = 120%) - - _loanManager.addLoanProduct(1 hours, 999998, 980000, 2000, 50000, true, 1010000); // due in 1hr for testing repayments ~1.75% p.a., (collateral ratio: initial = ~102%, minimum = 101%) - _loanManager.addLoanProduct(1 seconds, 999999, 990000, 3000, 50000, true, 1010000); // defaults in 1 secs for testing ~3153.6% p.a., (collateral ratio: initial = ~102%, minimum = 101%) + // term (in sec), discountRate, initialCollateralRatio (ppm), minDisbursedAmount (token), defaultingFeePt (ppm), isActive, minCollateralRatio (ppm) + _loanManager.addLoanProduct(365 days, 854701, 1850000, 1000, 50000, true, 1500000); // 17% p.a., (collateral ratio: initial = 185%, minimum = 150%) + _loanManager.addLoanProduct(180 days, 924753, 1850000, 1000, 50000, true, 1500000); // 16.5% p.a., (collateral ratio: initial = 185%, minimum = 150%) + + _loanManager.addLoanProduct(90 days, 962046, 1600000, 1000, 50000, true, 1200000); // 16%. p.a., (collateral ratio: initial = 160%, minimum = 120%) + _loanManager.addLoanProduct(60 days, 975154, 1600000, 1000, 50000, true, 1200000); // 15.5% p.a., (collateral ratio: initial = 160%, minimum = 120%) + _loanManager.addLoanProduct(30 days, 987822, 1600000, 1000, 50000, true, 1200000); // 15% p.a., (collateral ratio: initial = 160%, minimum = 120%) + _loanManager.addLoanProduct(14 days, 994280, 1600000, 1000, 50000, true, 1200000); // 15% p.a., (collateral ratio: initial = 160%, minimum = 120%) + _loanManager.addLoanProduct(7 days, 997132, 1600000, 1000, 50000, true, 1200000); // 15% p.a., (collateral ratio: initial = 160%, minimum = 120%) + + _loanManager.addLoanProduct(1 hours, 999000, 1230000, 2000, 50000, true, 1050000); // due in 1hr for testing repayments ~877% p.a., (collateral ratio: initial = 123%, minimum = 105%) + _loanManager.addLoanProduct(1 seconds, 999000, 1110000, 3000, 50000, true, 1020000); // defaults in 1 secs for testing ~3156757% p.a., (collateral ratio: initial = 111%, minimum = 102%) // add test lock products // (perTermInterest, durationInSecs, minimumLockAmount, isActive) diff --git a/contracts/SB_scripts/rinkeby/Rinkeby_0009_migrateToNewProxy.sol b/contracts/SB_scripts/rinkeby/Rinkeby_0009_migrateToNewProxy.sol new file mode 100644 index 00000000..6aa63f1c --- /dev/null +++ b/contracts/SB_scripts/rinkeby/Rinkeby_0009_migrateToNewProxy.sol @@ -0,0 +1,148 @@ +/* Migrate rinkeby contracts to the new StabilityBoardProxy - to be run via the old proxy! */ + +pragma solidity 0.4.24; + +import "../../AugmintReserves.sol"; +import "../../Exchange.sol"; +import "../../FeeAccount.sol"; +import "../../InterestEarnedAccount.sol"; +import "../../LoanManager.sol"; +import "../../Locker.sol"; +import "../../MonetarySupervisor.sol"; +import "../../Rates.sol"; +import "../../TokenAEur.sol"; +import "../../StabilityBoardProxy.sol"; + +contract Rinkeby_0009_migrateToNewProxy { + + /****************************************************************************** + * StabilityBoardProxies + ******************************************************************************/ + StabilityBoardProxy public constant OLD_STABILITY_BOARD_PROXY = StabilityBoardProxy(0xa612de13B629a1FF790c1f4E41d0422d2bB50a30); + StabilityBoardProxy public constant NEW_STABILITY_BOARD_PROXY = StabilityBoardProxy(0x9bB8F0855B8bbaEa064bCe9b4Ef88bC22E649aF5); + + + /****************************************************************************** + * Contracts + ******************************************************************************/ + + AugmintReserves public constant AUGMINT_RESERVES_1 = AugmintReserves(0xC036a1DD59Ac55e2fB6b3D7416cb4ECC44605834); + Exchange public constant EXCHANGE_1 = Exchange(0xDF47D51028DafF13424F42523FdAc73079ab901b); + FeeAccount public constant FEE_ACCOUNT_1 = FeeAccount(0xB77F9cDdA72eEC47a57793Be088C7b523f6b5014); + InterestEarnedAccount public constant INTEREST_EARNED_ACCOUNT_1 = InterestEarnedAccount(0x489cbf1674b575e6dFcFF0A4F2BBc74f7e9DDe28); + LoanManager public constant LOAN_MANAGER_1 = LoanManager(0x6CB7731c78E677f85942B5f1D646b3485E5820c1); + Locker public constant LOCKER_1 = Locker(0x6d84aB6c385B827E58c358D078AC7b1C61b68821); + MonetarySupervisor public constant MONETARY_SUPERVISOR_1 = MonetarySupervisor(0xCeC3574ECa89409b15a8A72A6E737C4171457871); + Rates public constant RATES_1 = Rates(0xDfA3a0aEb9645a55b684CB3aCE8C42D018405bDa); + TokenAEur public constant TOKEN_AEUR_1 = TokenAEur(0x0557183334Edc23a666201EDC6b0AA2787e2ad3F); + + AugmintReserves public constant AUGMINT_RESERVES_2 = AugmintReserves(0x33Bec125657470e53887400666BdeeD360b2168A); + Exchange public constant EXCHANGE_2 = Exchange(0xe5d6D0c107eaE79d2D30798F252Ac6FF5ECAd459); + FeeAccount public constant FEE_ACCOUNT_2 = FeeAccount(0xaa16EdE9093BB4140e2715ED9a1E41cdFD9D9c29); + InterestEarnedAccount public constant INTEREST_EARNED_ACCOUNT_2 = InterestEarnedAccount(0xDD96979697b76787b5062084eEA60BF929ddD844); + LoanManager public constant LOAN_MANAGER_2 = LoanManager(0x3792c5a5077DacfE331B81837ef73bC0Ea721d90); + Locker public constant LOCKER_2 = Locker(0xc0B97fE5CAD0d43D0c974C4E9A00312dc661f8Ab); + MonetarySupervisor public constant MONETARY_SUPERVISOR_2 = MonetarySupervisor(0x4A7F6EcbE8B324A55b85adcc45313A412957B8ea); + Rates public constant RATES_2 = Rates(0xEE8C7a3e99945A5207Dca026504d67527125Da9C); + TokenAEur public constant TOKEN_AEUR_2 = TokenAEur(0x79065a165Ec09E6A89D584a14872802717FE12a3); + + // Note: both of the above loanmanagers (#1 and #2) are "legacy" (a.k.a. "pre-margin"), the new loanmanager (#3) was already deployed under the new proxy. + + function execute(Rinkeby_0009_migrateToNewProxy /* self, not used */) external { + // called via StabilityBoardProxy + require(address(this) == address(OLD_STABILITY_BOARD_PROXY), "only execute via StabilityBoardProxy"); + + /****************************************************************************** + * Grant permissions for new proxy + ******************************************************************************/ + + // StabilityBoard permission + AUGMINT_RESERVES_1.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "StabilityBoard"); + EXCHANGE_1.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "StabilityBoard"); + FEE_ACCOUNT_1.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "StabilityBoard"); + INTEREST_EARNED_ACCOUNT_1.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "StabilityBoard"); + LOAN_MANAGER_1.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "StabilityBoard"); + LOCKER_1.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "StabilityBoard"); + MONETARY_SUPERVISOR_1.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "StabilityBoard"); + RATES_1.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "StabilityBoard"); + TOKEN_AEUR_1.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "StabilityBoard"); + + AUGMINT_RESERVES_2.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "StabilityBoard"); + EXCHANGE_2.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "StabilityBoard"); + FEE_ACCOUNT_2.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "StabilityBoard"); + INTEREST_EARNED_ACCOUNT_2.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "StabilityBoard"); + LOAN_MANAGER_2.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "StabilityBoard"); + LOCKER_2.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "StabilityBoard"); + MONETARY_SUPERVISOR_2.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "StabilityBoard"); + RATES_2.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "StabilityBoard"); + TOKEN_AEUR_2.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "StabilityBoard"); + + // PermissionGranter permission + AUGMINT_RESERVES_1.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "PermissionGranter"); + EXCHANGE_1.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "PermissionGranter"); + FEE_ACCOUNT_1.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "PermissionGranter"); + INTEREST_EARNED_ACCOUNT_1.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "PermissionGranter"); + LOAN_MANAGER_1.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "PermissionGranter"); + LOCKER_1.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "PermissionGranter"); + MONETARY_SUPERVISOR_1.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "PermissionGranter"); + RATES_1.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "PermissionGranter"); + TOKEN_AEUR_1.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "PermissionGranter"); + + AUGMINT_RESERVES_2.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "PermissionGranter"); + EXCHANGE_2.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "PermissionGranter"); + FEE_ACCOUNT_2.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "PermissionGranter"); + INTEREST_EARNED_ACCOUNT_2.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "PermissionGranter"); + LOAN_MANAGER_2.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "PermissionGranter"); + LOCKER_2.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "PermissionGranter"); + MONETARY_SUPERVISOR_2.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "PermissionGranter"); + RATES_2.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "PermissionGranter"); + TOKEN_AEUR_2.grantPermission(address(NEW_STABILITY_BOARD_PROXY), "PermissionGranter"); + + /****************************************************************************** + * Revoke permissions for old proxy + ******************************************************************************/ + + // StabilityBoard permission + AUGMINT_RESERVES_1.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "StabilityBoard"); + EXCHANGE_1.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "StabilityBoard"); + FEE_ACCOUNT_1.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "StabilityBoard"); + INTEREST_EARNED_ACCOUNT_1.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "StabilityBoard"); + LOAN_MANAGER_1.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "StabilityBoard"); + LOCKER_1.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "StabilityBoard"); + MONETARY_SUPERVISOR_1.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "StabilityBoard"); + RATES_1.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "StabilityBoard"); + TOKEN_AEUR_1.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "StabilityBoard"); + + AUGMINT_RESERVES_2.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "StabilityBoard"); + EXCHANGE_2.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "StabilityBoard"); + FEE_ACCOUNT_2.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "StabilityBoard"); + INTEREST_EARNED_ACCOUNT_2.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "StabilityBoard"); + LOAN_MANAGER_2.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "StabilityBoard"); + LOCKER_2.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "StabilityBoard"); + MONETARY_SUPERVISOR_2.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "StabilityBoard"); + RATES_2.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "StabilityBoard"); + TOKEN_AEUR_2.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "StabilityBoard"); + + // PermissionGranter permission + AUGMINT_RESERVES_1.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "PermissionGranter"); + EXCHANGE_1.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "PermissionGranter"); + FEE_ACCOUNT_1.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "PermissionGranter"); + INTEREST_EARNED_ACCOUNT_1.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "PermissionGranter"); + LOAN_MANAGER_1.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "PermissionGranter"); + LOCKER_1.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "PermissionGranter"); + MONETARY_SUPERVISOR_1.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "PermissionGranter"); + RATES_1.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "PermissionGranter"); + TOKEN_AEUR_1.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "PermissionGranter"); + + AUGMINT_RESERVES_2.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "PermissionGranter"); + EXCHANGE_2.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "PermissionGranter"); + FEE_ACCOUNT_2.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "PermissionGranter"); + INTEREST_EARNED_ACCOUNT_2.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "PermissionGranter"); + LOAN_MANAGER_2.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "PermissionGranter"); + LOCKER_2.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "PermissionGranter"); + MONETARY_SUPERVISOR_2.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "PermissionGranter"); + RATES_2.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "PermissionGranter"); + TOKEN_AEUR_2.revokePermission(address(OLD_STABILITY_BOARD_PROXY), "PermissionGranter"); + + } +} \ No newline at end of file diff --git a/contracts/SB_scripts/rinkeby/Rinkeby_0010_setupNewLoanManager.sol b/contracts/SB_scripts/rinkeby/Rinkeby_0010_setupNewLoanManager.sol new file mode 100644 index 00000000..b59a19bd --- /dev/null +++ b/contracts/SB_scripts/rinkeby/Rinkeby_0010_setupNewLoanManager.sol @@ -0,0 +1,56 @@ +/* Set up new loanmanager - to be run via the new proxy, only execute after Rinkeby_0009_migrateToNewProxy has been run in old proxy! */ + +pragma solidity 0.4.24; + +import "../../LoanManager.sol"; +import "../../StabilityBoardProxy.sol"; +import "../../FeeAccount.sol"; +import "../../MonetarySupervisor.sol"; + +contract Rinkeby_0010_setupNewLoanManager { + + StabilityBoardProxy public constant STABILITY_BOARD_PROXY = StabilityBoardProxy(0x9bB8F0855B8bbaEa064bCe9b4Ef88bC22E649aF5); + LoanManager public constant LOAN_MANAGER = LoanManager(0x99928c5121dE38cA6D23C7645CC9697a7263e859); + + FeeAccount public constant FEE_ACCOUNT = FeeAccount(0xaa16EdE9093BB4140e2715ED9a1E41cdFD9D9c29); + MonetarySupervisor public constant MONETARY_SUPERVISOR = MonetarySupervisor(0x4A7F6EcbE8B324A55b85adcc45313A412957B8ea); + + function execute(Rinkeby_0010_setupNewLoanManager /* self, not used */) external { + // called via StabilityBoardProxy + require(address(this) == address(STABILITY_BOARD_PROXY), "only execute via StabilityBoardProxy"); + + // StabilityBoard permission + LOAN_MANAGER.grantPermission(address(STABILITY_BOARD_PROXY), "StabilityBoard"); + + // NoTransferFee permission + FEE_ACCOUNT.grantPermission(address(LOAN_MANAGER), "NoTransferFee"); + + // LoanManager permission + MONETARY_SUPERVISOR.grantPermission(address(LOAN_MANAGER), "LoanManager"); + + /****************************************************************************** + * Add loan products + ******************************************************************************/ + // term (in sec), discountRate, initialCollateralRatio (ppm), minDisbursedAmount (token), + // defaultingFeePt (ppm), isActive, minCollateralRatio (ppm) + + LOAN_MANAGER.addLoanProduct(365 days, 1000000, 1800000, 800, 100000, true, 1200000); + LOAN_MANAGER.addLoanProduct(180 days, 1000000, 1800000, 800, 100000, true, 1200000); + LOAN_MANAGER.addLoanProduct(90 days, 1000000, 1800000, 800, 100000, true, 1200000); + LOAN_MANAGER.addLoanProduct(30 days, 1000000, 1800000, 800, 100000, true, 1200000); + LOAN_MANAGER.addLoanProduct(14 days, 1000000, 1800000, 800, 100000, true, 1200000); + LOAN_MANAGER.addLoanProduct(7 days, 1000000, 1800000, 800, 100000, true, 1200000); + + LOAN_MANAGER.addLoanProduct(1 hours, 1000000, 1500000, 400, 50000, true, 1150000); + LOAN_MANAGER.addLoanProduct(1 minutes, 1000000, 1500000, 400, 50000, true, 1150000); + + // discountRate: 1000000 => zero interest + // initialCollateralRatio: 1800000 => 180% + // minCollateralRatio: 1200000 => 120% + // minDisbursedAmount: 800 => 8 AEUR (extra +25% on frontend!) + // defaultingFeePt: 100000 => 10% + + // for the extra short term products slightly different values: + // zero / 150% / 115% / 4 AEUR / 5% + } +} \ No newline at end of file diff --git a/contracts/SB_scripts/rinkeby/Rinkeby_0011_setupSbProxySigners.sol b/contracts/SB_scripts/rinkeby/Rinkeby_0011_setupSbProxySigners.sol new file mode 100644 index 00000000..9e568bd2 --- /dev/null +++ b/contracts/SB_scripts/rinkeby/Rinkeby_0011_setupSbProxySigners.sol @@ -0,0 +1,18 @@ +pragma solidity 0.4.24; + +import "../../StabilityBoardProxy.sol"; + +contract Rinkeby_0011_setupSbProxySigners { + + StabilityBoardProxy public constant STABILITY_BOARD_PROXY = StabilityBoardProxy(0x9bB8F0855B8bbaEa064bCe9b4Ef88bC22E649aF5); + + function execute(Rinkeby_0011_setupSbProxySigners /* self, not used */) external { + // called via StabilityBoardProxy + require(address(this) == address(STABILITY_BOARD_PROXY), "only execute via StabilityBoardProxy"); + + address[] memory signersToAdd = new address[](2); // dynamic array needed for addSigners() & removeSigners() + signersToAdd[0] = 0xe71E9636e31B838aF0A3c38B3f3449cdC2b7aa87; // phraktle + signersToAdd[1] = 0x9aaf197F25d207ecE17DfBeb20780095f7623A23; // petro + STABILITY_BOARD_PROXY.addSigners(signersToAdd); + } +} \ No newline at end of file diff --git a/contracts/SB_scripts/rinkeby/Rinkeby_0012_noTransferFee.sol b/contracts/SB_scripts/rinkeby/Rinkeby_0012_noTransferFee.sol new file mode 100644 index 00000000..726565c6 --- /dev/null +++ b/contracts/SB_scripts/rinkeby/Rinkeby_0012_noTransferFee.sol @@ -0,0 +1,19 @@ +/* Eliminate transfer fee */ + +pragma solidity 0.4.24; + +import "../../StabilityBoardProxy.sol"; +import "../../FeeAccount.sol"; + +contract Rinkeby_0012_noTransferFee { + + StabilityBoardProxy public constant STABILITY_BOARD_PROXY = StabilityBoardProxy(0x9bB8F0855B8bbaEa064bCe9b4Ef88bC22E649aF5); + FeeAccount public constant FEE_ACCOUNT = FeeAccount(0xaa16EdE9093BB4140e2715ED9a1E41cdFD9D9c29); + + function execute(Rinkeby_0012_noTransferFee /* self, not used */) external { + // called via StabilityBoardProxy + require(address(this) == address(STABILITY_BOARD_PROXY), "only execute via StabilityBoardProxy"); + + FEE_ACCOUNT.setTransferFees(0, 0, 0); + } +} \ No newline at end of file diff --git a/contracts/legacy/1.0.12/LoanManager_1_0_12.sol b/contracts/legacy/1.0.12/LoanManager_1_0_12.sol new file mode 100644 index 00000000..0549c879 --- /dev/null +++ b/contracts/legacy/1.0.12/LoanManager_1_0_12.sol @@ -0,0 +1,320 @@ +/* + Contract to manage Augmint token loan contracts backed by ETH + For flows see: https://github.com/Augmint/augmint-contracts/blob/master/docs/loanFlow.png + + TODO: + - create MonetarySupervisor interface and use it instead? + - make data arg generic bytes? + - make collect() run as long as gas provided allows +*/ +pragma solidity 0.4.24; + +import "../../Rates.sol"; +import "../../generic/Restricted.sol"; +import "../../generic/SafeMath.sol"; +import "../../interfaces/AugmintTokenInterface.sol"; +import "../../MonetarySupervisor.sol"; + + +contract LoanManager_1_0_12 is Restricted, TokenReceiver { + using SafeMath for uint256; + + enum LoanState { Open, Repaid, Defaulted, Collected } // NB: Defaulted state is not stored, only getters calculate + + struct LoanProduct { + uint minDisbursedAmount; // 0: with decimals set in AugmintToken.decimals + uint32 term; // 1 + uint32 discountRate; // 2: discountRate in parts per million , ie. 10,000 = 1% + uint32 collateralRatio; // 3: loan token amount / colleteral pegged ccy value + // in parts per million , ie. 10,000 = 1% + uint32 defaultingFeePt; // 4: % of collateral in parts per million , ie. 50,000 = 5% + bool isActive; // 5 + } + + /* NB: we don't need to store loan parameters because loan products can't be altered (only disabled/enabled) */ + struct LoanData { + uint collateralAmount; // 0 + uint repaymentAmount; // 1 + address borrower; // 2 + uint32 productId; // 3 + LoanState state; // 4 + uint40 maturity; // 5 + } + + LoanProduct[] public products; + + LoanData[] public loans; + mapping(address => uint[]) public accountLoans; // owner account address => array of loan Ids + + Rates public rates; // instance of ETH/pegged currency rate provider contract + AugmintTokenInterface public augmintToken; // instance of token contract + MonetarySupervisor public monetarySupervisor; + + event NewLoan(uint32 productId, uint loanId, address indexed borrower, uint collateralAmount, uint loanAmount, + uint repaymentAmount, uint40 maturity); + + event LoanProductActiveStateChanged(uint32 productId, bool newState); + + event LoanProductAdded(uint32 productId); + + event LoanRepayed(uint loanId, address borrower); + + event LoanCollected(uint loanId, address indexed borrower, uint collectedCollateral, + uint releasedCollateral, uint defaultingFee); + + event SystemContractsChanged(Rates newRatesContract, MonetarySupervisor newMonetarySupervisor); + + constructor(address permissionGranterContract, AugmintTokenInterface _augmintToken, + MonetarySupervisor _monetarySupervisor, Rates _rates) + public Restricted(permissionGranterContract) { + augmintToken = _augmintToken; + monetarySupervisor = _monetarySupervisor; + rates = _rates; + } + + function addLoanProduct(uint32 term, uint32 discountRate, uint32 collateralRatio, uint minDisbursedAmount, + uint32 defaultingFeePt, bool isActive) + external restrict("StabilityBoard") { + + uint _newProductId = products.push( + LoanProduct(minDisbursedAmount, term, discountRate, collateralRatio, defaultingFeePt, isActive) + ) - 1; + + uint32 newProductId = uint32(_newProductId); + require(newProductId == _newProductId, "productId overflow"); + + emit LoanProductAdded(newProductId); + } + + function setLoanProductActiveState(uint32 productId, bool newState) + external restrict ("StabilityBoard") { + require(productId < products.length, "invalid productId"); // next line would revert but require to emit reason + products[productId].isActive = newState; + emit LoanProductActiveStateChanged(productId, newState); + } + + function newEthBackedLoan(uint32 productId) external payable { + require(productId < products.length, "invalid productId"); // next line would revert but require to emit reason + LoanProduct storage product = products[productId]; + require(product.isActive, "product must be in active state"); // valid product + + + // calculate loan values based on ETH sent in with Tx + uint tokenValue = rates.convertFromWei(augmintToken.peggedSymbol(), msg.value); + uint repaymentAmount = tokenValue.mul(product.collateralRatio).div(1000000); + + uint loanAmount; + (loanAmount, ) = calculateLoanValues(product, repaymentAmount); + + require(loanAmount >= product.minDisbursedAmount, "loanAmount must be >= minDisbursedAmount"); + + uint expiration = now.add(product.term); + uint40 maturity = uint40(expiration); + require(maturity == expiration, "maturity overflow"); + + // Create new loan + uint loanId = loans.push(LoanData(msg.value, repaymentAmount, msg.sender, + productId, LoanState.Open, maturity)) - 1; + + // Store ref to new loan + accountLoans[msg.sender].push(loanId); + + // Issue tokens and send to borrower + monetarySupervisor.issueLoan(msg.sender, loanAmount); + + emit NewLoan(productId, loanId, msg.sender, msg.value, loanAmount, repaymentAmount, maturity); + } + + /* repay loan, called from AugmintToken's transferAndNotify + Flow for repaying loan: + 1) user calls token contract's transferAndNotify loanId passed in data arg + 2) transferAndNotify transfers tokens to the Lender contract + 3) transferAndNotify calls Lender.transferNotification with lockProductId + */ + // from arg is not used as we allow anyone to repay a loan: + function transferNotification(address, uint repaymentAmount, uint loanId) external { + require(msg.sender == address(augmintToken), "msg.sender must be augmintToken"); + + _repayLoan(loanId, repaymentAmount); + } + + function collect(uint[] loanIds) external { + /* when there are a lots of loans to be collected then + the client need to call it in batches to make sure tx won't exceed block gas limit. + Anyone can call it - can't cause harm as it only allows to collect loans which they are defaulted + TODO: optimise defaulting fee calculations + */ + uint totalLoanAmountCollected; + uint totalCollateralToCollect; + uint totalDefaultingFee; + for (uint i = 0; i < loanIds.length; i++) { + require(loanIds[i] < loans.length, "invalid loanId"); // next line would revert but require to emit reason + LoanData storage loan = loans[loanIds[i]]; + require(loan.state == LoanState.Open, "loan state must be Open"); + require(now >= loan.maturity, "current time must be later than maturity"); + LoanProduct storage product = products[loan.productId]; + + uint loanAmount; + (loanAmount, ) = calculateLoanValues(product, loan.repaymentAmount); + + totalLoanAmountCollected = totalLoanAmountCollected.add(loanAmount); + + loan.state = LoanState.Collected; + + // send ETH collateral to augmintToken reserve + uint defaultingFeeInToken = loan.repaymentAmount.mul(product.defaultingFeePt).div(1000000); + uint defaultingFee = rates.convertToWei(augmintToken.peggedSymbol(), defaultingFeeInToken); + uint targetCollection = rates.convertToWei(augmintToken.peggedSymbol(), + loan.repaymentAmount).add(defaultingFee); + + uint releasedCollateral; + if (targetCollection < loan.collateralAmount) { + releasedCollateral = loan.collateralAmount.sub(targetCollection); + loan.borrower.transfer(releasedCollateral); + } + uint collateralToCollect = loan.collateralAmount.sub(releasedCollateral); + if (defaultingFee >= collateralToCollect) { + defaultingFee = collateralToCollect; + collateralToCollect = 0; + } else { + collateralToCollect = collateralToCollect.sub(defaultingFee); + } + totalDefaultingFee = totalDefaultingFee.add(defaultingFee); + + totalCollateralToCollect = totalCollateralToCollect.add(collateralToCollect); + + emit LoanCollected(loanIds[i], loan.borrower, collateralToCollect.add(defaultingFee), + releasedCollateral, defaultingFee); + } + + if (totalCollateralToCollect > 0) { + address(monetarySupervisor.augmintReserves()).transfer(totalCollateralToCollect); + } + + if (totalDefaultingFee > 0) { + address(augmintToken.feeAccount()).transfer(totalDefaultingFee); + } + + monetarySupervisor.loanCollectionNotification(totalLoanAmountCollected);// update KPIs + + } + + /* to allow upgrade of Rates and MonetarySupervisor contracts */ + function setSystemContracts(Rates newRatesContract, MonetarySupervisor newMonetarySupervisor) + external restrict("StabilityBoard") { + rates = newRatesContract; + monetarySupervisor = newMonetarySupervisor; + emit SystemContractsChanged(newRatesContract, newMonetarySupervisor); + } + + function getProductCount() external view returns (uint) { + return products.length; + } + + // returns loan products starting from some : + // [ productId, minDisbursedAmount, term, discountRate, collateralRatio, defaultingFeePt, maxLoanAmount, isActive ] + function getProducts(uint offset, uint16 chunkSize) + external view returns (uint[8][]) { + uint limit = SafeMath.min(offset.add(chunkSize), products.length); + uint[8][] memory response = new uint[8][](limit.sub(offset)); + + for (uint i = offset; i < limit; i++) { + LoanProduct storage product = products[i]; + response[i - offset] = [i, product.minDisbursedAmount, product.term, product.discountRate, + product.collateralRatio, product.defaultingFeePt, + monetarySupervisor.getMaxLoanAmount(product.minDisbursedAmount), product.isActive ? 1 : 0 ]; + } + return response; + } + + function getLoanCount() external view returns (uint) { + return loans.length; + } + + /* returns loans starting from some . Loans data encoded as: + [loanId, collateralAmount, repaymentAmount, borrower, productId, + state, maturity, disbursementTime, loanAmount, interestAmount] */ + function getLoans(uint offset, uint16 chunkSize) + external view returns (uint[10][]) { + uint limit = SafeMath.min(offset.add(chunkSize), loans.length); + uint[10][] memory response = new uint[10][](limit.sub(offset)); + + for (uint i = offset; i < limit; i++) { + response[i - offset] = getLoanTuple(i); + } + return response; + } + + function getLoanCountForAddress(address borrower) external view returns (uint) { + return accountLoans[borrower].length; + } + + /* returns loans of a given account, starting from some . Loans data encoded as: + [loanId, collateralAmount, repaymentAmount, borrower, productId, state, maturity, disbursementTime, + loanAmount, interestAmount ] */ + function getLoansForAddress(address borrower, uint offset, uint16 chunkSize) + external view returns (uint[10][]) { + uint[] storage loansForAddress = accountLoans[borrower]; + uint limit = SafeMath.min(offset.add(chunkSize), loansForAddress.length); + uint[10][] memory response = new uint[10][](limit.sub(offset)); + + for (uint i = offset; i < limit; i++) { + response[i - offset] = getLoanTuple(loansForAddress[i]); + } + return response; + } + + function getLoanTuple(uint loanId) public view returns (uint[10] result) { + require(loanId < loans.length, "invalid loanId"); // next line would revert but require to emit reason + LoanData storage loan = loans[loanId]; + LoanProduct storage product = products[loan.productId]; + + uint loanAmount; + uint interestAmount; + (loanAmount, interestAmount) = calculateLoanValues(product, loan.repaymentAmount); + uint disbursementTime = loan.maturity - product.term; + + LoanState loanState = + loan.state == LoanState.Open && now >= loan.maturity ? LoanState.Defaulted : loan.state; + + result = [loanId, loan.collateralAmount, loan.repaymentAmount, uint(loan.borrower), + loan.productId, uint(loanState), loan.maturity, disbursementTime, loanAmount, interestAmount]; + } + + function calculateLoanValues(LoanProduct storage product, uint repaymentAmount) + internal view returns (uint loanAmount, uint interestAmount) { + // calculate loan values based on repayment amount + loanAmount = repaymentAmount.mul(product.discountRate).div(1000000); + interestAmount = loanAmount > repaymentAmount ? 0 : repaymentAmount.sub(loanAmount); + } + + /* internal function, assuming repayment amount already transfered */ + function _repayLoan(uint loanId, uint repaymentAmount) internal { + require(loanId < loans.length, "invalid loanId"); // next line would revert but require to emit reason + LoanData storage loan = loans[loanId]; + require(loan.state == LoanState.Open, "loan state must be Open"); + require(repaymentAmount == loan.repaymentAmount, "repaymentAmount must be equal to tokens sent"); + require(now <= loan.maturity, "current time must be earlier than maturity"); + + LoanProduct storage product = products[loan.productId]; + uint loanAmount; + uint interestAmount; + (loanAmount, interestAmount) = calculateLoanValues(product, loan.repaymentAmount); + + loans[loanId].state = LoanState.Repaid; + + if (interestAmount > 0) { + augmintToken.transfer(monetarySupervisor.interestEarnedAccount(), interestAmount); + augmintToken.burn(loanAmount); + } else { + // negative or zero interest (i.e. discountRate >= 0) + augmintToken.burn(repaymentAmount); + } + + monetarySupervisor.loanRepaymentNotification(loanAmount); // update KPIs + + loan.borrower.transfer(loan.collateralAmount); // send back ETH collateral + + emit LoanRepayed(loanId, loan.borrower); + } +} \ No newline at end of file diff --git a/migrations/1001_topup_interestEarnedAccount.js b/migrations/1001_topup_interestEarnedAccount.js index f465eb1c..4db70cb7 100644 --- a/migrations/1001_topup_interestEarnedAccount.js +++ b/migrations/1001_topup_interestEarnedAccount.js @@ -8,7 +8,7 @@ module.exports = function(deployer) { const tokenAEur = TokenAEur.at(TokenAEur.address); const loanManager = LoanManager.at(LoanManager.address); - await loanManager.newEthBackedLoan(0, { value: web3.toWei(0.1066) }); // = 50 A-EUR + await loanManager.newEthBackedLoan(0, 0, { value: web3.toWei(0.10845) }); // = 50 A-EUR await tokenAEur.transferWithNarrative( InterestEarnedAccount.address, diff --git a/migrations/1003_add_legacyContracts.js b/migrations/1003_add_legacyContracts.js index 0934a883..ee4ed0ba 100644 --- a/migrations/1003_add_legacyContracts.js +++ b/migrations/1003_add_legacyContracts.js @@ -5,7 +5,7 @@ const TokenAEur = artifacts.require("./TokenAEur.sol"); const Rates = artifacts.require("./Rates.sol"); const MonetarySupervisor = artifacts.require("./MonetarySupervisor.sol"); const Locker = artifacts.require("./Locker.sol"); -const LoanManager = artifacts.require("./LoanManager.sol"); +const LoanManager_1_0_12 = artifacts.require("./legacy/1.0.12/LoanManager_1_0_12.sol"); const Exchange = artifacts.require("./Exchange.sol"); module.exports = async function(deployer, network, accounts) { @@ -16,7 +16,7 @@ module.exports = async function(deployer, network, accounts) { const oldToken = await TokenAEur.new(accounts[0], FeeAccount.address); const oldLocker = await Locker.new(accounts[0], oldToken.address, MonetarySupervisor.address); - const oldLoanManager = await LoanManager.new( + const oldLoanManager = await LoanManager_1_0_12.new( accounts[0], oldToken.address, MonetarySupervisor.address, @@ -46,10 +46,9 @@ module.exports = async function(deployer, network, accounts) { /* LoanManager permissions & products */ monetarySupervisor.grantPermission(oldLoanManager.address, "LoanManager"), feeAccount.grantPermission(oldLoanManager.address, "NoTransferFee"), - oldLoanManager.addLoanProduct(1, 999999, 990000, 1000, 50000, true, 0), // defaults in 1 secs for testing ? p.a. - oldLoanManager.addLoanProduct(3600, 999989, 980000, 1000, 50000, true, 0), // due in 1hr for testing repayments ? p.a. - oldLoanManager.addLoanProduct(31536000, 860000, 550000, 1000, 50000, true, 0), // 365d, 14% p.a. - + oldLoanManager.addLoanProduct(1, 999999, 990000, 1000, 50000, true), // defaults in 1 secs for testing ? p.a. + oldLoanManager.addLoanProduct(3600, 999989, 980000, 1000, 50000, true), // due in 1hr for testing repayments ? p.a. + oldLoanManager.addLoanProduct(31536000, 860000, 550000, 1000, 50000, true), // 365d, 14% p.a. /* Exchange permissions */ feeAccount.grantPermission(oldExchange.address, "NoTransferFee") ]); @@ -61,9 +60,7 @@ module.exports = async function(deployer, network, accounts) { oldToken.transferAndNotify(oldLocker.address, 1600, 1), oldLoanManager.newEthBackedLoan(0, { value: web3.toWei(0.1) }), - oldLoanManager.newEthBackedLoan(1, { value: web3.toWei(0.11) }), - oldLoanManager.newEthBackedLoan(2, { value: web3.toWei(0.12) }), - + oldLoanManager.newEthBackedLoan(2, { value: web3.toWei(0.2) }), oldToken.transferAndNotify(oldExchange.address, 2000, 1010000), oldToken.transferAndNotify(oldExchange.address, 1100, 980000), oldExchange.placeBuyTokenOrder(990000, { value: web3.toWei(0.01) }), @@ -74,7 +71,7 @@ module.exports = async function(deployer, network, accounts) { ` *** On local ganache - deployed a set of legacy mock contracts for manual testing: TokenAEur: ${oldToken.address} Locker: ${oldLocker.address} - LoanManager: ${oldLoanManager.address} + LoanManager_1_0_12: ${oldLoanManager.address} Exchange: ${oldExchange.address}` ); }); diff --git a/migrations/2000_setRateDefault.js b/migrations/2000_setRateDefault.js new file mode 100644 index 00000000..37a124e4 --- /dev/null +++ b/migrations/2000_setRateDefault.js @@ -0,0 +1,12 @@ +// sets the current rate to the default value +// run it with: "truffle migrate -f 2000 --to 2000" or "setrate:default" + +const Rates = artifacts.require("./Rates.sol"); +const newRate = 99800; + +module.exports = function(deployer) { + deployer.then(async () => { + const rates = Rates.at(Rates.address); + await rates.setRate("EUR", newRate); + }); +}; diff --git a/migrations/2001_setRateLow.js b/migrations/2001_setRateLow.js new file mode 100644 index 00000000..ccbd0abd --- /dev/null +++ b/migrations/2001_setRateLow.js @@ -0,0 +1,12 @@ +// sets the current rate to a low value (for testing margin loans) +// run it with: "truffle migrate -f 2001 --to 2001" or "setrate:low" + +const Rates = artifacts.require("./Rates.sol"); +const newRate = 42500; + +module.exports = function(deployer) { + deployer.then(async () => { + const rates = Rates.at(Rates.address); + await rates.setRate("EUR", newRate); + }); +}; diff --git a/migrations/2002_setRateHigh.js b/migrations/2002_setRateHigh.js new file mode 100644 index 00000000..f65011ac --- /dev/null +++ b/migrations/2002_setRateHigh.js @@ -0,0 +1,12 @@ +// sets the current rate to a high value +// run it with: "truffle migrate -f 2002 --to 2002" or "setrate:high" + +const Rates = artifacts.require("./Rates.sol"); +const newRate = 225000; + +module.exports = function(deployer) { + deployer.then(async () => { + const rates = Rates.at(Rates.address); + await rates.setRate("EUR", newRate); + }); +}; diff --git a/package.json b/package.json index 6b029b66..1ca3f8d5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@augmint/contracts", - "version": "1.0.12", + "version": "1.1.0", "description": "Augmint Stable Tokens - Solidity contract's abi and deployment descriptors", "author": "“Augmint”", "homepage": "https://github.com/Augmint/augmint-contracts#readme", @@ -25,6 +25,7 @@ }, "scripts": { "start": "yarn runmigrate", + "start_comp": "yarn runmigrate_comp", "build": "truffle compile", "clean": "rm build/contracts/*", "test": "truffle test --migrations_directory migrations_null", @@ -33,9 +34,10 @@ "compile": "yarn build", "ganache:run": "./scripts/runganache.sh", "ganache:stop": "echo TODO", - "runmigrate": "./scripts/runganache.sh & yarn migrate --reset && echo 'Migration done. Contracts deployed to ganache. Contract artifacts are in build/contracts folder.' & wait", + "runmigrate": "./scripts/runganache.sh & yarn migrate --reset --to 2000 && echo 'Migration done. Contracts deployed to ganache. Contract artifacts are in build/contracts folder.' & wait", + "runmigrate_comp": "./scripts/runganache_comp.sh & yarn migrate --reset --to 2000 && echo 'Migration done. Contracts deployed to ganache. Contract artifacts are in build/contracts folder.' & wait", "localchaindb:clean": "rm -rf localchaindb && mkdir localchaindb", - "localchaindb:build": "yarn localchaindb:clean && ./scripts/runganache.sh --db ./localchaindb & yarn migrate && echo 'Migration done. Ganache db in ./localchaindb folder.'", + "localchaindb:build": "yarn localchaindb:clean && ./scripts/runganache.sh --db ./localchaindb & yarn migrate --to 2000 && echo 'Migration done. Ganache db in ./localchaindb folder.'", "localchaindb:builddocker": "yarn localchaindb:build && yarn docker:build", "docker:build": "docker build . -t localdockerimage", "docker:run": "docker rm ganache ; docker run --init --name ganache -p 8545:8545 localdockerimage --db ./dockerLocalchaindb --gasLimit 0x694920 --gasPrice 1000000000 --networkId 999 -m \"hello build tongue rack parade express shine salute glare rate spice stock\" & wait", @@ -44,7 +46,10 @@ "docker:tag:build": "docker tag localdockerimage augmint/contracts:commit-$(git log -1 --format='%h'); docker tag localdockerimage augmint/contracts:build-$TRAVIS_BUILD_NUMBER", "docker:tag:staging": "docker tag localdockerimage augmint/contracts:staging;", "docker:tag:latest": "docker tag localdockerimage augmint/contracts:latest", - "docker:tag:version": "func () { docker tag localdockerimage augmint/contracts:${1}; }; func" + "docker:tag:version": "func () { docker tag localdockerimage augmint/contracts:${1}; }; func", + "setrate:default": "truffle migrate -f 2000 --to 2000", + "setrate:low": "truffle migrate -f 2001 --to 2001", + "setrate:high": "truffle migrate -f 2002 --to 2002" }, "devDependencies": { "abiniser": "0.5.1", diff --git a/rinkeby_migrations/10_deploy_Rinkeby_0011_setupSbProxySigners.js b/rinkeby_migrations/10_deploy_Rinkeby_0011_setupSbProxySigners.js new file mode 100644 index 00000000..686ba23a --- /dev/null +++ b/rinkeby_migrations/10_deploy_Rinkeby_0011_setupSbProxySigners.js @@ -0,0 +1,7 @@ +const Rinkeby_0011_setupSbProxySigners = artifacts.require("./Rinkeby_0011_setupSbProxySigners.sol"); + +module.exports = function(deployer) { + deployer.then(async () => { + await deployer.deploy(Rinkeby_0011_setupSbProxySigners); + }); +}; \ No newline at end of file diff --git a/rinkeby_migrations/11_deploy_Rinkeby_0012_noTransferFee.js b/rinkeby_migrations/11_deploy_Rinkeby_0012_noTransferFee.js new file mode 100644 index 00000000..0ba3673e --- /dev/null +++ b/rinkeby_migrations/11_deploy_Rinkeby_0012_noTransferFee.js @@ -0,0 +1,7 @@ +const Rinkeby_0012_noTransferFee = artifacts.require("./Rinkeby_0012_noTransferFee.sol"); + +module.exports = function(deployer) { + deployer.then(async () => { + await deployer.deploy(Rinkeby_0012_noTransferFee); + }); +}; diff --git a/rinkeby_migrations/7_deploy_loanmanager_and_sbproxy.js b/rinkeby_migrations/7_deploy_loanmanager_and_sbproxy.js new file mode 100644 index 00000000..803b093c --- /dev/null +++ b/rinkeby_migrations/7_deploy_loanmanager_and_sbproxy.js @@ -0,0 +1,17 @@ +const StabilityBoardProxy = artifacts.require("./StabilityBoardProxy.sol"); +const LoanManager = artifacts.require("./LoanManager.sol"); + +const TokenAEurAddress = "0x79065a165Ec09E6A89D584a14872802717FE12a3"; +const MonetarySupervisorAddress = "0x4A7F6EcbE8B324A55b85adcc45313A412957B8ea"; +const RatesAddress = "0xEE8C7a3e99945A5207Dca026504d67527125Da9C"; + +module.exports = function(deployer) { + deployer.then(async () => { + + // ### StabilityBoardProxy ### + await deployer.deploy(StabilityBoardProxy); + + // ### LoanManager ### + await deployer.deploy(LoanManager, StabilityBoardProxy.address, TokenAEurAddress, MonetarySupervisorAddress, RatesAddress); + }); +}; \ No newline at end of file diff --git a/rinkeby_migrations/8_deploy_Rinkeby_0009_migrateToNewProxy.js b/rinkeby_migrations/8_deploy_Rinkeby_0009_migrateToNewProxy.js new file mode 100644 index 00000000..f4460600 --- /dev/null +++ b/rinkeby_migrations/8_deploy_Rinkeby_0009_migrateToNewProxy.js @@ -0,0 +1,7 @@ +const Rinkeby_0009_migrateToNewProxy = artifacts.require("./Rinkeby_0009_migrateToNewProxy.sol"); + +module.exports = function(deployer) { + deployer.then(async () => { + await deployer.deploy(Rinkeby_0009_migrateToNewProxy); + }); +}; \ No newline at end of file diff --git a/rinkeby_migrations/9_deploy_Rinkeby_0010_setupNewLoanManager.js b/rinkeby_migrations/9_deploy_Rinkeby_0010_setupNewLoanManager.js new file mode 100644 index 00000000..e1282f93 --- /dev/null +++ b/rinkeby_migrations/9_deploy_Rinkeby_0010_setupNewLoanManager.js @@ -0,0 +1,7 @@ +const Rinkeby_0010_setupNewLoanManager = artifacts.require("./Rinkeby_0010_setupNewLoanManager.sol"); + +module.exports = function(deployer) { + deployer.then(async () => { + await deployer.deploy(Rinkeby_0010_setupNewLoanManager); + }); +}; \ No newline at end of file diff --git a/scripts/runganache_comp.sh b/scripts/runganache_comp.sh new file mode 100755 index 00000000..24c16abd --- /dev/null +++ b/scripts/runganache_comp.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +echo "launching ganache-cli (aka testrpc) with deterministic addresses in compatibility mode" +yarn ganache-cli \ +--gasLimit 0x694920 \ +--gasPrice 1000000000 \ +--networkId 999 \ +--noVMErrorsOnRPCResponse \ +--blockTime 1 \ +-m "hello build tongue rack parade express shine salute glare rate spice stock" \ +"${@}" \ No newline at end of file diff --git a/test/helpers/loanTestHelpers.js b/test/helpers/loanTestHelpers.js index 297b51b9..ea8fc30f 100644 --- a/test/helpers/loanTestHelpers.js +++ b/test/helpers/loanTestHelpers.js @@ -44,7 +44,7 @@ before(async function() { rates = Rates.at(Rates.address); }); -async function createLoan(testInstance, product, borrower, collateralWei) { +async function createLoan(testInstance, product, borrower, collateralWei, minRate = 0) { const loan = await calcLoanValues(rates, product, collateralWei); loan.state = 0; loan.borrower = borrower; @@ -60,7 +60,7 @@ async function createLoan(testInstance, product, borrower, collateralWei) { }) ]); - const tx = await loanManager.newEthBackedLoan(loan.product.id, { + const tx = await loanManager.newEthBackedLoan(loan.product.id, minRate, { from: loan.borrower, value: loan.collateralAmount }); @@ -74,7 +74,8 @@ async function createLoan(testInstance, product, borrower, collateralWei) { collateralAmount: loan.collateralAmount.toString(), loanAmount: loan.loanAmount.toString(), repaymentAmount: loan.repaymentAmount.toString(), - maturity: x => x + maturity: x => x, + currentRate: x => x }), testHelpers.assertEvent(augmintToken, "AugmintTransfer", { @@ -95,6 +96,7 @@ async function createLoan(testInstance, product, borrower, collateralWei) { loan.id = newLoanEvenResult.loanId.toNumber(); loan.maturity = newLoanEvenResult.maturity.toNumber(); + loan.currentRate = newLoanEvenResult.currentRate.toNumber(); const [totalSupplyAfter, totalLoanAmountAfter, ,] = await Promise.all([ augmintToken.totalSupply(), @@ -151,9 +153,10 @@ async function repayLoan(testInstance, loan) { augmintToken.totalSupply(), monetarySupervisor.totalLoanAmount(), - testHelpers.assertEvent(loanManager, "LoanRepayed", { + testHelpers.assertEvent(loanManager, "LoanRepaid", { loanId: loan.id, - borrower: loan.borrower + borrower: loan.borrower, + currentRate: x => x }), /* TODO: these are emmited but why not picked up by assertEvent? */ @@ -269,7 +272,8 @@ async function collectLoan(testInstance, loan, collector) { borrower: loan.borrower, collectedCollateral: collectedCollateral.toString(), releasedCollateral: releasedCollateral.toString(), - defaultingFee: defaultingFee.toString() + defaultingFee: defaultingFee.toString(), + currentRate: x => x }), loanAsserts(loan), @@ -317,7 +321,7 @@ async function getProductsInfo(offset, chunkSize) { minDisbursedAmount, term, discountRate, - collateralRatio, + initialCollateralRatio, defaultingFeePt, maxLoanAmount, isActive, @@ -329,7 +333,7 @@ async function getProductsInfo(offset, chunkSize) { minDisbursedAmount, term, discountRate, - collateralRatio, + initialCollateralRatio, defaultingFeePt, maxLoanAmount, isActive, @@ -385,8 +389,8 @@ async function calcLoanValues(rates, product, collateralWei) { ret.tokenValue = await rates.convertFromWei(peggedSymbol, collateralWei); ret.repaymentAmount = ret.tokenValue - .mul(product.collateralRatio) - .div(ppmDiv) + .mul(ppmDiv) + .div(product.initialCollateralRatio) .round(0, BigNumber.ROUND_DOWN); ret.loanAmount = ret.repaymentAmount diff --git a/test/loanCollection.js b/test/loanCollection.js index b7c51dbc..838401e9 100644 --- a/test/loanCollection.js +++ b/test/loanCollection.js @@ -29,15 +29,15 @@ contract("Loans collection tests", accounts => { ) ]); // These neeed to be sequential b/c product order assumed when retrieving via getProducts - // term (in sec), discountRate, loanCoverageRatio, minDisbursedAmount (w/ 2 decimals), defaultingFeePt, isActive, minCollateralRatio - await loanManager.addLoanProduct(86400, 970000, 850000, 3000, 50000, true, 0); // notDue - await loanManager.addLoanProduct(1, 970000, 850000, 1000, 50000, true, 0); // defaulting - await loanManager.addLoanProduct(1, 900000, 900000, 1000, 100000, true, 0); // defaultingNoLeftOver - await loanManager.addLoanProduct(1, 1000000, 900000, 2000, 50000, true, 0); // zeroInterest - await loanManager.addLoanProduct(1, 1100000, 900000, 2000, 50000, true, 0); // negativeInterest + // term (in sec), discountRate, initialCollateralRatio, minDisbursedAmount (w/ 2 decimals), defaultingFeePt, isActive, minCollateralRatio + await loanManager.addLoanProduct(86400, 970000, 1176471, 3000, 50000, true, 0); // notDue + await loanManager.addLoanProduct(1, 970000, 1176471, 1000, 50000, true, 0); // defaulting + await loanManager.addLoanProduct(1, 900000, 1111111, 1000, 100000, true, 0); // defaultingNoLeftOver + await loanManager.addLoanProduct(1, 1000000, 1111111, 2000, 50000, true, 0); // zeroInterest + await loanManager.addLoanProduct(1, 1100000, 1111111, 2000, 50000, true, 0); // negativeInterest await loanManager.addLoanProduct(1, 990000, 1000000, 2000, 50000, true, 0); // fullCoverage - await loanManager.addLoanProduct(1, 990000, 1200000, 2000, 50000, true, 0); // moreCoverage - await loanManager.addLoanProduct(86400, 970000, 625000, 3000, 50000, true, 1200000); // with margin (collateral ratio: initial = 160%, minimum = 120%) (1/1.6 = 0.625) + await loanManager.addLoanProduct(1, 990000, 833333, 2000, 50000, true, 0); // moreCoverage + await loanManager.addLoanProduct(86400, 970000, 1600000, 3000, 50000, true, 1200000); // with margin (collateral ratio: initial = 160%, minimum = 120%) const [newProducts] = await Promise.all([ loanTestHelpers.getProductsInfo(prodCount, 10), @@ -72,7 +72,7 @@ contract("Loans collection tests", accounts => { this, products.defaulting, accounts[1], - global.web3v1.utils.toWei("0.5") + global.web3v1.utils.toWei("0.05") ); await testHelpers.waitForTimeStamp(loan.maturity); @@ -144,7 +144,7 @@ contract("Loans collection tests", accounts => { await loanTestHelpers.collectLoan(this, loan, accounts[2]); }); - it("Should get and collect a loan with collateralRatio = 1", async function() { + it("Should get and collect a loan with initialCollateralRatio = 1", async function() { const loan = await loanTestHelpers.createLoan( this, products.fullCoverage, @@ -155,7 +155,7 @@ contract("Loans collection tests", accounts => { await loanTestHelpers.collectLoan(this, loan, accounts[2]); }); - it("Should get and collect a loan with collateralRatio > 1", async function() { + it("Should get and collect a loan with initialCollateralRatio < 1", async function() { const loan = await loanTestHelpers.createLoan( this, products.moreCoverage, @@ -169,15 +169,15 @@ contract("Loans collection tests", accounts => { it("Should collect multiple defaulted loans", async function() { const loanCount = (await loanManager.getLoanCount()).toNumber(); await Promise.all([ - loanManager.newEthBackedLoan(products.zeroInterest.id, { + loanManager.newEthBackedLoan(products.zeroInterest.id, 0 ,{ from: accounts[0], value: global.web3v1.utils.toWei("0.05") }), - loanManager.newEthBackedLoan(products.fullCoverage.id, { + loanManager.newEthBackedLoan(products.fullCoverage.id, 0, { from: accounts[1], value: global.web3v1.utils.toWei("0.05") }), - loanManager.newEthBackedLoan(products.negativeInterest.id, { + loanManager.newEthBackedLoan(products.negativeInterest.id, 0, { from: accounts[1], value: global.web3v1.utils.toWei("0.05") }) @@ -192,11 +192,11 @@ contract("Loans collection tests", accounts => { it("Should NOT collect multiple loans if one is not due", async function() { const loanCount = (await loanManager.getLoanCount()).toNumber(); await Promise.all([ - loanManager.newEthBackedLoan(products.notDue.id, { + loanManager.newEthBackedLoan(products.notDue.id, 0, { from: accounts[0], value: global.web3v1.utils.toWei("0.05") }), - loanManager.newEthBackedLoan(products.defaulting.id, { + loanManager.newEthBackedLoan(products.defaulting.id, 0, { from: accounts[1], value: global.web3v1.utils.toWei("0.05") }) @@ -305,6 +305,14 @@ contract("Loans collection tests", accounts => { }); testHelpers.logGasUse(this, tx, "addExtraCollateral"); + testHelpers.assertEvent(loanManager, "LoanChanged", { + loanId: loan.id, + borrower: loan.borrower, + collateralAmount: global.web3v1.utils.toWei("0.1").toString(), + repaymentAmount: loan.repaymentAmount.toString(), + currentRate: (await rates.rates("EUR"))[0].toString() + }); + // should not be collectable assert(await isCollectable(loan.id) === 0); await testHelpers.expectThrow(loanManager.collect([loan.id])); diff --git a/test/loanManager.js b/test/loanManager.js index 73a4994b..7d89b324 100644 --- a/test/loanManager.js +++ b/test/loanManager.js @@ -22,7 +22,7 @@ contract("loanManager tests", accounts => { minDisbursedAmount: 3000, term: 86400, discountRate: 970000, - collateralRatio: 850000, + initialCollateralRatio: 1176471, defaultingFeePt: 50000, isActive: true, minCollateralRatio: 0 @@ -30,7 +30,7 @@ contract("loanManager tests", accounts => { await loanManager.addLoanProduct( loanProduct.term, loanProduct.discountRate, - loanProduct.collateralRatio, + loanProduct.initialCollateralRatio, loanProduct.minDisbursedAmount, loanProduct.defaultingFeePt, loanProduct.isActive, @@ -83,7 +83,7 @@ contract("loanManager tests", accounts => { minDisbursedAmount: 3000, term: 86400, discountRate: 970000, - collateralRatio: 850000, + initialCollateralRatio: 1176471, defaultingFeePt: 50000, isActive: true, minCollateralRatio: 0 @@ -91,7 +91,7 @@ contract("loanManager tests", accounts => { const tx = await loanManager.addLoanProduct( prod.term, prod.discountRate, - prod.collateralRatio, + prod.initialCollateralRatio, prod.minDisbursedAmount, prod.defaultingFeePt, prod.isActive, @@ -121,7 +121,7 @@ contract("loanManager tests", accounts => { assert.equal(lastProduct.id.toNumber(), prod.id); assert.equal(lastProduct.term.toNumber(), prod.term); assert.equal(lastProduct.discountRate.toNumber(), prod.discountRate); - assert.equal(lastProduct.collateralRatio.toNumber(), prod.collateralRatio); + assert.equal(lastProduct.initialCollateralRatio.toNumber(), prod.initialCollateralRatio); assert.equal(lastProduct.minDisbursedAmount.toNumber(), prod.minDisbursedAmount); assert.equal(lastProduct.defaultingFeePt.toNumber(), prod.defaultingFeePt); const expMaxLoanAmount = await tokenTestHelpers.monetarySupervisor.getMaxLoanAmount( @@ -137,7 +137,7 @@ contract("loanManager tests", accounts => { minDisbursedAmount: 3000, term: 86400, discountRate: 970000, - collateralRatio: 850000, + initialCollateralRatio: 1176471, defaultingFeePt: 50000, isActive: true, minCollateralRatio: 0 @@ -145,7 +145,7 @@ contract("loanManager tests", accounts => { const tx = await loanManager.addLoanProduct( prod.term, prod.discountRate, - prod.collateralRatio, + prod.initialCollateralRatio, prod.minDisbursedAmount, prod.defaultingFeePt, prod.isActive, @@ -164,7 +164,7 @@ contract("loanManager tests", accounts => { assert.equal(lastProduct.id.toNumber(), prod.id); assert.equal(lastProduct.term.toNumber(), prod.term); assert.equal(lastProduct.discountRate.toNumber(), prod.discountRate); - assert.equal(lastProduct.collateralRatio.toNumber(), prod.collateralRatio); + assert.equal(lastProduct.initialCollateralRatio.toNumber(), prod.initialCollateralRatio); assert.equal(lastProduct.minDisbursedAmount.toNumber(), prod.minDisbursedAmount); assert.equal(lastProduct.defaultingFeePt.toNumber(), prod.defaultingFeePt); const expMaxLoanAmount = await tokenTestHelpers.monetarySupervisor.getMaxLoanAmount( diff --git a/test/loanToDepositRatioLimits.js b/test/loanToDepositRatioLimits.js index d2bb0215..f4625dbe 100644 --- a/test/loanToDepositRatioLimits.js +++ b/test/loanToDepositRatioLimits.js @@ -169,7 +169,7 @@ contract("Loan to Deposit ratio tests", accounts => { it("LTD when totalLock = 0 and totalLoan > 0 and allowed difference amount is in effect", async function() { // get a loan const collateralAmount = global.web3v1.utils.toWei((3000 / rate).toString()); - await loanManager.newEthBackedLoan(loanProductId, { value: collateralAmount }); + await loanManager.newEthBackedLoan(loanProductId, 0, { value: collateralAmount }); // Earned interest 0 let limits = await getLtdLimits(); @@ -198,7 +198,7 @@ contract("Loan to Deposit ratio tests", accounts => { it("LTD when totalLock = totalLoan and allowed difference amount is in effect", async function() { // get a loan const collateralAmount = global.web3v1.utils.toWei((3000 / rate).toString()); - await loanManager.newEthBackedLoan(loanProductId, { value: collateralAmount }); + await loanManager.newEthBackedLoan(loanProductId, 0, { value: collateralAmount }); // lock the same amount const amountToLock = 3000; @@ -236,7 +236,7 @@ contract("Loan to Deposit ratio tests", accounts => { // get a loan const collateralAmount = global.web3v1.utils.toWei((640000 / rate).toString()); - await loanManager.newEthBackedLoan(loanProductId, { value: collateralAmount }); + await loanManager.newEthBackedLoan(loanProductId, 0, { value: collateralAmount }); // lock less than the loan const amountToLock = 600000; @@ -281,7 +281,7 @@ contract("Loan to Deposit ratio tests", accounts => { // get a loan const collateralAmount = global.web3v1.utils.toWei((640000 / rate).toString()); - await loanManager.newEthBackedLoan(loanProductId, { value: collateralAmount }); + await loanManager.newEthBackedLoan(loanProductId, 0, { value: collateralAmount }); // lock less than the loan const amountToLock = 600000; @@ -322,7 +322,7 @@ contract("Loan to Deposit ratio tests", accounts => { // get a loan const collateralAmount = global.web3v1.utils.toWei((600000 / rate).toString()); - await loanManager.newEthBackedLoan(loanProductId, { value: collateralAmount }); + await loanManager.newEthBackedLoan(loanProductId, 0, { value: collateralAmount }); // lock more than the loan const amountToLock = 640000; @@ -367,7 +367,7 @@ contract("Loan to Deposit ratio tests", accounts => { // get a loan const collateralAmount = global.web3v1.utils.toWei((359974 / rate).toString()); - await loanManager.newEthBackedLoan(loanProductId, { value: collateralAmount }); + await loanManager.newEthBackedLoan(loanProductId, 0, { value: collateralAmount }); // lock more than the loan const amountToLock = 410000; @@ -420,6 +420,6 @@ contract("Loan to Deposit ratio tests", accounts => { it("should NOT allow to borrow more than maxLoanAmountAllowedByLtd ", async function() { const collateralAmount = global.web3v1.utils.toWei(((ltdParams.allowedDifferenceAmount + 1) / rate).toString()); - await testHelpers.expectThrow(loanManager.newEthBackedLoan(loanProductId, { value: collateralAmount })); + await testHelpers.expectThrow(loanManager.newEthBackedLoan(loanProductId, 0, { value: collateralAmount })); }); }); diff --git a/test/loans.js b/test/loans.js index 7cf33032..d6fba83c 100644 --- a/test/loans.js +++ b/test/loans.js @@ -37,16 +37,16 @@ contract("Loans tests", accounts => { ) ]); // These neeed to be sequential b/c product order assumed when retrieving via getProducts - // term (in sec), discountRate, loanCoverageRatio, minDisbursedAmount (w/ 2 decimals), defaultingFeePt, isActive, minCollateralRatio - await loanManager.addLoanProduct(86400, 970000, 850000, 3000, 50000, true, 0); // notDue - await loanManager.addLoanProduct(60, 985000, 900000, 2000, 50000, true, 0); // repaying - await loanManager.addLoanProduct(1, 970000, 850000, 1000, 50000, true, 0); // defaulting - await loanManager.addLoanProduct(1, 990000, 990000, 1000, 50000, false, 0); // disabledProduct - await loanManager.addLoanProduct(60, 1000000, 900000, 2000, 50000, true, 0); // zeroInterest - await loanManager.addLoanProduct(60, 1100000, 900000, 2000, 50000, true, 0); // negativeInterest + // term (in sec), discountRate, initialCollateralRatio, minDisbursedAmount (w/ 2 decimals), defaultingFeePt, isActive, minCollateralRatio + await loanManager.addLoanProduct(86400, 970000, 1176471, 3000, 50000, true, 0); // notDue + await loanManager.addLoanProduct(60, 985000, 1111111, 2000, 50000, true, 0); // repaying + await loanManager.addLoanProduct(1, 970000, 1176471, 1000, 50000, true, 0); // defaulting + await loanManager.addLoanProduct(1, 990000, 1010101, 1000, 50000, false, 0); // disabledProduct + await loanManager.addLoanProduct(60, 1000000, 1111111, 2000, 50000, true, 0); // zeroInterest + await loanManager.addLoanProduct(60, 1100000, 1111111, 2000, 50000, true, 0); // negativeInterest await loanManager.addLoanProduct(60, 990000, 1000000, 2000, 50000, true, 0); // fullCoverage - await loanManager.addLoanProduct(60, 990000, 1200000, 2000, 50000, true, 0); // moreCoverage - await loanManager.addLoanProduct(86400, 970000, 625000, 3000, 50000, true, 1200000); // with margin (collateral ratio: initial = 160%, minimum = 120%) (1/1.6 = 0.625) + await loanManager.addLoanProduct(60, 990000, 833333, 2000, 50000, true, 0); // moreCoverage + await loanManager.addLoanProduct(86400, 970000, 1600000, 3000, 50000, true, 1200000); // with margin (collateral ratio: initial = 160%, minimum = 120%) const [newProducts] = await Promise.all([ loanTestHelpers.getProductsInfo(prodCount, CHUNK_SIZE), @@ -81,6 +81,18 @@ contract("Loans tests", accounts => { await loanTestHelpers.createLoan(this, products.repaying, accounts[0], global.web3v1.utils.toWei("0.5")); }); + it("Should get an A-EUR loan if current rate is not below minRate", async function() { + const minRate = 22020; + await rates.setRate("EUR", minRate); + await loanTestHelpers.createLoan(this, products.repaying, accounts[1], global.web3v1.utils.toWei("0.5"), minRate); + }); + + it("Should NOT get an A-EUR loan if current rate is below minRate", async function() { + const minRate = 22020; + await rates.setRate("EUR", minRate - 1); + await testHelpers.expectThrow(loanManager.newEthBackedLoan(products.repaying.id, minRate, { from: accounts[1], value: global.web3v1.utils.toWei("0.5") })); + }); + it("Should NOT get a loan less than minDisbursedAmount", async function() { const prod = products.repaying; const loanAmount = prod.minDisbursedAmount @@ -89,16 +101,16 @@ contract("Loans tests", accounts => { .mul(1000000) .round(0, BigNumber.ROUND_DOWN); const weiAmount = (await rates.convertToWei(tokenTestHelpers.peggedSymbol, loanAmount)) - .div(prod.collateralRatio) + .div(prod.initialCollateralRatio) .mul(1000000) .round(0, BigNumber.ROUND_DOWN); - await testHelpers.expectThrow(loanManager.newEthBackedLoan(prod.id, { from: accounts[0], value: weiAmount })); + await testHelpers.expectThrow(loanManager.newEthBackedLoan(prod.id, 0, { from: accounts[0], value: weiAmount })); }); it("Shouldn't get a loan for a disabled product", async function() { await testHelpers.expectThrow( - loanManager.newEthBackedLoan(products.disabledProduct.id, { + loanManager.newEthBackedLoan(products.disabledProduct.id, 0, { from: accounts[0], value: global.web3v1.utils.toWei("0.05") }) @@ -171,9 +183,11 @@ contract("Loans tests", accounts => { from: accounts[0] }); - await testHelpers.assertEvent(loanManager, "LoanRepayed", { + const currentRate = (await rates.rates("EUR"))[0].toNumber(); + await testHelpers.assertEvent(loanManager, "LoanRepaid", { loanId: loan.id, - borrower: loan.borrower + borrower: loan.borrower, + currentRate: currentRate }); }); @@ -249,7 +263,7 @@ contract("Loans tests", accounts => { it("Should not get a loan when rates = 0", async function() { await rates.setRate("EUR", 0); await testHelpers.expectThrow( - loanManager.newEthBackedLoan(products.repaying.id, { + loanManager.newEthBackedLoan(products.repaying.id, 0, { from: accounts[1], value: global.web3v1.utils.toWei("0.1") }) @@ -354,11 +368,11 @@ contract("Loans tests", accounts => { await craftedLender.addLoanProduct(100000, 1000000, 1000000, 1000, 50000, true, 0); // testing Lender not having "LoanManager" permission on monetarySupervisor: - await testHelpers.expectThrow(craftedLender.newEthBackedLoan(0, { value: global.web3v1.utils.toWei("0.05") })); + await testHelpers.expectThrow(craftedLender.newEthBackedLoan(0, 0, { value: global.web3v1.utils.toWei("0.05") })); // grant permission to create new loan await monetarySupervisor.grantPermission(craftedLender.address, "LoanManager"); - await craftedLender.newEthBackedLoan(0, { value: global.web3v1.utils.toWei("0.05") }); + await craftedLender.newEthBackedLoan(0, 0, { value: global.web3v1.utils.toWei("0.05") }); // revoke permission and try to repay await monetarySupervisor.revokePermission(craftedLender.address, "LoanManager"), @@ -407,6 +421,7 @@ contract("Loans tests", accounts => { assert.equal(loan.loanAmount.toNumber(), 3025); // = 3118 * 0.97, round up (token) assert.equal(loan.interestAmount.toNumber(), 93); // = 3118 - 3025 (token) assert.equal(loan.state, 0); // = "Open" + assert.equal(loan.currentRate, 99800); // assert LoanData has proper numbers stored const loanInfo = loanTestHelpers.parseLoansInfo(await loanManager.getLoans(loan.id, 1)); @@ -436,6 +451,14 @@ contract("Loans tests", accounts => { }); testHelpers.logGasUse(this, tx, "addExtraCollateral"); + testHelpers.assertEvent(loanManager, "LoanChanged", { + loanId: loan.id, + borrower: loan.borrower, + collateralAmount: global.web3v1.utils.toWei("0.1").toString(), + repaymentAmount: loan.repaymentAmount.toString(), + currentRate: (await rates.rates("EUR"))[0].toString() + }); + // collateralAmount should double up, marginCallRate get halved const loanInfo3 = loanTestHelpers.parseLoansInfo(await loanManager.getLoans(loan.id, 1)); assert.equal(loanInfo3[0].collateralAmount.toNumber(), 2 * 5e16);