티스토리 뷰
1. README.md 파일 및 일부 Solidity 파일 업데이트
이전 버전의 문제가 적혀있는 README.md 파일을 전반적으로 최신 상태로 업데이트했다. 또한 업데이트된 문제와 맥락이 일치하도록 Solidity 파일을 업데이트했다.
2. Makefile 및 run.sh 파일 업데이트
새롭게 추가된 테스트를 간단한 키워드로 실행하기 위해 명령어를 추가하였다.
3. Mainnet 포크를 사용한 테스트
RPC URL과 블록 번호를 인수로 vm.createSelectFork를 호출하여 메인넷의 포크를 생성할 수 있다.
string public mainnetForkingURL = vm.envString("MAINNET_RPC_URL");
uint256 public constant MAINNET_BLOCK_NUMBER = 15450164;
uint256 public forkId;
function setUp() public {
// Fork from mainnet state
forkId = vm.createSelectFork(mainnetForkingURL, MAINNET_BLOCK_NUMBER);
...
}
또는 포크 생성 -> 블록 번호로 이동 -> 포크 선택 과정을 다음과 같이 각각 실행할 수도 있다.
forkId = vm.createFork(mainnetForkingURL);
vm.rollFork(forkId, MAINNET_BLOCK_NUMBER);
vm.selectFork(forkId);
RPC URL은 .env 파일에 저장된 값을 불러와 사용한다. 이때 Infura나 Alchemy 같은 프로바이더를 사용해도 좋고 퍼블릭 노드의 URL을 사용해도 된다.
MAINNET_RPC_URL=https://ethereum-rpc.publicnode.com
4. 가격 계산 정밀도 문제
이 코드를 Solidity로 옮겨 적자니 정밀도 문제가 발생한다.
import bn from 'bignumber.js'
import { BigNumber, BigNumberish } from 'ethers'
bn.config({ EXPONENTIAL_AT: 999999, DECIMAL_PLACES: 40 })
// returns the sqrt price as a 64x96
export function encodePriceSqrt(reserve1: BigNumberish, reserve0: BigNumberish): BigNumber {
return BigNumber.from(
new bn(reserve1.toString())
.div(reserve0.toString())
.sqrt()
.multipliedBy(new bn(2).pow(96))
.integerValue(3)
.toString()
)
}
정밀도 차이가 얼마나 문제에 영향을 미칠지는 아직 문제를 풀어보지 못해서 잘 모르겠다. 이 문제는 문제를 풀어보면서 다시 살펴보자.
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
library PriceEncoder {
// returns the sqrt price as a 64x96
function encodePriceSqrt(uint256 reserve1, uint256 reserve0) external pure returns (uint160) {
uint256 sqrtPriceX96 = sqrtPrice(reserve1, reserve0);
return uint160(sqrtPriceX96 * (2 ** 96) / 10 ** 9); // Multiply by 2^96 to match the 64x96 format
}
// Helper function to calculate square root
function sqrtPrice(uint256 reserve1, uint256 reserve0) internal pure returns (uint256) {
require(reserve0 > 0 && reserve1 > 0, "Reserves must be greater than 0");
uint256 x = reserve1 * 10 ** 18 / reserve0; // Scale reserve1 to match reserve0 precision
uint256 sqrtX = sqrt(x);
return sqrtX;
}
// Babylonian method for calculating square root
// Based on https://ethereum.stackexchange.com/a/29148
function sqrt(uint256 y) internal pure returns (uint256 z) {
if (y > 3) {
z = y;
uint256 x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
}
}
5. UniswapV3 라이브러리를 사용할 경우 컴파일러 충돌 문제
Puppet V3 문제에서 UniswapV3 라이브러리를 설치하여 사용하려고 하니, 파운드리에서 0.8 이상의 버전으로 컴파일할 때 버전 차이로 컴파일이 안 되는 문제가 발생한다. 다른 문제(Puppet, Puppet V2)에서는 UniswapV1과 V2를 사용하는데, 이 경우는 바이트코드를 사용하여 컨트랙트를 배포(foundry의 deployCode 함)하고 인터페이스로 감싸는 식으로 라이브러리를 사용하지 않고 문제를 해결했다. 컴파일 문제는 이전 버전의 개발자들도 해결하지 못한 문제이기 때문에 구태여 라이브러리를 사용하지 않고 인터페이스를 따로 파일로 불러와서 사용하는 방법을 선택했다.
6. Foundry 재밌다
Foundry 재밌다 헤헤
7. raw tx 실행의 어려움 (2024.02.29 추가)
13번 문제를 푸는 과정에서 signed tx를 테스트 환경에서 replay 해야 하는데 foundry의 cheatcode 중 raw tx를 실행할 수 있는 것이 없다. 이 문제를 해결하기 위해 anvil 로컬 네트워크를 실행해 놓고 RPC URL로 지정하여 anvil 네트워크를 사용하려 해 보았으나 테스트 코드는 다른 네트워크를 포크 하여 완전히 격리된 환경에서 실행되므로 로컬 네트워크를 사용하는 것은 아무런 의미가 없다.
raw tx를 실행할 수 있는 cheatcode를 추가하기 위한 노력이 곳곳에서 엿보이지만 아직은 해당 기능이 없는 것이 사실.
문제를 해결하는 과정에서 'ffi'라는 cheatcode의 존재를 확인했다. 이 cheatcode는 solidity 코드 상에서 cli 명령어를 실행할 수 있게 해준다. anvil 로컬 네트워크와 함께 사용하여 signed tx를 실행해 보고자 하였다. 그러나 방금 말한 대로 테스트 코드가 실행되는 환경은 anvil 네트워크와 격리되어 있어서 anvil 네트워크에서 signed tx를 실행한다 하더라도 테스트 환경에는 반영되지 않는다. 애초에 서명자의 잔액이 없어서 실행조차 되지 않는다.
string[] memory args = new string[](6);
args[0] = "cast";
args[1] = "tx";
args[2] = "0x06d2fa464546e99d2147e1fc997ddb624cec9c8c5e25a050cc381ee8a384eed3";
args[3] = "--rpc-url";
args[4] = vm.envString("MAINNET_RPC_URL");
args[5] = "--raw";
bytes memory rawTx = vm.ffi(args);
args = new string[](5);
args[0] = "cast";
args[1] = "publish";
args[2] = "--rpc-url";
args[3] = "http://localhost:8545";
args[4] = vm.toString(rawTx);
bytes memory result = vm.ffi(args);
이 문제는 포기해야 하나? 아니. signed tx를 replay하는 것은 테스트의 극히 일부분에 불과하다. replay 했다 치고 포크 떠서 나머지 테스트를 진행해보자. 생각해 보니까 setUp 함수로 다 세팅해 놓고 메인넷을 새로 포크 뜨면 이것 또한 별개의 환경이 된다. 음... 적당한 cheatcode가 나올 때까지 존버해야 할지도?
'개발 부스러기' 카테고리의 다른 글
[Solidity] 함수 시그니처를 사용해 동적으로 데이터를 인코딩하는 폼 만들기 (1) | 2024.09.06 |
---|---|
Next.js App Router + RainbowKit + SIWE + NextAuth.js를 사용해 지갑으로 로그인하는 기능 구현하기 (0) | 2024.07.31 |
Solidity 런타임 바이트코드 분해하기 (0) | 2024.02.13 |
Hyperlane V3를 사용한 Crosschain NFT (0) | 2024.02.12 |
mdBook을 사용해 markdown 형식의 정적 웹사이트 만들기 (0) | 2024.01.21 |