2. Angle Protocol Contest
• Contest 요약
• 컨테스트: https://code4rena.com/contests/2023-06-angle-protocol-invitational
• 결과 보고서: https://code4rena.com/reports/2023-06-angle
• 코드: https://github.com/code-423n4/2023-06-angle
• 2023.06.29~2023.07.08 Code4rena
• 프라이빗 오딧으로 진행, 하지만 보고서와 코드는 공개되었음.
• 수정 후 Mitigation 평가도 받음.
• 포상 총액 $52,500
• 5명의 분석가 참여
• High 3개, Medium 7개
3. Angle Protocol
• 오딧팅 범위 로직 분석
• 크게 transmuter와 merkl 서비스로 구성
• Transmuter: 스테이블 코인(agToken)의 가격 유지
• Transmuter
• Diamond(Multi-Facet Proxy) 구조로 구현.
• 담보를 맡기고 agToken을 얻거나 agToken을 주고 담보를 찾음. agToken과 담보 토큰으로 swap하는 기능 제공.
• 담보 관리와 agToken 관리. 담보 비율 관리.
• Saving
• agToken을 예치하고 이자를 벌 수 있는 풀.
• Transmuter에 과담보 또는 저담보 시 agToken를 mint 또는 burn하여 가격을 일정하게 유지.
• agToken mint시 Saving 컨트랙트에 발행하여 이자로 사용한다.
• Merkl: Uniswap V3같은 LP에 유동성을 제공하는 것을 장려하는 사이드 프로젝트
• 기존 Angle protocol 시스템(stablecoin 관련)과는 별개의 사이드 프로젝트
• LP에 유동성을 제공하는 것을 장려하는 사람들(인센티브 제공자)이 유동성 제공자들에게 토큰 보상을 줄 수 있는 플랫폼
• 유동성 제공자에게 추가적으로 보상을 제공하여 더 나은 유동성을 얻고 싶다.
• 머클 트리, 머클 proof를 이용하여 리워드 여부와 양 관리
4. Angle Protocol
• 오딧팅 범위 로직 분석
• Transmuter.Transmuter
• Angle stablecoin의 메인 민팅 메커니즘
• Swap, Redeemption을 통해 agToken을 mint 또는 burn함
출처: https://docs.angle.money/transmuter/transmuter
5. Angle Protocol
• 오딧팅 범위 로직 분석
• Transmuter.Transmuter
• Diamond(Multi-Facet Proxy) 구조
• Facets 디렉토리 내의 컨트랙트들은 로직 컨트랙트
• 이들을 모두 상속한 하나의 거대한 컨트랙트라고 생각하면 됨
• 추후 로직을 추가해 확장하는 등 가능. 코드 크기 제한을 피해 무한히 확장 가능
출처: https://www.info.diamonds/diamondPattern
6. Angle Protocol
• 오딧팅 범위 로직 분석
• Transmuter.Transmuter
• Facets.Redeemer
• agToken을 담보 토큰들로 변환
• Burn할 agToken amount / 전체 agToken * 담보 토큰 양 * 패널티
• 과담보 상태일 시 패널티를 높여 더 많은 담보 토큰을 돌려받는 것을 막는다. (스테이블 코인이므로 가치를 일정하게 유지하는 게 목표)
• 저담보 상태일 시 패널티가 높아지다가, 프로토콜이 돌이킬 수 없을 정도로 나쁜 상태가 되면 모든 사람이 공정하게 탈출할 수 있도록 패널티가 1로 돌아
온다.
출처: https://docs.angle.money/transmuter/redeem
7. Angle Protocol
• 오딧팅 범위 로직 분석
• Transmuter.Transmuter
• Facets.Redeemer
• redeemWithForfeit는 특정 담보 토큰은 받고 싶지 않을 때 사용
• agToken을 burn하고 담보 토큰을 돌려줌
8. Angle Protocol
• 오딧팅 범위 로직 분석
• Transmuter.Transmuter
• Facets.Redeemer
• 돌려줄 담보 토큰을 계산하는 로직
• Penalty를 적용해 거래를 조절한다.
9. Angle Protocol
• 오딧팅 범위 로직 분석
• Transmuter.Transmuter
• Facets.Swapper
• 담보 토큰을 agToken으로 swap 또는 agToken을 담보 토큰으로 swap
• agToken는 mint 또는 burn 됨
• 담보 토큰의 비율이 높아질수록 mint fee가 높아짐. 비율이 낮아질수록 burn fee가 높아짐.
출처: https://docs.angle.money/transmuter/mintburn
10. Angle Protocol
• 오딧팅 범위 로직 분석
• Transmuter.Transmuter
• Facets.Swapper
• 담보 토큰을 agToken으로 swap 또는 agToken을 담보 토큰으로 swap
• Fee를 제한 만큼 받을 수 있음
11. Angle Protocol
• 오딧팅 범위 로직 분석
• Transmuter.Transmuter
• Facets.Swapper
• agToken는 mint 또는 burn 됨
12. Angle Protocol
• 오딧팅 범위 로직 분석
• Transmuter.Savings
• agToken을 예치하면 이자를 받을 수 있는 Vault
• ERC4626(Tokenized Vault) 기반
• Asset 토큰을 예치하면 이자가 발생. Asset 토큰 예치 시 ERC20를 발행하고, 이를 다시 돌려주며 burn하면 Asset 토큰을 돌려받는다. 이때 이자가 붙어서
나온다.
• 대체로 withdraw(redeem)시 (교환할 ERC20 수 / 전체 ERC20 수) * 예치된 전체 Asset 수 로 비율로 계산해 분배
• 커스텀 로직이 있다면 override하여 추가
• Savings와 SavingsVest 두 종류가 있음
• 예시로? 커스텀 가능함을 보여주기 위함?
• SavingsVest를 살펴보자
13. Angle Protocol
• 오딧팅 범위 로직 분석
• Transmuter.Savings
• SavingsVest
• 관리자가 주기적으로 accrue를 실행해 이자를 발행 또는 소각해야 함
• 과담보 상태일 시 agToken을 SavingsVest에 mint
• 이는 이자로 사용됨
• 저담보 상태일 시 SavingsVest에 예치된 agToken을 burn
• 이자로 발행되었던 agToken 내에서만 burn하여 저담보 상태를 조절
• 이자로 발행된 토큰은 일정 기간 lock 된다.
• lockedProfit에서 이를 계산
14. Angle Protocol
• 오딧팅 범위 로직 분석
• Transmuter.Savings
• SavingsVest
• 이자로 발행된 토큰은 일정 기간 lock 되어 꺼낼 수 없다. (저담보 시 burn하기 위한 버퍼)
• totalAssets를 override하여 이를 구현
• withdraw/redeem시 totalAssets에서 lock된 토큰을 빼고 계산
• Lock은 시간이 지남에 따라 서서히 풀리는 구조
15. Angle Protocol
• 오딧팅 범위 로직 분석
• Merkl
• Uniswap 등에 유동성을 제공했을 때 추가적인 리워드를 주는 실험적인 프로젝트
• 이들에 유동성을 제공하는 것을 장려하기 위함
• LP 토큰을 예치하지 않아도 리워드를 받을 수 있음
• 머클 트리를 이용해 자격 확인
출처: https://docs.angle.money/side-products/merkl
16. Angle Protocol
• 오딧팅 범위 로직 분석
• Merkl
• Distributor
• 머클 트리를 이용해 리워드를 분배하는 컨트랙트
• 트리가 업데이트 되면 이의 제기 기간이 있다. 이동안은 새 트리를 사용하지 않고 이전 트리를 사용한다.
17. Angle Protocol
• 오딧팅 범위 로직 분석
• Merkl
• Distributor
• 머클 트리 업데이트
• 관리자만 업데이트 할 수 있다.
18. Angle Protocol
• 오딧팅 범위 로직 분석
• Merkl
• Distributor
• 악성 행위가 발생했을 때, 리워드 정보(머클 트리)에 동의하지 못할 때 유저는 이의를 제기할 수 있다.
• 이의 제기 가능 기간 내에만 가능하다.
• 이의 제기를 위해서는 disputeToken을 담보로 제출해야 한다. 이의를 인정하면 돌려받고, 아니면 관리자가 가져간다.
• 이의를 제기하면 해결될 때까지 머클 트리 업데이트가 중지된다.
19. Audit report 분석
• High 취약점
번호 취약점 요약
[H-01] Possible reentrancy during redemption/swap 담보 토큰이 ERC777인 경우 redeem/swap
으로 담보 토큰을 받을 시 tokensReceived
훅을 통해 재진입이 가능하고, 이를 이용
해 담보 해제를 재요청하면 실제보다 더
많은 담보 토큰을 돌려받을 수 있다.
[H-02] The first disputer might lose funds although his dispute is valid 이의 제기를 덮어쓰면 먼저 이의를 제기한
유저가 토큰을 돌려받지 못한다.
[H-03] Poor detection of disputed trees allows claiming tokens from a disputed tree claim에서 이의를 제기한 상태인지 확인하
지 않으므로, 빠르게 대처하지 않으면 논
란이 있는 머클 트리를 기반으로 claim이
가능하다.
20. Audit report 분석
• [H-01] Possible reentrancy during redemption/swap
• Summary
• 담보 토큰이 ERC777인 경우 redeem/swap으로 담보 토큰을 받을 시 tokensReceived 훅을 통해 재진입이 가능
• 재진입을 통해 담보 해제를 요청하면 실제보다 더 많은 담보 토큰을 돌려받을 수 있다.
• Keyword
• erc777, reentrancy, theft, stablecoin, defi
• Vulnerability
• 다수의 담보 토큰이 있고, 담보 해제를 통해 받은 토큰이 ERC777인 경우 tokensReceived 훅으로 재진입이 가능하다.
• 다른 담보 토큰은 아직 전송하지 않은 상태이다. 따라서 컨트랙트에 예치된 balance가 원래보다 높다.
• 돌려받을 담보 토큰의 수는 컨트랙트에 예치된 balanceOf를 기반으로 계산되므로, 재진입을 통해 다시 담보를 해제하면 받아야 하는
양보다 많은 양의 담보 토큰을 받아낼 수 있다.
21. Audit report 분석
• [H-01] Possible reentrancy during redemption/swap
• Vulnerability
• 담보 토큰을 맡기면 agToken을 얻을 수 있고, redeem 기능을 이용하면 agToken을 burn하며 담보 토큰을 되돌려 받을 수 있다.
• redeem을 호출하는 엔트리포인트는 여러 개 있지만, 모두 내부적으로 _redeem를 호출
• 돌려받을 담보 토큰의 수는 _quoteRedemptionCurve 함수를 통해 계산
22. Audit report 분석
• [H-01] Possible reentrancy during redemption/swap
• Vulnerability
• 돌려받을 담보 토큰의 수는 _quoteRedemptionCurve 함수를 통해 계산
• balances[i]는 컨트랙트에 예치된 담보 토큰 수
• 컨트랙트에 예치된 담보 토큰의 수에 비례하여 담보 토큰을 돌려받는다.
23. Audit report 분석
• [H-01] Possible reentrancy during redemption/swap
• Vulnerability
• 담보 토큰이 ERC777라면?
• 토큰 수신자 측의 tokensReceived 훅이 실행될 것
• 재진입 가드가 없으므로 재진입 가능
• 담보 토큰이 여러 개일 때, 재진입시 나머지 토큰은 아직 전송되지 않은 상태
• balance가 원래보다 높은 상태
24. Audit report 분석
• [H-01] Possible reentrancy during redemption/swap
• Vulnerability
• 공격 시나리오
• 2개의 담보 colA와 colB 토큰을 사용한다고 가정하자. Transmuter 컨트랙트는 colA와 colB토큰을 각각 1000개씩 가지고 있다. Alice는 20개의 agToken을
가지고 있다.
• Alice가 10개의 agToken을 주며 redeem을 호출하면 10개의 colA와 colB를 받아야 한다.
• colA 토큰은 ERC777이라고 가정하자. colA 토큰을 먼저 받고, tokensReceived 훅을 이용하여 redeem(10)를 재호출한다.
• 2번째 redeem에서, 총 담보 양은 colA = 990, colB = 1000인 상태이다. colB가 아직 첫번째 redeem에서 보내지지 않았기 때문이다.
• Alice는 2번째 redeem에서 원래 받아야 하는 양보다 더 많은 담보를 상환 받는다.
25. Audit report 분석
• [H-01] Possible reentrancy during redemption/swap
• Impact
• 원래보다 많은 양의 담보 토큰을 상환 받음
• Mitigation
• _redeem과 _swap 함수에 재진입 가드를 추가
26. Audit report 분석
• [H-02] The first disputer might lose funds although his dispute is valid
• Summary
• 이의 제기를 덮어쓰면 먼저 이의를 제기한 유저가 토큰을 돌려받지 못한다.
• Keyword
• logic flaw, frontrunning, token loss, griefing attack
• Vulnerability
• disputeTree 함수를 호출하여 이의를 제기할 수 있고, 이동안 머클 트리의 업데이트를 정지할 수 있다.
• 이의 제기자는 disputeToken을 맡겨야 한다.
• 이를 해제하기 위해서는 governor 또는 guardian 계정으로 resolveDispute를 호출해야 한다.
• 이의가 유효한 경우 disputer에게 disputeToken을 돌려주고 머클 트리를 이전 상태로 되돌린다.
• 유효하지 않은 경우 disputeToken은 관리자가 가져간다.
• disputeTree 에서는 이미 누군가 이의를 제기했는지 확인하지 않는다.
• 중복으로 호출 시 이의 제기자 주소가 덮어씌워진다.
• 이의가 유효한 경우 토큰을 돌려받아야 하는데, 덮어써진 이전 유저는 토큰을 돌려받지 못한다.
27. Audit report 분석
• [H-02] The first disputer might lose funds although his dispute is valid
• Vulnerability
• disputeTree 함수를 호출하여 이의를 제기할 수 있고, 이동안 머클 트리의 업데이트를 정지할 수 있다.
• 이의를 제기할 수 있는 시간 내인지만 확인한다. 누군가 이미 이의를 제기했는지는 확인하지 않는다.
28. Audit report 분석
• [H-02] The first disputer might lose funds although his dispute is valid
• Vulnerability
• 이의가 유효할 경우 resolveDispute에서 토큰을 돌려준다.
• 이때, 덮어써진 disputer는 토큰을 받지 못한다.
• 최악의 경우 frontrun에 의해 disputeToken을 돌려받지 못할 수도 있다.
• resolveDispute로 이의를 인정하는 콜을 했을 때 frontrun으로 disputeTree를 호출하면 기존 이의 제기자는 토큰을 환급 받지 못한다.
29. Audit report 분석
• [H-02] The first disputer might lose funds although his dispute is valid
• Impact
• 유효한 이의를 제기한 유저가 토큰을 돌려받지 못한다.
• Mitigation
• 기존에 이의를 제기한 유저가 있다면 더이상 이의를 제기할 수 없도록 막는다.
• Memo
• High냐 Medium이냐 논쟁이 있었음.
• 관리자가 recoverERC20 함수를 호출하여 직접 받을 주소와 양을 파라미터로 넘기면 토큰을 빼낼 수 있음
• 토큰이 완전히 묶이는 상황은 아니었음
• 하지만 이는 관리자의 추가적인 작업이 필요하며, 의도적인 방해 시도를 막지 못하므로 High과 Medium의 사이라고 판단했고, 최종적
으로 High
30. Audit report 분석
• [H-03] Poor detection of disputed trees allows claiming tokens from a disputed tree
• Summary
• claim에서 이의를 제기한 상태인지 확인하지 않으므로, 빠르게 대처하지 않으면 논란이 있는 머클 트리를 기반으로 claim이 가능하다.
• Keyword
• logic flaw
• Vulnerability
• claim 함수를 호출하면 보상을 얻을 수 있다. 머클 트리에서 proof를 확인하고 유효하다면 보상을 준다.
• 이의 제기가 가능 기간을 넘었다면 현재 머클 트리가 안정적이라 판단해 현재 머클 루트를 사용하고, 그렇지 않다면 직전의 머클 트리 루트를 사용한다.
• 단, 현재 머클 트리에 이의를 제기하여 분쟁 중인지는 확인하지 않는다.
• 시간이 지나 이의제기 가능 시점이 지나면 분쟁이 해결되지 않았음에도 현재 머클 트리를 기반으로 보상을 요청할 수 있다.
31. Audit report 분석
• [H-03] Poor detection of disputed trees allows claiming tokens from a disputed tree
• Vulnerability
• Claim시 proof를 확인하기 위해 getMerkleRoot로 머클 루트를 가져온다.
• 이의 제기 가능 기간을 지나면 현재 트리를, 아니면 이전 트리를 이용한다. 분쟁 중인지는 확인하지 않았다.
• 악성 행위가 발생하고 유저가 이에 빠르게 이의 제기를 했어도, governor/guardian이 아직 이의를 처리하지 않은 경우 시간만 지나면 claim이 가능하다.
32. Audit report 분석
• [H-03] Poor detection of disputed trees allows claiming tokens from a disputed tree
• Impact
• 이의를 제기했음에도 빠르게 대처하지 않으면 논란이 있는 머클 트리를 기반으로 claim이 가능하다.
• Mitigation
• 이의가 제기되었다면 이것이 해결될 때까지 과거 머클 트리를 이용한다.
diff --git a/contracts/Distributor.sol b/contracts/Distributor.sol
index bc4e49f..8fb6a4c 100644
--- a/contracts/Distributor.sol
+++ b/contracts/Distributor.sol
@@ -197,7 +197,7 @@ contract Distributor is UUPSHelper {
/// @notice Returns the MerkleRoot that is currently live for the contract
function getMerkleRoot() public view returns (bytes32) {
- if (block.timestamp >= endOfDisputePeriod) return tree.merkleRoot;
+ if (block.timestamp >= endOfDisputePeriod && disputer == address(0)) return tree.merkleRoot;
else return lastTree.merkleRoot;
}
33. Audit report 분석
• Medium 취약점
번호 취약점 요약
[M-01] LibHelpers.piecewiseLinear will revert when the value is less than the first
element of the array
예외상황 처리가 부족하여 언더플로우가
발생, redeem 이 실패할 수 있다.
[M-02] Unsafe cast in getCollateralRatio() 엣지케이스에서 캐스팅으로 인해
collatRatio 값이 절삭되어 잘못된 비율을
기반으로 계산하게 된다.
[M-03] Read-only reentrancy is possible 재진입으로 인해 담보가 과도하게 잡힌 것
으로 판단하게 하여 이자를 발행할 수 있
다.
[M-04] estimatedAPR() might return the wrong APR APR을 잘못 계산하여 유저가 잘못된 정보
를 얻게 된다.
34. Audit report 분석
• Medium 취약점
번호 취약점 요약
[M-05] uint128 changeAmount might overflow 특정 조건에 changeAmount가 절삭되어
SafeCast에 의해 revert 되고, 이로 인해
swap이 실패한다.
[M-06] Interest is not accrued before parameters are updated in SavingsVest 이자 발행의 조건이 되었을 때, 이자를 발
행하기 전에(accrue 호출 전에) setParams
함수를 호출한다면 변경된 변수를 기반으
로 이자가 계산된다. 따라서 홀더가 잘못
계산된 이자를 받는다. 변경 전에 먼저 과
거의 이자를 정산해야 한다는 의미.
[M-07] User may get less tokens than expected when collateral list order changes 담보 토큰에 변경이 생겨도 기존 파라미터
로 redeem 콜이 조용히 정상 실행된다. 이
로 인해 유저가 원하는 최소 양보다 적은
양의 토큰을 받으면 콜이 취소되어야 하는
데, 취소되지 않는다.
35. Audit report 분석
• [M-01] LibHelpers.piecewiseLinear will revert when the value is less than the first
element of the array
• Summary
• 예외상황 처리가 부족하여 언더플로우가 발생, redeem이 실패할 수 있다.
• Keyword
• integer underflow
• Vulnerability
• redeem 호출 시 담보가 부족한 경우 패널티를 주는데, 이 패널티를 계산하는 함수에서 언더플로우가 발생했다.
• 이로 인해 revert가 되고, redeem이 실패한다.
36. Audit report 분석
• [M-01] LibHelpers.piecewiseLinear will revert when the value is less than the first
element of the array
• Vulnerability
• findLowerBound(true, xArray, 1, x)는 정렬된 xArray 배열에서 x보다 값이 큰 첫번째 인덱스를 리턴한다.
• x가 xArray의 모든 값보다 작다면 findLowerBound는 0을 리턴한다.
• x - xArray[indexLowerBound]는 즉 x – xArray[0]이고, x < xArray[0]이므로 언더플로우가 발생해 revert 된다.
37. Audit report 분석
• [M-01] LibHelpers.piecewiseLinear will revert when the value is less than the first
element of the array
• Vulnerability
• piecewiseLiner는 어디에 사용되는 함수인가?
• Redeem을 할 때 토큰 수를 계산할 때 사용된다. 담보가 부족한 경우 패널티를 주는데, 이 패널티를 계산할 때 사용한다.
• piecewiseLiner에서 언더플로우가 발생하면 redee이 실패할 것
38. Audit report 분석
• [M-01] LibHelpers.piecewiseLinear will revert when the value is less than the first
element of the array
• Vulnerability
• x(=collatRatio)가 xRedemptionCurveMem(=xArray)의 모든 값보다 작은 상황이 발생할 수 있는가?
• 일단 collatRatio < 1e9 이다.
• ts.xRedemptionCurve 값의 범위를 찾아보자
39. Audit report 분석
• [M-01] LibHelpers.piecewiseLinear will revert when the value is less than the first
element of the array
• Vulnerability
• ts.xRedemptionCurve 값의 범위를 찾아보자
• 상한이 1e9 외에 특별히 없다.
• setRedemptionCurveParams는 관리자만 호출 가능
• 즉, collatRatio와 xRedemptionCurve 값들은 1e9보다 작은 임의의 수
• xRedemptionCurve는 관리자로부터 셋팅되므로 내부 상태로 인해 변하지도 않음
• collatRatio < xRedemptionCurve 모든 값이 불가할 이유는 없다!
40. Audit report 분석
• [M-01] LibHelpers.piecewiseLinear will revert when the value is less than the first
element of the array
• Impact
• 언더플로우로 인해 redeem 이 실패한다.
• Mitigation
• piecewiseLinear에서 x가 xArray의 모든 값보다 작은 경우를 따로 핸들링하여 언더플로우를 피한다.
function piecewiseLinear(uint64 x, uint64[] memory xArray, int64[] memory yArray) internal pure returns
(int64) {
uint256 indexLowerBound = findLowerBound(true, xArray, 1, x);
- if (indexLowerBound == xArray.length - 1) return yArray[xArray.length - 1];
+ if (indexLowerBound == 0 && x < xArray[0]) return yArray[0];
+ else if (indexLowerBound == xArray.length - 1) return yArray[xArray.length - 1];
return
yArray[indexLowerBound] +
((yArray[indexLowerBound + 1] - yArray[indexLowerBound]) * int64(x - xArray[indexLowerBound])) /
int64(xArray[indexLowerBound + 1] - xArray[indexLowerBound]);
}
41. Audit report 분석
• [M-02] Unsafe cast in getCollateralRatio()
• Summary
• 엣지케이스에서 캐스팅으로 인해 collatRatio 값이 절삭되어 잘못된 비율을 기반으로 계산하게 된다.
• Keyword
• overflow/underflow, casting
• Vulnerability
• collatRatio 값을 uint256값을 이용해 계산한 뒤 uint64로 캐스팅해 사용했다.
• 프로젝트 측은 비현실적인 케이스라고 심각도를 낮추려 했다.
• 심판 측은 그럼에도 이 값이 프로토콜에서 중요한 역할을 하므로 Medium으로 쳤다.
42. Audit report 분석
• [M-02] Unsafe cast in getCollateralRatio()
• Vulnerability
• getCollateralRatio 함수에서 collatRatio를 계산한다.
• 일반적으로 collatRatio 는 BASE_9 근처 값이어야 하지만, 초기에는 type(uint64).max보다 클 수도 있다.
• totalCollateralization은 담보 토큰 가치 합(오라클을 이용해 계산한 가치), stablecoinsIssued는 발행된 agToken의 수
• collatRatio = totalCollateralization * 1e9 / stablecoinsIssued
• stablecoinsIssued가 작다면 조작 가능성이 있다 (stablecoinsIssued가 1 wei면 1 USD로 충분하다)
• collatRatio = 1 * 1e18 (=1 USD) * 1e9 / 1 wei = 1e27 이고, 1e27 > 2^64 이므로 절삭된다.
43. Audit report 분석
• [M-02] Unsafe cast in getCollateralRatio()
• Impact
• 캐스팅으로 인해 collatRatio 값이 절삭되어 잘못된 비율을 기반으로 계산하게 된다.
• Mitigation
• SafeCast 라이브러리를 사용하여 캐스팅으로 인해 절삭된 상황에서 거래하는 것을 방지한다.
44. Audit report 분석
• [M-03] Read-only reentrancy is possible
• Summary
• 재진입으로 인해 담보가 과도하게 잡힌 것으로 판단하게 하여 이자를 발행할 수 있다.
• Keyword
• reentrancy, stablecoin
• Vulnerability
• 재진입으로 인해 view 함수의 값이 깨진다.
• ERC777 훅을 이용해 재진입, 다른 담보 토큰을 받지 않아 풀 상태와 실제 담보 balance 불일치
• 과담보 상태로 판단해 agToken를 mint하게 할 수 있다.
• 토큰 스테이킹에 이자 발행
45. Audit report 분석
• [M-03] Read-only reentrancy is possible
• Vulnerability
• 다음과 같은 공격 시나리오가 가능하다.
• 1. collatRatio가 BASE_9(100%) 라고 가정하자. Alice가 담보 토큰을 agToken으로 swap하려고 한다.
• 2. _swap 함수에서, 담보 토큰을 먼저 입금하고 agToken을 mint 한다. 담보 토큰이 ERC777인 경우 훅이 실행되고 재진입이 가능하다.
46. Audit report 분석
• [M-03] Read-only reentrancy is possible
• Vulnerability
• 다음과 같은 공격 시나리오가 가능하다.
• 3. Alice가 훅을 통해 SavingsVest.accrue를 호출한다.
• 과도하게 담보가 존재하는 경우 agToken을 민팅하여 스테이킹 이자로 사용하는 함수이다.
• 이 때, _transmuter.getCollateralRatio()는 잘못된 비율을 리턴할 것이다. 실제보다 큰 collatRatio를 리턴한다
• agToken이 아직 mint되지 않았고 담보 토큰의 양은 늘어났기 때문에, totalCollateralization는 커지고 stablecoinsIssued는 그대로
47. Audit report 분석
• [M-03] Read-only reentrancy is possible
• Vulnerability
• 다음과 같은 공격 시나리오가 가능하다.
• 4. collatRatio > BASE_9 + BASE_6 조건을 만족시키면(=100.1% 로 0.1% 이상 과담보 되면) 이자가 발행된다.
48. Audit report 분석
• [M-03] Read-only reentrancy is possible
• Impact
• 담보가 과도하게 잡힌 것으로 판단하게 하여 이자를 발행한다.
• Mitigation
• getCollateralRatio view 함수에도 재진입 가드를 추가한다.
49. Audit report 분석
• [M-04] estimatedAPR() might return the wrong APR
• Summary
• APR을 잘못 계산하여 유저가 잘못된 정보를 얻게 된다.
• Keyword
• misinformation, bug, logic flaw
• Vulnerability
• vestingPeriod가 끝나 더 이상 lock된 토큰이 없음에도 APR로 0이 아닌 값을 보여준다.
50. Audit report 분석
• [M-04] estimatedAPR() might return the wrong APR
• Vulnerability
• SavingsVest.estimatedAPR는 현재 vestingProfit와 vestingPeriod를 이용하여 APR을 계산한다.
• 이자가 발생하여 lockedProfit이 존재해야 풀에 예치했을 때 이자를 받을 수 있다.
• 이자가 lock되는 vestingPeriod가 지나면 lockedProfit은 0을 리턴한다.
• 하지만 따로 예외상황을 처리 하지 않고, APR 계산 로직은 동일하다.
• 이자가 새로 발생하고 lastUpdate, vestingProfit이 업데이트 되어야 다시 리워드가 나오는데, 실제로 리워드가 나오지 않지만 APR는 0보다 크게 나옴
• vestingProfit과 lastUpdate는 과담보 또는 저담보 상태에 SavingsVest.accrue를 호출하면 업데이트 된다.
• 과/저담보가 아니면 업데이트 되지 않는다.
51. Audit report 분석
• [M-04] estimatedAPR() might return the wrong APR
• Vulnerability
• 다음과 같은 시나리오가 발생할 수 있다.
• 처음에, vestingProfit = 100 이고 vestingPeriod = 10일 이라고 가정하자. 이 때의 estimatedAPR는 올바른 값을 리턴한다.
• 10일이 지난 뒤, 모든 vesting이 unlock 되었다. 하지만 collatRatio가 틀어지지 않아 과/저담보 상태가 되지 않았다. lastUpdate는 동일하게 유지된다.
• lockedProfit() 값은 0이 된다.
• estimatedAPR에서 vestingProfit = 100 이고 vestingPeriod = 10일로 여전히 동일한 식으로 계산된 APR을 리턴할 것이다.
• 하지만 실제로는 더이상 locked profit이 없으므로, APR은 0이어야 맞다.
52. Audit report 분석
• [M-04] estimatedAPR() might return the wrong APR
• Impact
• 유저가 잘못된 정보를 기반으로 이율을 계산하게 된다.
• Mitigation
• 마지막으로 발행된 이자의 vestingPeriod가 지나 lockedProfit가 0일 시 APR이 0을 리턴한다.
function estimatedAPR() external view returns (uint256 apr) {
+ if (lockedProfit() == 0) return 0; //check locked profit first
uint256 currentlyVestingProfit = vestingProfit;
uint256 weightedAssets = vestingPeriod * totalAssets();
if (currentlyVestingProfit != 0 && weightedAssets != 0)
apr = (currentlyVestingProfit * 3600 * 24 * 365 * BASE_18) / weightedAssets;
}
53. Audit report 분석
• [M-05] uint128 changeAmount might overflow
• Summary
• 특정 조건에 changeAmount가 절삭되어 SafeCast에 의해 revert 되고, 이로 인해 swap이 실패한다.
• Keyword
• overflow/underflow, casting
• Vulnerability
• Swap중, uint256 변수를 이용해 계산한 값을 uint128로 캐스팅해 사용한다.
• 인플레이션에 의해 토큰 사이의 가격이 벌어지면 계산 값이 커지고, 이것이 type(uint128).max를 넘으면 캐스팅 중 값이 절삭된다.
• SafeCast 라이브러리를 이용하므로 캐스팅 중 언더/오버플로우가 발생하면 revert 된다.
• 단순히 가격 차가 많이 나는 상황에 다량의 토큰을 swap하는, 정상적인 사용에도 터질 것
54. Audit report 분석
• [M-05] uint128 changeAmount might overflow
• Vulnerability
• Swap 중 changeAmount는 uint128로 캐스팅된다.
• changeAmount가 실제 값보다 절삭될 수 있다.
• SafeCast를 이용하므로 revert 될 것
• changeAmount 계산에 사용되는 변수는 다음과 같다.
• 1. normalizer
• 2. BASE_27
• 3. amountOut 또는 amountIn
55. Audit report 분석
• [M-05] uint128 changeAmount might overflow
• Vulnerability
• normalizer
• 1e18 ~ 1e36 사이의 값이다.
• 최악의 상황으로 1e18로 가정하자.
• BASE_27
• 1e27 상수
• amountOut 또는 amountIn
• agToken의 양을 의미하며, 18 decimal
56. Audit report 분석
• [M-05] uint128 changeAmount might overflow
• Vulnerability
• changeAmount의 계산 값은 대략 1e18 * 1e27 / 1e18 = 1e27
• uint128의 최대 값은 3.4e38
• 오버플로우가 발생하려면 amountOut 또는 amountIn 값은 3.4e38 / 1e27 = 3.4e11 이상이어야 한다.
• 이는 굉장히 큰 금액이지만..
• 특정 토큰에 인플레이션이 일어날 가능성, 오랜 기간 프로토콜이 운영되며 인플레이션이 일어날 가능성을 고려해야 함.
• 이 프로토콜이 세계 모든 국가에서 보편적으로 사용되려면 엣지케이스를 고려할 필요가 있다.
• USD와 금값과 비교하면, 1970년대 35 USD/oz 였던 것이 2000 USD/oz 로 약 60배 차이난다.
• 어떤 나라는 인플레이션율이 높다.
• 10~20년 시간이 더 길어지면 감가상각의 영향이 있을 수 있다.
• 예를 들어 현재 3.4e11의 IRR(이란 Rial)은 8M USD의 가치를 지닌다. 따라서 이 취약점이 트리거되려면 8M USD만큼의 IRR이 이동해야 한다.
• 만약 20년 후 인플레이션으로 인해 IRR이 100배 싸진다면, 80K의 USD만으로 취약점이 트리거될 수 있다. 이정도 금액이 움직이는 것은 현실적으
로 가능할 것이다.
57. Audit report 분석
• [M-05] uint128 changeAmount might overflow
• Impact
• 심한 인플레이션 상황에 swap이 실패한다.
• Mitigation
• changeAmount를 절삭하지 않고 uint256를 사용한다.
• normalizer를 좀 더 좁은 범위의 값을 사용한다.
• Memo
• 프로젝트 측은 오버플로우의 가능성은 있지만, 최악의 경우 SafeCast로 터지도록 하여 괜찮다고 했다.
• 인플레이션 가능성을 현실적으로 설명하는 부분이 인상적
58. Audit report 분석
• [M-06] Interest is not accrued before parameters are updated in SavingsVest
• Summary
• 이자 발행의 조건이 되었을 때, 이자를 발행하기 전에(accrue 호출 전에) setParams 함수를 호출한다면 변경된 변수를 기반으로 이자가
계산된다.
• 따라서 홀더가 잘못 계산된 이자를 받는다. 변경 전에 먼저 과거의 이자를 정산해야 한다는 의미.
• Keyword
• bug, logic flaw
• Vulnerability
• 이자 발행 조건을 만족하지만 아직 SavingsVest.accrue 를 호출하지 않아 이자가 발행되지 않은 상태를 가정하자
• 이 때 관리자가 설정을 변경하면 (과거에 조건을 충족했음에도) 관리자 마음대로 이자 양이 줄어들거나 vesting 기간이 늘어날 것
• 설정을 변경하기 전에, 기존 이자를 먼저 정산해주는 게 더 옳다는 의미
59. Audit report 분석
• [M-06] Interest is not accrued before parameters are updated in SavingsVest
• Vulnerability
• SavingsVest 컨트랙트에 스테이블 코인을 예치해두면 Transmuter 프로토콜에서 과담보 상태가 되었을 시 이자를 얻을 수 있다.
• 이익에 영향을 끼치는, 관리자가 설정하는 요소는 다음과 같다.
• protocolSafetyFee: SavingsVest의 수수료
• vestingPeriod: 이자가 lock되어 있는 기간
60. Audit report 분석
• [M-06] Interest is not accrued before parameters are updated in SavingsVest
• Vulnerability
• protocolSafetyFee와 vestingPeriod는 관리자가 변경할 수 있다
• 이자 발행의 조건이 되었을 때, 이자를 발행하기 전에(accrue 호출 전에) setParams 함수를 호출한다면 아직 발행되지 않은 이자
에도 적용이 된다.
• protocolSafetyFee를 올려 유저 몫을 줄이거나 vestingPeriod를 올려 이자를 오래 lock하고 수익률이 떨어지게 할 수 있음
61. Audit report 분석
• [M-06] Interest is not accrued before parameters are updated in SavingsVest
• Impact
• 홀더가 잘못 계산된 이자를 받는다.
• Mitigation
• setParams를 호출하는 시점에 아직 발행되지 않은 이자가 있다면 먼저 이 이자를 발행해준 뒤 설정을 변경한다.
function setParams(bytes32 what, uint64 param) external onlyGuardian {
if (param > BASE_9) revert InvalidParam();
- else if (what == "PF") protocolSafetyFee = param;
- else if (what == "VP") vestingPeriod = uint32(param);
else if (what == "UD") updateDelay = uint32(param);
else if (what == "P") paused = uint8(param);
- else revert InvalidParam();
+ else {
+ // Interest must be accrued with the current parameters before setting new `protocolSafetyFee`
+ // and `vestingPeriod`
+ accrue();
+ if (what == "PF") protocolSafetyFee = param;
+ else if (what == "VP") vestingPeriod = uint32(param);
+ else revert InvalidParam();
+ }
emit FiledUint64(param, what);
}
62. Audit report 분석
• [M-07] User may get less tokens than expected when collateral list order changes
• Summary
• 담보 토큰에 변경이 생겨도 기존 파라미터로 redeem 콜이 조용히 정상 실행된다.
• 이로 인해 유저가 원하는 최소 양보다 적은 양의 토큰을 받으면 콜이 취소되어야 하는데, 취소되지 않는다.
• Keyword
• bug, logic flaw
• Vulnerability
• 관리자가 담보 토큰을 제거할 수 있다.
• 유저가 redeem 시 받고 싶은 담보 토큰 최소량을 파라미터로 넘긴다.
• 코드에는 담보 토큰 최소량 배열의 순서는 담보 토큰 배열의 순서와 동일하다는 가정이 들어간다.
• 관리자가 담보 토큰을 삭제하여 담보 토큰 최소량 배열의 길이와 실제 담보 토큰의 수가 불일치해도 콜이 실패하지 않는다.
• 유저는 담보 토큰 최소량을 만족하지 못하면 콜이 revert 되기를 바랐지만, 조용히 성공한다.
63. Audit report 분석
• [M-07] User may get less tokens than expected when collateral list order changes
• Vulnerability
• 관리자가 revokeCollateral 를 호출하면 등록되어 있는 담보 토큰을 제거할 수 있다.
• 토큰을 삭제할 때, ts.collateralList에서도 담보 토큰이 삭제되며, 이 과정에서 ts.collateralList 배열내 순서가 변경된다.
64. Audit report 분석
• [M-07] User may get less tokens than expected when collateral list order changes
• Vulnerability
• Redeemer.redeem 함수는 ts.collateralList의 순서에 의존적이다.
• minAmountOuts 파라미터는 redeem을 하며 어떤 토큰을 얼마나 받을지를 정한다. 이 때, minAmountOuts와 ts.collateralList는 동일 index를 사용한다.
• _quoteRedemptionCurve에서 tokens와 amounts의 순서는 ts.collateralList와 동일
• 즉, minAmountOuts 배열은 순서에 의해 어떤 토큰에 대한 minAmountOut인지를 지정한다.
65. Audit report 분석
• [M-07] User may get less tokens than expected when collateral list order changes
• Vulnerability
• 만약 유저가 토큰이 추가된 것을 인지하지 못하고 redeem을 호출한다면 유저가 원하지 않는 상황이 발생할 수 있음
• 1. 담보 토큰 [tokenA, tokenB, tokenC]가 등록되어 있다고 가정하자. 이 순서로 ts.collateralList에 등록되어 있다.
• 2. tokenA의 normalizedStables는 0인 상태이다. 따라서 유저는 tokenA를 받고싶지 않다.
• 3. 유저는 tokenC를 100,000개를 반드시 받고 싶다. 그러므로 minAmountOuts 파라미터를 [0, 10000, 100000] 으로 하며 redeem을 호출했다.
• 4. 이 콜이 처리되기 전에 revokeCollateral 콜이 먼저 처리되어 tokenA를 담보에서 제거했다.
• 이로 인해 담보 배열 ts.collateralList는 [tokenC, tokenB]가 된다.
• 5. 유저의 콜이 실행되는 시점에는 minAmountOuts의 순서와 ts.collateralList의 순서가 맞지 않다.
• tokenC의 minAmountOut는 0으로 설정되었다. 따라서 tokenC가 원래 원했던 100000개보다 적게 나와도 콜이 성공한다.
• 관리자 입장에서는?
• 담보 토큰을 삭제하고 싶어도 누군가 redeem을 시도하는 중이라면 이런 문제가 발생하기 때문에 설정 변경이 어렵다.
• 프론트에서 기존 토큰을 기반으로 파라미터를 셋팅하므로, 컨트랙트의 설정을 변경할 때 프론트도 맞춰서 업데이트 해야 한다는 부담이 있다.
66. Audit report 분석
• [M-07] User may get less tokens than expected when collateral list order changes
• Impact
• redeem시 유저가 원한 최소 양보다 적은 양의 토큰을 받아도 콜이 취소되지 않는다.
• Mitigation
• minAmountOuts 파라미터 배열의 길이와 실제로 얻을 토큰 배열 길이가 동일한지 확인한다.
• 하지만 이는 담보가 삭제된 후 바로 다른 담보를 추가한 경우에는 유효하지 않음
• 파라미터로 토큰 주소도 직접 받는다.
• minAmountOuts와 ts.collateralList 가 동일 순서라 가정하지 않고, 유저가 명시적으로 어떤 토큰에 대한 minAmountOut인지를 지정한다.