일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- 컨트렉트 동일한 함수이름 호출
- ambiguous function description
- 프록시배포구조
- git rebase
- rust 기초
- nest.js설명
- ethers type
- 러스트 기초
- 러스트 기초 학습
- ethers v6
- ethers
- 티스토리챌린지
- 스마트컨트렉트 예약어 함수이름 중복
- multicall
- SBT표준
- 스마트 컨트렉트 함수이름 중복
- 컨트렉트 배포 자동화
- vue기초
- 오블완
- 체인의정석
- 머신러닝기초
- 스마트컨트렉트테스트
- 스마트컨트렉트 함수이름 중복 호출
- ethers typescript
- chainlink 설명
- Vue.js
- 러스트기초
- Vue
- ethers websocket
- 스마트컨트렉트프록시
- Today
- Total
체인의정석
Openzepplin - Address 라이브러리 분석 본문
zapper 코드를 분석하다가 Address 부분을 발견하여 글로 한번 남겨본다.
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol
* [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
또한 리턴도 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. (0) | 2022.08.11 |
Solidity) 스마트컨트렉트에서 이더리움을 보내는 방법 (0) | 2022.08.10 |
스마트컨트렉트 테스트에 Infinite Approve 적용하기 (0) | 2022.07.25 |