Skip to content

Commit

Permalink
Merge branch 'main' into yash/sync-extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
nkrishang authored Oct 16, 2023
2 parents c381c0b + 3935bec commit e26fe6d
Show file tree
Hide file tree
Showing 29 changed files with 2,073 additions and 205 deletions.
5 changes: 2 additions & 3 deletions contracts/extension/interface/IAccountPermissions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ interface IAccountPermissions {
* @param reqValidityStartTimestamp The UNIX timestamp at and after which a signature is valid.
* @param reqValidityEndTimestamp The UNIX timestamp at and after which a signature is invalid/expired.
* @param uid A unique non-repeatable ID for the payload.
* @param isAdmin Whether the signer should be an admin.
*/
struct SignerPermissionRequest {
address signer;
uint8 isAdmin;
address[] approvedTargets;
uint256 nativeTokenLimitPerTransaction;
uint128 permissionStartTimestamp;
Expand Down Expand Up @@ -107,9 +109,6 @@ interface IAccountPermissions {
External functions
//////////////////////////////////////////////////////////////*/

/// @notice Adds / removes an account as an admin.
function setAdmin(address account, bool isAdmin) external;

/// @notice Sets the permissions for a given signer.
function setPermissionsForSigner(SignerPermissionRequest calldata req, bytes calldata signature) external;
}
82 changes: 40 additions & 42 deletions contracts/extension/upgradeable/AccountPermissions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,53 +42,59 @@ abstract contract AccountPermissions is IAccountPermissions, EIP712 {

bytes32 private constant TYPEHASH =
keccak256(
"SignerPermissionRequest(address signer,address[] approvedTargets,uint256 nativeTokenLimitPerTransaction,uint128 permissionStartTimestamp,uint128 permissionEndTimestamp,uint128 reqValidityStartTimestamp,uint128 reqValidityEndTimestamp,bytes32 uid)"
"SignerPermissionRequest(address signer,uint8 isAdmin,address[] approvedTargets,uint256 nativeTokenLimitPerTransaction,uint128 permissionStartTimestamp,uint128 permissionEndTimestamp,uint128 reqValidityStartTimestamp,uint128 reqValidityEndTimestamp,bytes32 uid)"
);

modifier onlyAdmin() virtual {
require(isAdmin(msg.sender), "caller is not an admin");
_;
function _onlyAdmin() internal virtual {
require(isAdmin(msg.sender), "!admin");
}

/*///////////////////////////////////////////////////////////////
External functions
//////////////////////////////////////////////////////////////*/

/// @notice Adds / removes an account as an admin.
function setAdmin(address _account, bool _isAdmin) external virtual onlyAdmin {
_setAdmin(_account, _isAdmin);
}

/// @notice Sets the permissions for a given signer.
function setPermissionsForSigner(SignerPermissionRequest calldata _req, bytes calldata _signature) external {
address targetSigner = _req.signer;
require(!isAdmin(targetSigner), "signer is already an admin");

require(
_req.reqValidityStartTimestamp <= block.timestamp && block.timestamp < _req.reqValidityEndTimestamp,
"invalid request validity period"
"!period"
);

(bool success, address signer) = verifySignerPermissionRequest(_req, _signature);
require(success, "invalid signature");
require(success, "!sig");

_accountPermissionsStorage().allSigners.add(targetSigner);
_accountPermissionsStorage().executed[_req.uid] = true;

//isAdmin > 0, set admin or remove admin
if (_req.isAdmin > 0) {
//isAdmin = 1, set admin
//isAdmin > 1, remove admin
bool _isAdmin = _req.isAdmin == 1;

_setAdmin(targetSigner, _isAdmin);
return;
}

require(!isAdmin(targetSigner), "admin");

_accountPermissionsStorage().allSigners.add(targetSigner);

_accountPermissionsStorage().signerPermissions[targetSigner] = SignerPermissionsStatic(
_req.nativeTokenLimitPerTransaction,
_req.permissionStartTimestamp,
_req.permissionEndTimestamp
);

address[] memory currentTargets = _accountPermissionsStorage().approvedTargets[targetSigner].values();
uint256 currentLen = currentTargets.length;
uint256 len = currentTargets.length;

for (uint256 i = 0; i < currentLen; i += 1) {
for (uint256 i = 0; i < len; i += 1) {
_accountPermissionsStorage().approvedTargets[targetSigner].remove(currentTargets[i]);
}

uint256 len = _req.approvedTargets.length;
len = _req.approvedTargets.length;
for (uint256 i = 0; i < len; i += 1) {
_accountPermissionsStorage().approvedTargets[targetSigner].add(_req.approvedTargets[i]);
}
Expand Down Expand Up @@ -138,7 +144,7 @@ abstract contract AccountPermissions is IAccountPermissions, EIP712 {
virtual
returns (bool success, address signer)
{
signer = _recoverAddress(req, signature);
signer = _recoverAddress(_encodeRequest(req), signature);
success = !_accountPermissionsStorage().executed[req.uid] && isAdmin(signer);
}

Expand Down Expand Up @@ -168,34 +174,30 @@ abstract contract AccountPermissions is IAccountPermissions, EIP712 {

uint256 len = allSigners.length;
uint256 numOfActiveSigners = 0;
bool[] memory isSignerActive = new bool[](len);

for (uint256 i = 0; i < len; i += 1) {
address signer = allSigners[i];

bool isActive = isActiveSigner(signer);
isSignerActive[i] = isActive;
if (isActive) {
if (isActiveSigner(allSigners[i])) {
numOfActiveSigners++;
} else {
allSigners[i] = address(0);
}
}

signers = new SignerPermissions[](numOfActiveSigners);
uint256 index = 0;
for (uint256 i = 0; i < len; i += 1) {
if (!isSignerActive[i]) {
continue;
if (allSigners[i] != address(0)) {
address signer = allSigners[i];
SignerPermissionsStatic memory permissions = _accountPermissionsStorage().signerPermissions[signer];

signers[index++] = SignerPermissions(
signer,
_accountPermissionsStorage().approvedTargets[signer].values(),
permissions.nativeTokenLimitPerTransaction,
permissions.startTimestamp,
permissions.endTimestamp
);
}
address signer = allSigners[i];
SignerPermissionsStatic memory permissions = _accountPermissionsStorage().signerPermissions[signer];

signers[index++] = SignerPermissions(
signer,
_accountPermissionsStorage().approvedTargets[signer].values(),
permissions.nativeTokenLimitPerTransaction,
permissions.startTimestamp,
permissions.endTimestamp
);
}
}

Expand Down Expand Up @@ -225,13 +227,8 @@ abstract contract AccountPermissions is IAccountPermissions, EIP712 {
}

/// @dev Returns the address of the signer of the request.
function _recoverAddress(SignerPermissionRequest calldata _req, bytes calldata _signature)
internal
view
virtual
returns (address)
{
return _hashTypedDataV4(keccak256(_encodeRequest(_req))).recover(_signature);
function _recoverAddress(bytes memory _encoded, bytes calldata _signature) internal view virtual returns (address) {
return _hashTypedDataV4(keccak256(_encoded)).recover(_signature);
}

/// @dev Encodes a request for recovery of the signer in `recoverAddress`.
Expand All @@ -240,6 +237,7 @@ abstract contract AccountPermissions is IAccountPermissions, EIP712 {
abi.encode(
TYPEHASH,
_req.signer,
_req.isAdmin,
keccak256(abi.encodePacked(_req.approvedTargets)),
_req.nativeTokenLimitPerTransaction,
_req.permissionStartTimestamp,
Expand Down
89 changes: 89 additions & 0 deletions contracts/legacy-contracts/extension/BatchMintMetadata_V1.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

/// @author thirdweb

/**
* @title Batch-mint Metadata
* @notice The `BatchMintMetadata` is a contract extension for any base NFT contract. It lets the smart contract
* using this extension set metadata for `n` number of NFTs all at once. This is enabled by storing a single
* base URI for a batch of `n` NFTs, where the metadata for each NFT in a relevant batch is `baseURI/tokenId`.
*/

contract BatchMintMetadata_V1 {
/// @dev Largest tokenId of each batch of tokens with the same baseURI.
uint256[] private batchIds;

/// @dev Mapping from id of a batch of tokens => to base URI for the respective batch of tokens.
mapping(uint256 => string) private baseURI;

/**
* @notice Returns the count of batches of NFTs.
* @dev Each batch of tokens has an in ID and an associated `baseURI`.
* See {batchIds}.
*/
function getBaseURICount() public view returns (uint256) {
return batchIds.length;
}

/**
* @notice Returns the ID for the batch of tokens at the given index.
* @dev See {getBaseURICount}.
* @param _index Index of the desired batch in batchIds array.
*/
function getBatchIdAtIndex(uint256 _index) public view returns (uint256) {
if (_index >= getBaseURICount()) {
revert("Invalid index");
}
return batchIds[_index];
}

/// @dev Returns the id for the batch of tokens the given tokenId belongs to.
function _getBatchId(uint256 _tokenId) internal view returns (uint256 batchId, uint256 index) {
uint256 numOfTokenBatches = getBaseURICount();
uint256[] memory indices = batchIds;

for (uint256 i = 0; i < numOfTokenBatches; i += 1) {
if (_tokenId < indices[i]) {
index = i;
batchId = indices[i];

return (batchId, index);
}
}

revert("Invalid tokenId");
}

/// @dev Returns the baseURI for a token. The intended metadata URI for the token is baseURI + tokenId.
function _getBaseURI(uint256 _tokenId) internal view returns (string memory) {
uint256 numOfTokenBatches = getBaseURICount();
uint256[] memory indices = batchIds;

for (uint256 i = 0; i < numOfTokenBatches; i += 1) {
if (_tokenId < indices[i]) {
return baseURI[indices[i]];
}
}
revert("Invalid tokenId");
}

/// @dev Sets the base URI for the batch of tokens with the given batchId.
function _setBaseURI(uint256 _batchId, string memory _baseURI) internal {
baseURI[_batchId] = _baseURI;
}

/// @dev Mints a batch of tokenIds and associates a common baseURI to all those Ids.
function _batchMintMetadata(
uint256 _startId,
uint256 _amountToMint,
string memory _baseURIForTokens
) internal returns (uint256 nextTokenIdToMint, uint256 batchId) {
batchId = _startId + _amountToMint;
nextTokenIdToMint = batchId;

batchIds.push(batchId);

baseURI[batchId] = _baseURIForTokens;
}
}
114 changes: 114 additions & 0 deletions contracts/legacy-contracts/extension/LazyMintWithTier_V1.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

/// @author thirdweb

import "../../extension/interface/ILazyMintWithTier.sol";
import "./BatchMintMetadata_V1.sol";

/**
* The `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 LazyMintWithTier_V1 is ILazyMintWithTier, BatchMintMetadata_V1 {
struct TokenRange {
uint256 startIdInclusive;
uint256 endIdNonInclusive;
}

struct TierMetadata {
string tier;
TokenRange[] ranges;
string[] baseURIs;
}

/// @notice The tokenId assigned to the next new NFT to be lazy minted.
uint256 internal nextTokenIdToLazyMint;

/// @notice Mapping from a tier -> the token IDs grouped under that tier.
mapping(string => TokenRange[]) internal tokensInTier;

/// @notice A list of tiers used in this contract.
string[] private tiers;

/**
* @notice Lets an authorized address lazy mint a given amount of NFTs.
*
* @param _amount The number of NFTs to lazy mint.
* @param _baseURIForTokens The base URI for the 'n' number of NFTs being lazy minted, where the metadata for each
* of those NFTs is `${baseURIForTokens}/${tokenId}`.
* @param _data Additional bytes data to be used at the discretion of the consumer of the contract.
* @return batchId A unique integer identifier for the batch of NFTs lazy minted together.
*/
function lazyMint(
uint256 _amount,
string calldata _baseURIForTokens,
string calldata _tier,
bytes calldata _data
) public virtual override returns (uint256 batchId) {
if (!_canLazyMint()) {
revert("Not authorized");
}

if (_amount == 0) {
revert("0 amt");
}

uint256 startId = nextTokenIdToLazyMint;

(nextTokenIdToLazyMint, batchId) = _batchMintMetadata(startId, _amount, _baseURIForTokens);

// Handle tier info.
if (!(tokensInTier[_tier].length > 0)) {
tiers.push(_tier);
}
tokensInTier[_tier].push(TokenRange(startId, batchId));

emit TokensLazyMinted(_tier, startId, startId + _amount - 1, _baseURIForTokens, _data);

return batchId;
}

/// @notice Returns all metadata lazy minted for the given tier.
function _getMetadataInTier(string memory _tier)
private
view
returns (TokenRange[] memory tokens, string[] memory baseURIs)
{
tokens = tokensInTier[_tier];

uint256 len = tokens.length;
baseURIs = new string[](len);

for (uint256 i = 0; i < len; i += 1) {
baseURIs[i] = _getBaseURI(tokens[i].startIdInclusive);
}
}

/// @notice Returns all metadata for all tiers created on the contract.
function getMetadataForAllTiers() external view returns (TierMetadata[] memory metadataForAllTiers) {
string[] memory allTiers = tiers;
uint256 len = allTiers.length;

metadataForAllTiers = new TierMetadata[](len);

for (uint256 i = 0; i < len; i += 1) {
(TokenRange[] memory tokens, string[] memory baseURIs) = _getMetadataInTier(allTiers[i]);
metadataForAllTiers[i] = TierMetadata(allTiers[i], tokens, baseURIs);
}
}

/**
* @notice Returns whether any metadata is lazy minted for the given tier.
*
* @param _tier We check whether this given tier is empty.
*/
function isTierEmpty(string memory _tier) internal view returns (bool) {
return tokensInTier[_tier].length == 0;
}

/// @dev Returns whether lazy minting can be performed in the given execution context.
function _canLazyMint() internal view virtual returns (bool);
}
Loading

0 comments on commit e26fe6d

Please sign in to comment.