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

Fixes issues mentioned in the audit #41

Open
wants to merge 15 commits into
base: feature/audit-pr
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 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
70 changes: 27 additions & 43 deletions contracts/solidity/NFTXMarketplace0xZap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,18 @@ pragma solidity ^0.8.0;

import "./interface/INFTXVault.sol";
import "./interface/INFTXVaultFactory.sol";
import "./token/IERC1155Upgradeable.sol";
import "./token/ERC721HolderUpgradeable.sol";
import "./token/ERC1155HolderUpgradeable.sol";
import "./testing/IERC1155.sol";
import "./testing/ERC721Holder.sol";
import "./testing/ERC1155Holder.sol";
import "./util/Ownable.sol";
import "./util/ReentrancyGuard.sol";
import "./util/SafeERC20Upgradeable.sol";
import "./util/SafeERC20.sol";


/**
* @notice A partial ERC20 interface.
*/

interface IERC20 {
function balanceOf(address owner) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transfer(address to, uint256 amount) external returns (bool);
}


/**
* @notice A partial WETH interface.
Expand All @@ -43,9 +37,9 @@ interface IWETH {
* @author Twade
*/

contract NFTXMarketplace0xZap is Ownable, ReentrancyGuard, ERC721HolderUpgradeable, ERC1155HolderUpgradeable {
contract NFTXMarketplace0xZap is Ownable, ReentrancyGuard, ERC721Holder, ERC1155Holder {

using SafeERC20Upgradeable for IERC20Upgradeable;
using SafeERC20 for IERC20;

/// @notice Allows zap to be paused
bool public paused = false;
Expand Down Expand Up @@ -111,15 +105,13 @@ contract NFTXMarketplace0xZap is Ownable, ReentrancyGuard, ERC721HolderUpgradeab
*
* @param vaultId The ID of the NFTX vault
* @param ids An array of token IDs to be minted
* @param spender The `allowanceTarget` field from the API response
* @param swapCallData The `data` field from the API response
* @param to The recipient of the WETH from the tx
*/

function mintAndSell721(
uint256 vaultId,
uint256[] calldata ids,
address spender,
bytes calldata swapCallData,
address payable to
) external nonReentrant onlyOwnerIfPaused {
Expand All @@ -138,8 +130,8 @@ contract NFTXMarketplace0xZap is Ownable, ReentrancyGuard, ERC721HolderUpgradeab
// Emit our sale event
emit Sell(ids.length, amount, to);

// Transfer dust back to the spender
_transferDust(spender, vault);
// Transfer dust back to the sender
_transferDust(msg.sender, vault);
}


Expand All @@ -151,16 +143,14 @@ contract NFTXMarketplace0xZap is Ownable, ReentrancyGuard, ERC721HolderUpgradeab
* @param vaultId The ID of the NFTX vault
* @param idsIn An array of random token IDs to be minted
* @param specificIds An array of any specific token IDs to be minted
* @param spender The `allowanceTarget` field from the API response
* @param swapCallData The `data` field from the API response
* @param to The recipient of the WETH from the tx
*/

function buyAndSwap721(
uint256 vaultId,
uint256[] calldata idsIn,
uint256[] calldata specificIds,
address spender,
uint256[] calldata specificIds,
bytes calldata swapCallData,
address payable to
) external payable nonReentrant onlyOwnerIfPaused {
Expand All @@ -186,8 +176,8 @@ contract NFTXMarketplace0xZap is Ownable, ReentrancyGuard, ERC721HolderUpgradeab
_swap721(vaultId, idsIn, specificIds, to);
emit Swap(idsIn.length, amount, to);

// Transfer dust back to the spender
_transferDust(spender, vault);
// Transfer dust back to the sender
_transferDust(msg.sender, vault);
}


Expand All @@ -199,7 +189,6 @@ contract NFTXMarketplace0xZap is Ownable, ReentrancyGuard, ERC721HolderUpgradeab
* @param vaultId The ID of the NFTX vault
* @param amount The number of tokens to buy
* @param specificIds An array of any specific token IDs to be minted
* @param spender The `allowanceTarget` field from the API response
* @param swapCallData The `data` field from the API response
* @param to The recipient of the WETH from the tx
*/
Expand All @@ -208,7 +197,6 @@ contract NFTXMarketplace0xZap is Ownable, ReentrancyGuard, ERC721HolderUpgradeab
uint256 vaultId,
uint256 amount,
uint256[] calldata specificIds,
address spender,
bytes calldata swapCallData,
address payable to
) external payable nonReentrant onlyOwnerIfPaused {
Expand All @@ -218,9 +206,6 @@ contract NFTXMarketplace0xZap is Ownable, ReentrancyGuard, ERC721HolderUpgradeab
// Check that we have an amount specified
require(amount > 0, 'Must send amount');

// Check that we have a message value sent
require(msg.value >= amount, 'Invalid amount');

// Wrap ETH into WETH for our contract from the sender
WETH.deposit{value: msg.value}();

Expand All @@ -230,12 +215,15 @@ contract NFTXMarketplace0xZap is Ownable, ReentrancyGuard, ERC721HolderUpgradeab
// Buy vault tokens that will cover our transaction
uint256 quoteAmount = _fillQuote(address(WETH), vault, swapCallData);

// check if received sufficient vault tokens
require(quoteAmount >= amount * 1e18, 'Insufficient vault tokens');

// Redeem token IDs from the vault
_redeem(vaultId, amount, specificIds, to);
emit Buy(amount, quoteAmount, to);

// Transfer dust back to the spender
_transferDust(spender, vault);
// Transfer dust back to the sender
_transferDust(msg.sender, vault);
}


Expand All @@ -245,7 +233,6 @@ contract NFTXMarketplace0xZap is Ownable, ReentrancyGuard, ERC721HolderUpgradeab
* @param vaultId The ID of the NFTX vault
* @param ids An array of token IDs to be minted
* @param amounts The number of the corresponding ID to be minted
* @param spender The `allowanceTarget` field from the API response
* @param swapCallData The `data` field from the API response
* @param to The recipient of the WETH from the tx
*/
Expand All @@ -254,7 +241,6 @@ contract NFTXMarketplace0xZap is Ownable, ReentrancyGuard, ERC721HolderUpgradeab
uint256 vaultId,
uint256[] calldata ids,
uint256[] calldata amounts,
address spender,
bytes calldata swapCallData,
address payable to
) external nonReentrant onlyOwnerIfPaused {
Expand All @@ -274,8 +260,8 @@ contract NFTXMarketplace0xZap is Ownable, ReentrancyGuard, ERC721HolderUpgradeab
// Emit our sale event
emit Sell(totalAmount, amount, to);

// Transfer dust back to the spender
_transferDust(spender, vault);
// Transfer dust back to the sender
_transferDust(msg.sender, vault);
}


Expand All @@ -287,7 +273,6 @@ contract NFTXMarketplace0xZap is Ownable, ReentrancyGuard, ERC721HolderUpgradeab
* @param vaultId The ID of the NFTX vault
* @param idsIn An array of random token IDs to be minted
* @param specificIds An array of any specific token IDs to be minted
* @param spender The `allowanceTarget` field from the API response
* @param swapCallData The `data` field from the API response
* @param to The recipient of the WETH from the tx
*/
Expand All @@ -297,7 +282,6 @@ contract NFTXMarketplace0xZap is Ownable, ReentrancyGuard, ERC721HolderUpgradeab
uint256[] calldata idsIn,
uint256[] calldata amounts,
uint256[] calldata specificIds,
address spender,
bytes calldata swapCallData,
address payable to
) external payable nonReentrant onlyOwnerIfPaused {
Expand All @@ -324,8 +308,8 @@ contract NFTXMarketplace0xZap is Ownable, ReentrancyGuard, ERC721HolderUpgradeab
_swap1155(vaultId, idsIn, amounts, specificIds, to);
emit Swap(totalAmount, amount, to);

// Transfer dust back to the spender
_transferDust(spender, vault);
// Transfer dust back to the sender
_transferDust(msg.sender, vault);
}


Expand Down Expand Up @@ -372,8 +356,8 @@ contract NFTXMarketplace0xZap is Ownable, ReentrancyGuard, ERC721HolderUpgradeab

// Transfer tokens from the message sender to the vault
address assetAddress = INFTXVault(vault).assetAddress();
IERC1155Upgradeable(assetAddress).safeBatchTransferFrom(msg.sender, address(this), ids, amounts, "");
IERC1155Upgradeable(assetAddress).setApprovalForAll(vault, true);
IERC1155(assetAddress).safeBatchTransferFrom(msg.sender, address(this), ids, amounts, "");
IERC1155(assetAddress).setApprovalForAll(vault, true);

// Mint our tokens from the vault to this contract
INFTXVault(vault).mint(ids, amounts);
Expand Down Expand Up @@ -446,8 +430,8 @@ contract NFTXMarketplace0xZap is Ownable, ReentrancyGuard, ERC721HolderUpgradeab

// Transfer tokens to zap and mint to NFTX.
address assetAddress = INFTXVault(vault).assetAddress();
IERC1155Upgradeable(assetAddress).safeBatchTransferFrom(msg.sender, address(this), idsIn, amounts, "");
IERC1155Upgradeable(assetAddress).setApprovalForAll(vault, true);
IERC1155(assetAddress).safeBatchTransferFrom(msg.sender, address(this), idsIn, amounts, "");
IERC1155(assetAddress).setApprovalForAll(vault, true);
INFTXVault(vault).swapTo(idsIn, amounts, idsOut, to);

return vault;
Expand Down Expand Up @@ -565,9 +549,9 @@ contract NFTXMarketplace0xZap is Ownable, ReentrancyGuard, ERC721HolderUpgradeab
require(success, "Unable to send unwrapped WETH");
}

uint dustBalance = IERC20Upgradeable(vault).balanceOf(address(this));
uint dustBalance = IERC20(vault).balanceOf(address(this));
if (dustBalance > 0) {
IERC20Upgradeable(vault).transfer(spender, dustBalance);
IERC20(vault).transfer(spender, dustBalance);
}

emit DustReturned(remaining, dustBalance, spender);
Expand Down Expand Up @@ -648,7 +632,7 @@ contract NFTXMarketplace0xZap is Ownable, ReentrancyGuard, ERC721HolderUpgradeab
(bool success, ) = payable(msg.sender).call{value: address(this).balance}("");
require(success, "Address: unable to send value");
} else {
IERC20Upgradeable(token).safeTransfer(msg.sender, IERC20Upgradeable(token).balanceOf(address(this)));
IERC20(token).safeTransfer(msg.sender, IERC20(token).balanceOf(address(this)));
}
}

Expand Down
48 changes: 31 additions & 17 deletions contracts/solidity/NFTXYieldStakingZap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import "./interface/INFTXVaultFactory.sol";
import "./interface/IUniswapV2Router01.sol";
import "./util/Ownable.sol";
import "./util/ReentrancyGuard.sol";
import "./util/SafeERC20Upgradeable.sol";
import "./util/SafeERC20.sol";


/**
Expand All @@ -20,6 +20,7 @@ interface IWETH {
function transfer(address to, uint value) external returns (bool);
function withdraw(uint) external;
function balanceOf(address to) external view returns (uint256);
function approve(address guy, uint wad) external returns (bool);
}


Expand All @@ -32,7 +33,7 @@ interface IWETH {

contract NFTXYieldStakingZap is Ownable, ReentrancyGuard {

using SafeERC20Upgradeable for IERC20Upgradeable;
using SafeERC20 for IERC20;

/// @notice Allows zap to be paused
bool public paused = false;
Expand Down Expand Up @@ -83,6 +84,9 @@ contract NFTXYieldStakingZap is Ownable, ReentrancyGuard {

// Set our chain's WETH contract
WETH = IWETH(_weth);
// setting infinite approval here to save on subsequent gas costs
IWETH(_weth).approve(_sushiRouter, type(uint256).max);
IWETH(_weth).approve(_swapTarget, type(uint256).max);

// Set our 0x Swap Target
swapTarget = _swapTarget;
Expand All @@ -101,7 +105,7 @@ contract NFTXYieldStakingZap is Ownable, ReentrancyGuard {
function buyAndStakeInventory(
uint256 vaultId,
bytes calldata swapCallData
) external payable nonReentrant {
) external payable nonReentrant onlyOwnerIfPaused {
// Ensure we have tx value
require(msg.value > 0, 'Invalid value provided');

Expand All @@ -122,7 +126,7 @@ contract NFTXYieldStakingZap is Ownable, ReentrancyGuard {

// Make a direct timelock mint using the default timelock duration. This sends directly
// to our user, rather than via the zap, to avoid the timelock locking the tx.
IERC20Upgradeable(baseToken).transfer(inventoryStaking.vaultXToken(vaultId), vaultTokenAmount);
IERC20(baseToken).transfer(inventoryStaking.vaultXToken(vaultId), vaultTokenAmount);
inventoryStaking.timelockMintFor(vaultId, vaultTokenAmount, msg.sender, 2);

// Return any left of WETH to the user as ETH
Expand Down Expand Up @@ -160,7 +164,7 @@ contract NFTXYieldStakingZap is Ownable, ReentrancyGuard {
uint256 minWethIn,
uint256 wethIn

) external payable nonReentrant {
) external payable nonReentrant onlyOwnerIfPaused {
// Ensure we have tx value
require(msg.value > 0, 'Invalid value provided');
require(msg.value > wethIn, 'Insufficient vault sent for pairing');
Expand All @@ -181,13 +185,17 @@ contract NFTXYieldStakingZap is Ownable, ReentrancyGuard {
uint256 vaultTokenAmount = _fillQuote(baseToken, swapCallData);
require(vaultTokenAmount > minTokenIn, 'Insufficient tokens acquired');

// Check WETH balance
uint256 WETHAmount = WETH.balanceOf(address(this)) - wethBalance;
require(WETHAmount >= wethIn, 'Insufficient WETH remaining');

// Provide liquidity to sushiswap, using the vault token that we acquired from 0x and
// pairing it with the liquidity amount specified in the call.
IERC20Upgradeable(baseToken).safeApprove(address(sushiRouter), minTokenIn);
IERC20(baseToken).safeApprove(address(sushiRouter), vaultTokenAmount);
(uint256 amountToken, , uint256 liquidity) = sushiRouter.addLiquidity(
baseToken,
address(WETH),
minTokenIn,
vaultTokenAmount,
wethIn,
minTokenIn,
minWethIn,
Expand All @@ -197,13 +205,13 @@ contract NFTXYieldStakingZap is Ownable, ReentrancyGuard {

// Stake in LP rewards contract
address lpToken = pairFor(baseToken, address(WETH));
IERC20Upgradeable(lpToken).safeApprove(address(lpStaking), liquidity);
IERC20(lpToken).safeApprove(address(lpStaking), liquidity);
lpStaking.timelockDepositFor(vaultId, msg.sender, liquidity, 48 hours);

// Return any token dust to the caller
uint256 remainingTokens = vaultTokenAmount - amountToken;
if (remainingTokens != 0) {
IERC20Upgradeable(baseToken).transfer(msg.sender, remainingTokens);
IERC20(baseToken).transfer(msg.sender, remainingTokens);
}

// Return any left of WETH to the user as ETH
Expand Down Expand Up @@ -258,7 +266,7 @@ contract NFTXYieldStakingZap is Ownable, ReentrancyGuard {
(bool success, ) = payable(msg.sender).call{value: address(this).balance}("");
require(success, "Address: unable to send value");
} else {
IERC20Upgradeable(token).safeTransfer(msg.sender, IERC20Upgradeable(token).balanceOf(address(this)));
IERC20(token).safeTransfer(msg.sender, IERC20(token).balanceOf(address(this)));
}
}

Expand All @@ -275,19 +283,14 @@ contract NFTXYieldStakingZap is Ownable, ReentrancyGuard {
bytes calldata swapCallData
) internal returns (uint256) {
// Track our balance of the buyToken to determine how much we've bought.
uint256 boughtAmount = IERC20Upgradeable(buyToken).balanceOf(address(this));

// Give `swapTarget` an infinite allowance to spend this contract's `sellToken`.
// Note that for some tokens (e.g., USDT, KNC), you must first reset any existing
// allowance to 0 before being able to update it.
require(IERC20Upgradeable(address(WETH)).approve(swapTarget, type(uint256).max), 'Unable to approve contract');
uint256 boughtAmount = IERC20(buyToken).balanceOf(address(this));

// Call the encoded swap function call on the contract at `swapTarget`
(bool success,) = swapTarget.call(swapCallData);
require(success, 'SWAP_CALL_FAILED');

// Use our current buyToken balance to determine how much we've bought.
return IERC20Upgradeable(buyToken).balanceOf(address(this)) - boughtAmount;
return IERC20(buyToken).balanceOf(address(this)) - boughtAmount;
}


Expand Down Expand Up @@ -318,6 +321,17 @@ contract NFTXYieldStakingZap is Ownable, ReentrancyGuard {
paused = _paused;
}

/**
* @notice A modifier that only allows the owner to interact with the function
* if the contract is paused. If the contract is not paused then anyone can
* interact with the function.
*/

modifier onlyOwnerIfPaused() {
require(!paused || msg.sender == owner(), "Zap is paused");
_;
}


/**
* @notice Allows our contract to only receive WETH and reject everything else.
Expand Down
Loading