-
Notifications
You must be signed in to change notification settings - Fork 13
/
ERC4973.sol
113 lines (99 loc) · 3.28 KB
/
ERC4973.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.8;
import {SignatureChecker} from
"@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import {EIP712} from
"@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol";
import {
ERC721,
ERC721URIStorage
} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol";
import {IERC4973} from "./interfaces/IERC4973.sol";
bytes32 constant AGREEMENT_HASH =
keccak256("Agreement(address active,address passive,bytes metadata)");
/// @notice Reference implementation of EIP-4973 tokens.
/// @author Tim Daubenschütz, Rahul Rumalla (https://github.com/rugpullindex/ERC4973/blob/master/src/ERC4973.sol)
abstract contract ERC4973 is EIP712, ERC721URIStorage, IERC4973 {
using BitMaps for BitMaps.BitMap;
BitMaps.BitMap private _usedHashes;
constructor(string memory name, string memory symbol, string memory version)
EIP712(name, version)
ERC721(name, symbol)
{}
function decodeURI(bytes calldata metadata)
public
virtual
returns (string memory)
{
return string(metadata);
}
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override
returns (bool)
{
return interfaceId == type(IERC4973).interfaceId
|| super.supportsInterface(interfaceId);
}
function unequip(uint256 tokenId) public virtual override {
require(msg.sender == ownerOf(tokenId), "unequip: sender must be owner");
_usedHashes.unset(tokenId);
_burn(tokenId);
}
function give(address to, bytes calldata metadata, bytes calldata signature)
external
virtual
returns (uint256)
{
require(msg.sender != to, "give: cannot give from self");
uint256 tokenId = _safeCheckAgreement(msg.sender, to, metadata, signature);
string memory uri = decodeURI(metadata);
_safeMint(msg.sender, tokenId);
_setTokenURI(tokenId, uri);
transferFrom(msg.sender, to, tokenId);
_usedHashes.set(tokenId);
return tokenId;
}
function take(address from, bytes calldata metadata, bytes calldata signature)
external
virtual
returns (uint256)
{
require(msg.sender != from, "take: cannot take from self");
uint256 tokenId = _safeCheckAgreement(msg.sender, from, metadata, signature);
string memory uri = decodeURI(metadata);
_safeMint(from, tokenId);
_setTokenURI(tokenId, uri);
_transfer(from, msg.sender, tokenId);
_usedHashes.set(tokenId);
return tokenId;
}
function _safeCheckAgreement(
address active,
address passive,
bytes calldata metadata,
bytes calldata signature
) internal virtual returns (uint256) {
bytes32 hash = _getHash(active, passive, metadata);
uint256 tokenId = uint256(hash);
require(
SignatureChecker.isValidSignatureNow(passive, hash, signature),
"_safeCheckAgreement: invalid signature"
);
require(!_usedHashes.get(tokenId), "_safeCheckAgreement: already used");
return tokenId;
}
function _getHash(address active, address passive, bytes calldata metadata)
internal
view
returns (bytes32)
{
bytes32 structHash = keccak256(
abi.encode(AGREEMENT_HASH, active, passive, keccak256(metadata))
);
return _hashTypedDataV4(structHash);
}
}