Cheaper Gas
This commit is contained in:
parent
aefe632675
commit
28ac29b8ba
@ -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");
|
||||
}
|
||||
}
|
||||
|
32
contracts/example/FunWithStorage.sol
Normal file
32
contracts/example/FunWithStorage.sol
Normal 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
|
||||
}
|
||||
}
|
58
deploy/99-deploy-storage-fun.js
Normal file
58
deploy/99-deploy-storage-fun.js
Normal 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"];
|
@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user