먼저, 더 머지 이후 이더리움의 블록 생성 과정에 대해 알아보면,
32 ETH 이상 스테이킹한 사람이 검증자가 될 수 있다.
매 블록마다 블록 생성을 제안하는 사람은 검증자 중에서 무작위로 선정된다.
블록을 제안하는 검증자는 mempool (트랜잭션이 블록에 포함되기 위해 대기중인 풀) 에서 원하는 트랜잭션을 가져와 블록에 포함시킨다.
대부분은 Gas price 를 크게 설정한 트랜잭션을 우선순위로 블록에 포함시킨다.
(블록 제안자의 권한으로, 제안자의 마음대로 포함시킬 수 있다.)
블록에 포함된 트랜잭션이 스마트 컨트랙트 코드의 실행을 요청하고 있다면, EVM 을 통해 코드를 실행하고 이더리움 네트워크의 상태를 변경한다.
블록 생성 과정을 읽다보면, 가스 가격이 트랜잭션의 실행 순서에 영향을 미친다는 것을 알 수 있다.
가스 가격을 다른 트랜잭션 보다 높게 설정한 트랜잭션은 같은 멤풀에 있는 트랜잭션들 보다 먼저 블록에 담길 가능성이 높아진다.
그리고 블록에 담긴 트랜잭션의 순서대로 스마트 컨트랙트 코드가 실행된다.
가스 가격을 높게 설정한 트랜잭션의 코드 실행이 일반적으로 더 먼저 된다.
멤풀에서 대기중인 트랜잭션 중, 수익성이 있는 트랜잭션을 발견한다면 이 트랜잭션보다 먼저 블록에 담길 수 있도록 가스 가격을 높게 설정하는 것이다.
예를 들어, A 가 유니스왑을 이용해 1000 USDT => LINK
로 스왑하는 트랜잭션을 보내 멤풀에 대기 중.
B 는 멤풀을 모니터링 중, A 의 트랜잭션을 확인하고 프론트 러닝 공격으로 A 보다 먼저 트랜잭션을 실행시켜 이익을 챙기고, A 에게 금전적 손실을 입힌다.
유니스왑 CPMM 모델을 통해 A, B 는 손해와 이익을 보는지 확인해보면,
현재 유동성 풀에 예치된 물량은 9,000 USDT : 1,000 LINK
라고 가정,
CPMM 모델에 따라X * Y = K
,
9,000 USDT * 1,000 LINK = 9,000,000
(9,000 USDT + 3,000 USDT) * (1,000 LINK - Z) = 9,000,000
Z = 250
따라서, A 는 3,000 USDT => 250 LINK
로 스왑한다.
B 가 A 와 같은 내용의 트랜잭션으로 공격한다면,
A 는 B 가 스왑한 후의 비율인 12,000 USDT : 750 LINK
풀에서 스왑을 할 것이다.
(12,000 USDT + 3,000 USDT) * (750 LINK - Z) = 9,000,000
Z = 150
따라서, A 는 3,000 USDT => 150 LINK
로 스왑한다.
A 가 손해를 보며 스왑한 후, B 는 LINK => USDT
로 스왑하여 이익을 실현할 것이다.
15,000 USDT : 600 LINK
비율인 풀에서 스왑한다.
(15,000 USDT - Z) * (600 LINK + 250 LINK) = 9,000,000
Z = 4,412
따라서, B 는 4,412 USDT
만큼의 이익을 실현했다.
DEX 에서의 프론트 러닝 공격은 효과가 있었으며, 공격자는 한번 더 스왑을 하며 이익을 챙길 수 있다.
아래와 같이 이더 게임을 진행하는 컨트랙트가 있다고 한다면,
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
contract EtherGame {
address owner;
bool isSet;
receive() external payable { }
constructor() {
owner = msg.sender;
}
bytes32 public hash;
function guessAnswer(string calldata _answer) public {
require(isSet,"Game has not start yet.");
require(hash == keccak256(abi.encodePacked(_answer)), "Incorrect answer.");
isSet = false;
(bool sent, ) = msg.sender.call{value: 10 ether}("");
require(sent, "Failed to send Ether.");
}
function setHash(string memory _answer) public payable {
require(msg.sender == owner,"Owner only.");
require(isSet == false,"Game is still running.");
require(msg.value >= 10 ether,"Minimum 10 Ether to set hash.");
// 개최자가 이더를 걸고 게임을 개최
payable(address(this)).transfer(msg.value);
hash = keccak256(abi.encodePacked(_answer));
isSet = true;
}
}
개최자가 10 이더를 걸고 문제를 낸다. 문제의 정답을 맞추면 10 이더 획득.
A 가 정답을 입력해 트랜잭션을 보냄, 트랜잭션을 모니터링 하던 B 가 A 가 제출한 트랜잭션과 똑같이 제출.
이때, B 는 트랜잭션의 가스 가격을 A 가 제출한 트랜잭션보다 높게 설정,
B 의 트랜잭션이 먼저 실행되며 10 이더를 받아가고, A 의 트랜잭션은 무효화 됨.
최대 추출 가능 가치(MEV)는 블록 생성 시 트랜잭션을 포함하거나 제외하고, 그들의 순서를 변경함으로써 표준 블록 보상 및 가스 수수료 이상의 가치를 추출할 수 있는 최대 가치를 가리킵니다.
쟁글 리서치를 보며 프론트러닝, 백러닝, 샌드위치 공격 등을 노리는 봇들이 상당히 많음을 알 수 있었다.
MEV 는 항상 단점만 있는 것은 아니며, 장점도 분명히 존재한다고 설명하고 있다.
MEV 의 대안점으로 나온 것으로 Flashbots, 토큰화가 있다.
두 대안 모두 멤풀을 경매로 붙여 프론트러닝, 백러닝 등의 공격을 권한으로서 판매하자는 것이 되겠다.
dapp 개발자는 MEV 의 개념을 이해하며, 사용자에게 미치는 영향까지도 생각하며 개발을 진행해야 할 것이다.