Skip to content

Commit

Permalink
Public release v1.0.3
Browse files Browse the repository at this point in the history
  • Loading branch information
exception committed May 16, 2022
0 parents commit 944277d
Show file tree
Hide file tree
Showing 17 changed files with 41,902 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ETHERSCAN_API_KEY=ABC123ABC123ABC123ABC123ABC123ABC1
ROPSTEN_URL=https://eth-ropsten.alchemyapi.io/v2/<YOUR ALCHEMY KEY>
PRIVATE_KEY=0xabc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1
4 changes: 4 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
artifacts
cache
coverage
24 changes: 24 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module.exports = {
env: {
browser: false,
es2021: true,
mocha: true,
node: true,
},
plugins: ["@typescript-eslint"],
extends: [
"standard",
"plugin:prettier/recommended",
"plugin:node/recommended",
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 12,
},
rules: {
"node/no-unsupported-features/es-syntax": [
"error",
{ ignores: ["modules"] },
],
},
};
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules
.env
coverage
coverage.json
typechain

#Hardhat files
cache
artifacts
3 changes: 3 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
hardhat.config.ts
scripts
test
5 changes: 5 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
artifacts
cache
coverage*
gasReporterOutput.json
1 change: 1 addition & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
7 changes: 7 additions & 0 deletions .solhint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "solhint:recommended",
"rules": {
"compiler-version": ["error", "^0.8.0"],
"func-visibility": ["warn", { "ignoreConstructors": true }]
}
}
1 change: 1 addition & 0 deletions .solhintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
15 changes: 15 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
ISC License

Copyright (c) 2022 AlmostFancy

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
[![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://github.com/almostfancy/true-dutch/LICENSE.txt)
[![NPM](https://img.shields.io/npm/v/@almost-fancy/true-dutch)](https://www.npmjs.com/package/@almost-fancy/true-dutch)

## Installation

```sh
npm install -D @almost-fancy/true-dutch
```

## Usage

To get started, you can use the library as follows:

```solidity
pragma solidity ^0.8.4;
import "@almost-fancy/true-dutch/contracts/TrueDutchAuction.sol";
contract AlmostFancy is TrueDutchAuction, ERC721A {
constructor(address payable _beneficiary)
ERC721A("AlmostFancy", "AF")
TrueDutchAuction(
DutchAuctionConfig({
saleStartTime: dutchSaleTime,
startPriceWei: 0.99 ether,
endPriceWei: 0.09 ether,
duration: 9 hours,
dropInterval: 15 minutes,
maxBidsPerAddress: 0,
available: 1111,
maxPerTx: 3
}),
_beneficiary
)
{}
function placeBid(uint256 quantity) external payable {
_placeAuctionBid(msg.sender, quantity);
}
function _handleBidPlaced(
address whom,
uint256 quantity,
uint256 priceToPay
) internal override {
uint256 cost = priceToPay * quantity;
_safeMint(whom, quantity); // call ERC721A#_safeMint to actually get the asset to the caller
}
}
```

## License

Distributed under the ISC License. See `LICENSE.txt` for more information.

## Contact Us

- Erik (lead dev) - [@erosemberg\_](https://twitter.com/erosemberg_)
31 changes: 31 additions & 0 deletions contracts/ITrueDutchAuction.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
// TrueDutchAuction contracts, written by AlmostFancy
pragma solidity ^0.8.4;

interface ITrueDutchAuction {
struct AuctionBid {
uint256 quantity;
uint256 bid;
uint256 timestamp;
}

event RefundPaid(
address indexed to,
AuctionBid[] bids,
uint256 dutch,
uint256 refund
);

event BidPlaced(
address indexed from,
uint256 quantity,
uint256 currentPrice
);

function getBidsFrom(address bidder)
external
view
returns (AuctionBid[] memory);

function getDutchPrice() external view returns (uint256);
}
183 changes: 183 additions & 0 deletions contracts/TrueDutchAuction.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// SPDX-License-Identifier: MIT
// TrueDutchAuction contracts, written by AlmostFancy
pragma solidity ^0.8.4;

import "./ITrueDutchAuction.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

abstract contract TrueDutchAuction is ITrueDutchAuction, ReentrancyGuard {
struct DutchAuctionConfig {
uint256 saleStartTime;
uint256 startPriceWei;
uint256 endPriceWei;
uint256 duration;
uint256 dropInterval;
uint256 maxBidsPerAddress; // @dev if this is set to 0, there is no limit
uint256 available; // @dev total amount of tokens that can be sold during dutch
uint256 maxPerTx; // @dev max amount of bids per transaction
}

constructor(DutchAuctionConfig memory _config, address payable _beneficiary) {
dutchConfig = _config;
dropsPerStep =
(_config.startPriceWei - _config.endPriceWei) /
(_config.duration / _config.dropInterval);
beneficiary = _beneficiary;
}

address payable public beneficiary;

uint256 private totalSales; // @dev track amount of sales during dutch auction
DutchAuctionConfig public dutchConfig;
uint256 private dropsPerStep;

uint256 internal lastDutchPrice;

bool public refundsEnabled = false;
mapping(address => bool) private claimedRefunds;
mapping(address => AuctionBid[]) public auctionBids;
bool private auctionProfitsWithdrawn = false;

function _setBeneficiary(address payable _beneficiary) internal {
beneficiary = _beneficiary;
}

function _setDutchConfig(DutchAuctionConfig memory _config) internal {
dutchConfig = _config;
dropsPerStep =
(_config.startPriceWei - _config.endPriceWei) /
(_config.duration / _config.dropInterval);
}

// @dev this method is called by _placeAuctionBid() after the sanity checks have
// been completed.
// this is where safeMint() and similar functions should be called
function _handleBidPlaced(address whom, uint256 quantity, uint256 priceToPay) internal virtual;

function _placeAuctionBid(address who, uint256 quantity) internal virtual {
DutchAuctionConfig memory saleConfig = dutchConfig;
// solhint-disable-next-line reason-string
require(
// solhint-disable-next-line not-rely-on-time
saleConfig.saleStartTime != 0 && block.timestamp >= saleConfig.saleStartTime,
"TrueDutchAuction: Dutch auction has not started yet!"
);
// solhint-disable-next-line reason-string
require(quantity <= saleConfig.maxPerTx, "TrueDutchAuction: Exceeds max per tx");
uint256 dutchPrice = getDutchPrice();
// solhint-disable-next-line reason-string
require(msg.value >= quantity * dutchPrice, "TrueDutchAuction: Not enough ETH sent");
if (saleConfig.maxBidsPerAddress != 0) {
// solhint-disable-next-line reason-string
require(
auctionBids[who].length + quantity <=
saleConfig.maxBidsPerAddress,
"TrueDutchAuction: That amount will exceed the max allowed bids per address"
);
}
// solhint-disable-next-line reason-string
require(
totalSales + quantity <= saleConfig.available,
"TrueDutchAuction: Dutch Auction has already sold out!"
);

if (totalSales + quantity == saleConfig.available) {
lastDutchPrice = dutchPrice;
}

totalSales += quantity;
_handleBidPlaced(who, quantity, dutchPrice); // @dev check _handleBidPlaced() notes
// store auction data for refunds later on
auctionBids[who].push(
AuctionBid({
quantity: quantity,
bid: dutchPrice,
// solhint-disable-next-line not-rely-on-time
timestamp: block.timestamp
})
);
emit BidPlaced(who, quantity, dutchPrice);
}

// @dev returns the current dutch price by calculating the steps
function getDutchPrice() public view override returns (uint256) {
DutchAuctionConfig memory saleConfig = dutchConfig;
// solhint-disable-next-line not-rely-on-time
if (block.timestamp < saleConfig.saleStartTime) {
return saleConfig.startPriceWei;
}

if (
// solhint-disable-next-line not-rely-on-time
block.timestamp - saleConfig.saleStartTime >= saleConfig.duration
) {
return saleConfig.endPriceWei;
} else {
// solhint-disable-next-line not-rely-on-time
uint256 steps = (block.timestamp - saleConfig.saleStartTime) /
saleConfig.dropInterval;
return saleConfig.startPriceWei - (steps * dropsPerStep);
}
}

function _toggleRefunds(bool state) internal {
refundsEnabled = state;
}

function claimRefund() external nonReentrant {
// solhint-disable-next-line reason-string
require(refundsEnabled, "TrueDutchAuction: Refunds are not enabled!");
// solhint-disable-next-line reason-string
require(
!claimedRefunds[msg.sender],
"TrueDutchAuction: You have already claimed your refund!"
);
AuctionBid[] memory bids = auctionBids[msg.sender];
// solhint-disable-next-line reason-string
require(
bids.length > 0,
"TrueDutchAuction: You are not eligible for a refund!"
);

uint256 refund = 0;
for (uint256 i = 0; i < bids.length; i++) {
AuctionBid memory bid = bids[i];
refund += (bid.bid * bid.quantity) - lastDutchPrice; // should be safe from underflows
}
claimedRefunds[msg.sender] = true;
// solhint-disable-next-line reason-string
require(refund > 0, "TrueDutchAuction: You are not eligible for a refund!");
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = msg.sender.call{value: refund}("");
require(success, "TrueDutchAuction: Refund failed.");
emit RefundPaid(msg.sender, bids, lastDutchPrice, refund);
}

// @dev withdraws profits made from the dutch auction, multiplies
// the total amount of sales against the set dutch auction price
// can only be called once.
function _withdrawDutchProfits() internal nonReentrant {
// solhint-disable-next-line reason-string
require(!auctionProfitsWithdrawn, "TrueDutchAuction: Dutch Auction profits have already been paid out");
// solhint-disable-next-line reason-string
require(lastDutchPrice > 0, "TrueDutchAuction: The Dutch Auction has not ended yet");
uint256 profits = totalSales * lastDutchPrice;

// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = beneficiary.call{value: profits}("");
// solhint-disable-next-line reason-string
require(success, "TrueDutchAuction: Withdraw failed.");
auctionProfitsWithdrawn = true;
}

// @dev this is meant to be used as a last resort or for dev purposes
// this contract's authors are not made responsible for misuse of this
// method
function _forceDutchFloor(uint256 floor) internal {
lastDutchPrice = floor;
}

function getBidsFrom(address bidder) external view override returns(AuctionBid[] memory) {
return auctionBids[bidder];
}
}
27 changes: 27 additions & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as dotenv from "dotenv";

import { HardhatUserConfig } from "hardhat/config";
import "@nomiclabs/hardhat-waffle";
import "@typechain/hardhat";
import "hardhat-gas-reporter";
import "solidity-coverage";

dotenv.config();

const config: HardhatUserConfig = {
solidity: {
version: "0.8.4",
settings: {
optimizer: {
enabled: true,
runs: 500,
},
},
},
gasReporter: {
enabled: process.env.REPORT_GAS !== undefined,
currency: "USD",
},
};

export default config;
Loading

0 comments on commit 944277d

Please sign in to comment.