일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 컨트렉트 배포 자동화
- nestjs 튜토리얼
- 머신러닝기초
- chainlink 설명
- vue기초
- multicall
- 스마트컨트렉트 함수이름 중복 호출
- git rebase
- nest.js설명
- 컨트렉트 동일한 함수이름 호출
- 체인의정석
- ethers type
- ethers typescript
- ethers
- Vue
- 스마트컨트렉트 예약어 함수이름 중복
- 프록시배포구조
- SBT표준
- rust 기초
- 깃허브명령어
- 러스트 기초 학습
- Vue.js
- ethers v6
- 러스트기초
- 스마트컨트렉트테스트
- 러스트 기초
- ambiguous function description
- 스마트컨트렉트프록시
- ethers websocket
- 스마트 컨트렉트 함수이름 중복
Archives
- Today
- Total
체인의정석
ethers, hardhat 환경에서 이벤트를 통한 대용량 토큰 스냅샷 처리 프로그램 제작하기 (엑셀파일로 스냅샷 만들기) 본문
블록체인/Ethers & web3
ethers, hardhat 환경에서 이벤트를 통한 대용량 토큰 스냅샷 처리 프로그램 제작하기 (엑셀파일로 스냅샷 만들기)
체인의정석 2023. 3. 16. 18:24728x90
반응형
1. const 파일 만들기
먼저 constant에 해당될 토큰 주소와 decimal 토큰 이름들은 지정해두어서 해당 값들만 바꾸면 스냅샷이 가능하도록 만들어준다.
const { ethers } = require("hardhat");
import { tokenName, tokenAddress, startBlock, endBlock } from '../const';
const fs = require('fs');
2. 엑셀로 TransferTx 저장시키기
먼저 다음 구문을 통해서 이벤트를 다 가져온다.
이벤트를 가져올때 필터를 설정할 수 있는데 이때 transfer 필터를 잡아주고 token Address를 넣어준다.
async function getEvents() {
const concatArr: Array<any> = [];
for(let i=startBlock; i < endBlock; i+=86400) {
const endBlock = i+86400
console.log(`getting event tx fromBlockNumber${i} to to blockNumber${endBlock}`)
const erc20 = await ethers.getContractAt("MYTOKEN", tokenAddress);
const erc20Filter = {
address: tokenAddress,
fromBlock: i,
toBlock: endBlock,
topics: [erc20.filters.Transfer().topics]
};
const erc20Events = await ethers.provider.getLogs(erc20Filter);
concatArr.push(erc20Events);
}
const finalArray = concatArr.reduce((accumulator:any, currentValue:any) => accumulator.concat(currentValue));
return Promise.resolve(finalArray);
}
이를 writeFileSync를 통해서 엑셀파일로 만들어준다.
export function saveFileTransferTx() {
getEvents()
.then(async (txArray) => {
const answerArr: Array<any> = []
for(let i=0; i < txArray.length; i+=1000) {
console.log(`getting tx hash from ${i} txNum ${i+1000}`)
answerArr.push(await txArray.slice(i,i+1000).map((tx: any) => tx.transactionHash));
}
const finalArray = answerArr.reduce((accumulator:any, currentValue:any) => accumulator.concat(currentValue));
const setData = new Set(finalArray);
const newArrayData = [...setData];
console.log(`total transaction count is ${newArrayData.length}`)
fs.writeFileSync(
`./scripts/output/${tokenName}_${endBlock}_Transfer.json`,
JSON.stringify(newArrayData, null, 1))
})
}
이렇게 되면 트랜잭션 해시들만 나오게된다.
3. parseTransferEvents
parseTransferEvents를 해주면 그제서야 실제 이벤트 내역이 나오게 된다. 이렇게 되면 엑셀파일로 실제 전송된 값들이 저장되게 된다.
import fs from 'fs';
import { tokenName, tokenAddress, size, endBlock } from '../const';
import { parseTransferEvents } from './helper/logParseHelper';
import { flattened } from './helper/arrayFunctions';
const abi = require("../../../artifacts/contracts/interfaces/IERC20.sol/IERC20.json").abi;
async function getAllTxreceipt() {
const txListArr : Array<any>= []
for(let a=0; a < size; a+=30000) { //개수 보고 반복문 조정
const txList: Array<string> = JSON.parse(fs.readFileSync(`./scripts/quickSnapShot/output/${tokenName}_${endBlock}_txReceipt_from${a}_to${a+30000}.json`,'utf-8'));
txListArr.push(txList)
}
return flattened(txListArr);
}
export async function saveFileTransferData(): Promise<void> {
await getAllTxreceipt()
.then(async(txArray) => {
let answerArr: Array<any> = [];
for(let i=0; i < txArray.length; i+=500) {
console.log(`starting parseTransferEvents of ${i}/${txArray.length}`)
const answer= await
Promise.all(txArray.slice(i,i+500).map(async (tx: any) => {
const parsedEventInternal = await parseTransferEvents(tx.logs, abi, tokenAddress);
const returnJsonData = parsedEventInternal;
if(returnJsonData.value == '0') {
return '';
}
return returnJsonData;
}))
answerArr.push(answer)
}
return answerArr
})
.then((answerArr: any) => {
console.log(`starting flattening answerArr, length is ${answerArr.length}`)
const flatanswerArr: Array<any> = flattened(flattened(flattened(answerArr)));
const exceptNull = flatanswerArr.filter((r : any) => r != ('' || null || undefined));
const flatanswerArrJson: string = JSON.stringify(exceptNull, null, 1)
console.log(`total transaction count is ${exceptNull.length}`)
fs.writeFileSync(
`./scripts/output/${tokenName}_${endBlock}_transferInfo.json`,
flatanswerArrJson
)
})
}
4. 스냅샷 실행하기
스냅샷을 실행할 때는 각 from주소와 to 주소에 맞게 데이터를 처리해주는 로직을 넣어준다.
각 주소별 자산의 수를 계산하기 때문에 map을 사용하였다.
또한 수를 계산할 때는 bignumber를 사용해서 해주어야 한다.
const { ethers } = require("hardhat");
import fs from 'fs';
// import BigNumber from 'bignumber.js';
import 'blockchain-prototypes'
import { tokenName, decimal, endBlock } from '../const';
import { noExponents } from './helper/arrayFunctions';
const holderMap = new Map();
const addressZero = "0x0000000000000000000000000000000000000000";
async function getAllInfo() {
const txList = JSON.parse(fs.readFileSync(`./scripts/output/${tokenName}_${endBlock}_transferInfo.json`,'utf-8'));
console.log("total transfer count : ", txList.length);
return txList
}
export async function saveFileSnapShot() {
await getAllInfo()
.then((holder) =>{
for(const i in holder) {
holderMap.set(holder[i].to,0)
holderMap.set(holder[i].from,0)
}
holderMap.set(addressZero,0);
return {holderMap, holder}
})
.then((infos) => {
//holder 리스트 정보 초기화
const holderMap = infos.holderMap
const holder = infos.holder;
//각 holder 들의 잔고 계산
//holder 리스트 확보
for( let event of holder) {
const to = event.to;
const from = event.from;
const value = new ethers.BigNumber.from(event.value);
//mint인 경우 계산
if(from==addressZero) {
let beforeBalance = ethers.BigNumber.from(holderMap.get(to))
let afterBalance = beforeBalance.add(value);
holderMap.set(to,afterBalance);
}else if(to==addressZero) {
let beforeBalance = ethers.BigNumber.from(holderMap.get(from))
let afterBalace = beforeBalance.sub(value);
holderMap.set(from,afterBalace);
let afterBalanceZero = afterBalace.add(value);
holderMap.set(addressZero,afterBalanceZero);
}else if(from==addressZero && to==addressZero){
}else if(value=="0"){
}else {
let beforeBalanceFrom = ethers.BigNumber.from(holderMap.get(from))
let afterBalaceFrom = beforeBalanceFrom.sub(value);
holderMap.set(from,afterBalaceFrom);
let beforeBalanceTo = ethers.BigNumber.from(holderMap.get(to))
let afterBalaceTo = beforeBalanceTo.add(value);
holderMap.set(to,afterBalaceTo);
}
}
return holderMap
})
.then(async(holderMap) => {
noExponents
const arrayHolderMap: Array<any> = Array.from(holderMap)
const toStringHolder: Array<Array<string>> = arrayHolderMap.map((list) => {
list[1] = (list[1].toString() / 10 ** decimal).noExponents();
return list
})
console.log("total holder count : ", toStringHolder.length);
fs.writeFileSync(
`./scripts/output/${tokenName}_${endBlock}_snapShot.json`,
JSON.stringify(toStringHolder,null,1)
)
})
}
5. 만든 json파일 엑셀로 만들기
스냅샷의 경우 여러 데이터가 들어갈 경우 부하가 커진다 따라서 엑셀파일로 만든 후 이후 작업을 해주는 것이 수월하다.
excel.js를 사용하면 해당 작업을 할 수 있다.
import Excel from 'exceljs';
import path from 'path';
import fs from 'fs';
import { endBlock, tokenName } from '../const';
type User = {
address: string;
txhash: string;
tokenAmount: string;
};
export const exportUserFile = async () => {
console.log("exportUserFile", tokenName)
const users: Array<User> = JSON.parse(fs.readFileSync(`./scripts/output/${tokenName}_${endBlock}_snapShot.json`,'utf-8'));
const workbook = new Excel.Workbook();
const worksheet = workbook.addWorksheet('User List');
worksheet.columns = [
{ key: 'address', header: 'account address' },
{ key: 'amount', header: 'amount' },
];
users.forEach((item) => {
worksheet.addRow(item);
});
const exportPath = path.resolve(__dirname, `../output/${tokenName}_${endBlock}_snapShot_data.xlsx`);
await workbook.xlsx.writeFile(exportPath);
};
// exportUserFile();
여기까지 된다면 엑셀파일에 토큰의 amount가 나오게 되는데 이 때부터는 엑셀함수를 이용하여 여러가지 연산을 하거나 하면 된다.
또한 다차원 배열을 1차원으로 축소시키거나 가수부 관련 표기를 숫자로 바꾸는 부분은 다음 함수를 사용하였다.
export function flattened(array: Array<any>) {
const flattenedArray = array.reduce(
function(accumulator, currentValue) {
return accumulator.concat(currentValue);
},[]
);
return flattenedArray
}
export const noExponents = Number.prototype.noExponents = function () {
var data = String(this).split(/[eE]/);
if (data.length == 1) return data[0];
var z = '',
sign = this < 0 ? '-' : '',
str = data[0].replace('.', ''),
mag = Number(data[1]) + 1;
if (mag < 0) {
z = sign + '0.';
while (mag++) z += '0';
return z + str.replace(/^\-/, '');
}
mag -= str.length;
while (mag--) z += '0';
return str + z;
}
728x90
반응형
'블록체인 > Ethers & web3' 카테고리의 다른 글
스캔에 뜨지 않는 EVM 에러 코드 확인하는 법, hardhat console 사용 (0) | 2023.03.23 |
---|---|
hardhat node 버전 맞추기 + 여러 컨트렉트 소스 한번에 컴파일하기 (0) | 2023.03.17 |
큰 규모의 서비스에서의 백엔드 최적화 방안들 (0) | 2023.03.16 |
hardhat, ethers 테스트 코드 스크립트로 바꾸는 법 (여러 지갑주소로 서명하는 스크립트 만드는법) (0) | 2023.03.11 |
hardhat 환경에서 잘 작동하지 않는 benchmark와 그에 대한 해결 방안 (0) | 2023.02.21 |
Comments