ERC-721, NFT 스마트 컨트랙트 이해하기(2) - 권한 함수 편[TIL / Solidity]

알락·2022년 12월 6일
0

이더리움

목록 보기
12/16

openZeppelin이 제공하는 ERC721 라이브러리를 분석해본다.

NFT 컨트랙트는 Transfer 함수에 도달하는 과정까지의 총집합이다. 그리고 그 과정에는 특정 NFT에 대해 Transfer 함수를 호출할 수 있는 권한이 있는 사람들을 가려내는 작업이 필요하다. 이번에는 이 권한 관련한 함수들을 확인하려고 한다.


[권한 함수 방식]

권한

앞서 살펴봤듯이 특정 NFT에 대해서는 여러 당사자들의 관계가 얽혀있다. 그리고 ERC-721에서는 이 당사자들에게 권한을 주거나 권한을 확인하는 함수가 꼭 구현되게 준비되어있다.


권한 확인

⌞ ownerOf 함수

function ownerOf(uint256 tokenId) public view virtual override returns(address){
  	address owner = _onwerOf(tokenId);
  	require(owner != address(0), "ERC721: Invalid Token Id");
	return owner;
}

function _ownerOf(uint256 tokenId) internal view virutal returns(address){
	return _owner[tokenId];
}

토큰id 값을 입력받아 해당 NFT의 주인의 계정이 어떤 것인지 리턴하는 함수이다. 해당 NFT에 대한 원천 권한을 갖고 있는 계정을 반환하기 때문에 중요한 함수다.

⌞ 관련함수 : _exists

function _exists(uint256 tokenId) internal view virtual returns (bool){
  return _ownerOf(tokenId) != address(0);
}

mapping 상태 변수는 초기값이 0 이다. 때문에 아직 발행이 안된 토큰은 주소값이 0으로 지정되어 있다. 주소값이 0이면 아직 발행되지 않은 토큰이기 때문에 false를 반환하게 된다.

⌞ 관렴함수 : _requireMinted

function _requireMinted(uint256 tokenId) internal view virtual returns(bool){
  require(_exists(tokenId), "ERC721: Invalid Token Id");
}

_exists 함수에서 파생되는 함수다. _exists가 true를 반환하면 발행이 된 NFT고, _exists가 false를 반환하면 발행이 되지 않은 NFT이기 때문에 해당 관련하여 적당한 에러를 만들어주거나 통과시켜준다.


⌞ getApproved 함수

function getApproved(uint256 tokenId) public view virtual override returns(address){
  _requiredMinted(tokenId);
  return _tokenApprovals[tokenId];
}

이 함수는 특정한 NFT의 Owner가 지정한 Approver의 계정 주소를 반환하는 함수다. 이전에 현재 입력으로 들어온 tokenID 가 유효한지 확인한다.


⌞ isApprovedForAll 함수

function isApprovedForAll(address owner, address operator) public view virtual override returns(bool){
  return _operatorApprovals[owner][operator];
}

owner는 해당 NFT 컨트랙트에서, 자신과 거의 동등한 권한을 갖는 operator를 지정할 수 있다는 것을 이미 확인했었다. 이 함수는 owner가 지정한 operator가 맞는지 확인하는 함수이다. 맞다면 true를 반환하고 아니면 false를 반환하게 된다.

관련함수 : _isApprovedOrOwner

function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns(bool){
  address owner = ERC721.ownerOf(tokenId);
  return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
}

NFT를 소비하려는 계정의 입력값 spender가 Owner인지, Approver인지, 혹은 Operator인지를 확인하는 함수다. 참거짓값으로 반환하게 된다.


권한 부여

⌞ approve 함수

function approve(address to, uint256 tokenId) public virtual override{
  address owner = ERC721.ownerOf(tokenId);
  require(to != owner, "ERC721: approval to current owner");
  require(_msgSender() == owner || isApprovedForAll(owner, _msgSender()), "ERC721: approve caller is not token owner or approved for all");
  _approve(to, tokenId);
}

function _approve(address to, uint256 tokenId) internal virtual{
  _tokenApproval[tokenId] = to;
  emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
}

approve는 권한을 부여받는 주소 to 값과 특정 토큰ID 값을 입력받는다. 그래서 to 계정 값이 유효한지 확인하는 작업을 거치게된다. 최종적으로 권한부여에 대하여 값을 저장하는 함수는 _approve 함수다. 여기서는 또한 이벤트를 통하여 해당 데이터 변경 건에 대하여 블록에 데이터를 저장하게 된다.

⌞ setApprovalAll 함수

function setApprovalAll(address operator, bool approved) public virtual override {
  _setApprovalAll(_msgSender(), operator, approved);
}

function _setApprovalAll(address owner, address operator, bool approved) internal virtual{
  require(owner != operator, `ERC721: approve to caller`);
  _operatorApprovals[owner][operator] == approved;
  emit ApprovalForAll(owner, operator, approved);
}

operator를 지정하거나 해제하는 함수다. 함수 인자값에 operator로 지정할 계정 주소와 true(지정)-false(해제) 여부를 넘겨준다. 그리고 _setApprovalAll 함수를 통해서 실제 상태변수에 지정한 값들을 저장해준다.

이 함수에서 조금 헷갈렸던 것은 _setApprovalAll 함수를 setApprovalAll 함수에서 호출할 적에는 제일 앞 인자값에 _msgSender()가 추가된다는 것이다. 이 값은 이 함수를 외부에서 호출한 EOA의 계정 주소이다.
함수를 호출한 계정과 owner가 일치하는지 체크해야된다고 생각했다. 왜냐하면 operator는 owner와 동등한 권한을 갖기 때문에 아무나 권한 부여를 하는 것은 말도 안됐기 때문이다.
하지만 앞에서 언급했듯이 _msgSender(), 즉 현재 트랜잭션을 보낸 사람이 무조건 인자값으로 들어가니 체크할 필요가 없다는 것을 나중에서야 깨달았다.


정리

이것으로 권한 관련한 ERC721 함수들을 정리해보았다. 이 함수들에서 취약점이 생긴다면 해당 컨트랙트로 생성된 NFT 프로젝트는 신뢰를 크게 상실하게 될 것이다. 특히 Owner와 Operator, 그리고 Approver 의 삼각관계를 꼭 인지하고 있어야 한다.

profile
블록체인 개발 공부 중입니다, 프로그래밍 공부합시다!

0개의 댓글