Skip to content

Macro audit june 2022 #183

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

Merged
merged 45 commits into from
Jun 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
500f656
[H-2] Batch reveal is permanently corrupted by invocation of public g…
kumaryash90 Jun 16, 2022
382f23c
[L-3] LazyMint of a new batch can affect previous batch
kumaryash90 Jun 16, 2022
0fb253f
[L-4] Incorrect handling of invalid role approvals/removals in Permis…
Jun 16, 2022
c7ae404
[L-5] Incorrect processing of role approval in PermissionsEnumerable.sol
Jun 16, 2022
3b34c3d
[L-6] claimCondition.startTimestamp is not enforced
Jun 16, 2022
47cd19d
docs update
Jun 16, 2022
88d2fd9
reduce size by merging checks
kumaryash90 Jun 17, 2022
96faa03
update tests
kumaryash90 Jun 17, 2022
ac78939
[Q-1] Emitted TokensLazyMinted event does not match spec
Jun 17, 2022
e8b824c
[L-3] test fix
Jun 17, 2022
076687d
[Q-3] Some events could use indexed attributes
Jun 17, 2022
ba4e4fe
[Q-5] Visibility for following methods can be changed from public to …
Jun 17, 2022
83c99cf
[Q-4] Missing natspec comments
Jun 17, 2022
526170c
Merge branch 'signaturedrop-audit-macro' of https://github.com/thirdw…
Jun 17, 2022
0480543
[Q-3] test fixes
Jun 17, 2022
ae883c2
forge updates
Jun 17, 2022
83b8fd9
fix claim quantity limit
kumaryash90 Jun 21, 2022
bc93d4f
add test
kumaryash90 Jun 21, 2022
3109c5a
run prettier
kumaryash90 Jun 21, 2022
f298591
replace requires with custom errors
kumaryash90 Jun 15, 2022
3a60e1f
standardize errors
kumaryash90 Jun 24, 2022
da4542c
update tests
kumaryash90 Jun 24, 2022
f79abd3
run prettier
kumaryash90 Jun 24, 2022
6ce18b8
[L-6] claimCondition.startTimestamp is not enforced -- revised fix
kumaryash90 Jun 24, 2022
e7a11f9
[L-6] claimCondition.startTimestamp is not enforced
kumaryash90 Jun 25, 2022
826b577
reorganize errors and checks
kumaryash90 Jun 25, 2022
763f685
0xMacro audit Jun '22: Multiwrap (#181)
nkrishang Jun 26, 2022
fe7bc85
0xMacro audit Jun '22: DropERC1155 (#182)
nkrishang Jun 26, 2022
23e2221
custom errors cleanup mega commit
Jun 26, 2022
79c1790
delete commented require statements
Jun 26, 2022
0653099
merge solidity custom errors
Jun 26, 2022
04ceb02
fix timestamp check
Jun 26, 2022
93a83be
merge sigdrop audit fixes
Jun 26, 2022
97ba571
run prettier
Jun 27, 2022
1e747f7
REVERT G-3: breaks Marketplace
Jun 27, 2022
309ece8
Change revert test -> balances test post behaviour fix
Jun 27, 2022
84b7d2f
Fix faulty >= to >
Jun 27, 2022
9369eab
package bump
Jun 27, 2022
15fc367
package release
Jun 27, 2022
2c352ec
add bot check to SigDrop
Jun 29, 2022
2ff2300
merge main
Jun 29, 2022
3f2a33b
fix forge build errors
Jun 29, 2022
ab73980
Update permissions usage in pack tests
Jun 29, 2022
f5102b5
run prettier
Jun 29, 2022
d5ec953
pack docs update
Jun 29, 2022
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
Binary file added .DS_Store
Binary file not shown.
6 changes: 6 additions & 0 deletions contracts/drop/DropERC1155.sol
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,12 @@ contract DropERC1155 is
emit PrimarySaleRecipientUpdated(_saleRecipient);
}

/// @dev Lets a contract admin set the recipient for all primary sales.
function setSaleRecipientForToken(uint256 _tokenId, address _saleRecipient) external onlyRole(DEFAULT_ADMIN_ROLE) {
saleRecipient[_tokenId] = _saleRecipient;
emit SaleRecipientForTokenUpdated(_tokenId, _saleRecipient);
}

/// @dev Lets a contract admin update the default royalty recipient and bps.
function setDefaultRoyaltyInfo(address _royaltyRecipient, uint256 _royaltyBps)
external
Expand Down
12 changes: 11 additions & 1 deletion contracts/feature/ContractMetadata.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,23 @@ pragma solidity ^0.8.0;

import "./interface/IContractMetadata.sol";

/**
* Thirdweb's `ContractMetadata` is a contract extension for any base contracts. It lets you set a metadata URI
* for you contract.
*
* Additionally, `ContractMetadata` is necessary for NFT contracts that want royalties to get distributed on OpenSea.
*/

abstract contract ContractMetadata is IContractMetadata {
/// @dev Contract level metadata.
string public override contractURI;

/// @dev Lets a contract admin set the URI for contract-level metadata.
function setContractURI(string memory _uri) external override {
require(_canSetContractURI(), "Not authorized");
if (!_canSetContractURI()) {
revert ContractMetadata__NotAuthorized();
}

_setupContractURI(_uri);
}

Expand Down
14 changes: 9 additions & 5 deletions contracts/feature/DelayedReveal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ pragma solidity ^0.8.0;

import "./interface/IDelayedReveal.sol";

/**
* Thirdweb's `DelayedReveal` is a contract extension for base NFT contracts. It lets you create batches of
* 'delayed-reveal' NFTs. You can learn more about the usage of delayed reveal NFTs here - https://blog.thirdweb.com/delayed-reveal-nfts
*/

abstract contract DelayedReveal is IDelayedReveal {
/// @dev Mapping from id of a batch of tokens => to encrypted base URI for the respective batch of tokens.
mapping(uint256 => bytes) public encryptedBaseURI;
Expand All @@ -13,14 +18,13 @@ abstract contract DelayedReveal is IDelayedReveal {
}

/// @dev Returns the decrypted i.e. revealed URI for a batch of tokens.
function getRevealURI(uint256 _batchId, bytes calldata _key) public returns (string memory revealedURI) {
function getRevealURI(uint256 _batchId, bytes calldata _key) public view returns (string memory revealedURI) {
bytes memory encryptedURI = encryptedBaseURI[_batchId];
require(encryptedURI.length != 0, "nothing to reveal.");
if (encryptedURI.length == 0) {
revert DelayedReveal__NothingToReveal(_batchId);
}

revealedURI = string(encryptDecrypt(encryptedURI, _key));

// yash - added this, and removed view mutability
delete encryptedBaseURI[_batchId];
}

/// @dev See: https://ethereum.stackexchange.com/questions/69825/decrypt-message-on-chain
Expand Down
90 changes: 61 additions & 29 deletions contracts/feature/DropSinglePhase.sol
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "./interface/IClaimConditionsSinglePhase.sol";
import "./interface/IDrop.sol";
import "./LazyMint.sol";
import "./interface/IDropSinglePhase.sol";
import "../lib/MerkleProof.sol";
import "../lib/TWBitMaps.sol";

abstract contract DropSinglePhase is LazyMint, IDrop, IClaimConditionsSinglePhase {
abstract contract DropSinglePhase is IDropSinglePhase {
using TWBitMaps for TWBitMaps.BitMap;

/*///////////////////////////////////////////////////x////////////
/*///////////////////////////////////////////////////////////////
State variables
//////////////////////////////////////////////////////////////*/

Expand All @@ -36,6 +34,10 @@ abstract contract DropSinglePhase is LazyMint, IDrop, IClaimConditionsSinglePhas
*/
mapping(bytes32 => TWBitMaps.BitMap) private usedAllowlistSpot;

/*///////////////////////////////////////////////////////////////
Errors
//////////////////////////////////////////////////////////////*/

/*///////////////////////////////////////////////////////////////
Drop logic
//////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -68,7 +70,10 @@ abstract contract DropSinglePhase is LazyMint, IDrop, IClaimConditionsSinglePhas
);

// Verify claim validity. If not valid, revert.
bool toVerifyMaxQuantityPerTransaction = _allowlistProof.maxQuantityInAllowlist == 0;
// when there's allowlist present --> verifyClaimMerkleProof will verify the maxQuantityInAllowlist value with hashed leaf in the allowlist
// when there's no allowlist, this check is true --> verifyClaim will check for _quantity being equal/less than the limit
bool toVerifyMaxQuantityPerTransaction = _allowlistProof.maxQuantityInAllowlist == 0 ||
claimCondition.merkleRoot == bytes32(0);

verifyClaim(_dropMsgSender(), _quantity, _currency, _pricePerToken, toVerifyMaxQuantityPerTransaction);

Expand All @@ -90,14 +95,16 @@ abstract contract DropSinglePhase is LazyMint, IDrop, IClaimConditionsSinglePhas
// Mint the relevant NFTs to claimer.
uint256 startTokenId = transferTokensOnClaim(_receiver, _quantity);

emit TokensClaimed(0, _dropMsgSender(), _receiver, startTokenId, _quantity);
emit TokensClaimed(_dropMsgSender(), _receiver, startTokenId, _quantity);

_afterClaim(_receiver, _quantity, _currency, _pricePerToken, _allowlistProof, _data);
}

/// @dev Lets a contract admin set claim conditions.
function setClaimConditions(ClaimCondition calldata _condition, bool _resetClaimEligibility) external override {
require(_canSetClaimConditions(), "Not authorized");
if (!_canSetClaimConditions()) {
revert DropSinglePhase__NotAuthorized();
}

bytes32 targetConditionId = conditionId;
uint256 supplyClaimedAlready = claimCondition.supplyClaimed;
Expand All @@ -107,7 +114,9 @@ abstract contract DropSinglePhase is LazyMint, IDrop, IClaimConditionsSinglePhas
targetConditionId = keccak256(abi.encodePacked(_dropMsgSender(), block.number));
}

require(supplyClaimedAlready <= _condition.maxClaimableSupply, "max supply claimed already");
if (supplyClaimedAlready > _condition.maxClaimableSupply) {
revert DropSinglePhase__MaxSupplyClaimedAlready(supplyClaimedAlready);
}

claimCondition = ClaimCondition({
startTimestamp: _condition.startTimestamp,
Expand All @@ -134,24 +143,42 @@ abstract contract DropSinglePhase is LazyMint, IDrop, IClaimConditionsSinglePhas
) public view {
ClaimCondition memory currentClaimPhase = claimCondition;

require(
_currency == currentClaimPhase.currency && _pricePerToken == currentClaimPhase.pricePerToken,
"invalid currency or price."
);
if (_currency != currentClaimPhase.currency || _pricePerToken != currentClaimPhase.pricePerToken) {
revert DropSinglePhase__InvalidCurrencyOrPrice(
_currency,
currentClaimPhase.currency,
_pricePerToken,
currentClaimPhase.pricePerToken
);
}

// If we're checking for an allowlist quantity restriction, ignore the general quantity restriction.
require(
_quantity > 0 &&
(!verifyMaxQuantityPerTransaction || _quantity <= currentClaimPhase.quantityLimitPerTransaction),
"invalid quantity."
);
require(
currentClaimPhase.supplyClaimed + _quantity <= currentClaimPhase.maxClaimableSupply,
"exceed max claimable supply."
);
if (
_quantity == 0 ||
(verifyMaxQuantityPerTransaction && _quantity > currentClaimPhase.quantityLimitPerTransaction)
) {
revert DropSinglePhase__InvalidQuantity();
}

if (currentClaimPhase.supplyClaimed + _quantity > currentClaimPhase.maxClaimableSupply) {
revert DropSinglePhase__ExceedMaxClaimableSupply(
currentClaimPhase.supplyClaimed,
currentClaimPhase.maxClaimableSupply
);
}

(uint256 lastClaimedAt, uint256 nextValidClaimTimestamp) = getClaimTimestamp(_claimer);
require(lastClaimedAt == 0 || block.timestamp >= nextValidClaimTimestamp, "cannot claim.");
if (
currentClaimPhase.startTimestamp > block.timestamp ||
(lastClaimedAt != 0 && block.timestamp < nextValidClaimTimestamp)
) {
revert DropSinglePhase__CannotClaimYet(
block.timestamp,
currentClaimPhase.startTimestamp,
lastClaimedAt,
nextValidClaimTimestamp
);
}
}

/// @dev Checks whether a claimer meets the claim condition's allowlist criteria.
Expand All @@ -168,12 +195,17 @@ abstract contract DropSinglePhase is LazyMint, IDrop, IClaimConditionsSinglePhas
currentClaimPhase.merkleRoot,
keccak256(abi.encodePacked(_claimer, _allowlistProof.maxQuantityInAllowlist))
);
require(validMerkleProof, "not in whitelist.");
require(!usedAllowlistSpot[conditionId].get(merkleProofIndex), "proof claimed.");
require(
_allowlistProof.maxQuantityInAllowlist == 0 || _quantity <= _allowlistProof.maxQuantityInAllowlist,
"invalid quantity proof."
);
if (!validMerkleProof) {
revert DropSinglePhase__NotInWhitelist();
}

if (usedAllowlistSpot[conditionId].get(merkleProofIndex)) {
revert DropSinglePhase__ProofClaimed();
}

if (_allowlistProof.maxQuantityInAllowlist != 0 && _quantity > _allowlistProof.maxQuantityInAllowlist) {
revert DropSinglePhase__InvalidQuantityProof(_allowlistProof.maxQuantityInAllowlist);
}
}
}

Expand Down
14 changes: 11 additions & 3 deletions contracts/feature/LazyMint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ pragma solidity ^0.8.0;

import "./interface/ILazyMint.sol";

/**
* Thirdweb's `LazyMint` is a contract extension for any base NFT contract. It lets you 'lazy mint' any number of NFTs
* at once. Here, 'lazy mint' means defining the metadata for particular tokenIds of your NFT contract, without actually
* minting a non-zero balance of NFTs of those tokenIds.
*/

abstract contract LazyMint is ILazyMint {
/// @dev Largest tokenId of each batch of tokens with the same baseURI.
uint256[] private batchIds;
Expand All @@ -17,7 +23,9 @@ abstract contract LazyMint is ILazyMint {

/// @dev Returns the id for the batch of tokens the given tokenId belongs to.
function getBatchIdAtIndex(uint256 _index) public view returns (uint256) {
require(_index < getBaseURICount(), "invalid index.");
if (_index >= getBaseURICount()) {
revert LazyMint__InvalidIndex(_index);
}
return batchIds[_index];
}

Expand All @@ -32,7 +40,7 @@ abstract contract LazyMint is ILazyMint {
}
}

revert("No batch id for token.");
revert LazyMint__NoBatchIDForToken(_tokenId);
}

/// @dev Returns the baseURI for a token. The intended metadata URI for the token is baseURI + tokenId.
Expand All @@ -46,7 +54,7 @@ abstract contract LazyMint is ILazyMint {
}
}

revert("No base URI for token.");
revert LazyMint__NoBaseURIForToken(_tokenId);
}

/// @dev Sets the base URI for the batch of tokens with the given batchId.
Expand Down
2 changes: 1 addition & 1 deletion contracts/feature/LazyMintERC721.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import "../lib/TWStrings.sol";
abstract contract LazyMintERC721 is LazyMint {
using TWStrings for uint256;

event TokensLazyMinted(uint256 startTokenId, uint256 endTokenId, string baseURI, bytes extraData);
event TokensLazyMinted(uint256 indexed startTokenId, uint256 endTokenId, string baseURI, bytes extraData);

/// @dev the next available non-minted token id
uint256 public nextTokenIdToMint;
Expand Down
10 changes: 9 additions & 1 deletion contracts/feature/Ownable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,21 @@ pragma solidity ^0.8.0;

import "./interface/IOwnable.sol";

/**
* Thirdweb's `Ownable` is a contract extension to be used with any base contract. It exposes functions for setting and reading
* who the 'owner' of the inheriting smart contract is, and lets the inheriting contract perform conditional logic that uses
* information about who the contract's owner is.
*/

abstract contract Ownable is IOwnable {
/// @dev Owner of the contract (purpose: OpenSea compatibility)
address public override owner;

/// @dev Lets a contract admin set a new owner for the contract. The new owner must be a contract admin.
function setOwner(address _newOwner) external override {
require(_canSetOwner(), "Not authorized");
if (!_canSetOwner()) {
revert Ownable__NotAuthorized();
}
_setupOwner(_newOwner);
}

Expand Down
10 changes: 8 additions & 2 deletions contracts/feature/Permissions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@ contract Permissions is IPermissions {
return true;
}

function getRoleAdmin(bytes32 role) public view override returns (bytes32) {
function getRoleAdmin(bytes32 role) external view override returns (bytes32) {
return _getRoleAdmin[role];
}

function grantRole(bytes32 role, address account) public virtual override {
_checkRole(_getRoleAdmin[role], msg.sender);
if (_hasRole[role][account]) {
revert Permissions__CanOnlyGrantToNonHolders(account);
}
_setupRole(role, account);
}

Expand All @@ -42,7 +45,9 @@ contract Permissions is IPermissions {
}

function renounceRole(bytes32 role, address account) public virtual override {
require(msg.sender == account, "Can only renounce for self");
if (msg.sender != account) {
revert Permissions__CanOnlyRenounceForSelf(msg.sender, account);
}
_revokeRole(role, account);
}

Expand All @@ -58,6 +63,7 @@ contract Permissions is IPermissions {
}

function _revokeRole(bytes32 role, address account) internal virtual {
_checkRole(role, account);
delete _hasRole[role][account];
emit RoleRevoked(role, account, msg.sender);
}
Expand Down
15 changes: 3 additions & 12 deletions contracts/feature/PermissionsEnumerable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ contract PermissionsEnumerable is IPermissionsEnumerable, Permissions {
if (roleMembers[role].members[i] != address(0)) {
if (check == index) {
member = roleMembers[role].members[i];
return member;
}
} else {
check += 1;
Expand All @@ -38,18 +39,8 @@ contract PermissionsEnumerable is IPermissionsEnumerable, Permissions {
}
}

function grantRole(bytes32 role, address account) public override(IPermissions, Permissions) {
super.grantRole(role, account);
_addMember(role, account);
}

function revokeRole(bytes32 role, address account) public override(IPermissions, Permissions) {
super.revokeRole(role, account);
_removeMember(role, account);
}

function renounceRole(bytes32 role, address account) public override(IPermissions, Permissions) {
super.renounceRole(role, account);
function _revokeRole(bytes32 role, address account) internal override {
super._revokeRole(role, account);
_removeMember(role, account);
}

Expand Down
14 changes: 12 additions & 2 deletions contracts/feature/PlatformFee.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ pragma solidity ^0.8.0;

import "./interface/IPlatformFee.sol";

/**
* Thirdweb's `PlatformFee` is a contract extension to be used with any base contract. It exposes functions for setting and reading
* the recipient of platform fee and the platform fee basis points, and lets the inheriting contract perform conditional logic
* that uses information about platform fees, if desired.
*/

abstract contract PlatformFee is IPlatformFee {
/// @dev The address that receives all platform fees from all sales.
address private platformFeeRecipient;
Expand All @@ -17,13 +23,17 @@ abstract contract PlatformFee is IPlatformFee {

/// @dev Lets a contract admin update the platform fee recipient and bps
function setPlatformFeeInfo(address _platformFeeRecipient, uint256 _platformFeeBps) external override {
require(_canSetPlatformFeeInfo(), "Not authorized");
if (!_canSetPlatformFeeInfo()) {
revert PlatformFee__NotAuthorized();
}
_setupPlatformFeeInfo(_platformFeeRecipient, _platformFeeBps);
}

/// @dev Lets a contract admin update the platform fee recipient and bps
function _setupPlatformFeeInfo(address _platformFeeRecipient, uint256 _platformFeeBps) internal {
require(_platformFeeBps <= 10_000, "Exceeds max bps");
if (_platformFeeBps > 10_000) {
revert PlatformFee__ExceedsMaxBps(_platformFeeBps);
}

platformFeeBps = uint16(_platformFeeBps);
platformFeeRecipient = _platformFeeRecipient;
Expand Down
Loading