Skip to content

Fix claim condition state for supply claimed by wallet #647

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -8,16 +8,20 @@ artifacts/build-info
build/
scripts/reference-scripts
cache/
cache_hardhat/
cache_hardhat-zk/
coverage/
dist/
node_modules/
typechain/
typechain-types/
.parcel-cache/

abi/
contracts/abi/
contracts/README.md
artifacts/
artifacts-zk/
artifacts_forge/
contract_artifacts/

18 changes: 15 additions & 3 deletions contracts/extension/Drop.sol
Original file line number Diff line number Diff line change
@@ -60,8 +60,9 @@ abstract contract Drop is IDrop {
verifyClaim(activeConditionId, _dropMsgSender(), _quantity, _currency, _pricePerToken, _allowlistProof);

// Update contract state.
bytes32 activeConditionHash = claimCondition.conditionHash[activeConditionId];
claimCondition.conditions[activeConditionId].supplyClaimed += _quantity;
claimCondition.supplyClaimedByWallet[activeConditionId][_dropMsgSender()] += _quantity;
claimCondition.supplyClaimedByWallet[activeConditionHash][_dropMsgSender()] += _quantity;

// If there's a price, collect price.
_collectPriceOnClaim(address(0), _quantity, _currency, _pricePerToken);
@@ -113,6 +114,13 @@ abstract contract Drop is IDrop {
claimCondition.conditions[newStartIndex + i] = _conditions[i];
claimCondition.conditions[newStartIndex + i].supplyClaimed = supplyClaimedAlready;

bytes32 _conditionHash = claimCondition.conditionHash[newStartIndex + i];
if (_resetClaimEligibility || _conditionHash == bytes32(0)) {
claimCondition.conditionHash[newStartIndex + i] = keccak256(
abi.encodePacked((newStartIndex + i), block.number)
);
}

lastConditionStartTimestamp = _conditions[i].startTimestamp;
}

@@ -129,11 +137,13 @@ abstract contract Drop is IDrop {
if (_resetClaimEligibility) {
for (uint256 i = existingStartIndex; i < newStartIndex; i++) {
delete claimCondition.conditions[i];
delete claimCondition.conditionHash[i];
}
} else {
if (existingPhaseCount > _conditions.length) {
for (uint256 i = _conditions.length; i < existingPhaseCount; i++) {
delete claimCondition.conditions[newStartIndex + i];
delete claimCondition.conditionHash[newStartIndex + i];
}
}
}
@@ -151,6 +161,7 @@ abstract contract Drop is IDrop {
AllowlistProof calldata _allowlistProof
) public view virtual returns (bool isOverride) {
ClaimCondition memory currentClaimPhase = claimCondition.conditions[_conditionId];
bytes32 activeConditionHash = claimCondition.conditionHash[_conditionId];
uint256 claimLimit = currentClaimPhase.quantityLimitPerWallet;
uint256 claimPrice = currentClaimPhase.pricePerToken;
address claimCurrency = currentClaimPhase.currency;
@@ -186,7 +197,7 @@ abstract contract Drop is IDrop {
: claimCurrency;
}

uint256 supplyClaimedByWallet = claimCondition.supplyClaimedByWallet[_conditionId][_claimer];
uint256 supplyClaimedByWallet = claimCondition.supplyClaimedByWallet[activeConditionHash][_claimer];

if (_currency != claimCurrency || _pricePerToken != claimPrice) {
revert DropClaimInvalidTokenPrice(_currency, _pricePerToken, claimCurrency, claimPrice);
@@ -229,7 +240,8 @@ abstract contract Drop is IDrop {
uint256 _conditionId,
address _claimer
) public view returns (uint256 supplyClaimedByWallet) {
supplyClaimedByWallet = claimCondition.supplyClaimedByWallet[_conditionId][_claimer];
bytes32 _conditionHash = claimCondition.conditionHash[_conditionId];
supplyClaimedByWallet = claimCondition.supplyClaimedByWallet[_conditionHash][_claimer];
}

/*////////////////////////////////////////////////////////////////////
18 changes: 15 additions & 3 deletions contracts/extension/Drop1155.sol
Original file line number Diff line number Diff line change
@@ -69,8 +69,9 @@ abstract contract Drop1155 is IDrop1155 {
);

// Update contract state.
bytes32 activeConditionHash = claimCondition[_tokenId].conditionHash[activeConditionId];
claimCondition[_tokenId].conditions[activeConditionId].supplyClaimed += _quantity;
claimCondition[_tokenId].supplyClaimedByWallet[activeConditionId][_dropMsgSender()] += _quantity;
claimCondition[_tokenId].supplyClaimedByWallet[activeConditionHash][_dropMsgSender()] += _quantity;

// If there's a price, collect price.
collectPriceOnClaim(_tokenId, address(0), _quantity, _currency, _pricePerToken);
@@ -123,6 +124,13 @@ abstract contract Drop1155 is IDrop1155 {
conditionList.conditions[newStartIndex + i] = _conditions[i];
conditionList.conditions[newStartIndex + i].supplyClaimed = supplyClaimedAlready;

bytes32 _conditionHash = conditionList.conditionHash[newStartIndex + i];
if (_resetClaimEligibility || _conditionHash == bytes32(0)) {
conditionList.conditionHash[newStartIndex + i] = keccak256(
abi.encodePacked((newStartIndex + i), block.number)
);
}

lastConditionStartTimestamp = _conditions[i].startTimestamp;
}

@@ -139,11 +147,13 @@ abstract contract Drop1155 is IDrop1155 {
if (_resetClaimEligibility) {
for (uint256 i = existingStartIndex; i < newStartIndex; i++) {
delete conditionList.conditions[i];
delete conditionList.conditionHash[i];
}
} else {
if (existingPhaseCount > _conditions.length) {
for (uint256 i = _conditions.length; i < existingPhaseCount; i++) {
delete conditionList.conditions[newStartIndex + i];
delete conditionList.conditionHash[newStartIndex + i];
}
}
}
@@ -162,6 +172,7 @@ abstract contract Drop1155 is IDrop1155 {
AllowlistProof calldata _allowlistProof
) public view virtual returns (bool isOverride) {
ClaimCondition memory currentClaimPhase = claimCondition[_tokenId].conditions[_conditionId];
bytes32 activeConditionHash = claimCondition[_tokenId].conditionHash[_conditionId];
uint256 claimLimit = currentClaimPhase.quantityLimitPerWallet;
uint256 claimPrice = currentClaimPhase.pricePerToken;
address claimCurrency = currentClaimPhase.currency;
@@ -197,7 +208,7 @@ abstract contract Drop1155 is IDrop1155 {
: claimCurrency;
}

uint256 supplyClaimedByWallet = claimCondition[_tokenId].supplyClaimedByWallet[_conditionId][_claimer];
uint256 supplyClaimedByWallet = claimCondition[_tokenId].supplyClaimedByWallet[activeConditionHash][_claimer];

if (_currency != claimCurrency || _pricePerToken != claimPrice) {
revert DropClaimInvalidTokenPrice(_currency, _pricePerToken, claimCurrency, claimPrice);
@@ -245,7 +256,8 @@ abstract contract Drop1155 is IDrop1155 {
uint256 _conditionId,
address _claimer
) public view returns (uint256 supplyClaimedByWallet) {
supplyClaimedByWallet = claimCondition[_tokenId].supplyClaimedByWallet[_conditionId][_claimer];
bytes32 _conditionHash = claimCondition[_tokenId].conditionHash[_conditionId];
supplyClaimedByWallet = claimCondition[_tokenId].supplyClaimedByWallet[_conditionHash][_claimer];
}

/*////////////////////////////////////////////////////////////////////
8 changes: 6 additions & 2 deletions contracts/extension/interface/IClaimConditionMultiPhase.sol
Original file line number Diff line number Diff line change
@@ -25,15 +25,19 @@ interface IClaimConditionMultiPhase is IClaimCondition {
* @param count The total number of phases / claim conditions in the list
* of claim conditions.
*
* @param conditionHash Mapping from numeric claim condition ID to a unique hash.
* Used in supplyClaimedByWallet mapping below.
*
* @param conditions The claim conditions at a given uid. Claim conditions
* are ordered in an ascending order by their `startTimestamp`.
*
* @param supplyClaimedByWallet Map from a claim condition uid and account to supply claimed by account.
* @param supplyClaimedByWallet Map from a claim condition uid/hash and account to supply claimed by account.
*/
struct ClaimConditionList {
uint256 currentStartId;
uint256 count;
mapping(uint256 => bytes32) conditionHash;
mapping(uint256 => ClaimCondition) conditions;
mapping(uint256 => mapping(address => uint256)) supplyClaimedByWallet;
mapping(bytes32 => mapping(address => uint256)) supplyClaimedByWallet;
}
}
20 changes: 17 additions & 3 deletions contracts/extension/upgradeable/Drop.sol
Original file line number Diff line number Diff line change
@@ -49,8 +49,9 @@ abstract contract Drop is IDrop {
verifyClaim(activeConditionId, _dropMsgSender(), _quantity, _currency, _pricePerToken, _allowlistProof);

// Update contract state.
bytes32 activeConditionHash = _dropStorage().claimCondition.conditionHash[activeConditionId];
_dropStorage().claimCondition.conditions[activeConditionId].supplyClaimed += _quantity;
_dropStorage().claimCondition.supplyClaimedByWallet[activeConditionId][_dropMsgSender()] += _quantity;
_dropStorage().claimCondition.supplyClaimedByWallet[activeConditionHash][_dropMsgSender()] += _quantity;

// If there's a price, collect price.
_collectPriceOnClaim(address(0), _quantity, _currency, _pricePerToken);
@@ -102,6 +103,13 @@ abstract contract Drop is IDrop {
_dropStorage().claimCondition.conditions[newStartIndex + i] = _conditions[i];
_dropStorage().claimCondition.conditions[newStartIndex + i].supplyClaimed = supplyClaimedAlready;

bytes32 _conditionHash = _dropStorage().claimCondition.conditionHash[newStartIndex + i];
if (_resetClaimEligibility || _conditionHash == bytes32(0)) {
_dropStorage().claimCondition.conditionHash[newStartIndex + i] = keccak256(
abi.encodePacked((newStartIndex + i), block.number)
);
}

lastConditionStartTimestamp = _conditions[i].startTimestamp;
}

@@ -118,11 +126,13 @@ abstract contract Drop is IDrop {
if (_resetClaimEligibility) {
for (uint256 i = existingStartIndex; i < newStartIndex; i++) {
delete _dropStorage().claimCondition.conditions[i];
delete _dropStorage().claimCondition.conditionHash[i];
}
} else {
if (existingPhaseCount > _conditions.length) {
for (uint256 i = _conditions.length; i < existingPhaseCount; i++) {
delete _dropStorage().claimCondition.conditions[newStartIndex + i];
delete _dropStorage().claimCondition.conditionHash[newStartIndex + i];
}
}
}
@@ -140,6 +150,7 @@ abstract contract Drop is IDrop {
AllowlistProof calldata _allowlistProof
) public view virtual returns (bool isOverride) {
ClaimCondition memory currentClaimPhase = _dropStorage().claimCondition.conditions[_conditionId];
bytes32 activeConditionHash = _dropStorage().claimCondition.conditionHash[_conditionId];
uint256 claimLimit = currentClaimPhase.quantityLimitPerWallet;
uint256 claimPrice = currentClaimPhase.pricePerToken;
address claimCurrency = currentClaimPhase.currency;
@@ -175,7 +186,9 @@ abstract contract Drop is IDrop {
: claimCurrency;
}

uint256 supplyClaimedByWallet = _dropStorage().claimCondition.supplyClaimedByWallet[_conditionId][_claimer];
uint256 supplyClaimedByWallet = _dropStorage().claimCondition.supplyClaimedByWallet[activeConditionHash][
_claimer
];

if (_currency != claimCurrency || _pricePerToken != claimPrice) {
revert("!PriceOrCurrency");
@@ -218,7 +231,8 @@ abstract contract Drop is IDrop {
uint256 _conditionId,
address _claimer
) public view returns (uint256 supplyClaimedByWallet) {
supplyClaimedByWallet = _dropStorage().claimCondition.supplyClaimedByWallet[_conditionId][_claimer];
bytes32 _conditionHash = _dropStorage().claimCondition.conditionHash[_conditionId];
supplyClaimedByWallet = _dropStorage().claimCondition.supplyClaimedByWallet[_conditionHash][_claimer];
}

/*////////////////////////////////////////////////////////////////////
5 changes: 4 additions & 1 deletion src/test/sdk/extension/drop/verify-claim/verifyClaim.t.sol
Original file line number Diff line number Diff line change
@@ -31,10 +31,12 @@ contract MyDrop is Drop {

function setCondition(ClaimCondition calldata condition, uint256 _conditionId) public {
claimCondition.conditions[_conditionId] = condition;
claimCondition.conditionHash[_conditionId] = keccak256(abi.encodePacked(_conditionId, block.number));
}

function setSupplyClaimedByWallet(uint256 _conditionId, address _wallet, uint256 _supplyClaimed) public {
claimCondition.supplyClaimedByWallet[_conditionId][_wallet] = _supplyClaimed;
bytes32 _conditionHash = keccak256(abi.encodePacked(_conditionId, block.number));
claimCondition.supplyClaimedByWallet[_conditionHash][_wallet] = _supplyClaimed;
}
}

@@ -406,6 +408,7 @@ contract Drop_VerifyClaim is ExtensionUtilTest {
_currency = address(weth);
_pricePerToken = 2;
_quantity = 1;

vm.expectRevert(
abi.encodeWithSelector(
Drop.DropClaimExceedLimit.selector,
Original file line number Diff line number Diff line change
@@ -31,10 +31,14 @@ contract MyDropUpg is Drop {

function setCondition(ClaimCondition calldata condition, uint256 _conditionId) public {
_dropStorage().claimCondition.conditions[_conditionId] = condition;
_dropStorage().claimCondition.conditionHash[_conditionId] = keccak256(
abi.encodePacked(_conditionId, block.number)
);
}

function setSupplyClaimedByWallet(uint256 _conditionId, address _wallet, uint256 _supplyClaimed) public {
_dropStorage().claimCondition.supplyClaimedByWallet[_conditionId][_wallet] = _supplyClaimed;
bytes32 _conditionHash = keccak256(abi.encodePacked(_conditionId, block.number));
_dropStorage().claimCondition.supplyClaimedByWallet[_conditionHash][_wallet] = _supplyClaimed;
}
}


Unchanged files with check annotations Beta

import { ERC1155 } from "../eip/ERC1155.sol";
import "../extension/ContractMetadata.sol";

Check warning on line 8 in contracts/base/ERC1155Base.sol

GitHub Actions / lint

global import of path ../extension/ContractMetadata.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
import "../extension/Multicall.sol";

Check warning on line 9 in contracts/base/ERC1155Base.sol

GitHub Actions / lint

global import of path ../extension/Multicall.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
import "../extension/Ownable.sol";

Check warning on line 10 in contracts/base/ERC1155Base.sol

GitHub Actions / lint

global import of path ../extension/Ownable.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
import "../extension/Royalty.sol";

Check warning on line 11 in contracts/base/ERC1155Base.sol

GitHub Actions / lint

global import of path ../extension/Royalty.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
import "../extension/BatchMintMetadata.sol";

Check warning on line 12 in contracts/base/ERC1155Base.sol

GitHub Actions / lint

global import of path ../extension/BatchMintMetadata.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
import "../lib/Strings.sol";

Check warning on line 14 in contracts/base/ERC1155Base.sol

GitHub Actions / lint

global import of path ../lib/Strings.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
/**
* The `ERC1155Base` smart contract implements the ERC1155 NFT standard.
* @param _amount The amount of the same NFT to mint.
*/
function mintTo(address _to, uint256 _tokenId, string memory _tokenURI, uint256 _amount) public virtual {
require(_canMint(), "Not authorized to mint.");

Check warning on line 101 in contracts/base/ERC1155Base.sol

GitHub Actions / lint

Use Custom Errors instead of require statements
uint256 tokenIdToMint;
uint256 nextIdToMint = nextTokenIdToMint();
nextTokenIdToMint_ += 1;
_setTokenURI(nextIdToMint, _tokenURI);
} else {
require(_tokenId < nextIdToMint, "invalid id");

Check warning on line 111 in contracts/base/ERC1155Base.sol

GitHub Actions / lint

Use Custom Errors instead of require statements
tokenIdToMint = _tokenId;
}
uint256[] memory _amounts,
string memory _baseURI
) public virtual {
require(_canMint(), "Not authorized to mint.");

Check warning on line 136 in contracts/base/ERC1155Base.sol

GitHub Actions / lint

Use Custom Errors instead of require statements
require(_amounts.length > 0, "Minting zero tokens.");

Check warning on line 137 in contracts/base/ERC1155Base.sol

GitHub Actions / lint

Use Custom Errors instead of require statements
require(_tokenIds.length == _amounts.length, "Length mismatch.");
uint256 nextIdToMint = nextTokenIdToMint();