Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
0xHustling committed Sep 15, 2024
0 parents commit 40e75e9
Show file tree
Hide file tree
Showing 41 changed files with 16,661 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
PRIVATE_KEY=
INFURA_API_KEY=
ETHERSCAN_API_KEY=
MAX_SLOT=
MAX_NFTS_PER_SLOT=
ROYALTY_FEE_BPS=
DAO_ADDRESS=
SUPPORTED_BID_TOKENS=
ROYALTIES_REGISTRY=
31 changes: 31 additions & 0 deletions .github/workflows/slither.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
on: [push]
jobs:
main_job:
runs-on: ubuntu-latest
name: Solidity Security
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Install dependencies
run: yarn
- name: Configure Hardhat
env:
HARDHAT_CONFIG : ${{secrets.HARDHAT_CONFIG}}
run: echo "$HARDHAT_CONFIG" > hardhat.config.js
- name: Compile Contracts
run: yarn compile
- name: Slither Static Analysis
uses: luisfontes19/[email protected]
with:
slither-version: '0.8.2'
run-npm-install: true
high-threshold: 1
medium-threshold: 1
low-threshold: 1
optimization-threshold: 1
informative-threshold: 10
projectPath: "."
20 changes: 20 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
node_modules
.envrc
.env
.DS_Store
coverage
coverage.json
.openzeppelin

deployments/ganache
deployments/rinkeby
deployments/ropsten
deployments/mainnet

#Hardhat files
cache
artifacts
.vscode
.solhint.json
.prettierrc
arguments.js
38 changes: 38 additions & 0 deletions AUCTION_STAGES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
## Auctions specs/stages

### 1. Create an auction with specified configuration

- The auction is opened for deposits until the *startTime* timestamp is reached
- The depositor can deposit NFTs into each defined slot, up to the slot limit
- The depositor can withdraw NFTs before the auction has started
- The auction can be cancelled before the *startTime* timestamp
- If the auction has started and there are no deposited NFTs, the auction becomes void and does not accept deposits

### 2. Auction start

- Users are allowed to to bid to the auction (with ERC20 token or ETH)
- There is no restriction on the bid amount until all slots have been filled
- Once there are more bids than slots, every next bid should be higher than the next winning bid
- Each user is allowed to withdraw his bid if it is a non winning bid
- Users are allowed to bid until the *endTime* timestamp is reached

### 3. Auction end

- 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
- 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
- In the case of auction with little amount of slots, all slots revenue can be captured in a batch transaction *captureSlotRevenueRange*

### 4. Capture revenue

- 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.
- 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*
- To collect the revenue from the auctions, *distributeCapturedAuctionRevenue* should be called for each slot.

### Functions related to each stage:

- **Create** - *createAuction*
- **Cancel** - *cancelAuction*, *withdrawDepositedERC721*
- **Deposit** - *depositERC721*, *batchDepositToAuction*
- **Bidding** - *ethBid*, *erc20Bid*, *withdrawEthBid*, *withdrawERC20Bid*
- **Finalize** - *finalizeAuction*, *captureSlotRevenue*, *captureSlotRevenueRange*, *claimERC721Rewards*, *withdrawERC721FromNonWinningSlot*
- **Revenue distribution** - *distributeCapturedAuctionRevenue*, *distributeSecondarySaleFees*, *distributeRoyalties*
77 changes: 77 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# UniverseXYZ ERC721 Auctions

The repository contains ERC721 Auctions smart contracts written for UniverseXYZ. Credit to [UniverseXYZ](https://universe.xyz).

### Build the project

Run:

```
$ yarn
$ cp .envrc.example .envrc
$ source .envrc
$ yarn compile
```

### Run Tests

```
$ npx hardhat test
```

### Deploy to Ganache

```
$ ./start_ganache.sh
$ yarn deploy ganache
```

### Deploy to live networks

Edit .envrc.example then copy it to .envrc

```
$ cp .envrc.example .envrc
$ source .envrc
```

Make sure to update the enviroment variables with suitable values.

Now enable the env vars using [direnv](https://direnv.net/docs/installation.html)

```
$ eval "$(direnv hook bash)"
$ direnv allow
```

Deploy to a network:

```
$ yarn deploy rinkeby
```

### Verify smart contract on etherscan

To verify the deployed contract run:

```
$ yarn etherscan-verify rinkeby --address
```

### Gas cost estimation

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:

```
$ yarn test
```

### Rinkeby deployments

UniverseAuctionHouse - https://rinkeby.etherscan.io/address/0x2345164eFfE24EA125ECD0ec9C7539D5422c367f

UniverseERC721Factory - https://rinkeby.etherscan.io/address/0x26E84797880B6435861E8730171B75e6257bCBa0

UniverseERC721 - https://rinkeby.etherscan.io/address/0xF7B12892699D6c94E83d864805A381548cfB2A29

UniverseERC721Core - https://rinkeby.etherscan.io/address/0xfD7D165344a04241AB3Cd07d021eEC17F03ADc51
45 changes: 45 additions & 0 deletions contracts/ERC2981Royalties.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.11;

import "@openzeppelin/contracts/utils/introspection/ERC165Storage.sol";
import "./interfaces/IERC2981Royalties.sol";

/// @dev This is a contract used to add ERC2981 support to ERC721 and 1155
abstract contract ERC2981Royalties is ERC165Storage, IERC2981Royalties {
struct RoyaltyInfo {
address recipient;
uint24 amount;
}

mapping(uint256 => RoyaltyInfo) internal _royalties;

bytes4 private constant _INTERFACE_ID_ERC2981 = 0x2a55205a;

constructor() {
_registerInterface(_INTERFACE_ID_ERC2981);
}

/// @dev Sets token royalties
/// @param tokenId the token id fir which we register the royalties
/// @param recipient recipient of the royalties
/// @param value percentage (using 2 decimals - 10000 = 100, 0 = 0)
function _setTokenRoyalty(
uint256 tokenId,
address recipient,
uint256 value
) internal {
require(value <= 10000, "ERC2981Royalties: Too high");
_royalties[tokenId] = RoyaltyInfo(recipient, uint24(value));
}

function royaltyInfo(uint256 tokenId, uint256 value)
external
view
override
returns (address receiver, uint256 royaltyAmount)
{
RoyaltyInfo memory royalties = _royalties[tokenId];
receiver = royalties.recipient;
royaltyAmount = (value * royalties.amount) / 10000;
}
}
53 changes: 53 additions & 0 deletions contracts/ERC721Consumable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//SPDX-License-Identifier: MIT
pragma solidity 0.8.11;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "./interfaces/IERC721Consumable.sol";

abstract contract ERC721Consumable is IERC721Consumable, ERC721 {

// Mapping from token ID to consumer address
mapping (uint256 => address) _tokenConsumers;

/**
* @dev See {IERC721Consumable-consumerOf}
*/
function consumerOf(uint256 _tokenId) view external returns (address) {
require(_exists(_tokenId), "ERC721Consumable: consumer query for nonexistent token");
return _tokenConsumers[_tokenId];
}

/**
* @dev See {IERC721Consumable-changeConsumer}
*/
function changeConsumer(address _consumer, uint256 _tokenId) external {
address owner = this.ownerOf(_tokenId);
require(msg.sender == owner || msg.sender == getApproved(_tokenId) ||
isApprovedForAll(owner, msg.sender),
"ERC721Consumable: changeConsumer caller is not owner nor approved");
_changeConsumer(owner, _consumer, _tokenId);
}

/**
* @dev Changes the consumer
* Requirement: `tokenId` must exist
*/
function _changeConsumer(address _owner, address _consumer, uint256 _tokenId) internal {
_tokenConsumers[_tokenId] = _consumer;
emit ConsumerChanged(_owner, _consumer, _tokenId);
}

/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721) returns (bool) {
return interfaceId == type(IERC721Consumable).interfaceId || super.supportsInterface(interfaceId);
}

function _beforeTokenTransfer(address _from, address _to, uint256 _tokenId) internal virtual override (ERC721) {
super._beforeTokenTransfer(_from, _to, _tokenId);

_changeConsumer(_from, address(0), _tokenId);
}

}
45 changes: 45 additions & 0 deletions contracts/HasSecondarySaleFees.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.11;

import "@openzeppelin/contracts/utils/introspection/ERC165Storage.sol";

contract HasSecondarySaleFees is ERC165Storage {
struct Fee {
address payable recipient;
uint96 value;
}

// id => fees
mapping (uint256 => Fee[]) public fees;
event SecondarySaleFees(uint256 tokenId, address[] recipients, uint[] bps);

/*
* bytes4(keccak256('getFeeBps(uint256)')) == 0x0ebd4c7f
* bytes4(keccak256('getFeeRecipients(uint256)')) == 0xb9c4d9fb
*
* => 0x0ebd4c7f ^ 0xb9c4d9fb == 0xb7799584
*/
bytes4 private constant _INTERFACE_ID_FEES = 0xb7799584;
constructor() {
_registerInterface(_INTERFACE_ID_FEES);
}

function getFeeRecipients(uint256 id) external view returns (address payable[] memory) {
Fee[] memory _fees = fees[id];
address payable[] memory result = new address payable[](_fees.length);
for (uint i = 0; i < _fees.length; i++) {
result[i] = _fees[i].recipient;
}
return result;
}

function getFeeBps(uint256 id) external view returns (uint[] memory) {
Fee[] memory _fees = fees[id];
uint[] memory result = new uint[](_fees.length);
for (uint i = 0; i < _fees.length; i++) {
result[i] = _fees[i].value;
}
return result;
}

}
Loading

0 comments on commit 40e75e9

Please sign in to comment.