Skip to content

Commit

Permalink
Add PartialMirrorToken and ERC721 redeem minter
Browse files Browse the repository at this point in the history
  • Loading branch information
neokry committed Sep 20, 2023
1 parent 77940d9 commit f5a9450
Show file tree
Hide file tree
Showing 17 changed files with 723 additions and 1,120 deletions.
19 changes: 0 additions & 19 deletions src/lib/interfaces/IERC1271.sol

This file was deleted.

50 changes: 0 additions & 50 deletions src/lib/interfaces/IERC6551Registry.sol

This file was deleted.

7 changes: 0 additions & 7 deletions src/lib/interfaces/IERC721Votes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,4 @@ interface IERC721Votes is IERC721, IEIP712 {
bytes32 r,
bytes32 s
) external;

function batchDelegateBySigERC1271(
address[] calldata _fromAddresses,
address _toAddress,
uint256 _deadline,
bytes memory _signature
) external;
}
64 changes: 56 additions & 8 deletions src/lib/token/ERC721.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import { ERC721TokenReceiver } from "../utils/TokenReceiver.sol";
import { Address } from "../utils/Address.sol";

/// @title ERC721
/// @author Rohan Kulkarni
/// @author Rohan Kulkarni & Neokry
/// @notice Modified from OpenZeppelin Contracts v4.7.3 (token/ERC721/ERC721Upgradeable.sol)
/// - Uses custom errors declared in IERC721
/// - Uses _transfer from Openzepplin Contracts v4.9.3
abstract contract ERC721 is IERC721, Initializable {
/// ///
/// STORAGE ///
Expand Down Expand Up @@ -67,14 +68,14 @@ abstract contract ERC721 is IERC721, Initializable {

/// @notice The account approved to manage a token
/// @param _tokenId The ERC-721 token id
function getApproved(uint256 _tokenId) external view returns (address) {
function getApproved(uint256 _tokenId) public view virtual returns (address) {
return tokenApprovals[_tokenId];
}

/// @notice If an operator is authorized to manage all of an owner's tokens
/// @param _owner The owner address
/// @param _operator The operator address
function isApprovedForAll(address _owner, address _operator) external view returns (bool) {
function isApprovedForAll(address _owner, address _operator) public view virtual returns (bool) {
return operatorApprovals[_owner][_operator];
}

Expand All @@ -99,7 +100,7 @@ abstract contract ERC721 is IERC721, Initializable {
/// @notice Authorizes an account to manage a token
/// @param _to The account address
/// @param _tokenId The ERC-721 token id
function approve(address _to, uint256 _tokenId) external {
function approve(address _to, uint256 _tokenId) public virtual {
address owner = owners[_tokenId];

if (msg.sender != owner && !operatorApprovals[owner][msg.sender]) revert INVALID_APPROVAL();
Expand All @@ -112,7 +113,7 @@ abstract contract ERC721 is IERC721, Initializable {
/// @notice Authorizes an account to manage all tokens
/// @param _operator The account address
/// @param _approved If permission is being given or removed
function setApprovalForAll(address _operator, bool _approved) external {
function setApprovalForAll(address _operator, bool _approved) public virtual {
operatorApprovals[msg.sender][_operator] = _approved;

emit ApprovalForAll(msg.sender, _operator, _approved);
Expand All @@ -126,7 +127,7 @@ abstract contract ERC721 is IERC721, Initializable {
address _from,
address _to,
uint256 _tokenId
) public {
) public virtual {
if (_from != owners[_tokenId]) revert INVALID_OWNER();

if (_to == address(0)) revert ADDRESS_ZERO();
Expand Down Expand Up @@ -158,7 +159,7 @@ abstract contract ERC721 is IERC721, Initializable {
address _from,
address _to,
uint256 _tokenId
) external {
) public virtual {
transferFrom(_from, _to, _tokenId);

if (
Expand All @@ -176,7 +177,7 @@ abstract contract ERC721 is IERC721, Initializable {
address _to,
uint256 _tokenId,
bytes calldata _data
) external {
) public virtual {
transferFrom(_from, _to, _tokenId);

if (
Expand Down Expand Up @@ -206,6 +207,53 @@ abstract contract ERC721 is IERC721, Initializable {
_afterTokenTransfer(address(0), _to, _tokenId);
}

/**
* @dev Transfers `tokenId` from `from` to `to`.
* As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
*
* Emits a {Transfer} event.
* @param _from The sender address
* @param _to The recipient address
* @param _tokenId The ERC-721 token id
*/
function _transfer(
address _from,
address _to,
uint256 _tokenId
) internal virtual {
if (_from != owners[_tokenId]) revert INVALID_OWNER();

if (_to == address(0)) revert ADDRESS_ZERO();

_beforeTokenTransfer(_from, _to, _tokenId);

// Check that tokenId was not transferred by `_beforeTokenTransfer` hook
if (_from != owners[_tokenId]) revert INVALID_OWNER();

// Clear approvals from the previous owner
delete tokenApprovals[_tokenId];

unchecked {
// `_balances[from]` cannot overflow for the same reason as described in `_burn`:
// `from`'s balance is the number of token held, which is at least one before the current
// transfer.
// `_balances[to]` could overflow in the conditions described in `_mint`. That would require
// all 2**256 token ids to be minted, which in practice is impossible.
balances[_from] -= 1;
balances[_to] += 1;
}
owners[_tokenId] = _to;

emit Transfer(_from, _to, _tokenId);

_afterTokenTransfer(_from, _to, _tokenId);
}

/// @dev Burns a token to a recipient
/// @param _tokenId The ERC-721 token id
function _burn(uint256 _tokenId) internal virtual {
Expand Down
120 changes: 1 addition & 119 deletions src/lib/token/ERC721Votes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@
pragma solidity 0.8.16;

import { IERC721Votes } from "../interfaces/IERC721Votes.sol";
import { IERC1271 } from "../interfaces/IERC1271.sol";
import { ERC721 } from "../token/ERC721.sol";
import { EIP712 } from "../utils/EIP712.sol";

/// @title ERC721Votes
/// @author Rohan Kulkarni & Neokry
/// @author Rohan Kulkarni
/// @notice Modified from OpenZeppelin Contracts v4.7.3 (token/ERC721/extensions/draft-ERC721Votes.sol) & Nouns DAO ERC721Checkpointable.sol commit 2cbe6c7 - licensed under the BSD-3-Clause license.
/// - Uses custom errors defined in IERC721Votes
/// - Checkpoints are based on timestamps instead of block numbers
/// - Tokens are self-delegated by default
/// - The total number of votes is the token supply itself
/// - Added batch delegate with sig for ERC1271 accounts
abstract contract ERC721Votes is IERC721Votes, EIP712, ERC721 {
/// ///
/// CONSTANTS ///
Expand All @@ -22,9 +20,6 @@ abstract contract ERC721Votes is IERC721Votes, EIP712, ERC721 {
/// @dev The EIP-712 typehash to delegate with a signature
bytes32 internal constant DELEGATION_TYPEHASH = keccak256("Delegation(address from,address to,uint256 nonce,uint256 deadline)");

/// @dev The EIP-712 typehash to batch delegate with a signature
bytes32 internal constant BATCH_DELEGATION_TYPEHASH = keccak256("Delegation(address[] from,address to,uint256[] nonce,uint256 deadline)");

/// ///
/// STORAGE ///
/// ///
Expand Down Expand Up @@ -58,52 +53,6 @@ abstract contract ERC721Votes is IERC721Votes, EIP712, ERC721 {
}
}

/// @notice Gets the typed data hash for a delegation signature
/// @param _fromAddresses The accounts delegating votes from
/// @param _toAddress The account delegating votes to
/// @param _deadline The signature deadline
/// @dev All addresses in _fromAddress must be unique
function getBatchDelegateBySigTypedDataHash(
address[] calldata _fromAddresses,
address _toAddress,
uint256 _deadline
) public view returns (bytes32) {
uint256 length = _fromAddresses.length;

// Ensure the signature has not expired
if (block.timestamp > _deadline) revert EXPIRED_SIGNATURE();

// Cannot realistically overflow
unchecked {
// Store nonces for each from address
uint256[] memory currentNonces = new uint256[](length);

for (uint256 i = 0; i < length; ++i) {
// Add the addresses current nonce to the list of nonces
// Having two of the same from addresses will cause the nonce to be invalid when verifying the sig this is unsupported behavior but not checked here
currentNonces[i] = nonces[_fromAddresses[i]];
}

// Compute the hash of the domain seperator with the typed delegation data
return
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
BATCH_DELEGATION_TYPEHASH,
keccak256(abi.encodePacked(_fromAddresses)),
_toAddress,
keccak256(abi.encodePacked(currentNonces)),
_deadline
)
)
)
);
}
}

/// @notice The number of votes for an account at a past timestamp
/// @param _account The account address
/// @param _timestamp The past timestamp
Expand Down Expand Up @@ -224,73 +173,6 @@ abstract contract ERC721Votes is IERC721Votes, EIP712, ERC721 {
_delegate(_from, _to);
}

/// @notice Batch delegates votes from multiple ERC1271 accounts to one account
/// @param _fromAddresses The addresses delegating votes from
/// @param _toAddress The address delegating votes to
/// @param _deadline The signature deadline
/// @param _signature The signature
function batchDelegateBySigERC1271(
address[] calldata _fromAddresses,
address _toAddress,
uint256 _deadline,
bytes memory _signature
) external {
uint256 length = _fromAddresses.length;

// Used to store the digest
bytes32 digest;

// Ensure the signature has not expired
if (block.timestamp > _deadline) revert EXPIRED_SIGNATURE();

// Cannot realistically overflow
unchecked {
// Store nonces for each from address
uint256[] memory currentNonces = new uint256[](length);

for (uint256 i = 0; i < length; ++i) {
// Add the addresses current nonce to the list of nonces
currentNonces[i] = nonces[_fromAddresses[i]]++;
}

// Compute the hash of the domain seperator with the typed delegation data
digest = keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
BATCH_DELEGATION_TYPEHASH,
keccak256(abi.encodePacked(_fromAddresses)),
_toAddress,
keccak256(abi.encodePacked(currentNonces)),
_deadline
)
)
)
);

// Set delegation for all from addresses
for (uint256 i = 0; i < length; ++i) {
address cachedFromAddress = _fromAddresses[i];

// Call the ERC1271 isValidSignature function
(bool success, bytes memory result) = cachedFromAddress.staticcall(
abi.encodeWithSelector(IERC1271.isValidSignature.selector, digest, _signature)
);

// Ensure the signature is valid
if (success && result.length >= 32 && abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector)) {
// Update the delegate
_delegate(cachedFromAddress, _toAddress);
} else {
// Revert invalid signature
revert INVALID_SIGNATURE();
}
}
}
}

/// @dev Updates delegate addresses
/// @param _from The address delegating votes from
/// @param _to The address delegating votes to
Expand Down
Loading

0 comments on commit f5a9450

Please sign in to comment.