Keeping security in mind is important when developing smart contracts and there are a number of different techniques and philosophies in regards to your smart contract development and security.
Revisiting the NftFactory I was discussing earlier we’re deploying a new ERC721 from an address that isn’t verified so far. This could lead to anyone using our factory to deploy.
I only want to deploy tokens that I have approved. Keeping that in mind we can subscribe our ERC721 deployment address and verify it upon creation by the Factory now and update our current contracts.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;
import './interface/IFactoryItem.sol';
contract NftFactory {
event NftSpawned(address newNft, address _owner);
event ItemSubscribed(address newAddress);
uint public spawnedCount = 0;
address[] public spawnedNftArray;
mapping(address => bool) public spawnedNftMapping;
mapping(address => bool) public itemsSubscribed;
address public owner;
constructor() {
owner = msg.sender;
}
function spawn(
address nftToSpawn,
string calldata name,
string calldata symbol,
string calldata uri,
bytes calldata data
) external returns (address){
require(itemsSubscribed[nftToSpawn], 'Error: Address not subscribed');
address nft = IFactoryItem(nftToSpawn).spawnNft(
name,
symbol,
uri,
data
);
emit NftSpawned(nft, msg.sender);
spawnedCount++;
spawnedNftArray.push(nft);
return nft;
}
function subscribeItem(address item) external onlyOwner {
require(item != address(0x0), "Error: Not an address");
emit ItemSubscribed(item);
itemsSubscribed[item] = true;
}
function getSpawned(uint256 index) external view returns (address) {
require(index <= spawnedCount, "Error: Doesn't exist");
return spawnedNftArray[index];
}
modifier onlyOwner() {
require(
owner == msg.sender,
"Error: Only owner"
);
_;
}
}
Also a very popular solution for the reentrancy attack is using the OZ ReentrancyGuard contract. Here I’ve added the nonReentrant modifier to my basic mint one token function. It may be possible for an attacker to derive certain methods from a remote contract to maliciously mint 1000’s of bogus tokens if public mint was enabled.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract BasicERC721 is ERC721, ReentrancyGuard {
event UpdatedMintStatus( bool oldValue, bool newValue );
using Counters for Counters.Counter;
Counters.Counter public _tokenIds;
string private baseUri;
address public owner;
bool public publicMintStatus;
constructor(
string memory _name,
string memory _symbol,
string memory _uri)
ERC721(_name,_symbol) {
baseUri = _uri;
owner = msg.sender;
}
function mintOneNftByOwner() external onlyTheOwner {
_safeMint(msg.sender, _tokenIds.current());
_tokenIds.increment();
}
function mintOneNft() external onlyIfPublicMintStatus nonReentrant {
_safeMint(msg.sender, _tokenIds.current());
_tokenIds.increment();
}
function setPublicMintStatus(bool _mintStatus) external onlyTheOwner {
emit UpdatedMintStatus( publicMintStatus, _mintStatus );
publicMintStatus = _mintStatus;
}
modifier onlyTheOwner {
require(
msg.sender == owner,
'Only the Owner can call this function.'
);
_;
}
modifier onlyIfPublicMintStatus {
require(
publicMintStatus == true,
'Public minting is not available at this time'
);
_;
}
}
Basically what the ReentrancyGuard contract helps you with is; it saves you time in writing this functionality yourself and provides a good way to perform a check that only one transaction per client is initiated.
Open Zeppelin is a great resource with many amazing contracts and design patterns.
repo: https://github.com/bws9000/smart-contract-tests/tree/beef-up-security