계약서가 이렇게 쓰일 줄은 몰랐나요? 최근에 표현한 감정이에요~

최근에는 https://github.com/WTFAcademy/WTF-Dapp 에서 탈중앙화 거래소 개발 튜토리얼을 작성하던 중 Uniswap V3의 코드 구현을 참고하고 많은 지식을 얻었습니다. 저자는 이전에 간단한 NFT 계약을 개발한 적이 있지만, Defi 계약을 개발하는 것은 이번이 처음입니다. 이 팁이 계약 개발을 배우고 싶은 초보자에게 도움이 될 것이라고 믿습니다.

계약 개발 전문가는 https://github.com/WTFAcademy/WTF-Dapp 에 직접 가서 코드를 제공하고 Web3에 기여할 수 있습니다~

다음으로, 이러한 작은 트릭을 살펴보겠습니다. 그 중 일부는 기적적인 트릭이라고도 할 수 있습니다.

계약 배포의 계약 주소를 예측 가능하게 만드는 방법이 있습니다.

계약을 배포할 때 일반적으로 무작위로 보이는 주소를 얻습니다. 이는 "nonce"와 관련되어 있기 때문에 계약 주소를 예측하기 어렵습니다. 하지만 Uniswap에는 다음과 같은 요구 사항이 있습니다. 거래 쌍과 관련 정보를 통해 계약의 주소를 추론해야 합니다. 이는 거래 권한을 결정하거나 풀 주소를 얻는 등 많은 상황에서 유용합니다.

Uniswap에서는 "pool = address(new UniswapV3Pool{salt: keccak256(abi.encode(token0, token1, fee))}());"와 같은 코드를 통해 컨트랙트 생성이 생성됩니다. CREATE2 ( https://github.com/AmazingAng/WTF-Solidity/blob/main/25_Create2/readme.md )를 사용하여 계약을 생성하기 위해 "salt"를 추가하면 생성된 계약 주소가 예측 가능하다는 장점이 있습니다. 주소 생성 논리는 "새 주소 = 해시("0xFF", 작성자 주소, 소금, initcode)"입니다.

자세한 내용은 https://github.com/WTFAcademy/WTF-Dapp/blob/main/P103_Factory/readme.md에서 WTF-DApp 과정의 이 부분을 확인하세요.

콜백 함수를 잘 활용하라

Solidity에서는 계약이 서로 호출할 수 있습니다. A가 특정 메서드에서 B를 호출하고 B가 호출된 메서드에서 A를 다시 호출하는 시나리오가 있습니다. 이는 일부 시나리오에서도 잘 작동합니다.

Uniswap에서는 거래를 위해 "UniswapV3Pool" 계약의 "swap" 메서드를 호출하면 "swapCallback"이 다시 호출됩니다. 콜백은 이 거래에 실제로 필요한 계산된 "토큰"을 전달합니다. 콜백의 "토큰"은 거래에 필요합니다. 호출자가 호출할 수 있도록 "swap" 메서드를 두 부분으로 분할하는 대신 "UniswapV3Pool"로 전송합니다. 이렇게 하면 "swap" 메서드의 안전성이 보장되고 번거로운 변수 로깅 없이 전체 논리가 완전히 실행됩니다. 보안을 보장합니다.

코드 조각은 다음과 같습니다.

Web3 초보자 시리즈: Uniswap 코드에서 배운 계약 개발 팁

https://github.com/WTFAcademy/WTF-Dapp/blob/main/P106_PoolSwap/readme.md 과정의 거래 부분에 대해 자세히 알아볼 수 있습니다.

예외를 사용하여 정보를 전달하고 Try catch를 사용하여 트랜잭션을 추정합니다.

Uniswap의 코드를 참조할 때 우리는 해당 계약 https://github.com/Uniswap/v3-periphery/blob/main/contracts/lens/Quoter.sol 에서 "UniswapV3Pool"의 "swap" 메서드를 다음과 같이 래핑하는 것을 발견했습니다. "try catch"하고 실행합니다.

Web3 초보자 시리즈: Uniswap 코드에서 배운 계약 개발 팁

왜 이런가요? 거래에 필요한 토큰을 추정하기 위해서는 "스왑" 방법을 시뮬레이션해야 하지만, 추정 중에 토큰 교환이 실제로 발생하지 않기 때문에 오류가 보고됩니다. Uniswap에서는 트랜잭션 콜백 함수에서 특수 오류를 발생시킨 다음 오류를 캡처하고 오류 메시지에서 필요한 정보를 구문 분석합니다.

꽤 해키해보이지만 매우 실용적이기도 합니다. 이렇게 하면 추정 거래의 요구 사항을 충족하기 위해 스왑 방법을 수정할 필요가 없으며 논리가 더 간단해집니다. 우리 과정에서는 이 논리를 참조하여 https://github.com/WTFAcademy/WTF-Dapp/blob/main/demo-contract/contracts/wtfswap/SwapRouter.sol 계약도 구현했습니다.

정밀도 문제를 해결하려면 큰 숫자를 사용하세요.

Uniswap의 코드에는 현재 가격과 유동성을 기준으로 교환된 토큰을 계산하는 등 많은 계산 로직이 있습니다. 이 과정에서 분할 작업 중에 정밀도가 떨어지지 않도록 해야 합니다. Uniswap에서 계산 프로세스는 종종 "2^96"을 곱하는 것과 동일한 96비트 왼쪽 시프트를 나타내는 "<<FixedPoint96.RESOLUTION" 연산을 사용합니다. 나눗셈 연산은 왼쪽 시프트 이후에 수행되므로 일반 트랜잭션에서 오버플로 없이 정확성을 보장할 수 있습니다(보통 계산에는 "uint256"을 사용하면 충분합니다).

코드는 다음과 같습니다(가격과 유동성을 통해 거래에 필요한 토큰 수를 계산).

Web3 초보자 시리즈: Uniswap 코드에서 배운 계약 개발 팁

Uniswap에서는 먼저 가격에 제곱근 "2^96"(위 코드의 "sqrtRatioAX96" 및 "sqrtRatioBX96"에 해당)을 곱한 다음 유동성 "유동성"이 이동하는 것을 볼 수 있습니다. "numerator1"을 계산하기 위해 남았습니다. 아래 계산에서는 최종 결과를 얻기 위해 계산 과정에서 "2^96"을 뺍니다.

물론, 이론상으로는 여전히 정확성이 떨어지겠지만, 이 경우에는 허용 가능한 가장 작은 단위의 손실입니다.

자세한 내용은 https://github.com/WTFAcademy/WTF-Dapp/blob/main/P106_PoolSwap/readme.md에서 이 과정에 대해 자세히 알아볼 수 있습니다.

Share 방법을 사용하여 소득 계산

유니스왑에서는 LP(유동성 공급자)의 수수료 수입을 기록해야 합니다. 당연히 모든 거래에서 각 LP에 대한 처리 수수료를 자체적으로 기록할 수는 없습니다. 이렇게 하면 많은 가스가 소모되기 때문입니다. 그렇다면 어떻게 처리해야 할까요?

Uniswap에서는 "Position"에 다음과 같은 구조가 정의되어 있는 것을 볼 수 있습니다.

Web3 초보자 시리즈: Uniswap 코드에서 배운 계약 개발 팁

여기에는 "feeGrowthInside0LastX128 및 feeGrowthInside1LastX128"이 포함되어 있으며, 각 포지션에 대해 처리 수수료가 마지막으로 인출되었을 때 각 유동성이 받아야 하는 처리 수수료를 기록합니다.

간단히 말하면, 총 취급수수료와 각 유동성에 얼마만큼의 취급수수료를 할당해야 하는지 기록만 하면 LP가 취급수수료를 인출할 때 유동성에 따라 얼마만큼의 취급수수료를 인출할 수 있는지 계산할 수 있습니다. 손. 특정 회사의 주식을 보유하고 있는 것처럼, 주식 수익을 인출하려는 경우 해당 회사의 과거 주당 수익과 마지막으로 인출했을 때의 수익만 알면 됩니다.

이전에 우리는 "기발한 계약 설계, stETH가 어떻게 매일 자동으로 수입을 분배하는지 볼까요?" 에 있었습니다. 안정적인 이자를 얻기 위해 ETH를 서약에 참여시키십시오. 이 기사에서는 유사한 stETH의 수입 계산 방법도 소개했습니다.

체인에서 모든 정보를 얻을 필요는 없습니다.

온체인 스토리지는 상대적으로 비용이 많이 들기 때문에 모든 정보를 체인에 업로드하거나 체인에서 얻을 필요는 없습니다. 예를 들어 Uniswap 프런트 엔드 웹 사이트에서 호출되는 인터페이스 중 다수는 기존 Web2 인터페이스입니다.

트랜잭션 풀 목록, 트랜잭션 풀 정보 등은 일반 데이터베이스에 저장될 수 있으며 일부는 체인에서 정기적으로 동기화해야 할 수도 있지만 실제로 체인이나 노드 서비스에서 제공하는 PRC 인터페이스를 호출할 필요는 없습니다. 관련 데이터를 얻을 시간입니다.

물론, 현재 많은 블록체인 PRC 공급업체가 일부 고급 인터페이스를 제공하고 있으며, 이는 더 빠르고 저렴한 방법으로 일부 데이터를 얻을 수 있는 것과 같습니다. 예를 들어, ZAN은 특정 사용자의 모든 NFT를 얻는 것과 유사한 인터페이스를 제공합니다. 이 정보는 분명히 성능과 효율성을 향상시키기 위해 캐시될 수 있습니다. https://zan.top/service/advance-api를 방문하면 더 많은 정보를 얻을 수 있습니다.

물론 주요 거래는 체인에서 이루어져야 합니다.

계약 분할 방법을 배우고 ERC721과 같은 기존 표준 계약을 사용하는 방법도 배웁니다.

프로젝트에는 실제로 배포되는 여러 계약이 포함될 수 있습니다. 실제로 배포되는 계약이 하나만 있는 경우에도 코드는 상속을 통해 유지 관리를 위해 계약을 여러 계약으로 분할할 수 있습니다.

예를 들어 Uniswap에서 https://github.com/Uniswap/v3-periphery/blob/main/contracts/NonfungiblePositionManager.sol 계약은 많은 계약을 상속합니다. 코드는 다음과 같습니다.

Web3 초보자 시리즈: Uniswap 코드에서 배운 계약 개발 팁

그리고 "ERC721Permit" 컨트랙트의 구현을 보면 "@openzeppelin/contracts/token/ERC721/ERC721.sol" 컨트랙트를 직접적으로 사용하고 있어 NFT를 통한 포지션 관리가 편리하다는 것을 알 수 있습니다. 한편, 기존 표준 계약을 사용하여 계약 개발 효율성을 향상시킬 수도 있습니다.

우리 과정에서는 https://github.com/WTFAcademy/WTF-Dapp/blob/main/P108_PositionManager/readme.md를 배우고 포지션을 관리하기 위한 간단한 ERC721 계약을 개발해 볼 수 있습니다.

요약

아무리 많은 글을 읽어도 스스로 개발을 시작하는 것만큼 실용적이지 않습니다. 간단한 버전의 분산형 거래소를 직접 구현해 보는 과정에서 Uniswap의 코드 구현에 대해 더 깊이 이해할 수 있으며, 실제 프로젝트에 대해서도 자세히 알아보세요. 지식 포인트를 경험해 보세요.

WTF-DApp 과정은 ZAN의 개발자 커뮤니티와 WTF Academy 개발자 커뮤니티 학생들이 공동으로 완료한 오픈 소스 과정입니다. Web3 및 Defi 프로젝트 개발에도 관심이 있으시면 저희 실무 강좌 https://github.com/WTFAcademy/WTF-Dapp를 참고하셔서 간단한 버전의 교환을 단계별로 완료하시면 도움이 되리라 믿습니다. 도움이 됐어요~

이 글은 ZAN Team(X 계정 @zan_team )의 Fisher(X 계정 @yudao1024 )가 작성했습니다.