Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 옵저버패턴
- RBAC
- github
- 부트캠프
- 멀티시그
- 싱글톤패턴
- 팩토리패턴
- 블록체인
- 인센티브 기반 커뮤니티
- github 에러
- 디자인 패턴
- 투표
- 코드스테이츠
- git commit
- git add
- 일시정지
- solidity
- 프록시패턴
- 재진입
- 토큰노믹스
- 업그레이더블 컨트랙트
- 배팅
- 솔리디티
- 팀 프로젝트
- 스테이트머신패턴
- 트러블슈팅
- 웹툰
- 회로차단
- 코드리뷰
Archives
- Today
- Total
보다 더 생생한 기록
[Solidity] Lazy Minting 코드 분석 본문
https://github.com/yusefnapora/lazy-minting/blob/master/lazy-mint-example/contracts/LazyNFT.sol
GitHub - yusefnapora/lazy-minting: A work-in-progress example of minting NFTs at the point of sale
A work-in-progress example of minting NFTs at the point of sale - GitHub - yusefnapora/lazy-minting: A work-in-progress example of minting NFTs at the point of sale
github.com
https://www.youtube.com/watch?v=vYwYe-Gv_XI
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;
import "hardhat/console.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol";
contract LazyNFT is ERC721URIStorage, EIP712, AccessControl {
using ECDSA for bytes32;
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
mapping (address => uint256) pendingWithdrawals;
constructor(address payable minter)
ERC721("LazyNFT", "LAZ")
EIP712("LazyNFT-Voucher", "1") {
_setupRole(MINTER_ROLE, minter);
}
/// @notice 아직 블록체인에 기록되지 않은 발행되지 않은 NFT를 나타냅니다. 서명된 바우처는 교환 기능을 사용하여 실제 NFT로 교환할 수 있습니다.
struct NFTVoucher {
/// @notice 교환할 토큰의 ID입니다. 고유해야 합니다. 이 ID를 가진 다른 토큰이 이미 있는 경우 교환 기능이 되돌아갑니다.
uint256 tokenId;
/// @notice NFT 생성자가 이 NFT의 초기 판매에 대해 기꺼이 수락하는 최소 가격(wei 단위)입니다.
uint256 minPrice;
/// @notice 이 토큰과 연결할 메타데이터 URI입니다.
string uri;
}
/// @notice 실제 NFT에 대해 NFTVoucher를 사용하여 프로세스에서 생성합니다.
/// @param redeemer 성공 시 NFT를 받을 계정의 주소입니다.
/// @param voucher 교환될 NFT를 설명하는 NFT 바우처입니다.
/// @param signature NFT 작성자가 생성한 바우처의 EIP712 서명입니다.
function redeem(address redeemer, NFTVoucher calldata voucher, bytes memory signature) public payable returns (uint256) { // @@@ lazyminting 을 하는 단계 (이전까지는 onchain에 NFT가 올라간게아님)
// 서명이 유효한지 확인하고 서명자의 주소를 가져옵니다.
address signer = _verify(voucher, signature);
// 서명자가 NFT를 생성할 권한이 있는지 확인합니다.
require(hasRole(MINTER_ROLE, signer), "Signature invalid or unauthorized");
// 구속자가 구매자의 비용을 충당하기에 충분한 금액을 지불하고 있는지 확인합니다.
require(msg.value >= voucher.minPrice, "Insufficient funds to redeem");
// 먼저 서명자에게 토큰을 할당하여 온체인 출처를 설정합니다.
_mint(signer, voucher.tokenId);
_setTokenURI(voucher.tokenId, voucher.uri);
// 교환자에게 토큰 전송
_transfer(signer, redeemer, voucher.tokenId);
// 서명자의 인출 잔액에 대한 지불 기록
pendingWithdrawals[signer] += msg.value; // @@@ 아직은 서버계정의 지갑으로 인출된게 아니고, 컨트랙트 내에 있음
return voucher.tokenId;
}
function withdraw() public { // @@@ 한번에 모았다가 서버계정으로 전송 (가스비 아끼기위해)
require(hasRole(MINTER_ROLE, msg.sender), "Only authorized minters can withdraw");
// 중요: msg.sender를 지불 가능한 주소로 캐스팅하는 것은 minter 역할의 모든 구성원이 지불 가능한 주소인 경우에만 안전합니다.
address payable receiver = payable(msg.sender);
uint amount = pendingWithdrawals[receiver];
// 재진입 공격을 방지하기 위해 이체 전 제로 계정
// zero account before transfer to prevent re-entrancy attack
pendingWithdrawals[receiver] = 0;
receiver.transfer(amount);
}
function availableToWithdraw() public view returns (uint256) {
return pendingWithdrawals[msg.sender];
}
/// @notice EIP712 유형 데이터 해싱 규칙을 사용하여 준비된 주어진 NFTVoucher의 해시를 반환합니다.
/// @param voucher 해시할 NFTVoucher입니다.
function _hash(NFTVoucher calldata voucher) internal view returns (bytes32) { // lazyminting 과정중, 맨 처음 아티스트가 오프체인에 NFT를 올리기 위해, 관련된 파라미터를 집어넣고 해싱하는 과정
return _hashTypedDataV4(keccak256(abi.encode(
keccak256("NFTVoucher(uint256 tokenId,uint256 minPrice,string uri)"),
voucher.tokenId,
voucher.minPrice,
keccak256(bytes(voucher.uri))
)));
}
/// @notice 주어진 NFTVoucher에 대한 서명을 확인하여 서명자의 주소를 반환합니다.
/// @dev 서명이 유효하지 않으면 되돌립니다. 서명자가 NFT를 발행할 권한이 있는지 확인하지 않습니다.
/// @param voucher 발행되지 않은 NFT를 설명하는 NFTVoucher입니다.
/// @param signature 주어진 바우처의 EIP712 서명입니다
function _verify(NFTVoucher calldata voucher, bytes memory signature) internal view returns (address) {
bytes32 digest = _hash(voucher); // @@@ 해싱하고 리턴된 byte32 형태의 내용들이 들어감
return digest.toEthSignedMessageHash().recover(signature);
// @@@ digest를 sign된 메세지 해쉬로 만들고
// @@@ recover(hash, signature) -> address 이므로
// @@@ 리턴되는 값은, 메세지에 서명한 주소를 반환
// @@@ 이때 recover의 param인 signature은 비교하는 대상임 (데이터가 서로 동일한지)
function supportsInterface(bytes4 interfaceId) public view virtual override (AccessControl, ERC721) returns (bool) {
return ERC721.supportsInterface(interfaceId) || AccessControl.supportsInterface(interfaceId);
}
}
'블록체인' 카테고리의 다른 글
[Solidity] 크립토좀비 Ch1,2 정리 (0) | 2022.12.26 |
---|---|
[Solidity] UniSwap v2 - 이론 정리 (0) | 2022.12.25 |
CORS / OPTIONS / preflight (0) | 2022.11.10 |
[코드리뷰] Project 2 - 인센티브 기반 커뮤니티 (1) | 2022.11.10 |
[코드리뷰] Project 1 - Opensea.io 클론코딩 (1) | 2022.10.27 |