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/ 에서 함수 이름 최적화를 할 수 있다.