code4rena-2023-06-angle-protocol-m07
[M-07] User may get less tokens than expected when collateral list order changes
Summary
담보 토큰에 변경이 생겨도 기존 파라미터로 redeem 콜이 조용히 정상 실행된다. 이로 인해 유저가 원하는 최소 양보다 적은 양의 토큰을 받으면 콜이 취소되어야 하는데, 취소되지 않는다.
Keyword
bug, logic flaw
Vulnerability
관리자가 revokeCollateral 를 호출하면 등록되어 있는 담보 토큰을 제거할 수 있다. 토큰을 삭제할 때, ts.collateralList에서도 담보 토큰이 삭제되며, 이 과정에서 ts.collateralList 배열내 순서가 변경된다.
ts.collateralList[i] = collateralListMem[length - 1]; 코드에 의해 리스트의 맨 마지막에 있던 담보 토큰이 삭제된 토큰의 인덱스로 옮겨진다.
function revokeCollateral(address collateral) internal {
...
address[] memory collateralListMem = ts.collateralList;
uint256 length = collateralListMem.length;
for (uint256 i; i < length - 1; ++i) {
if (collateralListMem[i] == collateral) {
ts.collateralList[i] = collateralListMem[length - 1];
break;
}
}
ts.collateralList.pop();
...
}그런데 Redeemer.redeem 함수는 ts.collateralList의 순서에 의존적이다. minAmountOuts 파라미터는 redeem을 하며 어떤 토큰을 얼마나 받을지를 정한다. 이 때, minAmountOuts와 ts.collateralList는 동일 index를 사용한다. 즉, minAmountOuts 배열은 순서에 의해 어떤 토큰에 대한 minAmountOut인지를 지정한다.
function redeem(
uint256 amount,
address receiver,
uint256 deadline,
uint256[] memory minAmountOuts
) external returns (address[] memory tokens, uint256[] memory amounts) {
return _redeem(amount, receiver, deadline, minAmountOuts, new address[](0));
}만약 유저가 revokeCollateral가 호출된 것을 인지하지 못하고 redeem을 호출한다면 유저가 원하지 않는 상황이 발생할 수 있다.
- 담보 토큰 [tokenA, tokenB, tokenC]가 등록되어 있다고 가정하자. 이 순서로
ts.collateralList에 등록되어 있다. - tokenA의
normalizedStables는 0인 상태이다. 따라서 유저는 tokenA를 받고싶지 않다. - 유저는 tokenC를 100,000개를 반드시 받고싶다. 그러므로
minAmountOuts파라미터를 [0, 10000, 100000] 으로 하며redeem을 호출했다. - 이 콜이 실행되기 직전에
revokeCollateral가 호출되어 tokenA를 담보에서 제거했다. 이로 인해 담보 배열ts.collateralList는 [tokenC, tokenB]가 된다. - 유저의 콜이 실행되는 시점에는
minAmountOuts의 순서와ts.collateralList의 순서가 맞지 않다. tokenC의 minAmountOut는 0으로 설정되었다. 따라서 tokenC가 원래 원했던 100000개보다 적게 나와도 콜이 성공한다.
관리자 입장에서는, 담보 토큰을 삭제하고 싶어도 누군가 redeem을 시도하는 중이라면 위와 같은 문제가 발생하기 때문에 설정 변경이 어렵다. 또한 프론트엔드에서 기존 토큰을 기반으로 파라미터를 셋팅하고 있으므로 컨트랙트의 설정을 변경할 때 프론트엔드도 맞춰서 업데이트 해야한다는 부담이 생긴다.
Impact
redeem시 유저가 원한 최소 양보다 적은 양의 토큰을 받아도 콜이 취소되지 않는다.
Mitigation
minAmountOuts파라미터 배열의 길이와 실제로 얻을 토큰 배열 길이가 동일한지 확인한다.- 하지만 이는 담보가 삭제된 후 바로 다른 담보를 추가한 경우에는 유효하지 않다.
- 파라미터로 토큰 주소도 직접 받는다.
minAmountOuts와ts.collateralList가 동일 순서라 가정하지 않고, 유저가 명시적으로 어떤 토큰에 대한minAmountOut인지를 지정한다.
tags: bughunting, angle protocol, smart contract, solidity, stablecoin, logic flaw, severity medium