Skip to content

Commit 028da8f

Browse files
committed
CoinEngine and Test Update
1 parent 12dccc7 commit 028da8f

File tree

3 files changed

+139
-51
lines changed

3 files changed

+139
-51
lines changed

src/CoinEngine.sol

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ contract CoinEngine is ReentrancyGuard {
5050
error CoinEngine_TransferFailed();
5151
error CoinEngine_HealthFactorBroken();
5252
error CoinEngine_TokenAddressAndPriceFeedAddressLengthDontMatch();
53+
error CoinEngine_HealtFactorIsGood();
54+
error CoinEngine_HealthFactorNotImproved();
5355

5456
///////////////////
5557
// Types
@@ -66,6 +68,7 @@ contract CoinEngine is ReentrancyGuard {
6668
uint256 private constant ADDITIONAL_FEED_PRECISION = 1e10;
6769
uint256 private constant LIQUIDATION_THRESOLD = 50;
6870
uint256 private constant LIQUIDATION_PRECISION = 100;
71+
uint256 private constant LIQUIDATION_BONUS = 10;
6972

7073
/// @dev Mapping of token address to price feed address
7174
mapping(address tokenCollateralAddress => address priceFeed) private s_priceFeeds;
@@ -80,6 +83,7 @@ contract CoinEngine is ReentrancyGuard {
8083
// Events
8184
///////////////////
8285
event CollateralDeposited(address indexed user, address indexed tokenCollateralAddress, uint256 amount);
86+
event CollateralRedeemed(address indexed redeemFrom, address indexed redeemTo, address token, uint256 amount);
8387

8488
///////////////////
8589
// Modifiers
@@ -154,13 +158,13 @@ contract CoinEngine is ReentrancyGuard {
154158

155159
function mintSc(uint256 amountToMint) public nonReentrant moreThanZero(amountToMint) {
156160
s_scMinted[msg.sender] += amountToMint;
157-
_revertIfHealtFactorIsBroken(msg.sender);
161+
_revertIfHealthFactorIsBroken(msg.sender);
158162
}
159163

160164
// Burn you own SC.
161165
function burnSC(uint256 amount) external moreThanZero(amount) {
162166
_burnSc(amount, msg.sender, msg.sender);
163-
_revertIfHealtFactorIsBroken(msg.sender); // technically not needed to do this
167+
_revertIfHealthFactorIsBroken(msg.sender); // technically not needed to do this
164168
}
165169

166170
function redeemCollateral(address tokenCollateralAddress, uint256 amountCollateral)
@@ -171,7 +175,28 @@ contract CoinEngine is ReentrancyGuard {
171175
{
172176
s_collateralDeposited[msg.sender][tokenCollateralAddress] -= amountCollateral;
173177
_redeemCollateral(tokenCollateralAddress, amountCollateral, msg.sender, msg.sender);
174-
_revertIfHealtFactorIsBroken(msg.sender);
178+
_revertIfHealthFactorIsBroken(msg.sender);
179+
}
180+
181+
function liquidate(uint256 debtToCover, address tokenCollateralAddress, address user) external {
182+
uint256 startingHealthFactor = _getUserHealthFactor(user);
183+
184+
if (startingHealthFactor > MIN_HEALTH_FACTOR) {
185+
revert CoinEngine_HealtFactorIsGood();
186+
}
187+
uint256 tokenAmountToCoverFromDebt = getTokenValueFromUSD(debtToCover, tokenCollateralAddress);
188+
uint256 bonusCollateral = (tokenAmountToCoverFromDebt * LIQUIDATION_BONUS) / LIQUIDATION_PRECISION;
189+
190+
_redeemCollateral(tokenCollateralAddress, tokenAmountToCoverFromDebt + bonusCollateral, user, msg.sender);
191+
_burnSc(debtToCover, user, msg.sender);
192+
193+
uint256 endingUserHealthFactor = _getUserHealthFactor(user);
194+
195+
// This condtional will not hit
196+
if (endingUserHealthFactor < startingHealthFactor) {
197+
revert CoinEngine_HealthFactorNotImproved();
198+
}
199+
_revertIfHealthFactorIsBroken(msg.sender);
175200
}
176201

177202
//////////////////////////////
@@ -200,6 +225,7 @@ contract CoinEngine is ReentrancyGuard {
200225
private
201226
{
202227
s_collateralDeposited[from][tokenCollateralAddress] -= amountCollateral;
228+
emit CollateralRedeemed(from, to, tokenCollateralAddress, amountCollateral);
203229
(bool success) = IERC20(tokenCollateralAddress).transfer(to, amountCollateral);
204230
if (!success) {
205231
revert CoinEngine_TransferFailed();
@@ -213,7 +239,7 @@ contract CoinEngine is ReentrancyGuard {
213239
return ((uint256(price) * ADDITIONAL_FEED_PRECISION) * amount) / PRECISION;
214240
}
215241

216-
function _revertIfHealtFactorIsBroken(address user) internal view {
242+
function _revertIfHealthFactorIsBroken(address user) internal view {
217243
uint256 userHealthFactor = _getUserHealthFactor(user);
218244
if (userHealthFactor < MIN_HEALTH_FACTOR) {
219245
revert CoinEngine_HealthFactorBroken();
@@ -248,16 +274,30 @@ contract CoinEngine is ReentrancyGuard {
248274
pure
249275
returns (uint256)
250276
{
251-
// if (totalSCMinted == 0) return type(uint256).max;
277+
if (totalSCMinted == 0) return type(uint256).max;
252278
uint256 collateralThresold = (collateralValueInUSD * LIQUIDATION_THRESOLD) / LIQUIDATION_PRECISION;
253279
return (collateralThresold * PRECISION) / totalSCMinted;
254280
}
255281

256282
/////////////////////////////////////////////
257283
// External & Public View & Pure Functions
258284
/////////////////////////////////////////////
285+
function getTokenValueFromUSD(uint256 amountSC, address tokenAddress) public view returns (uint256) {
286+
AggregatorV3Interface priceFeed = AggregatorV3Interface(s_priceFeeds[tokenAddress]);
287+
(, int256 price,,,) = priceFeed.latestRoundData();
288+
289+
return ((amountSC * PRECISION) / (uint256(price) * ADDITIONAL_FEED_PRECISION));
290+
}
259291

260292
function getValueUSD(address tokenAddress, uint256 amount) external view returns (uint256) {
261293
return _getValueUSD(tokenAddress, amount);
262294
}
295+
296+
function getAccountInformation(address user)
297+
external
298+
view
299+
returns (uint256 totalSCMinted, uint256 collateralValueInUSD)
300+
{
301+
(totalSCMinted, collateralValueInUSD) = _getAccountInformation(user);
302+
}
263303
}

test/SCEngine.t.sol

Lines changed: 0 additions & 46 deletions
This file was deleted.

test/unit/SCEngine.t.sol

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
2+
pragma solidity 0.8;
3+
4+
import {Test} from "forge-std/Test.sol";
5+
import {DeploySC} from "script/DeploySC.s.sol";
6+
import {CoinEngine} from "src/CoinEngine.sol";
7+
import {StableCoin} from "src/StableCoin.sol";
8+
import {HelperConfig} from "script/HelperConfig.s.sol";
9+
import {ERC20Mock} from "../mocks/ERC20Mock.sol";
10+
11+
contract SCEngine is Test {
12+
CoinEngine ce;
13+
StableCoin sc;
14+
HelperConfig config;
15+
16+
address weth;
17+
address ethPriceFeed;
18+
address btcPriceFeed;
19+
address constant USER = address(1);
20+
uint256 constant AMOUNT_COLLATERAL = 10 ether;
21+
22+
function setUp() public {
23+
DeploySC deployer = new DeploySC();
24+
(sc, ce, config) = deployer.run();
25+
(ethPriceFeed,, weth,,) = config.activeNetworkConfig();
26+
27+
ERC20Mock(weth).mint(USER, 1000e18);
28+
}
29+
30+
///////////////////////
31+
// Constructor Tests //
32+
///////////////////////
33+
address[] public tokenAddresses;
34+
address[] public priceFeedAddresses;
35+
36+
function testIfTokenLengthDoesntMatchPriceFeeds() public {
37+
tokenAddresses.push(weth);
38+
priceFeedAddresses.push(ethPriceFeed);
39+
priceFeedAddresses.push(btcPriceFeed);
40+
vm.expectRevert(CoinEngine.CoinEngine_TokenAddressAndPriceFeedAddressLengthDontMatch.selector);
41+
new CoinEngine(tokenAddresses, priceFeedAddresses, address(sc));
42+
}
43+
44+
//////////////////
45+
// Price Tests //
46+
//////////////////
47+
48+
function testGetTokenValueFromUSD() public view {
49+
uint256 amountSc = 100 ether; // 100e18
50+
uint256 expectEthValue = 0.05 ether;
51+
uint256 actualEthValue = ce.getTokenValueFromUSD(amountSc, weth);
52+
assertEq(expectEthValue, actualEthValue);
53+
}
54+
55+
function testGetUsdValue() public view {
56+
uint256 ethAmount = 1 ether;
57+
// 1 ETH = $2000, with 18 decimals precision
58+
uint256 expectedUsd = 2000e18;
59+
60+
uint256 actualUsd = ce.getValueUSD(weth, ethAmount);
61+
assertEq(actualUsd, expectedUsd, "USD value incorrect");
62+
}
63+
64+
///////////////////////////////////////
65+
// depositCollateral Tests //
66+
///////////////////////////////////////
67+
function testRevertIfCollateralIsZero() public {
68+
vm.startPrank(USER);
69+
ERC20Mock(weth).approve(address(ce), AMOUNT_COLLATERAL);
70+
71+
vm.expectRevert(CoinEngine.CoinEngine_AmountShouldMoreThanZero.selector);
72+
ce.depositCollateral(weth, 0);
73+
vm.stopPrank();
74+
}
75+
76+
function testRevertsWithUnapprovedCollateral() public {
77+
ERC20Mock tempToken = new ERC20Mock("TESTToken", "TST", USER, 1000e18);
78+
vm.expectRevert(abi.encodeWithSelector(CoinEngine.CoinEngine_isAllowedToken.selector, address(tempToken)));
79+
ce.depositCollateral(address(tempToken), AMOUNT_COLLATERAL);
80+
}
81+
82+
function testCanDepositeCollateralAndGetUSERInfo() public {
83+
vm.startPrank(USER);
84+
ERC20Mock(weth).approve(address(ce), AMOUNT_COLLATERAL);
85+
ce.depositCollateral(weth, AMOUNT_COLLATERAL);
86+
vm.stopPrank();
87+
88+
(uint256 totalSCMinted, uint256 collateralValueInUSD) = ce.getAccountInformation(USER);
89+
uint256 expectedTotalSCMinted = 0;
90+
uint256 expectedDepositedValueinUSD = ce.getTokenValueFromUSD(collateralValueInUSD, weth);
91+
vm.assertEq(expectedTotalSCMinted, totalSCMinted);
92+
vm.assertEq(expectedDepositedValueinUSD, AMOUNT_COLLATERAL);
93+
}
94+
}

0 commit comments

Comments
 (0)