SlideShare a Scribd company logo
1 of 45
Download to read offline
Code4rena Audit Report Analysis
Nouns DAO
2023.07.08
J.J
Nouns DAO Contest
• Contest 요약
• https://code4rena.com/contests/2022-08-nouns-dao-contest
• https://code4rena.com/reports/2022-08-nounsdao
• 2022.08.23~2022.08.28 Code4rena
• DAO 기능과 관련된 6개의 파일 분석
• DAO V2로 업그레이드 하기 위한 버그헌팅
• 포상 총액 $50,000
• 160명의 분석가 참여
• High 1개, Medium 3개, Low 3개, Non-Critical 12개, Gas 23개
• 2023.07.04~2023.07.14 DAO V3로 업그레이드하기 위한 버그헌팅 시작
Nouns DAO
• 간단한 서비스 구조 분석
• 매일 NFT(ERC721)가 새로 생성되고, 경매가 열린다.
• 새로 생성된 NFT에 ETH로 입찰한다.
• 낙찰되면 낙찰금은 전부 Treasury로 모인다.
• NFT를 소유한 홀더는 투표권을 얻는다.
• https://nouns.center/dev/nouns-protocol
Nouns DAO
• 간단한 서비스 구조 분석
• 낙찰금을 이용하여 DAO를 운영한다.
• 홀더는 proposal을 생성할 수 있다.
• 홀더들은 제시된 Proposal에 투표하여 결정한다.
• Treasury에 모인 ETH를 어디에 사용할지 결정한다.
• 주로 Nouns를 주제로 어떤 프로젝트를 진행하겠으니 투자금을 달라는 용도
• 기본적으로 Compound Governance Brovo를 베이스로 수정해서 구현
• Proposal이 생성되면 pending -> active -> succeeded/defeated -> queued ->executed 단계를 거쳐 진행된다.
• https://nouns.center/funding/proposals
Nouns DAO
• 오딧팅 범위 로직 분석
• contracts/governance/NounsDAOLogicV2.sol
• contracts/governance/NounsDAOLogicV1.sol
• contracts/governance/NounsDAOInterfaces.sol
• contracts/governance/NounsDAOProxy.sol
• NounsDAOLogic에 붙여서 업그레이드 가능
• contracts/base/ERC721Checkpointable.sol
• NFT가 상속하며, 투표권 할당, 위임 기능
• contracts/base/ERC721Enumerable.sol
Nouns DAO
• 오딧팅 범위 로직 분석
• contracts/governance/NounsDAOLogicV2.sol
• Proposal 생성
• NFT의 N%(threshold) 만큼 소유한 유저만 proposal 생성 가능
Nouns DAO
• 오딧팅 범위 로직 분석
• contracts/governance/NounsDAOLogicV2.sol
• Proposal 취소
• 자신이 생성한 proposal을 취소하거나, proposal 생성자가 더 이상 자격이 없는 경우 (threshold 불만족) 타 유저가 취소 가능
Nouns DAO
• 오딧팅 범위 로직 분석
• contracts/governance/NounsDAOLogicV2.sol
• Proposal 거부
• admin이 proposal을 거부하는 기능
Nouns DAO
• 오딧팅 범위 로직 분석
• contracts/governance/NounsDAOLogicV2.sol
• 투표하기
• Refundable은 투표에 사용된 gas를 일부 환급 받을 수 있는 버전
• Reason은 투표한 이유를 적어내어 이벤트로 생성할 수 있는 기능
• BySig는 서명으로 투표할 수 있는 기능
Nouns DAO
• 오딧팅 범위 로직 분석
• contracts/governance/NounsDAOLogicV2.sol
• 투표하기
Nouns DAO
• 오딧팅 범위 로직 분석
• contracts/governance/NounsDAOLogicV2.sol
• 큐에 넣기
• 투표가 성공한 경우 아무나 호출하여 queue에 호출할 작업을 넣을 수 있다.
Nouns DAO
• 오딧팅 범위 로직 분석
• contracts/governance/NounsDAOLogicV2.sol
• 실행하기
• ETA가 지나면 아무나 호출하여 등록된 작업을 실행할 수 있다.
Nouns DAO
• 오딧팅 범위 로직 분석
• contracts/base/ERC721Checkpointable.sol
• 투표권 mint/burn/transfer
• 이전에 투표권을 위임했다면 위임자에게 투표권 mint/burn/transfer 처리
Nouns DAO
• 오딧팅 범위 로직 분석
• contracts/base/ERC721Checkpointable.sol
• 투표권 부여하기
• 이동이 일어날 때마다 새로운 checkpoint에 투표권 수 기록
Nouns DAO
• 오딧팅 범위 로직 분석
• contracts/base/ERC721Checkpointable.sol
• 투표권 위임하기
• BySig는 서명으로 위임하는 기능
• Delegatee가 address(0) 일 경우 위임 취소
Nouns DAO
• 오딧팅 범위 로직 분석
• contracts/base/ERC721Checkpointable.sol
• 투표권 위임하기
• 개수 컨트롤 불가, 가지고 있는 NFT 개수 만큼의 투표권을 위임 또는 위임 취소
Audit report 분석
• High/Medium 취약점
번호 취약점 요약
[H-01] ERC721Checkpointable: delegateBySig allows the user to vote to address 0,
which causes the user to permanently lose his vote and cannot transfer his NFT.
파라미터가 값이 address(0) 인지 확인하
지 않아 문제가 발생했다. delegateBySig
를 통해 delegatee address(0) 에게 표를
위임한 경우 유저가 소유한 투표권이 전부
사라지며 유저는 더이상 NFT를 transfer
할 수 없게 된다.
[M-01] Voters can burn large amounts of Ether by submitting votes with long reason
strings
투표 시 가스비용을 일부 환급해준다. 함
수 파라미터로 긴 문자열을 넣으면 가스를
더 많이 소모하여 더 많은 ETH를 환급 받
을 수 있다.
[M-02] User A cannot cancel User B’s proposal when User B’s prior number of votes at
relevant block is same as proposal threshold, which contradicts the fact that User
B actually cannot create the proposal when the prior number of votes is same as
proposal threshold
숫자 비교 시 edge case 고려를 하지 않아
로직에 결함이 생겼다.
[M-03] Loss of Veto Power can Lead to 51% Attack 실수로 vetoer를 잘못 설정하는 상황을 막
지 않는다. vetoer를 잃으면 51% 공격이
가능해지므로 중요한 문제이다.
Audit report 분석
• [H-01] ERC721Checkpointable: delegateBySig allows the user to vote to address 0,
which causes the user to permanently lose his vote and cannot transfer his NFT.
• Summary
• 파라미터가 값이 address(0) 인지 확인하지 않아 문제가 발생했다.
• delegateBySig 를 통해 delegatee address(0) 에게 표를 위임한 경우 유저가 소유한 투표권이 전부 사라지며, 유저는 더이상 자신의
NFT를 transfer/burn 할 수 없게 된다.
• Keyword
• input validation, logic flaw, dao, delegate vote, checkpoint, dos
• Vulnerability
• 기본적인 투표권 위임 기능 함수인 delegate 는 delegate 파라미터로 address(0) 가 들어오는 경우 기존 투표권 위임을 취소하고 위임
했던 표를 되돌려 받는다.
• 이와 유사하게 delegateBySig 함수는 유저의 서명을 받아 파라미터로 넣는, 유저가 직접 컨트랙트콜하지 않는 형식으로 투표권 위임을
할 수 있는 기능을 제공한다.
• 이 때, delegate 함수와는 다르게 delegatee 파라미터가 address(0) 인지 확인하는 로직이 빠져 있다.
• 즉, address(0) 를 delegatee 파라미터로 받은 경우 기존의 투표권 위임을 취소해야 하는데, 그게 아니라 address(0) 에게 위임하는 꼴이 된다.
• 이 투표권은 사라지게 된다. 또한 투표권을 위임한 delegator는 더 이상 NFT를 transfer 하거나 burn할 수 없게 된다.
Audit report 분석
• [H-01] ERC721Checkpointable: delegateBySig allows the user to vote to address 0,
which causes the user to permanently lose his vote and cannot transfer his NFT.
• Vulnerability
• contracts/base/ERC721Checkpointable.sol#L126-L144
Audit report 분석
• [H-01] ERC721Checkpointable: delegateBySig allows the user to vote to address 0,
which causes the user to permanently lose his vote and cannot transfer his NFT.
• Vulnerability
• contracts/base/ERC721Checkpointable.sol#L197-L208
• contracts/base/ERC721Checkpointable.sol#L210-L230
Audit report 분석
• [H-01] ERC721Checkpointable: delegateBySig allows the user to vote to address 0,
which causes the user to permanently lose his vote and cannot transfer his NFT.
• Vulnerability
• contracts/base/ERC721Checkpointable.sol#L97-L106
• contracts/base/ERC721Checkpointable.sol#L88-L91
Audit report 분석
• [H-01] ERC721Checkpointable: delegateBySig allows the user to vote to address 0,
which causes the user to permanently lose his vote and cannot transfer his NFT.
• Impact
• 위임한 투표권이 사라지며, 투표권을 위임한 유저가 자신의 NFT를 transfer하거나 burn할 수 없게 된다.
• Mitigation
• 1. delegateBySig 에 require(delegatee != address(0)); 를 추가하여 막거나
• 2. delegate 함수와 동일하게 delegatee가 address(0)인 경우 투표권 위임을 취소하는 기능을 구현한다.
Audit report 분석
• [M-01] Voters can burn large amounts of Ether by submitting votes with long reason
strings
• Summary
• 투표 시 가스비용을 일부 환급해준다.
• 함수 파라미터로 긴 문자열을 넣으면 가스를 더 많이 소모하여 더 많은 ETH를 환급 받을 수 있다.
• 예치된 ETH를 고갈시킴으로써 다른 유권자들이 환급 받지 못하게 방해하는 등의 행동이 가능하다.
• Keyword
• gas refund, DoS, input validation
• Vulnerability
• 투표를 하면 투표에 사용된 가스비를 일부 환급해주는 기능이 있다. 이 때, reason 파라미터에 임의의 문자열을 넣어 이벤트에 찍을 수
있다.
• reason 파라미터는 길이 제한이 없다.
• 공격자가 임의로 긴 문자열을 넣을 수 있다. 긴 문자열은 짧은 것보다 많은 가스를 소비하고, 그리하면 더 많은 가스비를 환급 받을 수 있다.
• 가스 환급은 컨트랙트에 예치된 ETH를 이용하므로 예치된 ETH를 기대보다 더 소비할 수 있게 된다.
• 환급을 받더라도 파라미터를 넘기는 가스비를 전부 받아내지는 못하기때문에 공격을 통해 경제적으로 이득을 보지는 못 함
• 따라서 경제적 이득을 바라고 공격을 하지는 않을 것. 하지만 이러한 경제적 보호에만 의존하기보다는 이를 막는 것이 낫다.
Audit report 분석
• [M-01] Voters can burn large amounts of Ether by submitting votes with long reason
strings
• Vulnerability
• contracts/governance/NounsDAOLogicV2.sol#L518-L524
• contracts/governance/NounsDAOLogicV2.sol#L533-L544
• contracts/governance/NounsDAOLogicV2.sol#L98
/// @notice The vote refund gas overhead, including 7K for ETH transfer and 29K for general
transaction overhead
uint256 public constant REFUND_BASE_GAS = 36000;
...
function castRefundableVoteWithReason(
uint256 proposalId,
uint8 support,
string calldata reason
) external {
castRefundableVoteInternal(proposalId, support, reason);
}
function castRefundableVoteInternal(
uint256 proposalId,
uint8 support,
string memory reason
) internal {
uint256 startGas = gasleft();
uint96 votes = castVoteInternal(msg.sender, proposalId, support);
emit VoteCast(msg.sender, proposalId, support, votes, reason);
if (votes > 0) {
_refundGas(startGas);
}
}
function _refundGas(uint256 startGas) internal {
unchecked {
uint256 balance = address(this).balance;
if (balance == 0) {
return;
}
uint256 gasPrice = min(tx.gasprice, block.basefee + MAX_REFUND_PRIORITY_FEE);
uint256 gasUsed = startGas - gasleft() + REFUND_BASE_GAS;
uint256 refundAmount = min(gasPrice * gasUsed, balance);
(bool refundSent, ) = msg.sender.call{ value: refundAmount }('');
emit RefundableVote(msg.sender, refundAmount, refundSent);
}
}
Audit report 분석
• [M-01] Voters can burn large amounts of Ether by submitting votes with long reason
strings
• Impact
• 미래에 calldata 가격이 낮아질 가능성이 높다. calldata를 넣는 가격이 낮아진다면 공격자가 경제적으로 손해를 보는 부분이 완화되어
좀 더 공격하기 좋은 상황이 올 것이다.
• 공격자가 임의의 텍스트를 이벤트로 쓰고자 하는 동기가 있을 수 있으며, 단순히 이를 위해 이 시스템을 악용할 수 있다.
• 단순히 프로토콜의 명성을 손상시키기 위해 악의적으로 ETH를 고갈시킬 수 있다. 이를 통해 다른 유저가 가스비용을 환급 받지 못하게
할 수 있다.
• Mitigation
• 해당 문자열 파라미터를 사용하는 emit VoteCast 코드를 가스 환급 대상 코드에서 제외한다. (가스비를 먼저 환급해준 후 이벤트 생성)
• 이벤트 생성에 대한 가스비를 환급하고 싶다면 REFUND_BASE_GAS 를 늘려 합리적인 양(적절한 reason 길이)까지만 환급해준다.
• reason 의 타입을 bytes 로 변경하고 길이를 제한한다.
• castRefundableVoteWithReason()에서 길이를 체크하고, 너무 긴 reason을 넣은 경우 revert 시킨다.
Audit report 분석
• [M-02] User A cannot cancel User B’s proposal when User B’s prior number of votes at
relevant block is same as proposal threshold, which contradicts the fact that User B
actually cannot create the proposal when the prior number of votes is same as
proposal threshold
• Summary
• 숫자 비교 시 edge case 고려를 하지 않아 로직에 결함이 생겼다.
• Keyword
• logic flaw, edge case
• Vulnerability
• Propose 함수로 새로운 proposal을 제시할 수 있다.
• 새로운 Proposal을 생성하려면 전체 NFT의 n%만큼을 소유하고 있어야 한다. (proposalThreshold)
• cancel 함수로 proposal을 제시한 자신이 제시한 proposal을 취소할 수 있다. 또한, 더이상 n%(proposal 생성 당시 기준) 소유를 만족할
수 없는 경우 다른 유저가 proposal을 취소시킬 수 있다.
• n% 소유를 확인하는 로직에서, 부등호를 섞어 사용하며 edge case를 놓쳤다. 이로 인해 정확히 n% 와 숫자가 일치할 때 로직 오류가
발생한다.
Audit report 분석
• [M-02] User A cannot cancel User B’s proposal when User B’s prior number of votes at
relevant block is same as proposal threshold, which contradicts the fact that User B
actually cannot create the proposal when the prior number of votes is same as
proposal threshold
• Vulnerability
• contracts/governance/NounsDAOLogicV2.sol#L184-L279
• contracts/governance/NounsDAOLogicV2.sol#L346-L368
Audit report 분석
• [M-02] User A cannot cancel User B’s proposal when User B’s prior number of votes at
relevant block is same as proposal threshold, which contradicts the fact that User B
actually cannot create the proposal when the prior number of votes is same as
proposal threshold
• Impact
• 로직이 의도한 대로 동작하지 않는다.
• proposal을 생성한 뒤, NFT를 옮겨 proposalThreshold 와 딱 맞춘다면 당시 proposal을 생성할 자격은 잃음에도 불구하고 타 유저로부터 cancel 당하지도
않는 상태가 된다.
• Mitigation
• propose나 cancel 함수 중 한쪽만 변경하여 양측의 조건이 일치하도록 한다.
• 1. propose 함수에서 >= 로 비교하여 proposalThreshold 와 동일한 수의 NFT를 소유했을 때에도 proposal을 생성할 수 있도록 변경하거나
• 2. cancel 함수에서 <= 로 비교하여 proposalThreshold 와 동일한 수의 NFT를 소유했을 때에 cancel이 가능하도록 수정한다.
Audit report 분석
• [M-03] Loss of Veto Power can Lead to 51% Attack
• Summary
• 실수로 vetoer를 잘못 설정하는 상황을 막지 않는다.
• vetoer를 잃으면 51% 공격이 가능해지므로 실수를 방지할 수 있어야 한다.
• Keyword
• input validation, 51% attack, sybil attack, dao
• Vulnerability
• 악성 proposal에 대한 거부권을 행사할 수 있는 vetoer라는 역할이 있다.
• 이는 initialize() 함수와 _setVetoer() 함수를 통해 설정할 수 있다.
• initialize() 함수와 _setVetoer() 함수에는 새로운 vetoer 의 주소가 올바른지(address(0) 가 아닌지) 확인하는 로직이 없다.
• vetoer 주소를 실수로 address(0) 로 설정한다면 vetoer가 공석이 되어 거부권을 행사할 수 없게 된다.
• 이 때, 51% 공격을 하면 임의의 proposal을 성공시킬 수 있다. 이를 통해 treasury의 모든 토큰을 탈취할 수 있다는 시나리오가 가능하다.
Audit report 분석
• [M-03] Loss of Veto Power can Lead to 51% Attack
• Vulnerability
• initialize() 함수
• NounsDAOLogicV1.sol#L150
• NounsDAOLogicV2.sol#L156
V1 V2
Audit report 분석
• [M-03] Loss of Veto Power can Lead to 51% Attack
• Vulnerability
• _setVetoer() 함수
• 기존 vetoer가 다음 vetoer에게 권한을 넘길 수 있다.
• NounsDAOLogicV1.sol#L637-L643
• NounsDAOLogicV2.sol#L839-L845
V2
V1
Audit report 분석
• [M-03] Loss of Veto Power can Lead to 51% Attack
• Impact
• 실수로 vetoer를 잘못 설정할 수 있으며, vetoer를 잃으면 51% 공격이 가능해진다.
• Mitigation
• initialize() 함수와 _setVetoer() 함수에서 address(0) 체크를 한다.
• 또한 _setVetoer() 함수로 vetoer를 설정할 시 2 step으로 설정하게 한다.
• 실수로 소유하지 않은 주소를 vetoer로 설정하지 않도록, 기존 vetoer A가 컨트랙트콜 하여 새로운 vetoer B에게 approve 한 후, B 계정으로 컨트랙트콜
하여 vetoer 권한을 옮겨가는 방식이다.
• 존재하지 않는 주소에 vetoer를 넘기는 상황이 발생하지 않게 된다
• Memo
• 단순히 input validation이 부족하다 라는 취약점에 51% attack을 엮어서 심각도가 더 높아 보이게 한 점이 인상적
Audit report 분석
• Low/Non-Critical 취약점
번호 취약점 요약
[L-01] Nouns will not be able to be transferred once the block.number passes
type(uint32).max
block number를 32비트로 잘라내 이용하는
데, 이러면 32비트 범위를 넘어가는 약
1260년 뒤에 정상 작동하지 않을 것이라 지
적했다.
[L-02] Unused/empty receive()/fallback() function 용도가 없는 receive/fallback은 삭제하라 제
안했다.
[L-03] Missing checks for address(0x0) when assigning values to address state variables address 변수에 값을 할당할 때 address(0)
인지 확인하지 않았음을 지적했다.
[N-01] public functions not called by the contract should be declared external instead 동일 컨트랙트에서 해당 함수를 호출하지
않는다면 public 대신 external로 선언하여
가스를 절약할 수 있다.
[N-02] Non-assembly method available 불필요하게 inline assembly를 사용하여 코
드 복잡도를 올리고 있음을 지적했다.
Audit report 분석
• Low/Non-Critical 취약점
번호 취약점 요약
[N-03] 2**<n> - 1 should be re-written as type(uint<n>).max 2**<n> - 1 형식으로 최대값을 이용하는 대
신 type(uint<n>).max 코드로 대체하라 제안
했다.
[N-04] constants should be defined rather than using magic numbers 상수 값을 직접 코드에 사용하는 것보다
constant 변수를 선언해 사용하는 쪽이 코드
의 readability에 좋다고 지적했다.
[N-05] Use a more recent version of solidity 최신 컴파일러를 이용하여
abi.encodePacked(<str>,<str>) 대신
string.concat() 를 이용하라고 제안했다.
[N-06] Expressions for constant values such as a call to keccak256(), should use immutable
rather than constant
expression, 계산된 값, constructor에서 전달
된 값 등은 constant 대신 immutable 변수에
저장해야 한다고 제안한다.
[N-07] Constant redefined elsewhere 동일한 constant를 여러 컨트랙트에서 중복
하여 정의하면 추후 값이 바뀌어야 할 때 모
든 컨트랙트에서 수정하는 것을 까먹어 싱크
가 안 맞는 상황이 생길 수 있다. 따라서
constant는 한 군데에서만 정의하는 게 좋다
고 제안했다.
Audit report 분석
• Low/Non-Critical 취약점
번호 취약점 요약
[N-08] Lines are too long 코드 한 라인에 글자가 너무 많다고 지적했
다.
[N-09] Non-library/interface files should use fixed compiler versions, not floating ones 라이브러리나 인터페이스 소스코드가 아닌
코드에는 고정된 컴파일러 버전을 이용하는
것이 좋다. floating pragma를 이용해 원하는
것보다 더 높은 버전의 컴파일러를 이용하게
되면 의도하지 않은 일이 발생할 수 있다.
[N-10] Event is missing indexed fields 이벤트 필드에 index를 걸지 않았음을 지적
했다.
[N-11] Not using the named return variables anywhere in the function is confusing Named return으로 작성해 놓고 정작 이를
이용하지 않고 직접 리턴했다. 헷갈리므로
unnamed return으로 변경하자고 제안했다.
[N-12] Typos 오탈자를 수정하라고 했다.
Audit report 분석
• Gas optimizations
번호 취약점 요약
[G-01] State checks unnecessarily re-fetch Proposals 동일한 storage를 여러 번 로드하여 가스를
낭비한다 지적했다. 한 번 읽어온 storage 변
수를 쭉 활용하라고 제안했다.
[G-02] Multiple address/ID mappings can be combined into a single mapping of an
address/ID to a struct, where appropriate
동일한 key를 이용하는 여러개의 mapping
변수는 struct을 이용하여 하나의 mapping
변수에 합치라고 제안했다.
[G-03] Structs can be packed into fewer storage slots struct 구조가 최적화되어 있지 않음을 지적
했다. struct를 최적화하여 적은 slot을 사용
하고, 이로인해 gas를 절약하라 제안했다.
[G-04] Using calldata instead of memory for read-only arguments in external functions saves
gas
calldata 로 선언해도 되는 array 변수를
memory로 선언하여 가스를 낭비한다고 지
적했다. 이를 calldata로 변경하라고 제안했
다.
[G-05] Using storage instead of memory for structs/arrays saves gas struct/array의 전체 필드가 필요하지 않은 경
우 굳이 memory에 복사하지 않고 storage를
그냥 이용하는 쪽이 낫다. 여러번 사용될 필
드라면 그 필드만 stack variable에 캐싱하는
게 저렴하다.
Audit report 분석
• Gas optimizations
번호 취약점 요약
[G-06] State variables should be cached in stack variables rather than re-reading them from
storage
state 변수를 매번 storage에서 읽는 것이 가
스 낭비적이라 지적했다. 지역 변수에 캐싱
하여 가스를 절약하라 제안했다.
[G-07] Multiple accesses of a mapping/array should use a local variable cache 여러번 접근하는 mapping이나 array의 값의
경우 로컬 변수에 캐싱하라고 제안했다.
[G-08] internal functions only called once can be inlined to save gas 한군데에서만 호출되는 internal 함수의 경우
함수를 쪼개지 말고, 그냥 코드를 합치는 게
가스 절약적이라고 제안했다.
[G-09] Add unchecked {} for subtractions where the operands cannot underflow because of
a previous require() or if-statement
언더플로우/오버플로우가 나지 않을 상황이
라면 unchecked 를 넣어 가스비를 절약하자
고 제안했다.
[G-10] <array>.length should not be looked up in every loop of a for-loop for 루프 조건문에 storage array의
<array>.length를 사용하는 것을 지적했다.
지역변수에 length를 저장한 뒤 이용하라 제
안한다.
Audit report 분석
• Gas optimizations
번호 취약점 요약
[G-11] ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them
to overflow, as is the case when used in for- and while-loops
오버플로우 날 가능성이 없는 반복문 인덱스
연산은 unchecked 하여 가스를 줄일 수 있다.
[G-12] require()/revert() strings longer than 32 bytes cost extra gas 32바이트보다 긴 에러메시지는 추가 gas를
소모하므로 짧은 에러 문구를 이용하라고 하
였다.
[G-13] Optimize names to save gas 함수의 이름을 최적화하면 함수 콜 시 함수
를 더 빨리 찾을 수 있다. 이를 통해 자주 사
용하는 함수의 가스를 줄이라 제안했다.
[G-14] Use a more recent version of solidity solidity 버전을 올려 컴파일러가 지원하는
여러가지 최적화와 기능을 이용하라고 제안
했다.
[G-15] ++i costs less gas than i++, especially when it’s used in for-loops (--i/i-- too) i++, i-- 보다 ++i, --i 가 gas 효율적이다. 특
히 반복문에서 주의해야 한다.
Audit report 분석
• Gas optimizations
번호 취약점 요약
[G-16] Splitting require() statements that use && saves gas 가능한 경우 require 문의 && 연산을 쪼개서
require문을 분리하면 런타임에 gas를 절약
할 수 있다.
[G-17] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead 메모리에서 워드보다 작은 크기의 자료형을
이용하는 경우 이점은 없고 gas만 더 나가므
로, 가능하면 워드 크기의 자료형을 이용하
라고 제안했다.
[G-18] Using private rather than public for constants, saves gas constant 변수의 getter를 굳이 사용하지 않
는다면 private으로 선언해도 괜찮다. private
으로 변경 시 배포시 gas를 절약할 수 있다.
[G-19] Don’t compare boolean expressions to boolean literals if (<x> == true) 나 if (<x> == false) 대신 if
(<x>) , if (!<x>) 를 사용해야 한다.
[G-20] Division by two should use bit shifting 2의 배수로 나누기 하는 것은 / 연산자보다
shift를 이용하는 것이 저렴하다.
Audit report 분석
• Gas optimizations
번호 취약점 요약
[G-21] require() or revert() statements that check input arguments should be at the top of
the function
require문, 특히 constant 를 이용하여 input
validation을 체크하는 require문은 가장 먼
저 하는 게 좋다.
storage 변수를 불러와서 비교하는 등의 더
큰 작업을 하기 전에 체크하면 실패시에도
gas를 절약할 수 있기 때문이다.
[G-22] Empty blocks should be removed or emit something 용도가 없는 receive 함수는 삭제하여 배포
시 gas를 절약하자 제안했다. 아니면 최소한
이벤트라도 생성하라고 했다.
[G-23] Use custom errors rather than revert()/require() strings to save gas solidity 0.8.4부터 사용 가능한 커스텀 에러
를 사용하면 ~50 gas만큼 줄일 수 있다.
revert 이유 문자열을 할당하는 작업을 피할
수 있기 때문이다.
Audit report 분석
• [G-11] ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for
them to overflow, as is the case when used in for- and while-loops
• Summary
• 오버플로우 날 가능성이 없는 반복문 인덱스 연산은 unchecked 하여 가스를 줄일 수 있다 제안했다.
• Keyword
• gas optimization, safemath, unchecked
• Not optimized
• 일반적으로 반복문은 for (uint256 i = 0; i < length; i++) 와 같이 이용된다.
• length의 타입이 동일하거나 i보다 작다면 i는 length보다 항상 작다. 따라서 대부분의 상황에서 i++ 나 ++i 연산 시 오버플로우가 발생하지 않는다.
• solidity 0.8.0 부터는 기본적으로 연산에 SafeMath가 적용된다. 이는 30~40 gas를 추가로 소모한다.
• 이는 unchecked를 이용하여 해제할 수 있다.
Audit report 분석
• [G-11] ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for
them to overflow, as is the case when used in for- and while-loops
• Not optimized
• contracts/governance/NounsDAOLogicV1.sol#L281
• contracts/governance/NounsDAOLogicV2.sol#L292
Audit report 분석
• [G-11] ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for
them to overflow, as is the case when used in for- and while-loops
• Impact
• for 루프를 돌 때마다 SafeMath를 체크하느라 30~40 gas가 낭비된다.
• Mitigation
• unchecked 를 이용하여 인덱스를 업데이트 한다.
• 다음과 같이 unchecked로 인덱스를 업데이트 하면 가스를 절약할 수 있다.
• 아직 루프문에 바로 unchecked 문을 사용할 수 없어 함수로 빼거나(이 또한 가스 낭비) 다음과 같이 반복문 내에서 처리한다.
for (uint i ; i < length;) {
// do something that doesn't change the value of I
…
unchecked{
++i;
}
}
Audit report 분석
• [G-13] Optimize names to save gas
• Summary
• 함수의 이름을 최적화하면 함수 콜 시 함수를 더 빨리 찾을 수 있다. 이를 통해 자주 사용하는 함수의 가스를 줄이라 제안했다.
• Keyword
• gas optimization, method id
• Not optimized
• 각 함수는 methodId를 기준으로 소팅되고, 함수 콜을 할 시 낮은 methodId부터 차례대로 함수를 찾는다.
• 순서대로 호출한 methodId와 비교 –> JUMP로 이동
• 즉, methodId가 낮을수록 더 빨리 찾아지고, 순서가 하나 밀릴수록 22 gas를 추가로 소비한다.
• 가장 자주 호출되는 public/external 함수의 methodId가 낮도록 하면 이를 호출하는 gas를 절약할 수 있다.
• 어떻게 methodId를 조작하는가? methodId는 기본적으로 함수 이름과 파라미터 타입을 해시하여 생성된다. 즉, 함수 이름을 bruteforce하여 낮은
methodId를 찾아볼 수 있다.
• 특히나 methodId에 0이 포함되면 가스가 더 절약된다. input 데이터의 바이트가 0이면 4 gas를, 0이 아니면 68 gas를 소모한다. 위 툴로 methodId의 앞
두 바이트가 0x00 인 함수 이름을 찾아내면, 함수 호출 시 전체 바이트가 0x00이 아닌 경우보다 128 gas를 절약할 수 있다.
• https://medium.com/joyso/solidity-how-does-function-name-affect-gas-consumption-in-smart-contract-47d270d8ac92 참고
Audit report 분석
• [G-13] Optimize names to save gas
• Impact
• 자주 사용되는 함수의 순서가 밀려 gas가 나간다.
• Mitigation
• 자주 사용되는 함수의 methodId를 낮게 하여 빠르게 찾을 수 있게 한다.
• https://emn178.github.io/solidity-optimize-name/ 에서 함수 이름 최적화를 할 수 있다.

More Related Content

Similar to Nouns DAO bughunting case study

Similar to Nouns DAO bughunting case study (7)

Toc quality management
Toc quality managementToc quality management
Toc quality management
 
[전득진_22년4월] AI_ML담당_Tech_seminar-emart.pdf
[전득진_22년4월] AI_ML담당_Tech_seminar-emart.pdf[전득진_22년4월] AI_ML담당_Tech_seminar-emart.pdf
[전득진_22년4월] AI_ML담당_Tech_seminar-emart.pdf
 
Block chain architecture and hyperledger fabric overview
Block chain architecture and hyperledger fabric overviewBlock chain architecture and hyperledger fabric overview
Block chain architecture and hyperledger fabric overview
 
if kakao dev 2019_Ground X_Session 03
if kakao dev 2019_Ground X_Session 03if kakao dev 2019_Ground X_Session 03
if kakao dev 2019_Ground X_Session 03
 
Hyperledger fabric - tuna fishing analysis
Hyperledger fabric - tuna fishing analysisHyperledger fabric - tuna fishing analysis
Hyperledger fabric - tuna fishing analysis
 
Core Ethereum Programming(Chapter1~Chapter2.2)
Core Ethereum Programming(Chapter1~Chapter2.2)Core Ethereum Programming(Chapter1~Chapter2.2)
Core Ethereum Programming(Chapter1~Chapter2.2)
 
Blockchain Basic Concept Theory (Beginner Version) / 초보자를 위한 블록체인 기초 개념 이론
Blockchain Basic Concept Theory (Beginner Version) / 초보자를 위한 블록체인 기초 개념 이론Blockchain Basic Concept Theory (Beginner Version) / 초보자를 위한 블록체인 기초 개념 이론
Blockchain Basic Concept Theory (Beginner Version) / 초보자를 위한 블록체인 기초 개념 이론
 

More from Jinkyoung Kim

More from Jinkyoung Kim (20)

Axelar 22.04 bughunting case study
Axelar 22.04 bughunting case studyAxelar 22.04 bughunting case study
Axelar 22.04 bughunting case study
 
해커가 되고 싶은 자는 나에게... 정보보안 입문과 길 찾기
해커가 되고 싶은 자는 나에게... 정보보안 입문과 길 찾기해커가 되고 싶은 자는 나에게... 정보보안 입문과 길 찾기
해커가 되고 싶은 자는 나에게... 정보보안 입문과 길 찾기
 
Web hacking introduction
Web hacking introductionWeb hacking introduction
Web hacking introduction
 
Linux reversing study_basic_4
Linux reversing study_basic_4Linux reversing study_basic_4
Linux reversing study_basic_4
 
Linux reversing study_basic_3
Linux reversing study_basic_3Linux reversing study_basic_3
Linux reversing study_basic_3
 
Linux reversing study_basic_2
Linux reversing study_basic_2Linux reversing study_basic_2
Linux reversing study_basic_2
 
Linux reversing study_basic_1
Linux reversing study_basic_1Linux reversing study_basic_1
Linux reversing study_basic_1
 
Pwnable study basic_3
Pwnable study basic_3Pwnable study basic_3
Pwnable study basic_3
 
Pwnable study basic_2
Pwnable study basic_2Pwnable study basic_2
Pwnable study basic_2
 
Pwnable study basic_1
Pwnable study basic_1Pwnable study basic_1
Pwnable study basic_1
 
Python
PythonPython
Python
 
System+os study 7
System+os study 7System+os study 7
System+os study 7
 
System+os study 6
System+os study 6System+os study 6
System+os study 6
 
System+os study 5
System+os study 5System+os study 5
System+os study 5
 
System+os study 4
System+os study 4System+os study 4
System+os study 4
 
System+os study 3
System+os study 3System+os study 3
System+os study 3
 
System+os study 2
System+os study 2System+os study 2
System+os study 2
 
System+os study 1
System+os study 1System+os study 1
System+os study 1
 
Windows reversing study_basic_9
Windows reversing study_basic_9Windows reversing study_basic_9
Windows reversing study_basic_9
 
Windows reversing study_basic_8
Windows reversing study_basic_8Windows reversing study_basic_8
Windows reversing study_basic_8
 

Nouns DAO bughunting case study

  • 1. Code4rena Audit Report Analysis Nouns DAO 2023.07.08 J.J
  • 2. Nouns DAO Contest • Contest 요약 • https://code4rena.com/contests/2022-08-nouns-dao-contest • https://code4rena.com/reports/2022-08-nounsdao • 2022.08.23~2022.08.28 Code4rena • DAO 기능과 관련된 6개의 파일 분석 • DAO V2로 업그레이드 하기 위한 버그헌팅 • 포상 총액 $50,000 • 160명의 분석가 참여 • High 1개, Medium 3개, Low 3개, Non-Critical 12개, Gas 23개 • 2023.07.04~2023.07.14 DAO V3로 업그레이드하기 위한 버그헌팅 시작
  • 3. Nouns DAO • 간단한 서비스 구조 분석 • 매일 NFT(ERC721)가 새로 생성되고, 경매가 열린다. • 새로 생성된 NFT에 ETH로 입찰한다. • 낙찰되면 낙찰금은 전부 Treasury로 모인다. • NFT를 소유한 홀더는 투표권을 얻는다. • https://nouns.center/dev/nouns-protocol
  • 4. Nouns DAO • 간단한 서비스 구조 분석 • 낙찰금을 이용하여 DAO를 운영한다. • 홀더는 proposal을 생성할 수 있다. • 홀더들은 제시된 Proposal에 투표하여 결정한다. • Treasury에 모인 ETH를 어디에 사용할지 결정한다. • 주로 Nouns를 주제로 어떤 프로젝트를 진행하겠으니 투자금을 달라는 용도 • 기본적으로 Compound Governance Brovo를 베이스로 수정해서 구현 • Proposal이 생성되면 pending -> active -> succeeded/defeated -> queued ->executed 단계를 거쳐 진행된다. • https://nouns.center/funding/proposals
  • 5. Nouns DAO • 오딧팅 범위 로직 분석 • contracts/governance/NounsDAOLogicV2.sol • contracts/governance/NounsDAOLogicV1.sol • contracts/governance/NounsDAOInterfaces.sol • contracts/governance/NounsDAOProxy.sol • NounsDAOLogic에 붙여서 업그레이드 가능 • contracts/base/ERC721Checkpointable.sol • NFT가 상속하며, 투표권 할당, 위임 기능 • contracts/base/ERC721Enumerable.sol
  • 6. Nouns DAO • 오딧팅 범위 로직 분석 • contracts/governance/NounsDAOLogicV2.sol • Proposal 생성 • NFT의 N%(threshold) 만큼 소유한 유저만 proposal 생성 가능
  • 7. Nouns DAO • 오딧팅 범위 로직 분석 • contracts/governance/NounsDAOLogicV2.sol • Proposal 취소 • 자신이 생성한 proposal을 취소하거나, proposal 생성자가 더 이상 자격이 없는 경우 (threshold 불만족) 타 유저가 취소 가능
  • 8. Nouns DAO • 오딧팅 범위 로직 분석 • contracts/governance/NounsDAOLogicV2.sol • Proposal 거부 • admin이 proposal을 거부하는 기능
  • 9. Nouns DAO • 오딧팅 범위 로직 분석 • contracts/governance/NounsDAOLogicV2.sol • 투표하기 • Refundable은 투표에 사용된 gas를 일부 환급 받을 수 있는 버전 • Reason은 투표한 이유를 적어내어 이벤트로 생성할 수 있는 기능 • BySig는 서명으로 투표할 수 있는 기능
  • 10. Nouns DAO • 오딧팅 범위 로직 분석 • contracts/governance/NounsDAOLogicV2.sol • 투표하기
  • 11. Nouns DAO • 오딧팅 범위 로직 분석 • contracts/governance/NounsDAOLogicV2.sol • 큐에 넣기 • 투표가 성공한 경우 아무나 호출하여 queue에 호출할 작업을 넣을 수 있다.
  • 12. Nouns DAO • 오딧팅 범위 로직 분석 • contracts/governance/NounsDAOLogicV2.sol • 실행하기 • ETA가 지나면 아무나 호출하여 등록된 작업을 실행할 수 있다.
  • 13. Nouns DAO • 오딧팅 범위 로직 분석 • contracts/base/ERC721Checkpointable.sol • 투표권 mint/burn/transfer • 이전에 투표권을 위임했다면 위임자에게 투표권 mint/burn/transfer 처리
  • 14. Nouns DAO • 오딧팅 범위 로직 분석 • contracts/base/ERC721Checkpointable.sol • 투표권 부여하기 • 이동이 일어날 때마다 새로운 checkpoint에 투표권 수 기록
  • 15. Nouns DAO • 오딧팅 범위 로직 분석 • contracts/base/ERC721Checkpointable.sol • 투표권 위임하기 • BySig는 서명으로 위임하는 기능 • Delegatee가 address(0) 일 경우 위임 취소
  • 16. Nouns DAO • 오딧팅 범위 로직 분석 • contracts/base/ERC721Checkpointable.sol • 투표권 위임하기 • 개수 컨트롤 불가, 가지고 있는 NFT 개수 만큼의 투표권을 위임 또는 위임 취소
  • 17. Audit report 분석 • High/Medium 취약점 번호 취약점 요약 [H-01] ERC721Checkpointable: delegateBySig allows the user to vote to address 0, which causes the user to permanently lose his vote and cannot transfer his NFT. 파라미터가 값이 address(0) 인지 확인하 지 않아 문제가 발생했다. delegateBySig 를 통해 delegatee address(0) 에게 표를 위임한 경우 유저가 소유한 투표권이 전부 사라지며 유저는 더이상 NFT를 transfer 할 수 없게 된다. [M-01] Voters can burn large amounts of Ether by submitting votes with long reason strings 투표 시 가스비용을 일부 환급해준다. 함 수 파라미터로 긴 문자열을 넣으면 가스를 더 많이 소모하여 더 많은 ETH를 환급 받 을 수 있다. [M-02] User A cannot cancel User B’s proposal when User B’s prior number of votes at relevant block is same as proposal threshold, which contradicts the fact that User B actually cannot create the proposal when the prior number of votes is same as proposal threshold 숫자 비교 시 edge case 고려를 하지 않아 로직에 결함이 생겼다. [M-03] Loss of Veto Power can Lead to 51% Attack 실수로 vetoer를 잘못 설정하는 상황을 막 지 않는다. vetoer를 잃으면 51% 공격이 가능해지므로 중요한 문제이다.
  • 18. Audit report 분석 • [H-01] ERC721Checkpointable: delegateBySig allows the user to vote to address 0, which causes the user to permanently lose his vote and cannot transfer his NFT. • Summary • 파라미터가 값이 address(0) 인지 확인하지 않아 문제가 발생했다. • delegateBySig 를 통해 delegatee address(0) 에게 표를 위임한 경우 유저가 소유한 투표권이 전부 사라지며, 유저는 더이상 자신의 NFT를 transfer/burn 할 수 없게 된다. • Keyword • input validation, logic flaw, dao, delegate vote, checkpoint, dos • Vulnerability • 기본적인 투표권 위임 기능 함수인 delegate 는 delegate 파라미터로 address(0) 가 들어오는 경우 기존 투표권 위임을 취소하고 위임 했던 표를 되돌려 받는다. • 이와 유사하게 delegateBySig 함수는 유저의 서명을 받아 파라미터로 넣는, 유저가 직접 컨트랙트콜하지 않는 형식으로 투표권 위임을 할 수 있는 기능을 제공한다. • 이 때, delegate 함수와는 다르게 delegatee 파라미터가 address(0) 인지 확인하는 로직이 빠져 있다. • 즉, address(0) 를 delegatee 파라미터로 받은 경우 기존의 투표권 위임을 취소해야 하는데, 그게 아니라 address(0) 에게 위임하는 꼴이 된다. • 이 투표권은 사라지게 된다. 또한 투표권을 위임한 delegator는 더 이상 NFT를 transfer 하거나 burn할 수 없게 된다.
  • 19. Audit report 분석 • [H-01] ERC721Checkpointable: delegateBySig allows the user to vote to address 0, which causes the user to permanently lose his vote and cannot transfer his NFT. • Vulnerability • contracts/base/ERC721Checkpointable.sol#L126-L144
  • 20. Audit report 분석 • [H-01] ERC721Checkpointable: delegateBySig allows the user to vote to address 0, which causes the user to permanently lose his vote and cannot transfer his NFT. • Vulnerability • contracts/base/ERC721Checkpointable.sol#L197-L208 • contracts/base/ERC721Checkpointable.sol#L210-L230
  • 21. Audit report 분석 • [H-01] ERC721Checkpointable: delegateBySig allows the user to vote to address 0, which causes the user to permanently lose his vote and cannot transfer his NFT. • Vulnerability • contracts/base/ERC721Checkpointable.sol#L97-L106 • contracts/base/ERC721Checkpointable.sol#L88-L91
  • 22. Audit report 분석 • [H-01] ERC721Checkpointable: delegateBySig allows the user to vote to address 0, which causes the user to permanently lose his vote and cannot transfer his NFT. • Impact • 위임한 투표권이 사라지며, 투표권을 위임한 유저가 자신의 NFT를 transfer하거나 burn할 수 없게 된다. • Mitigation • 1. delegateBySig 에 require(delegatee != address(0)); 를 추가하여 막거나 • 2. delegate 함수와 동일하게 delegatee가 address(0)인 경우 투표권 위임을 취소하는 기능을 구현한다.
  • 23. Audit report 분석 • [M-01] Voters can burn large amounts of Ether by submitting votes with long reason strings • Summary • 투표 시 가스비용을 일부 환급해준다. • 함수 파라미터로 긴 문자열을 넣으면 가스를 더 많이 소모하여 더 많은 ETH를 환급 받을 수 있다. • 예치된 ETH를 고갈시킴으로써 다른 유권자들이 환급 받지 못하게 방해하는 등의 행동이 가능하다. • Keyword • gas refund, DoS, input validation • Vulnerability • 투표를 하면 투표에 사용된 가스비를 일부 환급해주는 기능이 있다. 이 때, reason 파라미터에 임의의 문자열을 넣어 이벤트에 찍을 수 있다. • reason 파라미터는 길이 제한이 없다. • 공격자가 임의로 긴 문자열을 넣을 수 있다. 긴 문자열은 짧은 것보다 많은 가스를 소비하고, 그리하면 더 많은 가스비를 환급 받을 수 있다. • 가스 환급은 컨트랙트에 예치된 ETH를 이용하므로 예치된 ETH를 기대보다 더 소비할 수 있게 된다. • 환급을 받더라도 파라미터를 넘기는 가스비를 전부 받아내지는 못하기때문에 공격을 통해 경제적으로 이득을 보지는 못 함 • 따라서 경제적 이득을 바라고 공격을 하지는 않을 것. 하지만 이러한 경제적 보호에만 의존하기보다는 이를 막는 것이 낫다.
  • 24. Audit report 분석 • [M-01] Voters can burn large amounts of Ether by submitting votes with long reason strings • Vulnerability • contracts/governance/NounsDAOLogicV2.sol#L518-L524 • contracts/governance/NounsDAOLogicV2.sol#L533-L544 • contracts/governance/NounsDAOLogicV2.sol#L98 /// @notice The vote refund gas overhead, including 7K for ETH transfer and 29K for general transaction overhead uint256 public constant REFUND_BASE_GAS = 36000; ... function castRefundableVoteWithReason( uint256 proposalId, uint8 support, string calldata reason ) external { castRefundableVoteInternal(proposalId, support, reason); } function castRefundableVoteInternal( uint256 proposalId, uint8 support, string memory reason ) internal { uint256 startGas = gasleft(); uint96 votes = castVoteInternal(msg.sender, proposalId, support); emit VoteCast(msg.sender, proposalId, support, votes, reason); if (votes > 0) { _refundGas(startGas); } } function _refundGas(uint256 startGas) internal { unchecked { uint256 balance = address(this).balance; if (balance == 0) { return; } uint256 gasPrice = min(tx.gasprice, block.basefee + MAX_REFUND_PRIORITY_FEE); uint256 gasUsed = startGas - gasleft() + REFUND_BASE_GAS; uint256 refundAmount = min(gasPrice * gasUsed, balance); (bool refundSent, ) = msg.sender.call{ value: refundAmount }(''); emit RefundableVote(msg.sender, refundAmount, refundSent); } }
  • 25. Audit report 분석 • [M-01] Voters can burn large amounts of Ether by submitting votes with long reason strings • Impact • 미래에 calldata 가격이 낮아질 가능성이 높다. calldata를 넣는 가격이 낮아진다면 공격자가 경제적으로 손해를 보는 부분이 완화되어 좀 더 공격하기 좋은 상황이 올 것이다. • 공격자가 임의의 텍스트를 이벤트로 쓰고자 하는 동기가 있을 수 있으며, 단순히 이를 위해 이 시스템을 악용할 수 있다. • 단순히 프로토콜의 명성을 손상시키기 위해 악의적으로 ETH를 고갈시킬 수 있다. 이를 통해 다른 유저가 가스비용을 환급 받지 못하게 할 수 있다. • Mitigation • 해당 문자열 파라미터를 사용하는 emit VoteCast 코드를 가스 환급 대상 코드에서 제외한다. (가스비를 먼저 환급해준 후 이벤트 생성) • 이벤트 생성에 대한 가스비를 환급하고 싶다면 REFUND_BASE_GAS 를 늘려 합리적인 양(적절한 reason 길이)까지만 환급해준다. • reason 의 타입을 bytes 로 변경하고 길이를 제한한다. • castRefundableVoteWithReason()에서 길이를 체크하고, 너무 긴 reason을 넣은 경우 revert 시킨다.
  • 26. Audit report 분석 • [M-02] User A cannot cancel User B’s proposal when User B’s prior number of votes at relevant block is same as proposal threshold, which contradicts the fact that User B actually cannot create the proposal when the prior number of votes is same as proposal threshold • Summary • 숫자 비교 시 edge case 고려를 하지 않아 로직에 결함이 생겼다. • Keyword • logic flaw, edge case • Vulnerability • Propose 함수로 새로운 proposal을 제시할 수 있다. • 새로운 Proposal을 생성하려면 전체 NFT의 n%만큼을 소유하고 있어야 한다. (proposalThreshold) • cancel 함수로 proposal을 제시한 자신이 제시한 proposal을 취소할 수 있다. 또한, 더이상 n%(proposal 생성 당시 기준) 소유를 만족할 수 없는 경우 다른 유저가 proposal을 취소시킬 수 있다. • n% 소유를 확인하는 로직에서, 부등호를 섞어 사용하며 edge case를 놓쳤다. 이로 인해 정확히 n% 와 숫자가 일치할 때 로직 오류가 발생한다.
  • 27. Audit report 분석 • [M-02] User A cannot cancel User B’s proposal when User B’s prior number of votes at relevant block is same as proposal threshold, which contradicts the fact that User B actually cannot create the proposal when the prior number of votes is same as proposal threshold • Vulnerability • contracts/governance/NounsDAOLogicV2.sol#L184-L279 • contracts/governance/NounsDAOLogicV2.sol#L346-L368
  • 28. Audit report 분석 • [M-02] User A cannot cancel User B’s proposal when User B’s prior number of votes at relevant block is same as proposal threshold, which contradicts the fact that User B actually cannot create the proposal when the prior number of votes is same as proposal threshold • Impact • 로직이 의도한 대로 동작하지 않는다. • proposal을 생성한 뒤, NFT를 옮겨 proposalThreshold 와 딱 맞춘다면 당시 proposal을 생성할 자격은 잃음에도 불구하고 타 유저로부터 cancel 당하지도 않는 상태가 된다. • Mitigation • propose나 cancel 함수 중 한쪽만 변경하여 양측의 조건이 일치하도록 한다. • 1. propose 함수에서 >= 로 비교하여 proposalThreshold 와 동일한 수의 NFT를 소유했을 때에도 proposal을 생성할 수 있도록 변경하거나 • 2. cancel 함수에서 <= 로 비교하여 proposalThreshold 와 동일한 수의 NFT를 소유했을 때에 cancel이 가능하도록 수정한다.
  • 29. Audit report 분석 • [M-03] Loss of Veto Power can Lead to 51% Attack • Summary • 실수로 vetoer를 잘못 설정하는 상황을 막지 않는다. • vetoer를 잃으면 51% 공격이 가능해지므로 실수를 방지할 수 있어야 한다. • Keyword • input validation, 51% attack, sybil attack, dao • Vulnerability • 악성 proposal에 대한 거부권을 행사할 수 있는 vetoer라는 역할이 있다. • 이는 initialize() 함수와 _setVetoer() 함수를 통해 설정할 수 있다. • initialize() 함수와 _setVetoer() 함수에는 새로운 vetoer 의 주소가 올바른지(address(0) 가 아닌지) 확인하는 로직이 없다. • vetoer 주소를 실수로 address(0) 로 설정한다면 vetoer가 공석이 되어 거부권을 행사할 수 없게 된다. • 이 때, 51% 공격을 하면 임의의 proposal을 성공시킬 수 있다. 이를 통해 treasury의 모든 토큰을 탈취할 수 있다는 시나리오가 가능하다.
  • 30. Audit report 분석 • [M-03] Loss of Veto Power can Lead to 51% Attack • Vulnerability • initialize() 함수 • NounsDAOLogicV1.sol#L150 • NounsDAOLogicV2.sol#L156 V1 V2
  • 31. Audit report 분석 • [M-03] Loss of Veto Power can Lead to 51% Attack • Vulnerability • _setVetoer() 함수 • 기존 vetoer가 다음 vetoer에게 권한을 넘길 수 있다. • NounsDAOLogicV1.sol#L637-L643 • NounsDAOLogicV2.sol#L839-L845 V2 V1
  • 32. Audit report 분석 • [M-03] Loss of Veto Power can Lead to 51% Attack • Impact • 실수로 vetoer를 잘못 설정할 수 있으며, vetoer를 잃으면 51% 공격이 가능해진다. • Mitigation • initialize() 함수와 _setVetoer() 함수에서 address(0) 체크를 한다. • 또한 _setVetoer() 함수로 vetoer를 설정할 시 2 step으로 설정하게 한다. • 실수로 소유하지 않은 주소를 vetoer로 설정하지 않도록, 기존 vetoer A가 컨트랙트콜 하여 새로운 vetoer B에게 approve 한 후, B 계정으로 컨트랙트콜 하여 vetoer 권한을 옮겨가는 방식이다. • 존재하지 않는 주소에 vetoer를 넘기는 상황이 발생하지 않게 된다 • Memo • 단순히 input validation이 부족하다 라는 취약점에 51% attack을 엮어서 심각도가 더 높아 보이게 한 점이 인상적
  • 33. Audit report 분석 • Low/Non-Critical 취약점 번호 취약점 요약 [L-01] Nouns will not be able to be transferred once the block.number passes type(uint32).max block number를 32비트로 잘라내 이용하는 데, 이러면 32비트 범위를 넘어가는 약 1260년 뒤에 정상 작동하지 않을 것이라 지 적했다. [L-02] Unused/empty receive()/fallback() function 용도가 없는 receive/fallback은 삭제하라 제 안했다. [L-03] Missing checks for address(0x0) when assigning values to address state variables address 변수에 값을 할당할 때 address(0) 인지 확인하지 않았음을 지적했다. [N-01] public functions not called by the contract should be declared external instead 동일 컨트랙트에서 해당 함수를 호출하지 않는다면 public 대신 external로 선언하여 가스를 절약할 수 있다. [N-02] Non-assembly method available 불필요하게 inline assembly를 사용하여 코 드 복잡도를 올리고 있음을 지적했다.
  • 34. Audit report 분석 • Low/Non-Critical 취약점 번호 취약점 요약 [N-03] 2**<n> - 1 should be re-written as type(uint<n>).max 2**<n> - 1 형식으로 최대값을 이용하는 대 신 type(uint<n>).max 코드로 대체하라 제안 했다. [N-04] constants should be defined rather than using magic numbers 상수 값을 직접 코드에 사용하는 것보다 constant 변수를 선언해 사용하는 쪽이 코드 의 readability에 좋다고 지적했다. [N-05] Use a more recent version of solidity 최신 컴파일러를 이용하여 abi.encodePacked(<str>,<str>) 대신 string.concat() 를 이용하라고 제안했다. [N-06] Expressions for constant values such as a call to keccak256(), should use immutable rather than constant expression, 계산된 값, constructor에서 전달 된 값 등은 constant 대신 immutable 변수에 저장해야 한다고 제안한다. [N-07] Constant redefined elsewhere 동일한 constant를 여러 컨트랙트에서 중복 하여 정의하면 추후 값이 바뀌어야 할 때 모 든 컨트랙트에서 수정하는 것을 까먹어 싱크 가 안 맞는 상황이 생길 수 있다. 따라서 constant는 한 군데에서만 정의하는 게 좋다 고 제안했다.
  • 35. Audit report 분석 • Low/Non-Critical 취약점 번호 취약점 요약 [N-08] Lines are too long 코드 한 라인에 글자가 너무 많다고 지적했 다. [N-09] Non-library/interface files should use fixed compiler versions, not floating ones 라이브러리나 인터페이스 소스코드가 아닌 코드에는 고정된 컴파일러 버전을 이용하는 것이 좋다. floating pragma를 이용해 원하는 것보다 더 높은 버전의 컴파일러를 이용하게 되면 의도하지 않은 일이 발생할 수 있다. [N-10] Event is missing indexed fields 이벤트 필드에 index를 걸지 않았음을 지적 했다. [N-11] Not using the named return variables anywhere in the function is confusing Named return으로 작성해 놓고 정작 이를 이용하지 않고 직접 리턴했다. 헷갈리므로 unnamed return으로 변경하자고 제안했다. [N-12] Typos 오탈자를 수정하라고 했다.
  • 36. Audit report 분석 • Gas optimizations 번호 취약점 요약 [G-01] State checks unnecessarily re-fetch Proposals 동일한 storage를 여러 번 로드하여 가스를 낭비한다 지적했다. 한 번 읽어온 storage 변 수를 쭉 활용하라고 제안했다. [G-02] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate 동일한 key를 이용하는 여러개의 mapping 변수는 struct을 이용하여 하나의 mapping 변수에 합치라고 제안했다. [G-03] Structs can be packed into fewer storage slots struct 구조가 최적화되어 있지 않음을 지적 했다. struct를 최적화하여 적은 slot을 사용 하고, 이로인해 gas를 절약하라 제안했다. [G-04] Using calldata instead of memory for read-only arguments in external functions saves gas calldata 로 선언해도 되는 array 변수를 memory로 선언하여 가스를 낭비한다고 지 적했다. 이를 calldata로 변경하라고 제안했 다. [G-05] Using storage instead of memory for structs/arrays saves gas struct/array의 전체 필드가 필요하지 않은 경 우 굳이 memory에 복사하지 않고 storage를 그냥 이용하는 쪽이 낫다. 여러번 사용될 필 드라면 그 필드만 stack variable에 캐싱하는 게 저렴하다.
  • 37. Audit report 분석 • Gas optimizations 번호 취약점 요약 [G-06] State variables should be cached in stack variables rather than re-reading them from storage state 변수를 매번 storage에서 읽는 것이 가 스 낭비적이라 지적했다. 지역 변수에 캐싱 하여 가스를 절약하라 제안했다. [G-07] Multiple accesses of a mapping/array should use a local variable cache 여러번 접근하는 mapping이나 array의 값의 경우 로컬 변수에 캐싱하라고 제안했다. [G-08] internal functions only called once can be inlined to save gas 한군데에서만 호출되는 internal 함수의 경우 함수를 쪼개지 말고, 그냥 코드를 합치는 게 가스 절약적이라고 제안했다. [G-09] Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if-statement 언더플로우/오버플로우가 나지 않을 상황이 라면 unchecked 를 넣어 가스비를 절약하자 고 제안했다. [G-10] <array>.length should not be looked up in every loop of a for-loop for 루프 조건문에 storage array의 <array>.length를 사용하는 것을 지적했다. 지역변수에 length를 저장한 뒤 이용하라 제 안한다.
  • 38. Audit report 분석 • Gas optimizations 번호 취약점 요약 [G-11] ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops 오버플로우 날 가능성이 없는 반복문 인덱스 연산은 unchecked 하여 가스를 줄일 수 있다. [G-12] require()/revert() strings longer than 32 bytes cost extra gas 32바이트보다 긴 에러메시지는 추가 gas를 소모하므로 짧은 에러 문구를 이용하라고 하 였다. [G-13] Optimize names to save gas 함수의 이름을 최적화하면 함수 콜 시 함수 를 더 빨리 찾을 수 있다. 이를 통해 자주 사 용하는 함수의 가스를 줄이라 제안했다. [G-14] Use a more recent version of solidity solidity 버전을 올려 컴파일러가 지원하는 여러가지 최적화와 기능을 이용하라고 제안 했다. [G-15] ++i costs less gas than i++, especially when it’s used in for-loops (--i/i-- too) i++, i-- 보다 ++i, --i 가 gas 효율적이다. 특 히 반복문에서 주의해야 한다.
  • 39. Audit report 분석 • Gas optimizations 번호 취약점 요약 [G-16] Splitting require() statements that use && saves gas 가능한 경우 require 문의 && 연산을 쪼개서 require문을 분리하면 런타임에 gas를 절약 할 수 있다. [G-17] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead 메모리에서 워드보다 작은 크기의 자료형을 이용하는 경우 이점은 없고 gas만 더 나가므 로, 가능하면 워드 크기의 자료형을 이용하 라고 제안했다. [G-18] Using private rather than public for constants, saves gas constant 변수의 getter를 굳이 사용하지 않 는다면 private으로 선언해도 괜찮다. private 으로 변경 시 배포시 gas를 절약할 수 있다. [G-19] Don’t compare boolean expressions to boolean literals if (<x> == true) 나 if (<x> == false) 대신 if (<x>) , if (!<x>) 를 사용해야 한다. [G-20] Division by two should use bit shifting 2의 배수로 나누기 하는 것은 / 연산자보다 shift를 이용하는 것이 저렴하다.
  • 40. Audit report 분석 • Gas optimizations 번호 취약점 요약 [G-21] require() or revert() statements that check input arguments should be at the top of the function require문, 특히 constant 를 이용하여 input validation을 체크하는 require문은 가장 먼 저 하는 게 좋다. storage 변수를 불러와서 비교하는 등의 더 큰 작업을 하기 전에 체크하면 실패시에도 gas를 절약할 수 있기 때문이다. [G-22] Empty blocks should be removed or emit something 용도가 없는 receive 함수는 삭제하여 배포 시 gas를 절약하자 제안했다. 아니면 최소한 이벤트라도 생성하라고 했다. [G-23] Use custom errors rather than revert()/require() strings to save gas solidity 0.8.4부터 사용 가능한 커스텀 에러 를 사용하면 ~50 gas만큼 줄일 수 있다. revert 이유 문자열을 할당하는 작업을 피할 수 있기 때문이다.
  • 41. Audit report 분석 • [G-11] ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops • Summary • 오버플로우 날 가능성이 없는 반복문 인덱스 연산은 unchecked 하여 가스를 줄일 수 있다 제안했다. • Keyword • gas optimization, safemath, unchecked • Not optimized • 일반적으로 반복문은 for (uint256 i = 0; i < length; i++) 와 같이 이용된다. • length의 타입이 동일하거나 i보다 작다면 i는 length보다 항상 작다. 따라서 대부분의 상황에서 i++ 나 ++i 연산 시 오버플로우가 발생하지 않는다. • solidity 0.8.0 부터는 기본적으로 연산에 SafeMath가 적용된다. 이는 30~40 gas를 추가로 소모한다. • 이는 unchecked를 이용하여 해제할 수 있다.
  • 42. Audit report 분석 • [G-11] ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops • Not optimized • contracts/governance/NounsDAOLogicV1.sol#L281 • contracts/governance/NounsDAOLogicV2.sol#L292
  • 43. Audit report 분석 • [G-11] ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops • Impact • for 루프를 돌 때마다 SafeMath를 체크하느라 30~40 gas가 낭비된다. • Mitigation • unchecked 를 이용하여 인덱스를 업데이트 한다. • 다음과 같이 unchecked로 인덱스를 업데이트 하면 가스를 절약할 수 있다. • 아직 루프문에 바로 unchecked 문을 사용할 수 없어 함수로 빼거나(이 또한 가스 낭비) 다음과 같이 반복문 내에서 처리한다. for (uint i ; i < length;) { // do something that doesn't change the value of I … unchecked{ ++i; } }
  • 44. Audit report 분석 • [G-13] Optimize names to save gas • Summary • 함수의 이름을 최적화하면 함수 콜 시 함수를 더 빨리 찾을 수 있다. 이를 통해 자주 사용하는 함수의 가스를 줄이라 제안했다. • Keyword • gas optimization, method id • Not optimized • 각 함수는 methodId를 기준으로 소팅되고, 함수 콜을 할 시 낮은 methodId부터 차례대로 함수를 찾는다. • 순서대로 호출한 methodId와 비교 –> JUMP로 이동 • 즉, methodId가 낮을수록 더 빨리 찾아지고, 순서가 하나 밀릴수록 22 gas를 추가로 소비한다. • 가장 자주 호출되는 public/external 함수의 methodId가 낮도록 하면 이를 호출하는 gas를 절약할 수 있다. • 어떻게 methodId를 조작하는가? methodId는 기본적으로 함수 이름과 파라미터 타입을 해시하여 생성된다. 즉, 함수 이름을 bruteforce하여 낮은 methodId를 찾아볼 수 있다. • 특히나 methodId에 0이 포함되면 가스가 더 절약된다. input 데이터의 바이트가 0이면 4 gas를, 0이 아니면 68 gas를 소모한다. 위 툴로 methodId의 앞 두 바이트가 0x00 인 함수 이름을 찾아내면, 함수 호출 시 전체 바이트가 0x00이 아닌 경우보다 128 gas를 절약할 수 있다. • https://medium.com/joyso/solidity-how-does-function-name-affect-gas-consumption-in-smart-contract-47d270d8ac92 참고
  • 45. Audit report 분석 • [G-13] Optimize names to save gas • Impact • 자주 사용되는 함수의 순서가 밀려 gas가 나간다. • Mitigation • 자주 사용되는 함수의 methodId를 낮게 하여 빠르게 찾을 수 있게 한다. • https://emn178.github.io/solidity-optimize-name/ 에서 함수 이름 최적화를 할 수 있다.