はじめに(Introduction)
前回、USDTのコントラクトを読んでみました。
ERC20の他に以下の機能を有していることがわかりました。
- 認証(Ownable)
- 手数料(Fee)
- 一時停止(Pausable)
- アップグレード(Upgrade)
- ブラックリスト(BlackList)
- 発行(Issue)/ 償還(Redeem)
実際にどのような状況であるかをブロックチェーンから呼び出してみたいと思います。
準備
コントラクトアドレスは0xdAC17F958D2ee523a2206206994597C13D831ec7
です。
コントラクトが作成された、トランザクションハッシュは0x2f1c5c2b44f771e942a8506148e256f94f1a464babc938ae0690c6e34cd79190
です。
生成されたブロックは4634748
です。
RPCのエンドポイントは以下を使用します。
const { ethers } = require("ethers");
const USDT_ABI = [
// https://ethereum.org/ja/developers/docs/standards/tokens/erc-20/
'function name() public view returns (string)',
'function symbol() public view returns (string)',
'function decimals() public view returns (uint8)',
'function totalSupply() public view returns (uint256)',
'function balanceOf(address _owner) public view returns (uint256 balance)',
'function transfer(address _to, uint256 _value) public returns (bool success)',
'function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)',
'function approve(address _spender, uint256 _value) public returns (bool success)',
'function allowance(address _owner, address _spender) public view returns (uint256 remaining)',
'event Transfer(address indexed _from, address indexed _to, uint256 _value)',
'event Approval(address indexed _owner, address indexed _spender, uint256 _value)',
// Ownable
'function owner() public view returns (address)',
// fee
'function basisPointsRate() public view returns (uint256)',
'function maximumFee() public view returns (uint256)',
'event Params(uint feeBasisPoints, uint maxFee)',
// Pausable
'function paused() public view returns (bool)',
'event Pause()',
'event Unpause()',
// upgrade
'function deprecated() public view returns (bool)',
'event Deprecate(address newAddress)',
// BlackList
'function getBlackListStatus(address _maker) public view returns (bool)',
'event DestroyedBlackFunds(address _blackListedUser, uint _balance)',
'event AddedBlackList(address _user)',
'event RemovedBlackList(address _user)',
// Issue
'event Issue(uint amount)',
// Redeem
'event Redeem(uint amount)',
];
// https://www.publicnode.com/
const ETHEREUM_RPC_ENDPOINT = 'https://ethereum-rpc.publicnode.com';
const MAX_BLOCK_RANGE = 50000;
// https://etherscan.io/tx/0x2f1c5c2b44f771e942a8506148e256f94f1a464babc938ae0690c6e34cd79190
const TETHER_TOKEN_ADDRES = '0xdAC17F958D2ee523a2206206994597C13D831ec7';
const CREATE_BLOCK_NUMBER = 4634748;
async function main() {
const provider = new ethers.JsonRpcProvider(ETHEREUM_RPC_ENDPOINT);
const tether = new ethers.Contract(TETHER_TOKEN_ADDRES, USDT_ABI, provider);
const blockNumber = await provider.getBlockNumber();
console.log(`blockNumber : ${blockNumber}`);
// ここにコードを記述します。
}
main().then(() => {
process.exit(0);
}).catch((error) => {
console.error(error);
process.exit(1);
});
ERC20
ERC20の基本的な情報を見てみます。
// ERC20
const name = await tether.name();
const symbol = await tether.symbol();
const decimals = await tether.decimals();
const totalSupply = await tether.totalSupply();
console.log(`name : ${name}`);
console.log(`symbol : ${symbol}`);
console.log(`decimals : ${decimals}`);
console.log(`totalSupply : ${ethers.formatUnits(totalSupply, decimals)}`);
結果は以下のとおりです。
blockNumber : 20939907
name : Tether USD
symbol : USDT
decimals : 6
totalSupply : 54966490253.66394
54,966,490,253 USDT!?すごいですね、日本円して8兆円規模のトークンのようです。
コントラクトの作成時は、100 USDTだったのでかなりの規模に成長しているようです。
認証(Ownable)
現在のowner
を見てみます。
また、現在USDTをどのくらい持っているかも見てみます。
// Ownable
const owner = await tether.owner();
const balanceOf = await tether.balanceOf(owner);
console.log(`owner : ${owner}`);
console.log(`balanceOf : ${ethers.formatUnits(balanceOf, decimals)}`);
結果は以下のとおりです。
blockNumber : 20940260
owner : 0xC6CDE7C39eB2f0F0095F41570af89eFC2C1Ea828
balanceOf : 108983.76699
現在のowner
は0xC6CDE7C39eB2f0F0095F41570af89eFC2C1Ea828
だそうです。
EthscanによればMultiSigWallet
だそうです。
コントラクトの作成とは異なるので、transferOwnership
を利用して変更しています。
手数料(Fee)
現在の手数料関連のパラメータを見てみます。
// Fee
const basisPointsRate = await tether.basisPointsRate();
const maximumFee = await tether.maximumFee();
console.log(`basisPointsRate : ${basisPointsRate}`);
console.log(`maximumFee : ${maximumFee}`);
結果は以下のとおりです。
blockNumber : 20940460
basisPointsRate : 0
maximumFee : 0
手数料は設定されていないようです。
イベント検索でパラメータ設定時に発行されるParams
イベントを検索してみましたが検出できなかったので、手数料が発生していることはいままでに一度もなさそうです。
一時停止(Pausable)
一時停止の状況を取得します。
// Pausable
const paused = await tether.paused();
console.log(`paused : ${paused}`);
結果は以下のとおりです。
blockNumber : 20940586
paused : false
現在稼働中のトークンなので停止していることはないです。
イベント検索でPause
、Unpause
を検索してみたところ検出されませんでした、停止されたことは今までに一度もなさそうです。
アップグレード(Upgrade)
アップグレードされているかを見てみます。
// Upgrade
const deprecated = await tether.deprecated();
console.log(`deprecated : ${deprecated}`);
結果は以下のとおりです。
blockNumber : 20940787
deprecated : false
アップグレードされていないようです。
deprecated
をtrue
に変更するとコード的にはfalse
に変更することはできないため、今までに一度もアップグレードはされていないようです。
Deprecate
イベントも検出されませんでした。
ブラックリスト(BlackList)
ブロック番号20940838
の時点でのイベントを収集してみます。
イベントAddedBlackList
は、ブラックリストへ追加した時に発生するイベントです。
イベントDestroyedBlackFunds
は、ブラックリストに入っているアドレスの資産を破棄した時に発生するイベントです。
イベントRemovedBlackList
は、ブラックリストから除外する時に発生するイベントです。
イベントの回数は以下のとおりです。
イベント名 | 回数 | 総量 |
---|---|---|
AddedBlackList | 1,828 | - |
DestroyedBlackFunds | 853 | 243,627,751.978060 |
RemovedBlackList | 66 | - |
これらの操作はowner
だけが出来る操作です。
発行(Issue)/ 償還(Redeem)
ブロック番号20940978
の時点でのイベントを収集してみます。
イベントIssue
は、USDTを発行した時に発生するイベントです。
イベントRedeem
は、USDTを償還した時に発生するイベントです。
イベント名 | 回数 | 総量 |
---|---|---|
Issue | 180 | 64,710,018,005.642000 |
Redeem | 3 | 9,500,000,000.000000 |
これらの操作はowner
だけが出来る操作です。
まとめ(Conclusion)
USDT(Tether)は、7年前から運用されている老舗のトークンですが今では販売所などで取引できるトークンです。
トークンに必要な機能も備えているようです。
手数料や一時停止、アップグレードなどの機能は使われていないようですが、機能として想定していることがわかりました。
今後発行されるERC20の参考となると思います。
補足(Appendix)
イベント検索(Search Event)
対象のイベントにあるコメントを外して使用してください。
console.log('CREATE_BLOCK_NUMBER', CREATE_BLOCK_NUMBER, 'blockNumber', blockNumber);
let eventFilter = [[]];
// Pause
// eventFilter[0] = eventFilter[0].concat(await tether.filters.Pause().getTopicFilter());
// eventFilter[0] = eventFilter[0].concat(await tether.filters.Unpause().getTopicFilter());
// Fee
// eventFilter[0] = eventFilter[0].concat(await tether.filters.Params().getTopicFilter());
// Update
// eventFilter[0] = eventFilter[0].concat(await tether.filters.Deprecate().getTopicFilter());
// BlackList
// eventFilter[0] = eventFilter[0].concat(await tether.filters.DestroyedBlackFunds().getTopicFilter());
// eventFilter[0] = eventFilter[0].concat(await tether.filters.AddedBlackList().getTopicFilter());
// eventFilter[0] = eventFilter[0].concat(await tether.filters.RemovedBlackList().getTopicFilter());
// Issue / Redeem
// eventFilter[0] = eventFilter[0].concat(await tether.filters.Issue().getTopicFilter());
// eventFilter[0] = eventFilter[0].concat(await tether.filters.Redeem().getTopicFilter());
if (eventFilter[0].length > 0) {
let eventCount = 0;
for (let i = CREATE_BLOCK_NUMBER; i < blockNumber; i += MAX_BLOCK_RANGE + 1) {
const from = i;
const to = from + MAX_BLOCK_RANGE < blockNumber ? from + MAX_BLOCK_RANGE : blockNumber;
console.log('queryFilter', from, to);
const logs = await tether.queryFilter(eventFilter, from, to);
if (logs.length > 0) {
for (let log of logs) {
console.log('event', log.transactionHash, log.fragment.name, log.args.join(' '));
}
eventCount += logs.length;
}
}
console.log('event count :', eventCount);
}