-
-
Notifications
You must be signed in to change notification settings - Fork 562
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: replace greeter with lock contract (#182)
* refactor: replace greeter with lock contract * test: reorder test * fix: fix type error docs: improve parameter wording --------- Co-authored-by: Paul Razvan Berg <paul.razvan.berg@gmail.com>
- Loading branch information
1 parent
e7224e5
commit f43ec3c
Showing
14 changed files
with
239 additions
and
122 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity >=0.8.9; | ||
|
||
error InvalidUnlockTime(uint256 unlockTime); | ||
error NotOwner(address owner); | ||
error UnlockTimeNotReached(uint256 unlockTime); | ||
|
||
contract Lock { | ||
uint256 public unlockTime; | ||
address payable public owner; | ||
|
||
event Withdrawal(uint256 amount, uint256 when); | ||
|
||
constructor(uint256 _unlockTime) payable { | ||
if (block.timestamp >= _unlockTime) { | ||
revert InvalidUnlockTime(_unlockTime); | ||
} | ||
|
||
unlockTime = _unlockTime; | ||
owner = payable(msg.sender); | ||
} | ||
|
||
function withdraw() public { | ||
if (block.timestamp < unlockTime) { | ||
revert UnlockTimeNotReached(unlockTime); | ||
} | ||
|
||
if (msg.sender != owner) { | ||
revert NotOwner(owner); | ||
} | ||
|
||
emit Withdrawal(address(this).balance, block.timestamp); | ||
|
||
owner.transfer(address(this).balance); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,24 @@ | ||
import { DeployFunction } from "hardhat-deploy/types"; | ||
import { HardhatRuntimeEnvironment } from "hardhat/types"; | ||
|
||
const DAY_IN_SECONDS = 60 * 60 * 24; | ||
const NOW_IN_SECONDS = Math.round(Date.now() / 1000); | ||
const UNLOCK_IN_X_DAYS = NOW_IN_SECONDS + DAY_IN_SECONDS * 1; // 1 DAY | ||
|
||
const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { | ||
const { deployer } = await hre.getNamedAccounts(); | ||
const { deploy } = hre.deployments; | ||
const lockedAmount = hre.ethers.parseEther("0.01").toString(); | ||
|
||
const greeter = await deploy("Greeter", { | ||
const lock = await deploy("Lock", { | ||
from: deployer, | ||
args: ["Bonjour, le monde!"], | ||
args: [UNLOCK_IN_X_DAYS], | ||
log: true, | ||
value: lockedAmount, | ||
}); | ||
|
||
console.log(`Greeter contract: `, greeter.address); | ||
console.log(`Lock contract: `, lock.address); | ||
}; | ||
export default func; | ||
func.id = "deploy_greeter"; // id required to prevent reexecution | ||
func.tags = ["Greeter"]; | ||
func.id = "deploy_lock"; // id required to prevent reexecution | ||
func.tags = ["Lock"]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { task } from "hardhat/config"; | ||
import type { TaskArguments } from "hardhat/types"; | ||
|
||
function distance(past: number, future: number): string { | ||
// get total seconds between the times | ||
let delta = future - past; | ||
|
||
// calculate (and subtract) whole days | ||
const days = Math.floor(delta / 86400); | ||
delta -= days * 86400; | ||
|
||
// calculate (and subtract) whole hours | ||
const hours = Math.floor(delta / 3600) % 24; | ||
delta -= hours * 3600; | ||
|
||
// calculate (and subtract) whole minutes | ||
const minutes = Math.floor(delta / 60) % 60; | ||
delta -= minutes * 60; | ||
|
||
// what's left is seconds | ||
const seconds = delta % 60; // in theory the modulus is not required | ||
|
||
return `${days} day(s), ${hours} hour(s), ${minutes} minute(s) and ${seconds} second(s)`; | ||
} | ||
|
||
task("task:withdraw", "Calls the withdraw function of Lock Contract") | ||
.addOptionalParam("address", "Optionally specify the Lock address to withdraw") | ||
.addParam("account", "Specify which account [0, 9]") | ||
.setAction(async function (taskArguments: TaskArguments, hre) { | ||
const { ethers, deployments } = hre; | ||
|
||
const Lock = taskArguments.address ? { address: taskArguments.address } : await deployments.get("Lock"); | ||
|
||
const signers = await ethers.getSigners(); | ||
console.log(taskArguments.address); | ||
|
||
const lock = await ethers.getContractAt("Lock", Lock.address); | ||
|
||
const initialBalance = await ethers.provider.getBalance(Lock.address); | ||
await lock.connect(signers[taskArguments.account]).withdraw(); | ||
const finalBalance = await ethers.provider.getBalance(Lock.address); | ||
|
||
console.log("Contract balance before withdraw", ethers.formatEther(initialBalance)); | ||
console.log("Contract balance after withdraw", ethers.formatEther(finalBalance)); | ||
|
||
console.log("Lock Withdraw Success"); | ||
}); | ||
|
||
task("task:deployLock", "Deploys Lock Contract") | ||
.addParam("unlock", "When to unlock funds in seconds (number of seconds into the futrue)") | ||
.addParam("value", "How much ether you intend locking (in ether not wei, e.g., 0.1)") | ||
.setAction(async function (taskArguments: TaskArguments, { ethers }) { | ||
const NOW_IN_SECONDS = Math.round(Date.now() / 1000); | ||
|
||
const signers = await ethers.getSigners(); | ||
const lockedAmount = ethers.parseEther(taskArguments.value); | ||
const unlockTime = NOW_IN_SECONDS + parseInt(taskArguments.unlock); | ||
const lockFactory = await ethers.getContractFactory("Lock"); | ||
console.log(`Deploying Lock and locking ${taskArguments.value} ETH for ${distance(NOW_IN_SECONDS, unlockTime)}`); | ||
const lock = await lockFactory.connect(signers[0]).deploy(unlockTime, { value: lockedAmount }); | ||
await lock.waitForDeployment(); | ||
console.log("Lock deployed to: ", await lock.getAddress()); | ||
}); |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { time } from "@nomicfoundation/hardhat-network-helpers"; | ||
import { ethers } from "hardhat"; | ||
|
||
import type { Lock } from "../../types/Lock"; | ||
import type { Lock__factory } from "../../types/factories/Lock__factory"; | ||
|
||
export async function deployLockFixture() { | ||
const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60; | ||
const ONE_GWEI = 1_000_000_000; | ||
|
||
const lockedAmount = ONE_GWEI; | ||
const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS; | ||
|
||
// Contracts are deployed using the first signer/account by default | ||
const [owner, otherAccount] = await ethers.getSigners(); | ||
|
||
const Lock = (await ethers.getContractFactory("Lock")) as Lock__factory; | ||
const lock = (await Lock.deploy(unlockTime, { value: lockedAmount })) as Lock; | ||
const lock_address = await lock.getAddress(); | ||
|
||
return { lock, lock_address, unlockTime, lockedAmount, owner, otherAccount }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs"; | ||
import { loadFixture, time } from "@nomicfoundation/hardhat-network-helpers"; | ||
import { expect } from "chai"; | ||
import { ethers } from "hardhat"; | ||
|
||
import type { Signers } from "../types"; | ||
import { deployLockFixture } from "./Lock.fixture"; | ||
|
||
describe("Lock", function () { | ||
before(async function () { | ||
this.signers = {} as Signers; | ||
|
||
const signers = await ethers.getSigners(); | ||
this.signers.admin = signers[0]; | ||
|
||
this.loadFixture = loadFixture; | ||
}); | ||
|
||
describe("Deployment", function () { | ||
beforeEach(async function () { | ||
const { lock, lock_address, unlockTime, owner, lockedAmount } = await this.loadFixture(deployLockFixture); | ||
this.lock = lock; | ||
this.lock_address = lock_address; | ||
this.unlockTime = unlockTime; | ||
this.owner = owner; | ||
this.lockedAmount = lockedAmount; | ||
}); | ||
|
||
it("Should fail if the unlockTime is not in the future", async function () { | ||
// We don't use the fixture here because we want a different deployment | ||
const latestTime = await time.latest(); | ||
const Lock = await ethers.getContractFactory("Lock"); | ||
await expect(Lock.deploy(latestTime, { value: 1 })).to.be.revertedWithCustomError(Lock, "InvalidUnlockTime"); | ||
}); | ||
|
||
it("Should set the right unlockTime", async function () { | ||
expect(await this.lock.unlockTime()).to.equal(this.unlockTime); | ||
}); | ||
|
||
it("Should set the right owner", async function () { | ||
expect(await this.lock.owner()).to.equal(this.owner.address); | ||
}); | ||
|
||
it("Should receive and store the funds to lock", async function () { | ||
expect(await ethers.provider.getBalance(this.lock_address)).to.equal(this.lockedAmount); | ||
}); | ||
}); | ||
|
||
describe("Withdrawals", function () { | ||
beforeEach(async function () { | ||
const { lock, unlockTime, owner, lockedAmount, otherAccount } = await this.loadFixture(deployLockFixture); | ||
this.lock = lock; | ||
this.unlockTime = unlockTime; | ||
this.owner = owner; | ||
this.lockedAmount = lockedAmount; | ||
this.otherAccount = otherAccount; | ||
}); | ||
|
||
describe("Validations", function () { | ||
it("Should revert with the right error if called too soon", async function () { | ||
await expect(this.lock.withdraw()).to.be.revertedWithCustomError(this.lock, "UnlockTimeNotReached"); | ||
}); | ||
|
||
it("Should revert with the right error if called from another account", async function () { | ||
// We can increase the time in Hardhat Network | ||
await time.increaseTo(this.unlockTime); | ||
|
||
// We use lock.connect() to send a transaction from another account | ||
await expect(this.lock.connect(this.otherAccount).withdraw()).to.be.revertedWithCustomError( | ||
this.lock, | ||
"NotOwner", | ||
); | ||
}); | ||
|
||
it("Shouldn't fail if the unlockTime has arrived and the owner calls it", async function () { | ||
// Transactions are sent using the first signer by default | ||
await time.increaseTo(this.unlockTime); | ||
|
||
await expect(this.lock.withdraw()).not.to.be.reverted; | ||
}); | ||
}); | ||
|
||
describe("Events", function () { | ||
it("Should emit an event on withdrawals", async function () { | ||
await time.increaseTo(this.unlockTime); | ||
|
||
await expect(this.lock.withdraw()).to.emit(this.lock, "Withdrawal").withArgs(this.lockedAmount, anyValue); // We accept any value as `when` arg | ||
}); | ||
}); | ||
|
||
describe("Transfers", function () { | ||
it("Should transfer the funds to the owner", async function () { | ||
await time.increaseTo(this.unlockTime); | ||
|
||
await expect(this.lock.withdraw()).to.changeEtherBalances( | ||
[this.owner, this.lock], | ||
[this.lockedAmount, -this.lockedAmount], | ||
); | ||
}); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.