새로운 것을 만들기 위해 작업하는 내용들을 그때그때 메모하는 글입니다.
npx create-next-app@latest
Vscode 플러그인 설치 - stylelint, Prettier-code formatter, ESLint
{
"singleQuote": true,
"semi": true,
"useTabs": false,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 120,
"arrowParens": "always"
}
vscode 자동저장
npm install react-redux
npm install @reduxjs/tookit
npm install next-redux-wrapper
store/index.ts
import { combineReducers, Store, CombinedState, AnyAction } from 'redux';
import { configureStore, EnhancedStore } from '@reduxjs/toolkit';
import { MakeStore, createWrapper, HYDRATE } from 'next-redux-wrapper';
// import testReducer from './redux/reduxSlice';
const rootReducer = (state: any, action: AnyAction): CombinedState<any> => {
switch (action.type) {
case HYDRATE:
return { ...state, ...action.payload };
default: {
const combinedReducer = combineReducers({
// testReducer,
});
return combinedReducer(state, action);
}
}
};
export const store = configureStore({
reducer: rootReducer,
devTools: process.env.NODE_ENV !== 'production',
});
const makeStore: MakeStore<EnhancedStore> = () => store;
export const wrapper = createWrapper<Store>(makeStore, { debug: process.env.NODE_ENV !== 'production' });
export type RootState = ReturnType<typeof rootReducer>;
export type AppDispatch = typeof store.dispatch;
npm install solc
타입스크립트 사용을 위해 types 설치
npm install types-solc
참고: https://github.com/uiwjs/react-textarea-code-editor
React Hook "useAppDispatch" is called in function "fetchContractSourceCode" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
export const fetchContractSourceCode = async (params: EtherScanApiQueryParams) => {
const dispatch = useAppDispatch();
try {
const response = await etherscan.get<ContractSourceCodeResponse>('', {
params,
});
if (response.data.result[0]) {
dispatch(fetchSuccessContractSourceCode(response.data.result[0]));
}
return response.data;
} catch (error) {
console.error('Error fetching articles:', error);
}
};
export const viewerSlice = createSlice({
name: 'viewer',
initialState,
reducers: {
init: () => initialState,
fetchSuccessContractSourceCode: (state, action: PayloadAction<ContractSourceCode>) => {
state.sourceCode = action.payload;
},
},
});
리액트 훅 사용 규칙 위반했다고 한다. 리액트 컴포넌트가 아닌 상위의 위치에서 훅을 사용하면 안된다고 한다.
component (view)에서 api를 호출하고, api (server, api.ts)에서 응답 결과를 reducer에 저장하려고 했다.
위와 같은 이유로 패턴을 다음과 같이 변경한다.
component (view) -> action -> api 호출 -> api.ts (server) -> reducer
생각해보면 항상 사용하던 flux 패턴처럼 됐는데, redux-toolkit을 사용한 덕분에 action, reducer를 단일 파일로 관리하게 되었고, 백앤드 서버를 next.js 14에서 자체적으로 서버 사이드에서 api 처리가 가능해지는 바람에 좀 더 간결하게 하려했다.
redux-toolkit docs: "Redux Toolkit은 Redux 기능의 코드를 여러 개의 분리된 파일에 분산하는 대신 단일 파일에 작성하기 쉽게 만들어줍니다"
이더스캔에서 제공하는 스마트 컨트랙트의 소스코드 타입은 solidity, solidity (json), vyper로 구분하고 있다.
스마트 컨트랙트 0.4.0에서 컴파일된 구버전 소스코드를 살펴보면 소스코드 구조가 평문으로 저장되어있고 (solidity), 0.6.0 버전의 유니스왑 스마트 컨트랙트는 json 형태로 구조화되어 있는 것을 확인할 수 있다. (solidity json)
이더리움 문서에서 컴파일시 json 인아웃풋 인터페이스를 사용한다고 한다.
참고자료: https://docs.soliditylang.org/en/v0.5.8/using-the-compiler.html#compiler-input-and-output-json-description
0.5.8버전 문서에 recommended라고 써져있고, 0.4.0 컴파일된 구버전 소스코드 저장방식을 확인했을 때 구버전 및 현재도 옵션으로 사용되는 것으로 보인다.
자세한 차이점을 확인하기 위해 etherscan에 sourcecode verify를 통해 확인한다.
코드 업로드는 my-virtual-animal에서 goerli 네트워크에 배포했던 소스코드를 사용한다.
먼저 단일 파일 업로드로 테스트한다.
이 때 import 컨트랙트를 어떻게 처리하는지 궁금하다.
예상처럼 import 컨트랙트 파일이 없으니 단일 파일로는 컴파일이 실패한다. 소스코드를 입력받을 때 해당 파일에서 불러오는 외부 컨트랙트의 solidity 버전을 모르는데 어떻게 컴파일하지 라는 의문을 해결했다.
직접 외부 컨트랙트 코드를 멀티 파일로 업로드하거나, 해당 외부 컨트랙트 코드를 복사해서 단일 파일로 만들어서 업로드해야 할 것 같다.
단일 파일로 합치려고 했는데, 파일이 너무 많아서 새로운 코드를 배포하고 테스트를 진행한다.
참고자료: https://solidity-by-example.org/app/erc20/
코드는 erc-20 토큰을 발행하는 컨트랙트를 만들어 배포하고, 검증은 단일 파일로 만들어서 테스트한다.
코드 발행은 오픈제플린 라이브러리를 사용하여 예제 contract -> erc-20 -> ierc-20으로 총 3개의 컨트랙트를 사용한다.
테스트 코드는 다음과 같다. RagonErc20Test라는 소스코드를 3개 생성했다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./ERC20.sol";
contract RagonErc20Test01 is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {
// Mint 100 tokens to msg.sender
// Similar to how
// 1 dollar = 100 cents
// 1 token = 1 * (10 ** decimals)
_mint(msg.sender, 100 * 10 ** uint(decimals()));
}
}
import한 erc20 파일은
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/contracts/token/ERC20/ERC20.sol
소스 코드를 사용했고, 오픈제플린 erc20에서는 ierc20과 context util 파일을 사용했는데, 해당 유틸파일 사용 없이 할 수 있도록 약간의 수정했다.
따라서, ierc20, 수정한 오픈제플린 erc20, RagonErc20Test으로 이루어져있다.
배포는 총 3개 RagonErc20Test01, 02, 03 컨트랙트를 배포해서 각각 단일 파일, 멀티 파일, 그리고 json 방식이 아닌 구버전 컴파일러로 할 수 있도록 준비했다.
const RagonErc20Test01 = artifacts.require("RagonErc20Test01");
module.exports = function (deployer) {
deployer.deploy(RagonErc20Test01, "Ragon01", "RAGON");
};
테스트로 배포하는 토큰 이름, 심볼은 Ragon01, RAGON으로 했다.
배포 컴파일러 - solc: 0.8.21+commit.d9974bed.Emscripten.clang
truffle migrate --network goerli
Georli 네트워크에 다음과 같이 스마트 컨트랙트 배포 완료
RagonErc20Test01 Address: 0x2fc29817e2728e86929f40dB6b010F7Cc1227Ad2
RagonErc20Test02 Address: 0x0e932bcE29e926880dcC325704B941bc83eaa0AC
RagonErc20Test03 Address: 0x6123b29d4A9320b8c31f482C8f0841ab294F735e
Ragon01을 기준으로 이더스캔에서 조회하면 스마트컨트랙트 코드는 없고, 배포된 바이트 코드를 확인할 수 있다.
실험단계의 디컴파일 기능을 제공하여 사용한 결과
해당 컨트랙트의 최초 디컴파일이고, 30초후에 새로고침해서 결과를 확인하라고 한다.
하지만, 정상적으로 소스코드가 디컴파일 되진 않았다.
유사 스마트코드 검색 기능이 있어서 사용해봤다.
마찬가지로, exact 옵션으로 이더리움 메인넷과 테스트넷을 함께 검색했지만 결과가 나오지 않았다.
아마도, 오픈제플린 유틸 코드를 수정해서 안나온 것 같다.
하지만, similar로 변경시 방금 배포한 ragon02, ragon03과 함께 많은 결과물이 나왔다.
ragon1, 2, 3이 exact로 안나오고 similar에서 나온 이유는 생성자 파라미터만 달라서 그런 것 같다.
어차피 바이트 코드의 일치 여부는 이후 contract verify때 진행될 예정이므로 동일한 코드를 배포하진 않는다.
추가적으로 diff checker라는 서비스를 제공하고 있는데, 소스코드가 없기 때문에 정보를 제공하지 못하고 있다.
이제 소스코드를 업로드해서 컴파일되는 방식과 바이트 코드 유사도, 이더스캔에서 제공하는 여러 서비스를 테스트해본다.
const RagonErc20Test01 = artifacts.require("RagonErc20Test01");
module.exports = function (deployer) {
deployer.deploy(RagonErc20Test01, "Ragon01", "RAGON");
};
테스트로 배포하는 토큰 이름, 심볼은 Ragon01, RAGON으로 했다.
배포 컴파일러 - solc: 0.8.21+commit.d9974bed.Emscripten.clang
truffle migrate --network goerli
Georli 네트워크에 다음과 같이 스마트 컨트랙트 배포 완료
RagonErc20Test01 Address: 0x2fc29817e2728e86929f40dB6b010F7Cc1227Ad2
RagonErc20Test01 Address: 0x0e932bcE29e926880dcC325704B941bc83eaa0AC
RagonErc20Test01 Address: 0x6123b29d4A9320b8c31f482C8f0841ab294F735e
Ragon01을 기준으로 이더스캔에서 조회하면 스마트컨트랙트 코드는 없고, 배포된 바이트 코드를 확인할 수 있다.
실험단계의 디컴파일 기능을 제공하여 사용한 결과
해당 컨트랙트의 최초 디컴파일이고, 30초후에 새로고침해서 결과를 확인하라고 한다.
하지만, 정상적으로 소스코드가 디컴파일 되진 않았다.
유사 스마트코드 검색 기능이 있어서 사용해봤다.
마찬가지로, exact 옵션으로 이더리움 메인넷과 테스트넷을 함께 검색했지만 결과가 나오지 않았다.
아마도, 오픈제플린 유틸 코드를 수정해서 안나온 것 같다.
하지만, similar로 변경시 방금 배포한 ragon02, ragon03과 함께 많은 결과물이 나왔다.
ragon1, 2, 3이 exact로 안나오고 similar에서 나온 이유는 생성자 파라미터만 달라서 그런 것 같다.
어차피 바이트 코드의 일치 여부는 이후 contract verify때 진행될 예정이므로 동일한 코드를 배포하진 않는다.
추가적으로 diff checker라는 서비스를 제공하고 있는데, 소스코드가 없기 때문에 정보를 제공하지 못하고 있다.
이제 소스코드를 업로드해서 컴파일되는 방식과 바이트 코드 유사도, 이더스캔에서 제공하는 여러 서비스를 테스트해본다.
컴파일러 타입은 싱글파일 (ragon1), 멀티파트파일 (ragon2), json-input (ragon3) 타입으로 각각 테스트한다.
첫 번째 싱글 파일의 컨트랙트 Ragon은 소스 코드에 MIT License로 명시했지만 이더스캔 인터페이스에는 No License (None)으로 체크하여 컴파일 결과를 확인해본다.
기본적으로 이더스캔에서 해당 컨트랙트의 바이트코드를 읽어 constructor arguments 바이트 코드를 추정한다.
0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000075261676f6e30310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000055241474f4e000000000000000000000000000000000000000000000000000000
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.0/contracts/token/ERC20/IERC20.sol
interface IERC20 {
...
}
pragma solidity ^0.8.0;
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/contracts/token/ERC20/ERC20.sol
contract ERC20 is IERC20 {
...
}
pragma solidity ^0.8.20;
contract RagonErc20Test01 is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {
// Mint 100 tokens to msg.sender
// Similar to how
// 1 dollar = 100 cents
// 1 token = 1 * (10 ** decimals)
_mint(msg.sender, 100 * 10 ** uint(decimals()));
}
}
변경점은 SPDX 라이센스가 중복되어 오류가 발생해서 최상단에 하나만 기재했고, import 문구를 없애고 종속성 순서대로 나열했다. (IERC20 -> ERC20 -> Ragon01)
코드가 너무 길어서 ...으로 생략했다.
모든 컨트랙트가 식별됐지만 바이트 코드가 일치하지 않아서 에러가 발생했다.
아마도, constructor 파라미터를 제거해서 결과가 달라진 것 같다.
Bytecode (what we are looking for):
608060405234801562000010575f80fd5b5060405162001d3e38038062001d3e
... 55241474f4e000000000000000000000000000000000000000000000000000000
Ragon01
608060405234801562000010575f80fd5b5060405162000e1a38038062000e1a
...
11156102605761026061084e56fe{ipfs}64736f6c63430008150033
실제 바이트 코드 Ragon1
0x608060405234801561000f575f80fd5b50600436106100a
...
e11cd25634e64736f6c63430008150033
이더스캔에서 제공하는 constructor 관련 문서: https://info.etherscan.com/contract-verification-constructor-arguments/
동일한 에러 발생
에러는 발생했지만, ragon01 컨트랙트의 바이트 코드 마지막에 입력한 생성자 바이트 코드가 추가로 붙은걸 확인할 수 있었다.
여기서 한가지 의아한 점은 이더스캔에서 verify에서 제공하는 Bytecode (what we are looking for)와 실제 배포된 바이트 코드와 다르다.
성공한 소스코드 바이트 코드를 보여준다.
추가적으로 ABI도 생성해준다.
문제는 생성된 바이트코드를 비교해본 결과 약간 다른점을 찾을 수 있었다.
위 그림은 이더스캔에서 소스코드 업로드 성공 후 생성된 바이트 코드이다.
문제는, 하이라이트 되어있는 부분부터가 실제 배포된 바이트 코드와 일치하는 영역이고 그 전 부분은 실제 배포된 바이트 코드와 다르다.
이더스캔 스마트 컨트랙트 페이지에서 업로드된 데이터를 살펴보면 코드 맨 윗줄에 이더스캔에서 주석을 추가했다는 정보가 포함되어 있다.
따라서, 이더스캔에서 소스코드를 입력받으면 자체적으로 데이터를 추가해서 저장하는 것으로 추정된다.
추가적으로 하단에 컨트랙트 생성 코드와 배포된 바이트 코드를 보여준다. 여기에 있는 컨트랙트 생성 코드가 파일 업로드 후 변경되었던 스마트 컨트랙트 바이트 코드와 동일하므로 이더스캔에서 변경하여 저장된 정보라고 생각된다. 이 데이터를 바이트 디컴파일 서비스로 돌렸을 때, 실제 소스코드와 동일한 값이 출력된다면 이더스캔은 바이트 코드로만 저장하고 실제 소스코드를 보여줄 땐 디컴파일하여 보여주는 것으로 생각해볼 수 있겠다.
등록에 성공했으니 etherscan api를 통해 해당 소스코드를 호출해본다.
api-goerli.etherscan.io/api 컨트랙트 조회 결과
입력했던 정보들이 잘 나오고, SourceCode 시작점을 보면 이더스캔에서 생성했던 수정된 코드가 아닌, 원본 코드를 정상적으로 반환하는 것을 확인할 수 있다.
json viewer로 확인한 결과, 다음과 같이 json 구조가 아닌 소스코드 텍스트를 확인할 수 있다.
이더스캔에서는 다음과 같이 verify contracts 목록에서 확인할 수 있었다.
다음으로 Solidity(JSON) 이라고 분류되어 있는 Standard JSON-interface 방식으로 스마트 컨트랙트를 컴파일한다.
이전에 생성해놓은 ragon2를 multi-part files로 업로드한다.
첫 번째 시도
이번엔 업로드 옵션으로 실제 배포했던 컴파일러 버전보다 하위 버전으로 시도한다.
사용한 세 개의 컨트랙트 파일을 업로드한다.
실패했다.
두 번째 시도
컴파일러 버전을 실제 배포환경과 동일하게 변경하고 시도한다.
evm은 항상 지정하지 않고 디폴트를 사용하더라도 컴파일러 버전은 정확히 일치해야 하는 것 같다.
마찬가지로, ragon2 컨트랙트도 api를 통해 조회한다.
Ragon1과 동일한 결과라서 굳이 JSON Viewer로 확인하지 않아도 된다.
multi-part files 방식의 컴파일 특징은, 단일 파일 컴파일과 다르게 중복된 import를 삭제하지 않았고 SPDX 라이센스의 중복도 따로 변경하지 않았다. 이 부분은 컴파일러에서 자동으로 해결하는 것 같은데 이후에 solc 사용하면서 다시 재확인이 필요해 보인다.
마지막으로, Standard-Json-Input 컴파일 타입으로 소스코드를 업로드한다.
다음과 같이 Json 파일 업로드에 주의사항이 적혀있다.
- Contract sources in the json file must be formatted as Literal contents and NOT as urls
- Use multiple literal {"content": "", ...} for multi part contracts containing multiple source files
- A serializing raw text tool for converting objects to JSON string is also available.
설명이 부족해서 일단 truffle compile에서 생성된 abi json 파일들을 입력했다.
멀티파트 입력이 아니라서, 우선 ragon2의 abi json 파일만 업로드한다.
당연히 에러가 발생했고, 정보를 좀 더 찾아본다.
참고 자료: https://docs.soliditylang.org/en/v0.5.7/using-the-compiler.html#compiler-input-and-output-json-description
제공되는 공식 문서에 따르면, Input Description을 확인할 수 있다.
solidity JSON Input은 사용자가 직접 만들어서 solc에 입력으로 쓸 수 있고, JSON Output은 solc에서 컴파일 옵션을 통해 생성된다.
따라서, solc 컴파일러를 먼저 사용해서 json 입출력 파일을 생성해본다.
npm i -g solc
npx solc --version
// 0.4.26+commit.4563c3fc.Emscripten.clang
최신 컴파일러 버전은 0.8.23인데, 0.4.26버전이라고 출력된다.
npm ls -g
npm uninstall -g solc
npm i -g solc@0.8.23-fixed
npx solc --version
// 0.8.23+commit.f704f362.Emscripten.clang
npm 저장소에 있는 최신 버전을 보고 직접 입력해서 재설치하니 정상적으로 최신업데이트가 됐다.
help를 통해 지원하는 solc 옵션을 살펴본다.
사용법은 단순해보인다. 최적화 옵션은 truffle 배포시에 사용하지 않았으니, 기본값 false로 사용한다.
optimize-runs는 해당 컨트랙트의 실행될 횟수에 맞춰서 최적화가 된다고 한다. 기본적으로 runs=200을 기준으로 default 최적화가 이루어진다.
npx solc --bin ./contracts/RagonErc20Test01.sol -o solcOutput/
solcOuput 파일에 생성된 bin 파일은 다음과 같다.
contracts_RagonErc20Test01_sol_ERC20.bin
contracts_RagonErc20Test01_sol_IERC20.bin
contracts_RagonErc20Test01_RagonErc20Test01.bin
특이사항으로는 IERC20은 인터페이스라서 바이트코드가 생성되지 않았다.
solc --bin ./contracts/RagonErc20Test01.sol
solc로 직접 컴파일한 결과와 deployed bytecode, creation bytecode 전부 일치하지 않았다.
solc로 컴파일한 bytecode는 etherscan에서 Contract Creation Code이고, 해당 코드는 최초 스마트 컨트랙트 코드를 발행하는 코드라고 한다. 이 코드에서 생성자 함수나, 인자 정보가 삭제되어 deployed bytecode로 변환되어 블록체인 네트워크에 실행할 수 있도록 저장된다고 한다.
하지만, solc에서 deployed bytecode로 컴파일하는 옵션이 없었고 creation code로부터 cbor 디코딩을 통해 deployed bytecode를 생성하는 방법을 찾기 어려웠다.
따라서 조금 더 단순한 컨트랙트 ragon4, ragon5를 여러개 배포해서 직접 변환되는 바이트 코드를 확인하려 한다.
ragon1같은 경우 단일 파일로 생성하기 위해 import contract 코드들을 직접 수정했기 때문에 비교하기가 어려웠기 때문이다.
ragon4는 단순한 view 함수 하나만 작성했고, ragon5는 creation bytecode와 deployed bytecode의 변경을 확인하기 위해 constructor 함수를 사용해서 상태변경까지 추가했다.
ragon4_1 address: 0x4F2Ae35A6E072449728E0932834a486849A74698
ragon4_2 address: 0xc2F760240DbF0dD14315ed6cCC4c7dB5851ec386
ragon5_1 address: 0x
ragon5_2 address: 0x
ragon5_3 address: 0x
ragon4 deployed bytecode는 다음과 같다.
0x608060405234801561000f575f80fd5b5060043610610029575f3560e01c80637b4957e91461002d575b5f80fd5b61003561004b565b6040516100429190610112565b60405180910390f35b60606040518060400160405280600b81526020017f48656c6c6f205261676f6e000000000000000000000000000000000000000000815250905090565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156100bf5780820151818401526020810190506100a4565b5f8484015250505050565b5f601f19601f8301169050919050565b5f6100e482610088565b6100ee8185610092565b93506100fe8185602086016100a2565b610107816100ca565b840191505092915050565b5f6020820190508181035f83015261012a81846100da565b90509291505056fea2646970667358221220b91c17c8380e06ffaee9c83bfc5f1acf53daa082b5b1f4866c757aab6da470e864736f6c63430008150033
0x608060405234801561000f575f80fd5b506101688061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c80637b4957e91461002d575b5f80fd5b61003561004b565b6040516100429190610112565b60405180910390f35b60606040518060400160405280600b81526020017f48656c6c6f205261676f6e000000000000000000000000000000000000000000815250905090565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156100bf5780820151818401526020810190506100a4565b5f8484015250505050565b5f601f19601f8301169050919050565b5f6100e482610088565b6100ee8185610092565b93506100fe8185602086016100a2565b610107816100ca565b840191505092915050565b5f6020820190508181035f83015261012a81846100da565b90509291505056fea264697066735822122037c3ea18e8403b7ca82ab4fae04738cc7ec92b627467a1b5ddd36e614637c14664736f6c63430008150033
608060405234801561000f575f80fd5b5060dd8061001c5f395ff3fe6080604052348015600e575f80fd5b50600436106026575f3560e01c80637b4957e914602a575b5f80fd5b604080518082018252600b81526a2432b63637902930b3b7b760a91b6020820152905160559190605e565b60405180910390f35b5f6020808352835180828501525f5b81811015608757858101830151858201604001528201606d565b505f604082860101526040601f19601f830116850101925050509291505056fea26469706673582212201e5d93a435c83ea30186255a49012b31ddc37edcb775e16cd3d73b2854416f1d64736f6c63430008150033
608060405234801561000f575f80fd5b506101688061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c80637b4957e91461002d575b5f80fd5b61003561004b565b6040516100429190610112565b60405180910390f35b60606040518060400160405280600b81526020017f48656c6c6f205261676f6e000000000000000000000000000000000000000000815250905090565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156100bf5780820151818401526020810190506100a4565b5f8484015250505050565b5f601f19601f8301169050919050565b5f6100e482610088565b6100ee8185610092565b93506100fe8185602086016100a2565b610107816100ca565b840191505092915050565b5f6020820190508181035f83015261012a81846100da565b90509291505056fea264697066735822122037c3ea18e8403b7ca82ab4fae04738cc7ec92b627467a1b5ddd36e614637c14664736f6c63430008150033
혹시나 해서 확인해봤는데 위의 optimize-runs 200 코드는 solc의 defualt 컴파일 옵션과 동일했다.
solc --help에서는 optimize가 기본으로 false라고 했는데 뭔가 좀 기분이 그렇다.
정리하자면 solc --bin default 컴파일 옵션은 runs200을 기준으로 최적화된 바이트 코드를 생성해준다.
etherscan에 optimize true, contract 이름을 변경 후 업로드
optimize false로 설정하니 정상적으로 업로드됐다.
생성된 contractBytecode
608060405234801561000f575f80fd5b506101688061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c80637b4957e91461002d575b5f80fd5b61003561004b565b6040516100429190610112565b60405180910390f35b60606040518060400160405280600b81526020017f48656c6c6f205261676f6e000000000000000000000000000000000000000000815250905090565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156100bf5780820151818401526020810190506100a4565b5f8484015250505050565b5f601f19601f8301169050919050565b5f6100e482610088565b6100ee8185610092565b93506100fe8185602086016100a2565b610107816100ca565b840191505092915050565b5f6020820190508181035f83015261012a81846100da565b90509291505056fea26469706673582212206a341065a640e669bf4d090650bbe532a53b74fadc2e541075ee9037cc60f01d64736f6c63430008150033
deployed Bytecode
0x608060405234801561000f575f80fd5b5060043610610029575f3560e01c80637b4957e91461002d575b5f80fd5b61003561004b565b6040516100429190610112565b60405180910390f35b60606040518060400160405280600b81526020017f48656c6c6f205261676f6e000000000000000000000000000000000000000000815250905090565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156100bf5780820151818401526020810190506100a4565b5f8484015250505050565b5f601f19601f8301169050919050565b5f6100e482610088565b6100ee8185610092565b93506100fe8185602086016100a2565b610107816100ca565b840191505092915050565b5f6020820190508181035f83015261012a81846100da565b90509291505056fea2646970667358221220b91c17c8380e06ffaee9c83bfc5f1acf53daa082b5b1f4866c757aab6da470e864736f6c63430008150033
ragon4_1 소스코드 업로드 후, ragon4_2를 컴파일러 버전 변경과 동일한 소스코드 입력했을 때 동일한 creation bytecode가 나오는지 확인하려고 했는데 다음과 같은 문구가 나왔다.
이로써 테스트 해볼 방법이 줄어들었다.
우선, ragon4_1 소스코드는 같은 컴파일러 옵션일 때 solc로 직접 컴파일한 bin 파일과 deployed bytecode가 달랐는데, etherscan은 어떻게 바이트 코드를 일치시킬 수 있는지 궁금하다.
이더스캔에서 동일한 deployed bytecode에 대한 verify를 막아서 메타데이터가 변경되는지 확인하기 어려웠지만, 검색을 통해 여러 정보를 얻을 수 있었다.
가장 먼저, solc 컴파일로는 deployed bytecode를 생성할 수 없다고 한다. 가장 의아한 부분인데, creation contract bytecode를 생성하고 블록체인에 전달하면 블록체인 노드에서 해당 데이터를 파싱해서 deployed bytecode만 저장하기 때문인 것 같다. 왜 옵션으로 deployed bytecode를 생성해주지 않는지는 이해하기 어렵다.
따라서, solc 컴파일로 생성한 creation contract bytecode를 구조를 분석해서 deployed bytecode로 변환하려고 시도했다.
creation contract 시작 부분에는 생성자 로직을 처리하는 방식이나 최적화 옵션에 따라 추가 정보가 생긴다. 코드의 마지막 2바이트는 소스코드의 해시, 컴파일 정보, 소스코드 위치 등을 포함하는 메타데이터를 CBOR 인코딩 방식으로 저장한다.
이를 통해 시작 부분과, 마지막 부분의 메타 데이터를 없애서 deployed contract를 생성하려고 했으나, 모든 컴파일러 버전에서 동일한 방식으로 적용될 것 같지 않아 호환성 문제와 더불어 수 많은 에러를 발생될 것을 우려해 다른 방법을 찾았다.
참고자료: https://ethereum.stackexchange.com/questions/63022/how-to-match-the-etherscan-io-bytecode-using-the-solc-compiler
이더리움 개발 환경 hardhat에서 소스코드로 deployed bytecode를 생성해준다고 해서 테스트를 진행한다.
hardhat도 내부적으로는 solc를 사용해서 컴파일을 진행하지만 어떻게 다르게 처리하는지 직접 살펴본다.
hardhat 참고자료: https://hardhat.org/hardhat-runner/docs/guides/compile-contracts
npm i -g hardhat
npx hardhat init
hardhat 프로젝트는 타입스크립트로 생성했고, 다음과 같이 기본 종속성을 추가한다.
npm install --save-dev "hardhat@^2.19.2" "@nomicfoundation/hardhat-toolbox@^4.0.0"
contracts 폴더에 ragon4 컨트랙트 코드를 복사하고 컴파일한다.
npx hardhat compile
컴파일은 compiler 버전 0.8.21이고 evm은 paris로 설정되어있다.
artifacts/contracts/RagonTest04.json 파일을 보면 내부에 bytecode와 deployedBytecode가 생성된걸 확인할 수 있다.
생성된 bytecode
0x608060405234801561000f575f80fd5b506101688061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c80637b4957e91461002d575b5f80fd5b61003561004b565b6040516100429190610112565b60405180910390f35b60606040518060400160405280600b81526020017f48656c6c6f205261676f6e000000000000000000000000000000000000000000815250905090565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156100bf5780820151818401526020810190506100a4565b5f8484015250505050565b5f601f19601f8301169050919050565b5f6100e482610088565b6100ee8185610092565b93506100fe8185602086016100a2565b610107816100ca565b840191505092915050565b5f6020820190508181035f83015261012a81846100da565b90509291505056fea264697066735822122037c3ea18e8403b7ca82ab4fae04738cc7ec92b627467a1b5ddd36e614637c14664736f6c63430008150033
생성된 deployed bytecode
0x608060405234801561000f575f80fd5b5060043610610029575f3560e01c80637b4957e91461002d575b5f80fd5b61003561004b565b6040516100429190610112565b60405180910390f35b60606040518060400160405280600b81526020017f48656c6c6f205261676f6e000000000000000000000000000000000000000000815250905090565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156100bf5780820151818401526020810190506100a4565b5f8484015250505050565b5f601f19601f8301169050919050565b5f6100e482610088565b6100ee8185610092565b93506100fe8185602086016100a2565b610107816100ca565b840191505092915050565b5f6020820190508181035f83015261012a81846100da565b90509291505056fea264697066735822122037c3ea18e8403b7ca82ab4fae04738cc7ec92b627467a1b5ddd36e614637c14664736f6c63430008150033
비교해보니, 예상과는 다르게 deployed bytecode가 생성은 되지만 실제 배포된 바이트 코드랑 일치하지 않았다.
근데 첫 번째 bytecode는 solc --bin으로 생성한 바이트 코드와 정확하게 일치한다.
정리해보면 다음과 같다.
etherscan: Creation Contract Bytecode (1 - 시작, 끝부분에 데이터 붙음), Deployed Bytecode (2)
solc: --bin --optimize-runs 200 (3)
hardhat: Bytecode (3과 동일), Deployed Bytecode (4)
이제 추측할 수 있는건 deployed bytecode 마지막에 붙는 메타 데이터만 제거해서 바이트코드를 비교하는게 아닐까 라는 생각이 든다.
hardhat.config에 solidity 설정에 대한 모든 값을 찾던 중 메타데이터 관련 옵션을 확인했다.
const config: HardhatUserConfig = {
solidity: {
version: "0.8.21",
settings: {
evmVersion: "shanghai",
optimizer: {
enabled: false,
},
metadata: {
// 메타데이터 설정
bytecodeHash: "none", // 바이트코드 해시 옵션
},
},
},
};
메타데이터 설정에서 bytecodeHash: none으로 체크 후 새로 컴파일해서 생성한 Deployed Bytecode
0x608060405234801561000f575f80fd5b5060043610610029575f3560e01c80637b4957e91461002d575b5f80fd5b61003561004b565b6040516100429190610112565b60405180910390f35b60606040518060400160405280600b81526020017f48656c6c6f205261676f6e000000000000000000000000000000000000000000815250905090565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156100bf5780820151818401526020810190506100a4565b5f8484015250505050565b5f601f19601f8301169050919050565b5f6100e482610088565b6100ee8185610092565b93506100fe8185602086016100a2565b610107816100ca565b840191505092915050565b5f6020820190508181035f83015261012a81846100da565b90509291505056fea164736f6c6343000815000a
마지막 끝자리에 붙는 메타 데이터의 길이만 줄어들고 아예 없어지진 않았다. 따라서, 완전하게 동일하진 않는다.
하지만, bytecode도 동일하게 변경됐다.
0x608060405234801561000f575f80fd5b5061013f8061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c80637b4957e91461002d575b5f80fd5b61003561004b565b6040516100429190610112565b60405180910390f35b60606040518060400160405280600b81526020017f48656c6c6f205261676f6e000000000000000000000000000000000000000000815250905090565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156100bf5780820151818401526020810190506100a4565b5f8484015250505050565b5f601f19601f8301169050919050565b5f6100e482610088565b6100ee8185610092565b93506100fe8185602086016100a2565b610107816100ca565b840191505092915050565b5f6020820190508181035f83015261012a81846100da565b90509291505056fea164736f6c6343000815000a
solc --bin 바이트 코드와 일치했었는데, 달라진 걸 보니 hardhat 에서 컴파일하는 기본 옵션에서 벗어난 것으로 생각된다.
지금까지의 내용을 토대로 유추해보면 etherscan에서 스마트 컨트랙트 소스 코드의 verify는 입력받은 소스코드의 컴파일을 통해 deployed bytecode의 메타 데이터를 제외한 나머지 바이트 코드가 일치하는지 확인하는 것으로 추정된다.
마지막으로 verify 과정에 대한 추론이 맞는지 다음과 같이 확인한다.
먼저, solc로 생성한 바이트 코드와 동일했던 hardhat 컴파일 옵션으로 deployed bytecode를 생성한다.
hardhat을 통해 실제로 스마트 컨트랙트를 배포한 후 etherscan에서 Deployed Bytecode가 일치하는지 확인한다.
해당 소스코드를 업로드해서 Bytecode가 동일하지 않지만 verify 과정이 통과 되는지 확인하면 될 것 같다.
점점 더 미궁에 빠져서 세상 모든 구글 정보를 다 찾아보던 찰나 다음과 같은 글에서 힌트를 얻었다.
참고자료: https://ethereum.org/en/developers/docs/smart-contracts/verifying/
소스코드 컴파일된 바이트 코드에 포함된 메타데이터는 컴파일러 버전이나 파일 해시에 대한 정보등을 저장하고 있으며, 메타데이터를 포함하여 모든 바이트 코드가 일치하면 "full verification", 메타 데이터 해시가 일치하지 않거나 검증에 고려되지 않으면 "partial match"라고 한다.
본 글에서는 이더스캔은 메타 데이터 해시를 검증하지 않아 partial match라고 하고, 이더스캔 블로그 글을 링크로 걸었지만 확인해본 결과, 해당 블로그 글에 메타 데이터 검증에 관련된 내용은 없었다.
하지만, 힌트를 얻은 부분이 있다.
hardhat에서 컴파일해서 얻은 deployed bytecode가 실제 배포시에 etherscan에서 eth_getcode로 조회한 내용이 같아서 검증할 방법이 없다고 생각했다.
메타 데이터를 검증하는지 확인하기 위해 동일한 스마트 컨트랙트 코드에서 다른 메타 데이터를 추가한 서로 다른 deployed bytecode를 가지고 etherscan verify를 시도하면 될 것 같다.
deploy는 hardhat init 프로젝트에서 제공하는 Lock 컨트랙트 배포 샘플을 참고했다.
import { ethers } from "hardhat";
async function main() {
const lock = await ethers.deployContract("RagonTest04");
await lock.waitForDeployment();
console.log(`RagonTest04 deployed to ${lock.target}`);
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
ragon4는 생성자 파라미터가 없고, value 전송이 필요 없으므로 불필요한 코드를 걷어냈다.
그 다음 배포하기 위해 web provider (endpoint) url과 account 정보를 설정한다.
web provider는 지난번에 사용한 infura를 사용하고, account는 mnemonic으로 설정했다.
// hardhat.config.ts
const config: HardhatUserConfig = {
solidity: {
version: "0.8.21",
settings: {
evmVersion: "shanghai",
optimizer: {
enabled: false,
},
},
},
networks: {
goerli: {
url: INFURA_ENDPOINT_URL,
accounts: {
mnemonic: MNEMONIC,
},
},
},
};
npx hardhat run ./scripts/deploy.ts --network goerli
An unexpected error occurred:
error TS5109: Option 'moduleResolution' must be set to 'NodeNext' (or left unspecified) when option 'module' is set to 'NodeNext'.
에러 해결을 위해 tsconfig.json 생성해서 설정
{
"compilerOptions": {
"target": "es2020",
"module": "NodeNext",
"strict": true,
"esModuleInterop": true,
"outDir": "dist",
"moduleResolution": "NodeNext"
},
"include": ["./scripts", "./test"],
"files": ["./hardhat.config.ts"]
}
배포 완료 후 etherscan에서 컨트랙트 조회
0x608060405234801561000f575f80fd5b5060043610610029575f3560e01c80637b4957e91461002d575b5f80fd5b61003561004b565b6040516100429190610112565b60405180910390f35b60606040518060400160405280600b81526020017f48656c6c6f205261676f6e000000000000000000000000000000000000000000815250905090565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156100bf5780820151818401526020810190506100a4565b5f8484015250505050565b5f601f19601f8301169050919050565b5f6100e482610088565b6100ee8185610092565b93506100fe8185602086016100a2565b610107816100ca565b840191505092915050565b5f6020820190508181035f83015261012a81846100da565b90509291505056fea264697066735822122037c3ea18e8403b7ca82ab4fae04738cc7ec92b627467a1b5ddd36e614637c14664736f6c63430008150033
npx hardhat clean
npx hardhat compile
// hardhat.config.ts
const config: HardhatUserConfig = {
solidity: {
version: "0.8.21",
settings: {
evmVersion: "shanghai",
optimizer: {
enabled: false,
//runs: 200 결과 동일
},
},
},
};
결과
0x608060405234801561000f575f80fd5b5060043610610029575f3560e01c80637b4957e91461002d575b5f80fd5b61003561004b565b6040516100429190610112565b60405180910390f35b60606040518060400160405280600b81526020017f48656c6c6f205261676f6e000000000000000000000000000000000000000000815250905090565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156100bf5780820151818401526020810190506100a4565b5f8484015250505050565b5f601f19601f8301169050919050565b5f6100e482610088565b6100ee8185610092565b93506100fe8185602086016100a2565b610107816100ca565b840191505092915050565b5f6020820190508181035f83015261012a81846100da565b90509291505056fea264697066735822122037c3ea18e8403b7ca82ab4fae04738cc7ec92b627467a1b5ddd36e614637c14664736f6c63430008150033
const config: HardhatUserConfig = {
solidity: {
version: "0.8.22",
settings: {
evmVersion: "shanghai",
optimizer: {
enabled: false,
runs: 200,
},
},
},
};
0.8.20 버전 컴파일은 실패, 0.8.22로 시도했다.
The Solidity version pragma statement in these files doesn't match any of the configured compilers in your config. Change the pragma or configure additional compiler versions in your hardhat config.
- contracts/RagonTest04.sol (^0.8.21)
- contracts/RagonTest09.sol (^0.8.21)
결과
0x608060405234801561000f575f80fd5b5060043610610029575f3560e01c80637b4957e91461002d575b5f80fd5b61003561004b565b6040516100429190610112565b60405180910390f35b60606040518060400160405280600b81526020017f48656c6c6f205261676f6e000000000000000000000000000000000000000000815250905090565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156100bf5780820151818401526020810190506100a4565b5f8484015250505050565b5f601f19601f8301169050919050565b5f6100e482610088565b6100ee8185610092565b93506100fe8185602086016100a2565b610107816100ca565b840191505092915050565b5f6020820190508181035f83015261012a81846100da565b90509291505056fea26469706673582212200dda0916e3f5b32a4683c7e9ddd1675b3487f0576e8ef0b6d88c4ce4e46498dd64736f6c63430008160033
0.8.21 버전 배포 (origin), 0.8.20 버전 실패
0.8.21 버전 배포 (origin), 0.8.22 버전 실패
compilers: {
solc: {
version: "0.8.22", // Fetch exact version from solc-bin (default: truffle's version)
// docker: true, // Use "0.5.1" you've installed locally with docker (default: false)
// settings: { // See the solidity docs for advice about optimization and evmVersion
// optimizer: {
// enabled: false,
// runs: 200
// },
// evmVersion: "byzantium"
// }
},
},
address
0x1783F67b911767d42213465FDBBF560008AFd925
deployed bytecode (etherscan)
0x608060405234801561000f575f80fd5b5060043610610029575f3560e01c806322a952b51461002d575b5f80fd5b61003561004b565b6040516100429190610112565b60405180910390f35b60606040518060400160405280600d81526020017f48656c6c6f205261676f6e303600000000000000000000000000000000000000815250905090565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156100bf5780820151818401526020810190506100a4565b5f8484015250505050565b5f601f19601f8301169050919050565b5f6100e482610088565b6100ee8185610092565b93506100fe8185602086016100a2565b610107816100ca565b840191505092915050565b5f6020820190508181035f83015261012a81846100da565b90509291505056fea2646970667358221220c3809c60a8237c5c1f7834f8add875c35effb45c7d3ef7e4da5d24978bf672fb64736f6c63430008160033
compilers: {
solc: {
version: "0.8.22", // Fetch exact version from solc-bin (default: truffle's version)
// docker: true, // Use "0.5.1" you've installed locally with docker (default: false)
settings: {
// See the solidity docs for advice about optimization and evmVersion
optimizer: {
enabled: false,
runs: 200,
},
evmVersion: "shanghai",
},
},
},
address
0x0844A0575E6e2a43B0Ae5220eb9707C4d848E1FF
deployed bytecode (etherscan)
0x608060405234801561000f575f80fd5b5060043610610029575f3560e01c806322a952b51461002d575b5f80fd5b61003561004b565b6040516100429190610112565b60405180910390f35b60606040518060400160405280600d81526020017f48656c6c6f205261676f6e303600000000000000000000000000000000000000815250905090565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156100bf5780820151818401526020810190506100a4565b5f8484015250505050565b5f601f19601f8301169050919050565b5f6100e482610088565b6100ee8185610092565b93506100fe8185602086016100a2565b610107816100ca565b840191505092915050565b5f6020820190508181035f83015261012a81846100da565b90509291505056fea2646970667358221220c3809c60a8237c5c1f7834f8add875c35effb45c7d3ef7e4da5d24978bf672fb64736f6c63430008160033
위와 동일
solidity: {
version: "0.8.22",
settings: {
evmVersion: "shanghai",
optimizer: {
enabled: false,
runs: 200,
},
// metadata: {
// bytecodeHash: "none",
// },
},
},
bytecode
0x608060405234801561000f575f80fd5b506101688061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c806322a952b51461002d575b5f80fd5b61003561004b565b6040516100429190610112565b60405180910390f35b60606040518060400160405280600d81526020017f48656c6c6f205261676f6e303600000000000000000000000000000000000000815250905090565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156100bf5780820151818401526020810190506100a4565b5f8484015250505050565b5f601f19601f8301169050919050565b5f6100e482610088565b6100ee8185610092565b93506100fe8185602086016100a2565b610107816100ca565b840191505092915050565b5f6020820190508181035f83015261012a81846100da565b90509291505056fea26469706673582212205e87229a124df8d7563a55ae06b048e65bbaa4c2380451f42b1e86a29150995d64736f6c63430008160033
deployed bytecode
0x608060405234801561000f575f80fd5b5060043610610029575f3560e01c806322a952b51461002d575b5f80fd5b61003561004b565b6040516100429190610112565b60405180910390f35b60606040518060400160405280600d81526020017f48656c6c6f205261676f6e303600000000000000000000000000000000000000815250905090565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156100bf5780820151818401526020810190506100a4565b5f8484015250505050565b5f601f19601f8301169050919050565b5f6100e482610088565b6100ee8185610092565b93506100fe8185602086016100a2565b610107816100ca565b840191505092915050565b5f6020820190508181035f83015261012a81846100da565b90509291505056fea26469706673582212205e87229a124df8d7563a55ae06b048e65bbaa4c2380451f42b1e86a29150995d64736f6c63430008160033
solidity: {
version: "0.8.22",
settings: {
evmVersion: "shanghai",
optimizer: {
enabled: false,
runs: 200,
},
// metadata: {
// bytecodeHash: "none",
// },
},
},
address
0x93d9F96Da899b09851ADd2B08e33De977E28Cd48
bytecode
0x608060405234801561000f575f80fd5b506101688061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c806322a952b51461002d575b5f80fd5b61003561004b565b6040516100429190610112565b60405180910390f35b60606040518060400160405280600d81526020017f48656c6c6f205261676f6e303600000000000000000000000000000000000000815250905090565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156100bf5780820151818401526020810190506100a4565b5f8484015250505050565b5f601f19601f8301169050919050565b5f6100e482610088565b6100ee8185610092565b93506100fe8185602086016100a2565b610107816100ca565b840191505092915050565b5f6020820190508181035f83015261012a81846100da565b90509291505056fea26469706673582212205e87229a124df8d7563a55ae06b048e65bbaa4c2380451f42b1e86a29150995d64736f6c63430008160033
deployed bytecode
0x608060405234801561000f575f80fd5b5060043610610029575f3560e01c806322a952b51461002d575b5f80fd5b61003561004b565b6040516100429190610112565b60405180910390f35b60606040518060400160405280600d81526020017f48656c6c6f205261676f6e303600000000000000000000000000000000000000815250905090565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156100bf5780820151818401526020810190506100a4565b5f8484015250505050565b5f601f19601f8301169050919050565b5f6100e482610088565b6100ee8185610092565b93506100fe8185602086016100a2565b610107816100ca565b840191505092915050565b5f6020820190508181035f83015261012a81846100da565b90509291505056fea26469706673582212205e87229a124df8d7563a55ae06b048e65bbaa4c2380451f42b1e86a29150995d64736f6c63430008160033
etherscan bytecode
0x608060405234801561000f575f80fd5b5060043610610029575f3560e01c806322a952b51461002d575b5f80fd5b61003561004b565b6040516100429190610112565b60405180910390f35b60606040518060400160405280600d81526020017f48656c6c6f205261676f6e303600000000000000000000000000000000000000815250905090565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156100bf5780820151818401526020810190506100a4565b5f8484015250505050565b5f601f19601f8301169050919050565b5f6100e482610088565b6100ee8185610092565b93506100fe8185602086016100a2565b610107816100ca565b840191505092915050565b5f6020820190508181035f83015261012a81846100da565b90509291505056fea26469706673582212205e87229a124df8d7563a55ae06b048e65bbaa4c2380451f42b1e86a29150995d64736f6c63430008160033
truffle 배포 (0x1783F67b911767d42213465FDBBF560008AFd925)
hardhat 배포 (0x93d9F96Da899b09851ADd2B08e33De977E28Cd48)는 스마트 컨트랙트가 동일하지만, etherscan에서 조회하는 deployed bytecode는 일치하지 않음.
이 때, truffle 배포 코드에 verify
성공
이번엔 hardhat 배포 코드에 verify
성공
여기까지 알아낸 내용은 같은 컴파일러, 최적화 등 설정이 같아도 hardhat, truffle 환경에서 같은 소스코드를 가지고 다른 메타데이터를 만들어낸다. 결과적으로는 같은 컴파일러를 사용하지만 다른 이유는 내부적으로 사용하는 기본 메타데이터 설정이 다른 것으로 보임.
마찬가지로 두 소스 코드가 메타데이터가 다르므로, 서로 다른 deployed bytecode는 이더스캔에서도 다른 컨트랙트라고 생각한다.
하지만, verify 과정에서 컴파일러 메타 데이터를 수정하면 컨트랙트 소스코드가 일치하지 않다는 걸 인식할 수 있다.
따라서, etherscan에서 모든 메타데이터를 검증하지는 않지만 적어도, 컴파일러 버전 만큼은 식별한다.
playground.sourcify 라는 사이트를 발견했다.
해당 사이트에서 컴파일된 바이트 코드의 메타 데이터 부분을 자세히 설명해준다.
ragon4 컨트랙트를 컴파일러 0.8.21 버전으로 컴파일한 바이트 코드를 예시로 가져와서 분석한다.
0x608060405234801561000f575f80fd5b5060043610610029575f3560e01c80637b4957e91461002d575b5f80fd5b61003561004b565b6040516100429190610112565b60405180910390f35b60606040518060400160405280600b81526020017f48656c6c6f205261676f6e000000000000000000000000000000000000000000815250905090565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156100bf5780820151818401526020810190506100a4565b5f8484015250505050565b5f601f19601f8301169050919050565b5f6100e482610088565b6100ee8185610092565b93506100fe8185602086016100a2565b610107816100ca565b840191505092915050565b5f6020820190508181035f83015261012a81846100da565b90509291505056fea264697066735822122037c3ea18e8403b7ca82ab4fae04738cc7ec92b627467a1b5ddd36e614637c14664736f6c63430008150033
메타 데이터는 컨트랙트에 대한 정보를 가지고 있고, 해당 메타 데이터는 컨트랙트가 컴파일될 때 바이트 코드 끝에 추가된다.
메타 데이터는 CBOR 방식의 인코딩이 적용되어있다.
마지막 2바이트 (0x0033 = 51byte)는 메타 데이터의 길이를 나타낸다.
CBOR 디코더로 디코딩한 결과
{
"ipfs": h'122037C3EA18E8403B7CA82AB4FAE04738CC7EC92B627467A1B5DDD36E614637C146',
"solc": '\u0000\b\u0015'
}
CBOR은 key: value 타입으로 바이너리 json 포맷으로 데이터를 인코딩한다.
ipfs 데이터는 멀티베이스로 인코딩 되어있고, 멀티베이스 접두사 0x12는 Base58btc 으로 디코딩하면 실제 ipfs에 저장되어있는 CID를 알아낼 수 있다.
solc는 컴파일러 버전을 의미하고 여기서는 메타 데이터 중 마지막 3바이트 (0x000815 = 0.8.21버전)에 해당한다.
따라서, 시행착오를 통해 생성하고 비교했던 여러 테스트 컨트랙트를 종합해보면 메타 데이터가 달라서 deployed bytecode의 전체 검증이 불가능했다.
메타 데이터에는 ipfs cid, solc version과 같이 코드 저장 위치, 컴파일러 버전 정보등이 저장되어 있어서 변경될 수 있었다. 지난 시도때 컴파일러 버전 변경으로 verify가 실패했던 이유는 메타 데이터안에 solc 버전이 명시되어 있기 때문이었다.
여기까지 종합해보면, etherscan verify는 메타데이터 중 solc의 버전은 검증하고 ipfs cid는 비교하지 않는다는 것을 알 수 있다.