Skip to content

Commit 3a61fcc

Browse files
committed
Initial commit
0 parents  commit 3a61fcc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+16661
-0
lines changed

.env.example

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
PRIVATE_KEY=
2+
INFURA_API_KEY=
3+
ETHERSCAN_API_KEY=
4+
MAX_SLOT=
5+
MAX_NFTS_PER_SLOT=
6+
ROYALTY_FEE_BPS=
7+
DAO_ADDRESS=
8+
SUPPORTED_BID_TOKENS=
9+
ROYALTIES_REGISTRY=

.github/workflows/slither.yml

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
on: [push]
2+
jobs:
3+
main_job:
4+
runs-on: ubuntu-latest
5+
name: Solidity Security
6+
steps:
7+
- name: Checkout
8+
uses: actions/checkout@v2
9+
- name: Setup Node.js
10+
uses: actions/setup-node@v2
11+
with:
12+
node-version: '14'
13+
- name: Install dependencies
14+
run: yarn
15+
- name: Configure Hardhat
16+
env:
17+
HARDHAT_CONFIG : ${{secrets.HARDHAT_CONFIG}}
18+
run: echo "$HARDHAT_CONFIG" > hardhat.config.js
19+
- name: Compile Contracts
20+
run: yarn compile
21+
- name: Slither Static Analysis
22+
uses: luisfontes19/[email protected]
23+
with:
24+
slither-version: '0.8.2'
25+
run-npm-install: true
26+
high-threshold: 1
27+
medium-threshold: 1
28+
low-threshold: 1
29+
optimization-threshold: 1
30+
informative-threshold: 10
31+
projectPath: "."

.gitignore

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
node_modules
2+
.envrc
3+
.env
4+
.DS_Store
5+
coverage
6+
coverage.json
7+
.openzeppelin
8+
9+
deployments/ganache
10+
deployments/rinkeby
11+
deployments/ropsten
12+
deployments/mainnet
13+
14+
#Hardhat files
15+
cache
16+
artifacts
17+
.vscode
18+
.solhint.json
19+
.prettierrc
20+
arguments.js

AUCTION_STAGES.md

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
## Auctions specs/stages
2+
3+
### 1. Create an auction with specified configuration
4+
5+
- The auction is opened for deposits until the *startTime* timestamp is reached
6+
- The depositor can deposit NFTs into each defined slot, up to the slot limit
7+
- The depositor can withdraw NFTs before the auction has started
8+
- The auction can be cancelled before the *startTime* timestamp
9+
- If the auction has started and there are no deposited NFTs, the auction becomes void and does not accept deposits
10+
11+
### 2. Auction start
12+
13+
- Users are allowed to to bid to the auction (with ERC20 token or ETH)
14+
- There is no restriction on the bid amount until all slots have been filled
15+
- Once there are more bids than slots, every next bid should be higher than the next winning bid
16+
- Each user is allowed to withdraw his bid if it is a non winning bid
17+
- Users are allowed to bid until the *endTime* timestamp is reached
18+
19+
### 3. Auction end
20+
21+
- When the *endTime* timestamp is reached, the *finalizeAuction* function should be called. It will check which slots have been won, assign the winners and the bid amounts
22+
- Once the auction is finalized, the revenue for each slot should be captured. Without this step, the auctioneer wouldn’t be able to collect his winnings and the bidders wouldn’t be able to withdraw their NFTs
23+
- In the case of auction with little amount of slots, all slots revenue can be captured in a batch transaction *captureSlotRevenueRange*
24+
25+
### 4. Capture revenue
26+
27+
- When the revenue has been captured, the winners are allowed to withdraw the NFTs they’ve won. This is done by calling the *claimERC721Rewards*. There could be a case, where there are more NFTs in a slot and one transaction could not be enough (due to gas limitations). In this case the function should be called multiple times with the remaining amounts.
28+
- In the case there is a slot which hasn’t been won by anyone (either because the reserve price hasn't been met or there weren't enough bids), the depositor can withdraw his NFTs with the *withdrawERC721FromNonWinningSlot* function. It has the same mechanics as *claimERC721Rewards*
29+
- To collect the revenue from the auctions, *distributeCapturedAuctionRevenue* should be called for each slot.
30+
31+
### Functions related to each stage:
32+
33+
- **Create** - *createAuction*
34+
- **Cancel** - *cancelAuction*, *withdrawDepositedERC721*
35+
- **Deposit** - *depositERC721*, *batchDepositToAuction*
36+
- **Bidding** - *ethBid*, *erc20Bid*, *withdrawEthBid*, *withdrawERC20Bid*
37+
- **Finalize** - *finalizeAuction*, *captureSlotRevenue*, *captureSlotRevenueRange*, *claimERC721Rewards*, *withdrawERC721FromNonWinningSlot*
38+
- **Revenue distribution** - *distributeCapturedAuctionRevenue*, *distributeSecondarySaleFees*, *distributeRoyalties*

README.md

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# ERC721 Auctions
2+
3+
The repository contains ERC721 Auctions smart contracts written for UniverseXYZ. Credit to [UniverseXYZ](https://universe.xyz).
4+
5+
### Build the project
6+
7+
Run:
8+
9+
```
10+
$ yarn
11+
$ cp .envrc.example .envrc
12+
$ source .envrc
13+
$ yarn compile
14+
```
15+
16+
### Run Tests
17+
18+
```
19+
$ npx hardhat test
20+
```
21+
22+
### Deploy to Ganache
23+
24+
```
25+
$ ./start_ganache.sh
26+
$ yarn deploy ganache
27+
```
28+
29+
### Deploy to live networks
30+
31+
Edit .envrc.example then copy it to .envrc
32+
33+
```
34+
$ cp .envrc.example .envrc
35+
$ source .envrc
36+
```
37+
38+
Make sure to update the enviroment variables with suitable values.
39+
40+
Now enable the env vars using [direnv](https://direnv.net/docs/installation.html)
41+
42+
```
43+
$ eval "$(direnv hook bash)"
44+
$ direnv allow
45+
```
46+
47+
Deploy to a network:
48+
49+
```
50+
$ yarn deploy rinkeby
51+
```
52+
53+
### Verify smart contract on etherscan
54+
55+
To verify the deployed contract run:
56+
57+
```
58+
$ yarn etherscan-verify rinkeby --address
59+
```
60+
61+
### Gas cost estimation
62+
63+
To get a gas estimation for deployment of contracts and functions calls, the `REPORT_GAS` env variable must be set to true. To estimate with certaing gas price update the hardhat.config.js file. Gas estimation happens during test, only functions specified in tests will get an estimation. run with:
64+
65+
```
66+
$ yarn test
67+
```
68+
69+
### Rinkeby deployments
70+
71+
UniverseAuctionHouse - https://rinkeby.etherscan.io/address/0x2345164eFfE24EA125ECD0ec9C7539D5422c367f
72+
73+
UniverseERC721Factory - https://rinkeby.etherscan.io/address/0x26E84797880B6435861E8730171B75e6257bCBa0
74+
75+
UniverseERC721 - https://rinkeby.etherscan.io/address/0xF7B12892699D6c94E83d864805A381548cfB2A29
76+
77+
UniverseERC721Core - https://rinkeby.etherscan.io/address/0xfD7D165344a04241AB3Cd07d021eEC17F03ADc51

contracts/ERC2981Royalties.sol

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.11;
3+
4+
import "@openzeppelin/contracts/utils/introspection/ERC165Storage.sol";
5+
import "./interfaces/IERC2981Royalties.sol";
6+
7+
/// @dev This is a contract used to add ERC2981 support to ERC721 and 1155
8+
abstract contract ERC2981Royalties is ERC165Storage, IERC2981Royalties {
9+
struct RoyaltyInfo {
10+
address recipient;
11+
uint24 amount;
12+
}
13+
14+
mapping(uint256 => RoyaltyInfo) internal _royalties;
15+
16+
bytes4 private constant _INTERFACE_ID_ERC2981 = 0x2a55205a;
17+
18+
constructor() {
19+
_registerInterface(_INTERFACE_ID_ERC2981);
20+
}
21+
22+
/// @dev Sets token royalties
23+
/// @param tokenId the token id fir which we register the royalties
24+
/// @param recipient recipient of the royalties
25+
/// @param value percentage (using 2 decimals - 10000 = 100, 0 = 0)
26+
function _setTokenRoyalty(
27+
uint256 tokenId,
28+
address recipient,
29+
uint256 value
30+
) internal {
31+
require(value <= 10000, "ERC2981Royalties: Too high");
32+
_royalties[tokenId] = RoyaltyInfo(recipient, uint24(value));
33+
}
34+
35+
function royaltyInfo(uint256 tokenId, uint256 value)
36+
external
37+
view
38+
override
39+
returns (address receiver, uint256 royaltyAmount)
40+
{
41+
RoyaltyInfo memory royalties = _royalties[tokenId];
42+
receiver = royalties.recipient;
43+
royaltyAmount = (value * royalties.amount) / 10000;
44+
}
45+
}

contracts/ERC721Consumable.sol

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.11;
3+
4+
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
5+
import "./interfaces/IERC721Consumable.sol";
6+
7+
abstract contract ERC721Consumable is IERC721Consumable, ERC721 {
8+
9+
// Mapping from token ID to consumer address
10+
mapping (uint256 => address) _tokenConsumers;
11+
12+
/**
13+
* @dev See {IERC721Consumable-consumerOf}
14+
*/
15+
function consumerOf(uint256 _tokenId) view external returns (address) {
16+
require(_exists(_tokenId), "ERC721Consumable: consumer query for nonexistent token");
17+
return _tokenConsumers[_tokenId];
18+
}
19+
20+
/**
21+
* @dev See {IERC721Consumable-changeConsumer}
22+
*/
23+
function changeConsumer(address _consumer, uint256 _tokenId) external {
24+
address owner = this.ownerOf(_tokenId);
25+
require(msg.sender == owner || msg.sender == getApproved(_tokenId) ||
26+
isApprovedForAll(owner, msg.sender),
27+
"ERC721Consumable: changeConsumer caller is not owner nor approved");
28+
_changeConsumer(owner, _consumer, _tokenId);
29+
}
30+
31+
/**
32+
* @dev Changes the consumer
33+
* Requirement: `tokenId` must exist
34+
*/
35+
function _changeConsumer(address _owner, address _consumer, uint256 _tokenId) internal {
36+
_tokenConsumers[_tokenId] = _consumer;
37+
emit ConsumerChanged(_owner, _consumer, _tokenId);
38+
}
39+
40+
/**
41+
* @dev See {IERC165-supportsInterface}.
42+
*/
43+
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721) returns (bool) {
44+
return interfaceId == type(IERC721Consumable).interfaceId || super.supportsInterface(interfaceId);
45+
}
46+
47+
function _beforeTokenTransfer(address _from, address _to, uint256 _tokenId) internal virtual override (ERC721) {
48+
super._beforeTokenTransfer(_from, _to, _tokenId);
49+
50+
_changeConsumer(_from, address(0), _tokenId);
51+
}
52+
53+
}

contracts/HasSecondarySaleFees.sol

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.11;
3+
4+
import "@openzeppelin/contracts/utils/introspection/ERC165Storage.sol";
5+
6+
contract HasSecondarySaleFees is ERC165Storage {
7+
struct Fee {
8+
address payable recipient;
9+
uint96 value;
10+
}
11+
12+
// id => fees
13+
mapping (uint256 => Fee[]) public fees;
14+
event SecondarySaleFees(uint256 tokenId, address[] recipients, uint[] bps);
15+
16+
/*
17+
* bytes4(keccak256('getFeeBps(uint256)')) == 0x0ebd4c7f
18+
* bytes4(keccak256('getFeeRecipients(uint256)')) == 0xb9c4d9fb
19+
*
20+
* => 0x0ebd4c7f ^ 0xb9c4d9fb == 0xb7799584
21+
*/
22+
bytes4 private constant _INTERFACE_ID_FEES = 0xb7799584;
23+
constructor() {
24+
_registerInterface(_INTERFACE_ID_FEES);
25+
}
26+
27+
function getFeeRecipients(uint256 id) external view returns (address payable[] memory) {
28+
Fee[] memory _fees = fees[id];
29+
address payable[] memory result = new address payable[](_fees.length);
30+
for (uint i = 0; i < _fees.length; i++) {
31+
result[i] = _fees[i].recipient;
32+
}
33+
return result;
34+
}
35+
36+
function getFeeBps(uint256 id) external view returns (uint[] memory) {
37+
Fee[] memory _fees = fees[id];
38+
uint[] memory result = new uint[](_fees.length);
39+
for (uint i = 0; i < _fees.length; i++) {
40+
result[i] = _fees[i].value;
41+
}
42+
return result;
43+
}
44+
45+
}

0 commit comments

Comments
 (0)