Cheaper Gas

This commit is contained in:
hoelee 2024-08-06 18:49:09 +08:00
parent aefe632675
commit 28ac29b8ba
4 changed files with 228 additions and 25 deletions

View File

@ -41,12 +41,12 @@ contract FundMe {
/***** 6.1 Type Declarations *****/
using PriceConverter for uint256;
uint256 public constant MINIMUM_USD = 50 * 1e18;
address[] public funders;
address[] public s_funders;
address public immutable i_owner; // immutable only can declare 1 time at contructor
AggregatorV3Interface public priceFeed;
AggregatorV3Interface public s_priceFeed;
/***** 6.2 State Variable *****/
mapping(address => uint256) public addressToAmountFunded;
mapping(address => uint256) public s_addressToAmountFunded;
/***** 6.3 Modifier *****/
modifier onlyOwner() {
@ -69,9 +69,9 @@ contract FundMe {
* View / Pure
* *****/
constructor(address priceFeedAddress) {
constructor(address s_priceFeedAddress) {
i_owner = msg.sender;
priceFeed = AggregatorV3Interface(priceFeedAddress);
s_priceFeed = AggregatorV3Interface(s_priceFeedAddress);
}
// What happens if someone send this contract ETH without calling the fund function
@ -92,11 +92,11 @@ contract FundMe {
function fund() public payable {
//require (msg.value >= 1e18, "Didn't send enough"); // 1e18 = 1 * 10 * 18 = 1000000000000000000
require(
msg.value.getConversionRate(priceFeed) >= MINIMUM_USD,
msg.value.getConversionRate(s_priceFeed) >= MINIMUM_USD,
"Didn't send enough"
);
funders.push(msg.sender);
addressToAmountFunded[msg.sender] = msg.value;
s_funders.push(msg.sender);
s_addressToAmountFunded[msg.sender] = msg.value;
}
// Owner of this contract only can withdraw
@ -107,14 +107,14 @@ contract FundMe {
/* starting index, eding index, step amount */
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;
}
// reset the array
funders = new address[](0);
s_funders = new address[](0);
/*
// transfer - over 2300 gas will cause exception
@ -130,4 +130,21 @@ contract FundMe {
}("");
require(callSuccess, "Call failed");
}
function cheaperWithdraw() public onlyOwner {
address[] memory funders = s_funders;
for (
uint256 funderIndex = 0;
funderIndex < funders.length;
funderIndex++
) {
address funder = funders[funderIndex];
s_addressToAmountFunded[funder] = 0;
}
// reset the array
s_funders = new address[](0);
(bool callSuccess, ) = i_owner.call{value: address(this).balance}("");
require(callSuccess, "Call failed");
}
}

View File

@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract FunWithStorage {
uint256 favoriteNumber; // Stored at slot 0
bool someBool; // Stored at slot 1
uint256[] myArray; /* Array Length Stored at slot 2,
but the objects will be the keccak256(2), since 2 is the storage slot of the array */
mapping(uint256 => bool) myMap; /* An empty slot is held at slot 3
and the elements will be stored at keccak256(h(k) . p)
p: The storage slot (aka, 3)
k: The key in hex
h: Some function based on the type. For uint256, it just pads the hex
*/
uint256 constant NOT_IN_STORAGE = 123;
uint256 immutable i_not_in_storage;
constructor() {
favoriteNumber = 25; // See stored spot above // SSTORE
someBool = true; // See stored spot above // SSTORE
myArray.push(222); // SSTORE
myMap[0] = true; // SSTORE
i_not_in_storage = 123;
}
function doStuff() public {
uint256 newVar = favoriteNumber + 1; // SLOAD
bool otherVar = someBool; // SLOAD
// ^^ memory variables
}
}

View File

@ -0,0 +1,58 @@
const { network, ethers } = require("hardhat");
const { developmentChains } = require("../helper-hardhat-config");
const { verify } = require("../utils/verify");
module.exports = async ({ getNamedAccounts, deployments }) => {
const { deploy, log } = deployments;
const { deployer } = await getNamedAccounts();
log("----------------------------------------------------");
log("Deploying FunWithStorage and waiting for confirmations...");
const funWithStorage = await deploy("FunWithStorage", {
from: deployer,
args: [],
log: true,
// we need to wait if on a live network so we can verify properly
waitConfirmations: network.config.blockConfirmations || 1,
});
if (
!developmentChains.includes(network.name) &&
process.env.ETHERSCAN_API_KEY
) {
await verify(funWithStorage.address, []);
}
log("Logging storage...");
for (let i = 0; i < 10; i++) {
log(
`Location ${i}: ${await ethers.provider.getStorageAt(
funWithStorage.address,
i,
)}`,
);
}
// You can use this to trace!
// const trace = await network.provider.send("debug_traceTransaction", [
// funWithStorage.transactionHash,
// ])
// for (structLog in trace.structLogs) {
// if (trace.structLogs[structLog].op == "SSTORE") {
// console.log(trace.structLogs[structLog])
// }
// }
// const firstelementLocation = ethers.utils.keccak256(
// "0x0000000000000000000000000000000000000000000000000000000000000002"
// )
// const arrayElement = await ethers.provider.getStorageAt(
// funWithStorage.address,
// firstelementLocation
// )
// log(`Location ${firstelementLocation}: ${arrayElement}`)
// Can you write a function that finds the storage slot of the arrays and mappings?
// And then find the data in those slots?
};
module.exports.tags = ["storage"];

View File

@ -25,18 +25,18 @@ describe("FundMe", function () {
eth1 = ethers.utils.parseEther("1"); // Same 1 ETH
describe("constructor", async function () {
it("Set the aggregator priceFeedAddress correctly", async function () {
const response = await fundMe.priceFeed(); // AggregatorV3Interface
const response = await fundMe.s_priceFeed(); // AggregatorV3Interface
//console.log("AggregatorV3Interface" + response);
assert.equal(response, mockV3Aggregator.address);
});
it("Updated the amount funded data structure", async function () {
await fundMe.fund({ value: eth1 });
const response = await fundMe.addressToAmountFunded(deployer);
const response = await fundMe.s_addressToAmountFunded(deployer);
assert.equal(response.toString(), eth1.toString()); // Send 1 ETH
});
it("Adds funder to array of funders", async function () {
await fundMe.fund({ value: eth1 });
const funder = await fundMe.funders(0);
const funder = await fundMe.s_funders(0);
assert.equal(funder, deployer);
});
});
@ -80,11 +80,11 @@ describe("FundMe", function () {
});
// Check if addr1 is added to funders
expect(await fundMe.funders(0)).to.equal(addr1.address);
expect(await fundMe.s_funders(0)).to.equal(addr1.address);
// Check if the amount funded is recorded
expect(await fundMe.addressToAmountFunded(addr1.address)).to.equal(
ethers.utils.parseEther("1.0"),
);
expect(
await fundMe.s_addressToAmountFunded(addr1.address),
).to.equal(ethers.utils.parseEther("1.0"));
});
it("should revert if the amount sent is below the minimum funding value", async function () {
@ -110,10 +110,10 @@ describe("FundMe", function () {
}),
).not.to.be.reverted;
expect(await fundMe.funders(0)).to.equal(addr1.address);
expect(await fundMe.addressToAmountFunded(addr1.address)).to.equal(
minFunding,
);
expect(await fundMe.s_funders(0)).to.equal(addr1.address);
expect(
await fundMe.s_addressToAmountFunded(addr1.address),
).to.equal(minFunding);
});
});
@ -191,11 +191,11 @@ describe("FundMe", function () {
endingDeployerBalance.add(withdrawGasCost).toString(),
);
// Make a getter for storage variables
await expect(fundMe.funders(0)).to.be.reverted;
await expect(fundMe.s_funders(0)).to.be.reverted;
for (i = 1; i < 6; i++) {
assert.equal(
await fundMe.addressToAmountFunded(accounts[i].address),
await fundMe.s_addressToAmountFunded(accounts[i].address),
0,
);
}
@ -211,4 +211,100 @@ describe("FundMe", function () {
).to.be.revertedWith("FundMe__NotOwner");
});
});
describe("cheaperWithdraw", async function () {
beforeEach(async function () {
await fundMe.fund({ value: eth1 }); // Add 1 ETH first
});
it("Withdraw ETH from a single funder", async function () {
/*
* Deployer = funder
* fundMe and replace with ethers, important to getBalance
*/
// Arrange
const startingFundMeBalance = await fundMe.provider.getBalance(
fundMe.address,
);
const startingDeployerBalance =
await fundMe.provider.getBalance(deployer);
// Act
const transactionResponse = await fundMe.cheaperWithdraw();
const transactionReceipt = await transactionResponse.wait(1);
const endingFundMeBalance = await fundMe.provider.getBalance(
fundMe.address,
);
const endingDeployerBalance =
await fundMe.provider.getBalance(deployer);
const { gasUsed, effectiveGasPrice } = transactionReceipt; //gasUsed = "0x8b14"; effectiveGasPrice = "0x4e4814ec";
const gasCost = gasUsed.mul(effectiveGasPrice);
// Assert
assert.equal(endingFundMeBalance, 0);
assert.equal(
startingFundMeBalance.add(startingDeployerBalance),
endingDeployerBalance.add(gasCost).toString(),
);
});
it("Allow us to withdraw with multiple funders", async function () {
const accounts = await ethers.getSigners();
for (let a = 0; a < 6; a++) {
const fundMeConnectedContract = await fundMe.connect(
accounts[a],
);
await fundMeConnectedContract.fund({ value: eth1 });
}
const startingFundMeBalance = await fundMe.provider.getBalance(
fundMe.address,
);
const startingDeployerBalance =
await fundMe.provider.getBalance(deployer);
// Act
const transactionResponse = await fundMe.cheaperWithdraw();
// Let's comapre gas costs :)
// const transactionResponse = await fundMe.cheaperWithdraw()
const transactionReceipt = await transactionResponse.wait();
const { gasUsed, effectiveGasPrice } = transactionReceipt;
const withdrawGasCost = gasUsed.mul(effectiveGasPrice);
console.log(`GasCost: ${withdrawGasCost}`);
console.log(`GasUsed: ${gasUsed}`);
console.log(`GasPrice: ${effectiveGasPrice}`);
const endingFundMeBalance = await fundMe.provider.getBalance(
fundMe.address,
);
const endingDeployerBalance =
await fundMe.provider.getBalance(deployer);
// Assert
assert.equal(
startingFundMeBalance.add(startingDeployerBalance).toString(),
endingDeployerBalance.add(withdrawGasCost).toString(),
);
// Make a getter for storage variables
await expect(fundMe.s_funders(0)).to.be.reverted;
for (i = 1; i < 6; i++) {
assert.equal(
await fundMe.s_addressToAmountFunded(accounts[i].address),
0,
);
}
});
it("Only allow the owner to withdraw", async function () {
const accounts = await ethers.getSigners();
const attacker = accounts[1];
const attacherConnectedContract = await fundMe.connect(attacker);
await expect(attacherConnectedContract.cheaperWithdraw()).to.be
.reverted;
await expect(
attacherConnectedContract.cheaperWithdraw(),
).to.be.revertedWith("FundMe__NotOwner");
});
});
});