| 일 | 월 | 화 | 수 | 목 | 금 | 토 | 
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 | 
| 9 | 10 | 11 | 12 | 13 | 14 | 15 | 
| 16 | 17 | 18 | 19 | 20 | 21 | 22 | 
| 23 | 24 | 25 | 26 | 27 | 28 | 29 | 
| 30 | 
- cloud hsm 서명
 - rust 기초
 - 스마트컨트렉트 예약어 함수이름 중복
 - erc4337 contract
 - ethers v6
 - git rebase
 - 체인의정석
 - ethers typescript
 - Vue
 - 러스트 기초
 - 컨트렉트 동일한 함수이름 호출
 - vue기초
 - 러스트 기초 학습
 - 티스토리챌린지
 - ambiguous function description
 - 오블완
 - ethers type
 - ethers websocket
 - Vue.js
 - cloud hsm 사용하기
 - 스마트 컨트렉트 함수이름 중복
 - redux 기초
 - redux toolkit 설명
 - 러스트기초
 - cloud hsm
 - erc4337
 - 스마트컨트렉트 함수이름 중복 호출
 - 머신러닝기초
 - SBT표준
 - 계정추상화
 
- Today
 
- Total
 
체인의정석
Openzepplin - Address 라이브러리 분석 본문
zapper 코드를 분석하다가 Address 부분을 발견하여 글로 한번 남겨본다.
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol
GitHub - OpenZeppelin/openzeppelin-contracts: OpenZeppelin Contracts is a library for secure smart contract development.
OpenZeppelin Contracts is a library for secure smart contract development. - GitHub - OpenZeppelin/openzeppelin-contracts: OpenZeppelin Contracts is a library for secure smart contract development.
github.com
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.
        return account.code.length > 0;
    }
지갑 구조에서 EOA는 스토리지가 없기 때문에 이를 이용해서 컨트렉트 인지 보는 부분인데 여기서 플래시론 공격을 가지고 체크하는 로직이 있나보다. + 컨트렉트 생성자에서 하는 호출 및 gnosis safe의 멀티시그 월렛에서 서비스를 사용못하는 문제가있다고 한다.
생성자 호출에 대해서는 주석이 더 달려있는데
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.
이 부분을 보면 컨트렉트가 생성되는 생성자 부분에서는 생성자를 실행하는 마지막 단계에서 코드가 저장된다고 나와 있다.
한마디로 컨트렉트 호출을 생성자에서 한번 쏠때는 codesize가 0인 상태로 트랜잭션이 갈 수 있다고 볼 수 있다.
그럼 isContract를 하고 싶다면 어떻게 해야할까?
바로
msg.sender == tx.origin을 써야 한다고 한다.
tx.origin을 직접 명시하면 누가 서명했는지를 알 수 있다고 한다.
또한 보통 컨트렉트 형태의 지갑은 일반인들이 잘 사용하지 않지만 gnosis safe는 일반 유저들도 쉽게 멀티시그 지갑을 제공해 주고 있어서 많이 사용되는것 같다. 아무튼 지갑이라 해서 EOA만 있지는 않아서 그런것 같다.
다음 eth send 할 때 gas limt 관련 sendValue를 따로 만들어 놨다.
이건 매우 유명한 오류인데 transfer 함수라고 이더리움을 보내는 함수 가 잇는데 이때 이걸 보통 call을 사용해서 구현한다. 그 이유는 내장된 tansfer 구문의 경우 하드포크 이후에 영향을 받아 2300가스 리밋을 넘어가서 안되는 경우가 발생하기 때문이다.
아무튼 이더리움 전송해야한다? 오픈제플린의 sendValue를 쓰면 될것 같다.
    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");
        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }
그 다음 functionCall
그냥 call을 부르는 것은 안전하지 않기 때문에 이 함수를 쓰라고 한다.
만약 대상이 revert reason과 함께 반환을 하게 되면 bubble up? 더 명확해진다는 뜻인거 같은데
아무튼 이걸 사용해서 call을 하면 일반적인 솔리디티 call에서 revert 메세지를 리턴할때 정상적으로 수행해줄 수 있다. 이런 의미 같다.
https://www.macmillandictionary.com/dictionary/british/bubble-up
BUBBLE UP (phrasal verb) definition and synonyms | Macmillan Dictionary
Definition of BUBBLE UP (phrasal verb): increase and become more obvious
www.macmillandictionary.com
또한 리턴도 raw data를 리텅해서 정확히 받는 값과 같이 되게 나오는 것이라고 한다.
여기서 target은 당연히 컨트렉트일 것이고,
target을 data로 불렀을 때 revert가 나면 안된다는 조건이 걸려있다.
이게 정말 call을 종류별로 다 쪼개놨는데 조건이 다 다르다.
    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }
    
        /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }
그 다음은 delegateCall을 하는 부분인데 나오는 응답값이 성공적으로 나온다고 해도 컨트렉트로 보내는 경우가 아닌 일반 지갑주소가 보낸 경우는 잡아낸다.
    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }
    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }
    
        /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }
마지막으로 verifyCallResult가 있는데 revert 이유를 명확히 제시해주거나 나온 에러를 사용하여 보여주는 것이라고 할 수 있다.
    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }
    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
일단 성공이 안된 상황인데 데이터가 있다면 assembly로 다시 한번 접근을 해서 데이터 사이즈를 가져와서 이를 revert에 태워서 오류 메세지를 보내준다.
'블록체인 > Solidity' 카테고리의 다른 글
| Contract `` has a constructor Define an initializer instead (0) | 2023.02.28 | 
|---|---|
| 스마트컨트렉트 주석 달기 및 solidity-docgen 사용하기 (0) | 2023.01.12 | 
| payable 사용시 발생하는 에러 ParserError: Expected primary expression. (1) | 2022.08.11 | 
| Solidity) 스마트컨트렉트에서 이더리움을 보내는 방법 (0) | 2022.08.10 | 
| 스마트컨트렉트 테스트에 Infinite Approve 적용하기 (0) | 2022.07.25 |