SlideShare a Scribd company logo
1 of 66
Download to read offline
Code4rena Audit Report Analysis
Angle Protocol
2023.08.19
J.J
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개
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를 이용하여 리워드 여부와 양 관리
Angle Protocol
• 오딧팅 범위 로직 분석
• Transmuter.Transmuter
• Angle stablecoin의 메인 민팅 메커니즘
• Swap, Redeemption을 통해 agToken을 mint 또는 burn함
출처: https://docs.angle.money/transmuter/transmuter
Angle Protocol
• 오딧팅 범위 로직 분석
• Transmuter.Transmuter
• Diamond(Multi-Facet Proxy) 구조
• Facets 디렉토리 내의 컨트랙트들은 로직 컨트랙트
• 이들을 모두 상속한 하나의 거대한 컨트랙트라고 생각하면 됨
• 추후 로직을 추가해 확장하는 등 가능. 코드 크기 제한을 피해 무한히 확장 가능
출처: https://www.info.diamonds/diamondPattern
Angle Protocol
• 오딧팅 범위 로직 분석
• Transmuter.Transmuter
• Facets.Redeemer
• agToken을 담보 토큰들로 변환
• Burn할 agToken amount / 전체 agToken * 담보 토큰 양 * 패널티
• 과담보 상태일 시 패널티를 높여 더 많은 담보 토큰을 돌려받는 것을 막는다. (스테이블 코인이므로 가치를 일정하게 유지하는 게 목표)
• 저담보 상태일 시 패널티가 높아지다가, 프로토콜이 돌이킬 수 없을 정도로 나쁜 상태가 되면 모든 사람이 공정하게 탈출할 수 있도록 패널티가 1로 돌아
온다.
출처: https://docs.angle.money/transmuter/redeem
Angle Protocol
• 오딧팅 범위 로직 분석
• Transmuter.Transmuter
• Facets.Redeemer
• redeemWithForfeit는 특정 담보 토큰은 받고 싶지 않을 때 사용
• agToken을 burn하고 담보 토큰을 돌려줌
Angle Protocol
• 오딧팅 범위 로직 분석
• Transmuter.Transmuter
• Facets.Redeemer
• 돌려줄 담보 토큰을 계산하는 로직
• Penalty를 적용해 거래를 조절한다.
Angle Protocol
• 오딧팅 범위 로직 분석
• Transmuter.Transmuter
• Facets.Swapper
• 담보 토큰을 agToken으로 swap 또는 agToken을 담보 토큰으로 swap
• agToken는 mint 또는 burn 됨
• 담보 토큰의 비율이 높아질수록 mint fee가 높아짐. 비율이 낮아질수록 burn fee가 높아짐.
출처: https://docs.angle.money/transmuter/mintburn
Angle Protocol
• 오딧팅 범위 로직 분석
• Transmuter.Transmuter
• Facets.Swapper
• 담보 토큰을 agToken으로 swap 또는 agToken을 담보 토큰으로 swap
• Fee를 제한 만큼 받을 수 있음
Angle Protocol
• 오딧팅 범위 로직 분석
• Transmuter.Transmuter
• Facets.Swapper
• agToken는 mint 또는 burn 됨
Angle Protocol
• 오딧팅 범위 로직 분석
• Transmuter.Savings
• agToken을 예치하면 이자를 받을 수 있는 Vault
• ERC4626(Tokenized Vault) 기반
• Asset 토큰을 예치하면 이자가 발생. Asset 토큰 예치 시 ERC20를 발행하고, 이를 다시 돌려주며 burn하면 Asset 토큰을 돌려받는다. 이때 이자가 붙어서
나온다.
• 대체로 withdraw(redeem)시 (교환할 ERC20 수 / 전체 ERC20 수) * 예치된 전체 Asset 수 로 비율로 계산해 분배
• 커스텀 로직이 있다면 override하여 추가
• Savings와 SavingsVest 두 종류가 있음
• 예시로? 커스텀 가능함을 보여주기 위함?
• SavingsVest를 살펴보자
Angle Protocol
• 오딧팅 범위 로직 분석
• Transmuter.Savings
• SavingsVest
• 관리자가 주기적으로 accrue를 실행해 이자를 발행 또는 소각해야 함
• 과담보 상태일 시 agToken을 SavingsVest에 mint
• 이는 이자로 사용됨
• 저담보 상태일 시 SavingsVest에 예치된 agToken을 burn
• 이자로 발행되었던 agToken 내에서만 burn하여 저담보 상태를 조절
• 이자로 발행된 토큰은 일정 기간 lock 된다.
• lockedProfit에서 이를 계산
Angle Protocol
• 오딧팅 범위 로직 분석
• Transmuter.Savings
• SavingsVest
• 이자로 발행된 토큰은 일정 기간 lock 되어 꺼낼 수 없다. (저담보 시 burn하기 위한 버퍼)
• totalAssets를 override하여 이를 구현
• withdraw/redeem시 totalAssets에서 lock된 토큰을 빼고 계산
• Lock은 시간이 지남에 따라 서서히 풀리는 구조
Angle Protocol
• 오딧팅 범위 로직 분석
• Merkl
• Uniswap 등에 유동성을 제공했을 때 추가적인 리워드를 주는 실험적인 프로젝트
• 이들에 유동성을 제공하는 것을 장려하기 위함
• LP 토큰을 예치하지 않아도 리워드를 받을 수 있음
• 머클 트리를 이용해 자격 확인
출처: https://docs.angle.money/side-products/merkl
Angle Protocol
• 오딧팅 범위 로직 분석
• Merkl
• Distributor
• 머클 트리를 이용해 리워드를 분배하는 컨트랙트
• 트리가 업데이트 되면 이의 제기 기간이 있다. 이동안은 새 트리를 사용하지 않고 이전 트리를 사용한다.
Angle Protocol
• 오딧팅 범위 로직 분석
• Merkl
• Distributor
• 머클 트리 업데이트
• 관리자만 업데이트 할 수 있다.
Angle Protocol
• 오딧팅 범위 로직 분석
• Merkl
• Distributor
• 악성 행위가 발생했을 때, 리워드 정보(머클 트리)에 동의하지 못할 때 유저는 이의를 제기할 수 있다.
• 이의 제기 가능 기간 내에만 가능하다.
• 이의 제기를 위해서는 disputeToken을 담보로 제출해야 한다. 이의를 인정하면 돌려받고, 아니면 관리자가 가져간다.
• 이의를 제기하면 해결될 때까지 머클 트리 업데이트가 중지된다.
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이
가능하다.
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를 기반으로 계산되므로, 재진입을 통해 다시 담보를 해제하면 받아야 하는
양보다 많은 양의 담보 토큰을 받아낼 수 있다.
Audit report 분석
• [H-01] Possible reentrancy during redemption/swap
• Vulnerability
• 담보 토큰을 맡기면 agToken을 얻을 수 있고, redeem 기능을 이용하면 agToken을 burn하며 담보 토큰을 되돌려 받을 수 있다.
• redeem을 호출하는 엔트리포인트는 여러 개 있지만, 모두 내부적으로 _redeem를 호출
• 돌려받을 담보 토큰의 수는 _quoteRedemptionCurve 함수를 통해 계산
Audit report 분석
• [H-01] Possible reentrancy during redemption/swap
• Vulnerability
• 돌려받을 담보 토큰의 수는 _quoteRedemptionCurve 함수를 통해 계산
• balances[i]는 컨트랙트에 예치된 담보 토큰 수
• 컨트랙트에 예치된 담보 토큰의 수에 비례하여 담보 토큰을 돌려받는다.
Audit report 분석
• [H-01] Possible reentrancy during redemption/swap
• Vulnerability
• 담보 토큰이 ERC777라면?
• 토큰 수신자 측의 tokensReceived 훅이 실행될 것
• 재진입 가드가 없으므로 재진입 가능
• 담보 토큰이 여러 개일 때, 재진입시 나머지 토큰은 아직 전송되지 않은 상태
• balance가 원래보다 높은 상태
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에서 원래 받아야 하는 양보다 더 많은 담보를 상환 받는다.
Audit report 분석
• [H-01] Possible reentrancy during redemption/swap
• Impact
• 원래보다 많은 양의 담보 토큰을 상환 받음
• Mitigation
• _redeem과 _swap 함수에 재진입 가드를 추가
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 에서는 이미 누군가 이의를 제기했는지 확인하지 않는다.
• 중복으로 호출 시 이의 제기자 주소가 덮어씌워진다.
• 이의가 유효한 경우 토큰을 돌려받아야 하는데, 덮어써진 이전 유저는 토큰을 돌려받지 못한다.
Audit report 분석
• [H-02] The first disputer might lose funds although his dispute is valid
• Vulnerability
• disputeTree 함수를 호출하여 이의를 제기할 수 있고, 이동안 머클 트리의 업데이트를 정지할 수 있다.
• 이의를 제기할 수 있는 시간 내인지만 확인한다. 누군가 이미 이의를 제기했는지는 확인하지 않는다.
Audit report 분석
• [H-02] The first disputer might lose funds although his dispute is valid
• Vulnerability
• 이의가 유효할 경우 resolveDispute에서 토큰을 돌려준다.
• 이때, 덮어써진 disputer는 토큰을 받지 못한다.
• 최악의 경우 frontrun에 의해 disputeToken을 돌려받지 못할 수도 있다.
• resolveDispute로 이의를 인정하는 콜을 했을 때 frontrun으로 disputeTree를 호출하면 기존 이의 제기자는 토큰을 환급 받지 못한다.
Audit report 분석
• [H-02] The first disputer might lose funds although his dispute is valid
• Impact
• 유효한 이의를 제기한 유저가 토큰을 돌려받지 못한다.
• Mitigation
• 기존에 이의를 제기한 유저가 있다면 더이상 이의를 제기할 수 없도록 막는다.
• Memo
• High냐 Medium이냐 논쟁이 있었음.
• 관리자가 recoverERC20 함수를 호출하여 직접 받을 주소와 양을 파라미터로 넘기면 토큰을 빼낼 수 있음
• 토큰이 완전히 묶이는 상황은 아니었음
• 하지만 이는 관리자의 추가적인 작업이 필요하며, 의도적인 방해 시도를 막지 못하므로 High과 Medium의 사이라고 판단했고, 최종적
으로 High
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를 확인하고 유효하다면 보상을 준다.
• 이의 제기가 가능 기간을 넘었다면 현재 머클 트리가 안정적이라 판단해 현재 머클 루트를 사용하고, 그렇지 않다면 직전의 머클 트리 루트를 사용한다.
• 단, 현재 머클 트리에 이의를 제기하여 분쟁 중인지는 확인하지 않는다.
• 시간이 지나 이의제기 가능 시점이 지나면 분쟁이 해결되지 않았음에도 현재 머클 트리를 기반으로 보상을 요청할 수 있다.
Audit report 분석
• [H-03] Poor detection of disputed trees allows claiming tokens from a disputed tree
• Vulnerability
• Claim시 proof를 확인하기 위해 getMerkleRoot로 머클 루트를 가져온다.
• 이의 제기 가능 기간을 지나면 현재 트리를, 아니면 이전 트리를 이용한다. 분쟁 중인지는 확인하지 않았다.
• 악성 행위가 발생하고 유저가 이에 빠르게 이의 제기를 했어도, governor/guardian이 아직 이의를 처리하지 않은 경우 시간만 지나면 claim이 가능하다.
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;
}
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을 잘못 계산하여 유저가 잘못된 정보
를 얻게 된다.
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 콜이 조용히 정상 실행된다. 이
로 인해 유저가 원하는 최소 양보다 적은
양의 토큰을 받으면 콜이 취소되어야 하는
데, 취소되지 않는다.
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이 실패한다.
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 된다.
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이 실패할 것
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 값의 범위를 찾아보자
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 모든 값이 불가할 이유는 없다!
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]);
}
Audit report 분석
• [M-02] Unsafe cast in getCollateralRatio()
• Summary
• 엣지케이스에서 캐스팅으로 인해 collatRatio 값이 절삭되어 잘못된 비율을 기반으로 계산하게 된다.
• Keyword
• overflow/underflow, casting
• Vulnerability
• collatRatio 값을 uint256값을 이용해 계산한 뒤 uint64로 캐스팅해 사용했다.
• 프로젝트 측은 비현실적인 케이스라고 심각도를 낮추려 했다.
• 심판 측은 그럼에도 이 값이 프로토콜에서 중요한 역할을 하므로 Medium으로 쳤다.
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 이므로 절삭된다.
Audit report 분석
• [M-02] Unsafe cast in getCollateralRatio()
• Impact
• 캐스팅으로 인해 collatRatio 값이 절삭되어 잘못된 비율을 기반으로 계산하게 된다.
• Mitigation
• SafeCast 라이브러리를 사용하여 캐스팅으로 인해 절삭된 상황에서 거래하는 것을 방지한다.
Audit report 분석
• [M-03] Read-only reentrancy is possible
• Summary
• 재진입으로 인해 담보가 과도하게 잡힌 것으로 판단하게 하여 이자를 발행할 수 있다.
• Keyword
• reentrancy, stablecoin
• Vulnerability
• 재진입으로 인해 view 함수의 값이 깨진다.
• ERC777 훅을 이용해 재진입, 다른 담보 토큰을 받지 않아 풀 상태와 실제 담보 balance 불일치
• 과담보 상태로 판단해 agToken를 mint하게 할 수 있다.
• 토큰 스테이킹에 이자 발행
Audit report 분석
• [M-03] Read-only reentrancy is possible
• Vulnerability
• 다음과 같은 공격 시나리오가 가능하다.
• 1. collatRatio가 BASE_9(100%) 라고 가정하자. Alice가 담보 토큰을 agToken으로 swap하려고 한다.
• 2. _swap 함수에서, 담보 토큰을 먼저 입금하고 agToken을 mint 한다. 담보 토큰이 ERC777인 경우 훅이 실행되고 재진입이 가능하다.
Audit report 분석
• [M-03] Read-only reentrancy is possible
• Vulnerability
• 다음과 같은 공격 시나리오가 가능하다.
• 3. Alice가 훅을 통해 SavingsVest.accrue를 호출한다.
• 과도하게 담보가 존재하는 경우 agToken을 민팅하여 스테이킹 이자로 사용하는 함수이다.
• 이 때, _transmuter.getCollateralRatio()는 잘못된 비율을 리턴할 것이다. 실제보다 큰 collatRatio를 리턴한다
• agToken이 아직 mint되지 않았고 담보 토큰의 양은 늘어났기 때문에, totalCollateralization는 커지고 stablecoinsIssued는 그대로
Audit report 분석
• [M-03] Read-only reentrancy is possible
• Vulnerability
• 다음과 같은 공격 시나리오가 가능하다.
• 4. collatRatio > BASE_9 + BASE_6 조건을 만족시키면(=100.1% 로 0.1% 이상 과담보 되면) 이자가 발행된다.
Audit report 분석
• [M-03] Read-only reentrancy is possible
• Impact
• 담보가 과도하게 잡힌 것으로 판단하게 하여 이자를 발행한다.
• Mitigation
• getCollateralRatio view 함수에도 재진입 가드를 추가한다.
Audit report 분석
• [M-04] estimatedAPR() might return the wrong APR
• Summary
• APR을 잘못 계산하여 유저가 잘못된 정보를 얻게 된다.
• Keyword
• misinformation, bug, logic flaw
• Vulnerability
• vestingPeriod가 끝나 더 이상 lock된 토큰이 없음에도 APR로 0이 아닌 값을 보여준다.
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를 호출하면 업데이트 된다.
• 과/저담보가 아니면 업데이트 되지 않는다.
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이어야 맞다.
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;
}
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하는, 정상적인 사용에도 터질 것
Audit report 분석
• [M-05] uint128 changeAmount might overflow
• Vulnerability
• Swap 중 changeAmount는 uint128로 캐스팅된다.
• changeAmount가 실제 값보다 절삭될 수 있다.
• SafeCast를 이용하므로 revert 될 것
• changeAmount 계산에 사용되는 변수는 다음과 같다.
• 1. normalizer
• 2. BASE_27
• 3. amountOut 또는 amountIn
Audit report 분석
• [M-05] uint128 changeAmount might overflow
• Vulnerability
• normalizer
• 1e18 ~ 1e36 사이의 값이다.
• 최악의 상황으로 1e18로 가정하자.
• BASE_27
• 1e27 상수
• amountOut 또는 amountIn
• agToken의 양을 의미하며, 18 decimal
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만으로 취약점이 트리거될 수 있다. 이정도 금액이 움직이는 것은 현실적으
로 가능할 것이다.
Audit report 분석
• [M-05] uint128 changeAmount might overflow
• Impact
• 심한 인플레이션 상황에 swap이 실패한다.
• Mitigation
• changeAmount를 절삭하지 않고 uint256를 사용한다.
• normalizer를 좀 더 좁은 범위의 값을 사용한다.
• Memo
• 프로젝트 측은 오버플로우의 가능성은 있지만, 최악의 경우 SafeCast로 터지도록 하여 괜찮다고 했다.
• 인플레이션 가능성을 현실적으로 설명하는 부분이 인상적
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 기간이 늘어날 것
• 설정을 변경하기 전에, 기존 이자를 먼저 정산해주는 게 더 옳다는 의미
Audit report 분석
• [M-06] Interest is not accrued before parameters are updated in SavingsVest
• Vulnerability
• SavingsVest 컨트랙트에 스테이블 코인을 예치해두면 Transmuter 프로토콜에서 과담보 상태가 되었을 시 이자를 얻을 수 있다.
• 이익에 영향을 끼치는, 관리자가 설정하는 요소는 다음과 같다.
• protocolSafetyFee: SavingsVest의 수수료
• vestingPeriod: 이자가 lock되어 있는 기간
Audit report 분석
• [M-06] Interest is not accrued before parameters are updated in SavingsVest
• Vulnerability
• protocolSafetyFee와 vestingPeriod는 관리자가 변경할 수 있다
• 이자 발행의 조건이 되었을 때, 이자를 발행하기 전에(accrue 호출 전에) setParams 함수를 호출한다면 아직 발행되지 않은 이자
에도 적용이 된다.
• protocolSafetyFee를 올려 유저 몫을 줄이거나 vestingPeriod를 올려 이자를 오래 lock하고 수익률이 떨어지게 할 수 있음
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);
}
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 되기를 바랐지만, 조용히 성공한다.
Audit report 분석
• [M-07] User may get less tokens than expected when collateral list order changes
• Vulnerability
• 관리자가 revokeCollateral 를 호출하면 등록되어 있는 담보 토큰을 제거할 수 있다.
• 토큰을 삭제할 때, ts.collateralList에서도 담보 토큰이 삭제되며, 이 과정에서 ts.collateralList 배열내 순서가 변경된다.
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인지를 지정한다.
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을 시도하는 중이라면 이런 문제가 발생하기 때문에 설정 변경이 어렵다.
• 프론트에서 기존 토큰을 기반으로 파라미터를 셋팅하므로, 컨트랙트의 설정을 변경할 때 프론트도 맞춰서 업데이트 해야 한다는 부담이 있다.
Audit report 분석
• [M-07] User may get less tokens than expected when collateral list order changes
• Impact
• redeem시 유저가 원한 최소 양보다 적은 양의 토큰을 받아도 콜이 취소되지 않는다.
• Mitigation
• minAmountOuts 파라미터 배열의 길이와 실제로 얻을 토큰 배열 길이가 동일한지 확인한다.
• 하지만 이는 담보가 삭제된 후 바로 다른 담보를 추가한 경우에는 유효하지 않음
• 파라미터로 토큰 주소도 직접 받는다.
• minAmountOuts와 ts.collateralList 가 동일 순서라 가정하지 않고, 유저가 명시적으로 어떤 토큰에 대한 minAmountOut인지를 지정한다.

More Related Content

More from Jinkyoung Kim

More from Jinkyoung Kim (20)

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
 
Windows reversing study_basic_7
Windows reversing study_basic_7Windows reversing study_basic_7
Windows reversing study_basic_7
 
Windows reversing study_basic_6
Windows reversing study_basic_6Windows reversing study_basic_6
Windows reversing study_basic_6
 
Windows reversing study_basic_5
Windows reversing study_basic_5Windows reversing study_basic_5
Windows reversing study_basic_5
 
Windows reversing study_basic_4
Windows reversing study_basic_4Windows reversing study_basic_4
Windows reversing study_basic_4
 

Angle Protocol bughunting case study

  • 1. Code4rena Audit Report Analysis Angle Protocol 2023.08.19 J.J
  • 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인지를 지정한다.