From cca7ae3d5f1554a28e5e77bfd6f2bd7cfa1e34c7 Mon Sep 17 00:00:00 2001 From: rambo Date: Tue, 19 Nov 2024 18:57:37 -0300 Subject: [PATCH] feat: gas optimizations --- .gas-snapshot | 10 ++++ src/FundMe.sol | 52 ++++++++++++++++---- test/FundMeTest.t.sol | 111 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 162 insertions(+), 11 deletions(-) create mode 100644 .gas-snapshot diff --git a/.gas-snapshot b/.gas-snapshot new file mode 100644 index 0000000..136e84b --- /dev/null +++ b/.gas-snapshot @@ -0,0 +1,10 @@ +FundMeTest:testAddsFunderToArrayOfFunders() (gas: 98086) +FundMeTest:testCheaperWithdrawFromMultipleFunders() (gas: 484620) +FundMeTest:testFundFailsWithoutEnoughEth() (gas: 20851) +FundMeTest:testFundUpdatesFundedDataStructure() (gas: 97749) +FundMeTest:testMinimumDollarIsFive() (gas: 8445) +FundMeTest:testOnlyOwnerCanWithdraw() (gas: 97909) +FundMeTest:testOwnerIsMessageSender() (gas: 11301) +FundMeTest:testPriceFeedVersionIsAccurate() (gas: 11538) +FundMeTest:testWithdarawWithASingleFunder() (gas: 83425) +FundMeTest:testWithdrawFromMultipleFunders() (gas: 484601) \ No newline at end of file diff --git a/src/FundMe.sol b/src/FundMe.sol index 63cd93a..4742525 100644 --- a/src/FundMe.sol +++ b/src/FundMe.sol @@ -10,11 +10,10 @@ error FundMe_NotOwner(); contract FundMe { using PriceConverter for uint256; - mapping(address => uint256) public addressToAmountFunded; - address[] public funders; + mapping(address => uint256) private s_addressToAmountFunded; + address[] private s_funders; - // Could we make this constant? /* hint: no! We should make it immutable! */ - address public immutable i_owner; + address private immutable i_owner; uint256 public constant MINIMUM_USD = 5 * 10 ** 18; AggregatorV3Interface private immutable s_priceFeed; @@ -29,8 +28,8 @@ contract FundMe { "You need to spend more ETH!" ); // require(PriceConverter.getConversionRate(msg.value) >= MINIMUM_USD, "You need to spend more ETH!"); - addressToAmountFunded[msg.sender] += msg.value; - funders.push(msg.sender); + s_addressToAmountFunded[msg.sender] += msg.value; + s_funders.push(msg.sender); } function getVersion() public view returns (uint256) { @@ -43,16 +42,31 @@ contract FundMe { _; } + function cheaperWithdraw() public onlyOwner { + uint256 fundersLength = s_funders.length; + + for ( + uint256 funderIndex = 0; + funderIndex < fundersLength; + funderIndex++ + ) { + address funder = s_funders[funderIndex]; + s_addressToAmountFunded[funder] = 0; + } + + s_funders = new address[](0); + } + function withdraw() public onlyOwner { for ( uint256 funderIndex = 0; - funderIndex < funders.length; + funderIndex < s_funders.length; funderIndex++ ) { - address funder = funders[funderIndex]; - addressToAmountFunded[funder] = 0; + address funder = s_funders[funderIndex]; + s_addressToAmountFunded[funder] = 0; } - funders = new address[](0); + s_funders = new address[](0); // // transfer // payable(msg.sender).transfer(address(this).balance); @@ -86,6 +100,24 @@ contract FundMe { receive() external payable { fund(); } + + /* + View / Pure functions (Getters) + */ + + function getAddressToAmountFunded( + address addr + ) external view returns (uint256) { + return s_addressToAmountFunded[addr]; + } + + function getFunder(uint256 index) external view returns (address) { + return s_funders[index]; + } + + function getOwner() external view returns (address) { + return i_owner; + } } // Concepts we didn't cover yet (will cover in later sections) diff --git a/test/FundMeTest.t.sol b/test/FundMeTest.t.sol index 9dc1deb..71e2e95 100644 --- a/test/FundMeTest.t.sol +++ b/test/FundMeTest.t.sol @@ -7,10 +7,21 @@ import {DeployFundMe} from "../script/DeployFundMe.s.sol"; contract FundMeTest is Test { FundMe fundMe; + address USER = makeAddr("me"); + uint256 constant SEND_VALUE = 0.1 ether; + uint256 constant STARTING_BALANCE = 10 ether; + uint256 constant GAS_PRICE = 1; + + modifier funded() { + vm.prank(USER); // prank -> The next TX is gonna be sent by USER + fundMe.fund{value: SEND_VALUE}(); + _; + } function setUp() external { DeployFundMe deployFundMe = new DeployFundMe(); fundMe = deployFundMe.run(); + vm.deal(USER, STARTING_BALANCE); } function testMinimumDollarIsFive() public { @@ -19,11 +30,109 @@ contract FundMeTest is Test { function testOwnerIsMessageSender() public { console.log(address(this)); - assertEq(fundMe.i_owner(), msg.sender); + assertEq(fundMe.getOwner(), msg.sender); } function testPriceFeedVersionIsAccurate() public { uint256 version = fundMe.getVersion(); assertEq(version, 4); } + + function testFundFailsWithoutEnoughEth() public { + vm.expectRevert(); // next line should revert + fundMe.fund(); + } + + function testFundUpdatesFundedDataStructure() public funded { + uint256 amountFunded = fundMe.getAddressToAmountFunded(USER); + assertEq(amountFunded, SEND_VALUE); + } + + function testAddsFunderToArrayOfFunders() public funded { + address funder = fundMe.getFunder(0); + assertEq(funder, USER); + } + + function testOnlyOwnerCanWithdraw() public funded { + vm.expectRevert(); + vm.prank(USER); + fundMe.withdraw(); + } + + function testWithdarawWithASingleFunder() public funded { + // Arrange + uint256 startingOwnerBalance = fundMe.getOwner().balance; + uint256 startingFundMeBalance = address(fundMe).balance; + + // Act + vm.prank(fundMe.getOwner()); + fundMe.withdraw(); + + // Assert + uint256 endingOwnerBalance = fundMe.getOwner().balance; + uint256 endingFundMeBalance = address(fundMe).balance; + assertEq(endingFundMeBalance, 0); + assertEq( + startingFundMeBalance + startingOwnerBalance, + endingOwnerBalance + ); + } + + function testWithdrawFromMultipleFunders() public funded { + // Arrange + uint160 numberOfFunders = 10; // we need to use uint160 because it has the same amount of btyes than an address. This way we can create a new address() + uint160 startingFunderIndex = 1; + + for (uint160 i = startingFunderIndex; i < numberOfFunders; i++) { + // vm.prank + // vm.deal + // fund fundMe + // hoax does the same as prank, the difference is that you can add some balance to the address on the second parameter. + hoax(address(i), SEND_VALUE); + fundMe.fund{value: SEND_VALUE}(); + } + + uint256 startingOwnerBalance = fundMe.getOwner().balance; + uint256 startingFundMeBalance = address(fundMe).balance; + + // Act + vm.startPrank(fundMe.getOwner()); + fundMe.withdraw(); + vm.stopPrank(); + + assert(address(fundMe).balance == 0); + assert( + startingFundMeBalance + startingOwnerBalance == + fundMe.getOwner().balance + ); + } + + function testCheaperWithdrawFromMultipleFunders() public funded { + // Arrange + uint160 numberOfFunders = 10; // we need to use uint160 because it has the same amount of btyes than an address. This way we can create a new address() + uint160 startingFunderIndex = 1; + + for (uint160 i = startingFunderIndex; i < numberOfFunders; i++) { + // vm.prank + // vm.deal + // fund fundMe + // hoax does the same as prank, the difference is that you can add some balance to the address on the second parameter. + hoax(address(i), SEND_VALUE); + fundMe.fund{value: SEND_VALUE}(); + } + + uint256 startingOwnerBalance = fundMe.getOwner().balance; + uint256 startingFundMeBalance = address(fundMe).balance; + + // Act + vm.startPrank(fundMe.getOwner()); + fundMe.withdraw(); + vm.stopPrank(); + + assert(address(fundMe).balance == 0); + assert( + startingFundMeBalance + startingOwnerBalance == + fundMe.getOwner().balance + ); + } }