Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auction bidder sanction check #876

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,17 @@ import { PausableUpgradeable } from '@openzeppelin/contracts-upgradeable/securit
import { ReentrancyGuardUpgradeable } from '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol';
import { OwnableUpgradeable } from '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';
import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import { INounsAuctionHouseV2 } from './interfaces/INounsAuctionHouseV2.sol';
import { INounsAuctionHouseV3 } from './interfaces/INounsAuctionHouseV3.sol';
import { INounsToken } from './interfaces/INounsToken.sol';
import { IWETH } from './interfaces/IWETH.sol';
import { IChainalysisSanctionsList } from './external/chainalysis/IChainalysisSanctionsList.sol';

/**
* @dev The contract inherits from PausableUpgradeable & ReentrancyGuardUpgradeable most of all the keep the same
* storage layout as the NounsAuctionHouse contract
*/
contract NounsAuctionHouseV2 is
INounsAuctionHouseV2,
contract NounsAuctionHouseV3 is
INounsAuctionHouseV3,
PausableUpgradeable,
ReentrancyGuardUpgradeable,
OwnableUpgradeable
Expand All @@ -64,11 +65,14 @@ contract NounsAuctionHouseV2 is
uint8 public minBidIncrementPercentage;

/// @notice The active auction
INounsAuctionHouseV2.AuctionV2 public auctionStorage;
INounsAuctionHouseV3.AuctionV2 public auctionStorage;

/// @notice The Nouns price feed state
mapping(uint256 => SettlementState) settlementHistory;

/// @notice The contract used to verify bidders are not sanctioned wallets
IChainalysisSanctionsList public sanctionsOracle;

constructor(INounsToken _nouns, address _weth, uint256 _duration) initializer {
nouns = _nouns;
weth = _weth;
Expand All @@ -83,7 +87,8 @@ contract NounsAuctionHouseV2 is
function initialize(
uint192 _reservePrice,
uint56 _timeBuffer,
uint8 _minBidIncrementPercentage
uint8 _minBidIncrementPercentage,
IChainalysisSanctionsList _sanctionsOracle
) external initializer {
__Pausable_init();
__ReentrancyGuard_init();
Expand All @@ -94,6 +99,9 @@ contract NounsAuctionHouseV2 is
reservePrice = _reservePrice;
timeBuffer = _timeBuffer;
minBidIncrementPercentage = _minBidIncrementPercentage;
sanctionsOracle = _sanctionsOracle;

emit SanctionsOracleSet(address(_sanctionsOracle));
}

/**
Expand Down Expand Up @@ -127,14 +135,15 @@ contract NounsAuctionHouseV2 is
* @dev This contract only accepts payment in ETH.
*/
function createBid(uint256 nounId, uint32 clientId) public payable override {
INounsAuctionHouseV2.AuctionV2 memory _auction = auctionStorage;
INounsAuctionHouseV3.AuctionV2 memory _auction = auctionStorage;

(uint192 _reservePrice, uint56 _timeBuffer, uint8 _minBidIncrementPercentage) = (
reservePrice,
timeBuffer,
minBidIncrementPercentage
);

_requireNotSanctioned(msg.sender);
require(_auction.nounId == nounId, 'Noun not up for auction');
require(block.timestamp < _auction.endTime, 'Auction expired');
require(msg.value >= _reservePrice, 'Must send at least reservePrice');
Expand Down Expand Up @@ -238,6 +247,16 @@ contract NounsAuctionHouseV2 is
emit AuctionMinBidIncrementPercentageUpdated(_minBidIncrementPercentage);
}

/**
* @notice Set the sanctions oracle address.
* @dev Only callable by the owner.
*/
function setSanctionsOracle(address newSanctionsOracle) public onlyOwner {
sanctionsOracle = IChainalysisSanctionsList(newSanctionsOracle);

emit SanctionsOracleSet(newSanctionsOracle);
}

/**
* @notice Create an auction.
* @dev Store the auction details in the `auction` state variable and emit an AuctionCreated event.
Expand Down Expand Up @@ -270,7 +289,7 @@ contract NounsAuctionHouseV2 is
* @dev If there are no bids, the Noun is burned.
*/
function _settleAuction() internal {
INounsAuctionHouseV2.AuctionV2 memory _auction = auctionStorage;
INounsAuctionHouseV3.AuctionV2 memory _auction = auctionStorage;

require(_auction.startTime != 0, "Auction hasn't begun");
require(!_auction.settled, 'Auction has already been settled');
Expand Down Expand Up @@ -320,6 +339,16 @@ contract NounsAuctionHouseV2 is
return success;
}

/**
* @notice Revert if `sanctionsOracle` is set and `account` is sanctioned.
*/
function _requireNotSanctioned(address account) internal view {
IChainalysisSanctionsList sanctionsOracle_ = sanctionsOracle;
if (address(sanctionsOracle_) != address(0)) {
require(!sanctionsOracle_.isSanctioned(account), 'Sanctioned bidder');
}
}

/**
* @notice Set historic prices; only callable by the owner, which in Nouns is the treasury (timelock) contract.
* @dev This function lowers auction price accuracy from 18 decimals to 10 decimals, as part of the price history
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.19;

interface IChainalysisSanctionsList {
function isSanctioned(address addr) external view returns (bool);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

pragma solidity ^0.8.19;

import { INounsToken } from './INounsToken.sol';

interface INounsAuctionHouseV2 {
struct AuctionV2 {
// ID for the Noun (ERC721 token ID)
Expand Down Expand Up @@ -151,4 +153,14 @@ interface INounsAuctionHouseV2 {
function duration() external view returns (uint256);

function biddingClient(uint256 nounId) external view returns (uint32 clientId);

function minBidIncrementPercentage() external view returns (uint8);

function nouns() external view returns (INounsToken);

function weth() external view returns (address);

function reservePrice() external view returns (uint192);

function timeBuffer() external view returns (uint56);
}
158 changes: 158 additions & 0 deletions packages/nouns-contracts/contracts/interfaces/INounsAuctionHouseV3.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// SPDX-License-Identifier: GPL-3.0

/// @title Interface for Noun Auction Houses V2

/*********************************
* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
* ░░░░░░█████████░░█████████░░░ *
* ░░░░░░██░░░████░░██░░░████░░░ *
* ░░██████░░░████████░░░████░░░ *
* ░░██░░██░░░████░░██░░░████░░░ *
* ░░██░░██░░░████░░██░░░████░░░ *
* ░░░░░░█████████░░█████████░░░ *
* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
*********************************/

pragma solidity ^0.8.19;

interface INounsAuctionHouseV3 {
struct AuctionV2 {
// ID for the Noun (ERC721 token ID)
uint96 nounId;
// ID of the client that facilitated the latest bid, used for client rewards
uint32 clientId;
// The current highest bid amount
uint128 amount;
// The time that the auction started
uint40 startTime;
// The time that the auction is scheduled to end
uint40 endTime;
// The address of the current highest bid
address payable bidder;
// Whether or not the auction has been settled
bool settled;
}

/// @dev We use this struct as the return value of the `auction` function, to maintain backwards compatibility.
struct AuctionV2View {
// ID for the Noun (ERC721 token ID)
uint96 nounId;
// The current highest bid amount
uint128 amount;
// The time that the auction started
uint40 startTime;
// The time that the auction is scheduled to end
uint40 endTime;
// The address of the current highest bid
address payable bidder;
// Whether or not the auction has been settled
bool settled;
}

struct SettlementState {
// The block.timestamp when the auction was settled.
uint32 blockTimestamp;
// The winning bid amount, with 10 decimal places (reducing accuracy to save bits).
uint64 amount;
// The address of the auction winner.
address winner;
// ID of the client that facilitated the winning bid, used for client rewards.
uint32 clientId;
// Used only to warm up the storage slot for clientId without setting the clientId value.
bool slotWarmedUp;
}

struct Settlement {
// The block.timestamp when the auction was settled.
uint32 blockTimestamp;
// The winning bid amount, converted from 10 decimal places to 18, for better client UX.
uint256 amount;
// The address of the auction winner.
address winner;
// ID for the Noun (ERC721 token ID).
uint256 nounId;
// ID of the client that facilitated the winning bid, used for client rewards
uint32 clientId;
}

/// @dev Using this struct when setting historic prices, and excluding clientId to save gas.
struct SettlementNoClientId {
// The block.timestamp when the auction was settled.
uint32 blockTimestamp;
// The winning bid amount, converted from 10 decimal places to 18, for better client UX.
uint256 amount;
// The address of the auction winner.
address winner;
// ID for the Noun (ERC721 token ID).
uint256 nounId;
}

event AuctionCreated(uint256 indexed nounId, uint256 startTime, uint256 endTime);

event AuctionBid(uint256 indexed nounId, address sender, uint256 value, bool extended);

event AuctionBidWithClientId(uint256 indexed nounId, uint256 value, uint32 indexed clientId);

event AuctionExtended(uint256 indexed nounId, uint256 endTime);

event AuctionSettled(uint256 indexed nounId, address winner, uint256 amount);

event AuctionSettledWithClientId(uint256 indexed nounId, uint32 indexed clientId);

event AuctionTimeBufferUpdated(uint256 timeBuffer);

event AuctionReservePriceUpdated(uint256 reservePrice);

event AuctionMinBidIncrementPercentageUpdated(uint256 minBidIncrementPercentage);

event SanctionsOracleSet(address newSanctionsOracle);

function settleAuction() external;

function settleCurrentAndCreateNewAuction() external;

function createBid(uint256 nounId) external payable;

function createBid(uint256 nounId, uint32 clientId) external payable;

function pause() external;

function unpause() external;

function setTimeBuffer(uint56 timeBuffer) external;

function setReservePrice(uint192 reservePrice) external;

function setMinBidIncrementPercentage(uint8 minBidIncrementPercentage) external;

function setSanctionsOracle(address newSanctionsOracle) external;

function auction() external view returns (AuctionV2View memory);

function getSettlements(
uint256 auctionCount,
bool skipEmptyValues
) external view returns (Settlement[] memory settlements);

function getPrices(uint256 auctionCount) external view returns (uint256[] memory prices);

function getSettlements(
uint256 startId,
uint256 endId,
bool skipEmptyValues
) external view returns (Settlement[] memory settlements);

function getSettlementsFromIdtoTimestamp(
uint256 startId,
uint256 endTimestamp,
bool skipEmptyValues
) external view returns (Settlement[] memory settlements);

function warmUpSettlementState(uint256 startId, uint256 endId) external;

function duration() external view returns (uint256);

function biddingClient(uint256 nounId) external view returns (uint32 clientId);
}
5 changes: 3 additions & 2 deletions packages/nouns-contracts/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ libs = ['lib', '../../node_modules']
cache_path = 'foundry-cache'
out = 'foundry-out'
solc_version = '0.8.23'
fs_permissions = [{ access = "read", path = "./"}]
fs_permissions = [{ access = "read", path = "./" }]
ignored_warnings_from = ["contracts/test/Multicall2.sol"]
evm_version = 'shanghai'

[profile.lite]
optimizer = false
optimizer = false

This file was deleted.

This file was deleted.

This file was deleted.

Loading
Loading