Gas fee 상향에 따른 baobab transfer 문제

기존에는 문제 없었습니다. baobab의 gas price가 상향되면서 문제가 발생하였습니다.

문제발생) payable(msg.sender).transfer(wad);

이 코드 사용시 해결됨) payable(msg.sender).call{ value: wad }("");

dapp 구조상 payable contract로 klay를 transfer할 일이 잦습니다.
그동안 payable(컨트랙트).transfer(wad); 로 아무 문제없이 사용하고 있었는데요,
바오밥 가스 상향 후 문제가 발생하였습니다.

참고로 컨트랙트의 fallback function은 완전히 비어진 채로 작성되어 있습니다.
물론 hardhat의 gas기준은 750ston으로 잘 변경하였습니다.

eoa → A contract → B contract → C contract → D Contract: transfer klay to C
이 지점에서 revert가 발생합니다.

a,b,c,d 모든 컨트랙트의 fallback은 다음과 같습니다
fallback() external payable {}

  1. payable(msg.sender).call{ value: wad }(""); 로 수정시 정상작동합니다.
  2. eoa에서 곧바로 eoa → D Contract: transfer klay to eoa 를 사용하면 정상작동합니다.

해결법이 어떻게 되나요?

해당 문제로 wrappedKlay의 withdraw도 수정해야 하는 상황입니다.

그러나 upgradeable이 아니므로 굉장히 난감한 상황입니다.

하드햇 설정은 아래와 같습니다.

baobab: {
            url: "https://kaikas.baobab.klaytn.net:8651",
            chainId: 1001,
            gas: 20000000,
            gasPrice: 750000000000,
...

A,B,C,D 컨트랙트는 프록시 패턴입니다.

현재 완전히 같은 코드를 cypress에 올렸을때는 작동합니다.

Istanbul패치로 sload의 price가 올라가는군요?

proxy contract로의 transfer fallback시 엄청난 에러가 나올것으로 보이는데 어떻게 보시나요?
cypress도 완전히 같은 패치가 진행되나요?

3월 3일 Baobab에 적용된 것은 Gas Cost가 아닌 Gas Price의 변경입니다. 현재 말씀주신 내용으로는 internal contract 가 revert되는 이유를 확인하기 어려운데, 더 자세한 내용을 부탁드려도 될까요?

참고로 Baobab에 Istanbul 하드포크 내용은 2021년 11월에 이미 적용되었습니다. Sload gas cost 변경이 영향을 준 것으로 파악하고 계신가요?

1개의 좋아요

Cypress의 경우, 3월 31일에 Istanbul 하드포크 적용될 예정이고 (다른 하드포크도 함께 적용), gas price 변경은 4월 3일에 적용될 예정입니다.

11월 패치가 영향이 준것으로 판단하지는 않습니다.

부분입니다.
해당 gas cost가 아직 baobab에 반영되지 않았다는 말씀이신가요?

문제를 단순화하기 위해 아주 간단한 컨트랙트를 짜고 테스트해보았습니다.

contract MockWrap {
    function withdrawFromWKLAY(address payable wklay) public {
        // [baobab] normal contract: 성공
        // [baobab] proxy contract: 실패 (eoa -> proxy contract -> contract)
        // [cypress] normal contract: 성공
        // [cypress] proxy contract: 성공
        uint256 wad = IWKLAY(wklay).balanceOf(address(this));
        IWKLAY(wklay).withdraw(wad);
    }

    receive() external payable {}

    fallback() external payable {}
}

나머지가 모드 동일한 상황입니다. baobab에서 MockWrap contract르 transparrent proxy contract로 배포하여 위 function을 사용할시에만 문제가 발생합니다.

반영되었습니다. 다만, Baobab에 4달전에 반영되었는데 이슈는 최근에 직면하신 것 같아서 혹시나 싶어 말씀드렸습니다.

gas cost 상향은 의도된 부분이지요?
이 부분을 놓친 규모가 있는 dapp들이 꽤있지 않을까 싶습니다

네, gas cost 상향은 의도된 부분이며 작년 10월 Klaytn v1.7.0 release note에 바오밥 적용일자와 함께 공지되었습니다. 그런데, 아무래도 생태계에 충분히 전달되지 않은 듯 싶네요.

저희쪽에서도 공유주신 코드를 바탕으로 상세한 내용을 확인한 뒤, 이 부분에 대한 상세한 공지 또는 대응을 준비하도록 하겠습니다. 문제상황 및 재현코드 공유드린점 감사드리며, 확인 후 여기도 추가 답변드리겠습니다.

감사합니다.

만약 생태계 관점에서 대응이 된다면, sload 등의 코스트 상승을 보류하거나 transfer의 2300 limit을 상향시키는 방향으로 진행되는 걸지요?

@kltnutd 공유 주신 transaction의 실행과정을 trace 하여 다음 내용을 확인할 수 있었습니다.

  • 0xcaae92c6ec8db8992ced4af61a4d6687453ffd6d contract의 fallback은 비어있지 않으며, 두번의 SLOAD를 수행하고 있음 (ADMIN_SLOT, IMPLEMENTATION_SLOT 데이터 읽기 목적)
  • 해당 contract는 OpenZepplin의 BaseAdminUpgradeabilityProxy.solBaseUpgradeabilityProxy.sol 를 상속받아 구현된 것으로 추정되며, Proxy.sol fallbcak 함수 동작과 동일하게 fallback 호출 시 _fallback() 함수를 수행하는 것으로 파악됨

즉, 모든 Proxy 패턴의 contract에 문제가 생기는 것이 아니며 예시로 전달 주신 contract의 fallback 함수에 이미 2300에 가까운 gas cost를 사용하도록 구현되어 있었다고 판단됩니다.

그리고, 다음과 같은 이유로 기존의 CallStipend gas cost의 변경없이 예정된 변경 (SLOAD gas cost) 상승을 그대로 진행하려 합니다. 오늘 공지 예정이던 Klaytn v1.8.0 Release note에 이와 관련된 내용만 다시 한번 언급하려하는데, 혹시 다른 의견 있으시면 댓글 또는 DM으로 공유해주시면 감사하겠습니다.

  • SLOAD의 gas cost 상승은 이미 5개월 전 Klaytn v1.7.0 Release note 공지되었으며, Baobab에는 4개월전 적용되어 수정할 시간이 충분히 제공되었다고 생각됨
  • 이더리움 진영에서 2019년에 동일한 문제를 겪고난 뒤, fallback 함수에서는 gas cost의 변경에 대비하여 많은 gas cost를 사용하지 않는 것은 잘 알려진 solidity 개발 practice라고 생각됨
  • 변경 후, fallback 함수에 2300 이상 gas cost를 사용하는 것은 모든 Proxy contract의 공통적인 문제가 아니기에 Klaytn 생태계 파급력이 과도하다고 파악하기 어려움 (Open source 하지 않은 프로젝트들이 많아 파급력 파악이 어려움)

이와 관련하여 KIP 제안주신 내용은 검토해보겠지만, 현재 이슈에 대한 근본적인 답은 아니라고 생각됩니다. fallback에서 사용하는 Opcode 종류에 따라서 이번 변경으로 인해 증가될 gas cost는 다를 것입니다. 또한, gas cost는 지속적으로 변경되는 값이기에 이번에 CallStipend을 향상 시키더라도 다음 하드포크 때 동일한 문제가 발생하리라 생각됩니다.

빠르게 확인해주시고 답변주신 점 감사드립니다.
공지는 올바른 시점에 되었다고 생각합니다.

혹시라도 이 사안을 파악하지 못한 팀이 있다면 저희 보고가 도움이 되었길 바라는 마음에서 contribution 하였습니다:)
생태계 관점에서 추가로 확인하실만한 정보가 들려오면 공유드리겠습니다.

많은 도움이 되었습니다. 덕분에 다시 한번 변경으로 인해 발생할 수 있는 부분을 미리 검토해볼 수 있었고, 공지도 신경쓸 수 있게되었습니다. 포럼에 올려주셔서 다른 프로젝트들도 볼 수 있게 공유해주셔서 감사합니다.

앞으로는 생태계에 영향을 줄 수 있는 Klaytn의 변경을 개발과정에도 공유하고 서비스 개발자들과 소통할 수 있는 구조를 만들도록 하겠습니다. 공지 전달력도 조금 향상시켜야할거 같네요.

그리고, KIP도 처음으로 GroundX나 Krust가 소속이 아닌 분께서 의미있는 제안을 해주셔서 반가웠습니다. :slight_smile:

감사합니다!

1개의 좋아요

안녕하세요,

죄송한데 제가 이해가 부족하여 이 상황이 왜 벌어지는지 궁금해서 답글을 달게 되었습니다.
Proxy contract 로 인해 일반 contract 에서보다 gas 를 더 많이 사용하고 따라서 total gas fee 가 더 많이 사용된다는 것은 이해를 합니다.
그리고 1 gas에 대한 fee 상향이 돼서 기존 보다 더 많은 gas 가 나간다는 점도 이해가 됩니다.
revert 가 된 이유는, transfer() 함수는 2300 gas 가 고정인데, gas price 가 올라서 transaction이 필요한 gas 가 2300이 넘어서 block 에 기록이 되지 않았다는 것으로 이해했습니다.

근데 contract에 transfer() 함수를 사용할때 gas 가 2300 limit 이 있는 이유가 reentry attack 을 방어하기 위함인것으로 아는데 이게 고정이면 gasPrice 가 오르면 transfer() 를 proxy pattern 으로 사용하는 모든 코드가 영향을 받는것은 사실 아닌가요? (대부분 openzep 을 상속받아 구현할테니)
=> 5개월전 공지 됐던 거라 그전에 알아서 gas 를 더 넉넉하게 쓰는 call() 함수로 수정했었어야 한다. 가 klaytn 쪽의 설명인건가요? 그리고 앞으로도 transfer() 함수를 쓰지 말고 call() 위주로 구현해야한다가 결론이 될까요?

감사합니다!