மோசடி வில்லைகள் பயன்படுத்தும் சில தந்திரங்கள் மற்றும் அவற்றைக் கண்டறிவது எப்படி
இந்த வழிகாட்டியில், மோசடி செய்பவர்கள் விளையாடும் சில தந்திரங்கள் மற்றும் அவற்றை அவர்கள் எவ்வாறு செயல்படுத்துகிறார்கள் என்பதைப் பார்க்க ஒரு மோசடி வில்லையை (புதிய தாவலில் திறக்கப்படும்) நாங்கள் பகுப்பாய்வு செய்கிறோம். இந்த வழிகாட்டியின் முடிவில், ERC-20 வில்லை ஒப்பந்தங்கள், அவற்றின் திறன்கள் மற்றும் சந்தேகம் ஏன் அவசியம் என்பது பற்றிய விரிவான பார்வையை நீங்கள் பெறுவீர்கள். பின்னர் அந்த மோசடி வில்லை வெளியிடும் நிகழ்வுகளைப் பார்த்து, அது முறையானது அல்ல என்பதை நாம் எவ்வாறு தானாகவே கண்டறியலாம் என்பதைப் பார்ப்போம்.
மோசடி வில்லைகள் - அவை என்ன, மக்கள் ஏன் அவற்றை உருவாக்குகிறார்கள், மற்றும் அவற்றைத் தவிர்ப்பது எப்படி
எத்திரியத்தின் மிகவும் பொதுவான பயன்பாடுகளில் ஒன்று, ஒரு குழு வர்த்தகம் செய்யக்கூடிய ஒரு வில்லையை உருவாக்குவதாகும், ஒரு வகையில் அது அவர்களின் சொந்த நாணயமாகும். இருப்பினும், மதிப்பைத் தரும் முறையான பயன்பாட்டு நிகழ்வுகள் எங்கு இருந்தாலும், அந்த மதிப்பைத் தங்களுக்காகத் திருட முயற்சிக்கும் குற்றவாளிகளும் இருக்கிறார்கள்.
ஒரு பயனரின் கண்ணோட்டத்தில் இந்த விஷயத்தைப் பற்றி ethereum.org இல் வேறு எங்கும் நீங்கள் மேலும் படிக்கலாம். இந்த வழிகாட்டி ஒரு மோசடி வில்லையை பகுப்பாய்வு செய்து, அது எவ்வாறு செய்யப்படுகிறது மற்றும் அதை எவ்வாறு கண்டறியலாம் என்பதில் கவனம் செலுத்துகிறது.
wARB ஒரு மோசடி என்று எனக்கு எப்படித் தெரியும்?
நாம் பகுப்பாய்வு செய்யும் வில்லை wARB (புதிய தாவலில் திறக்கப்படும்) ஆகும், இது முறையான ARB வில்லைக்கு (புதிய தாவலில் திறக்கப்படும்) சமமானதாக நடிக்கிறது.
எது முறையான வில்லை என்பதை அறிய எளிதான வழி, அதை உருவாக்கிய நிறுவனமான ஆர்பிட்ரம் (புதிய தாவலில் திறக்கப்படும்) ஐப் பார்ப்பதாகும். முறையான முகவரிகள் அவர்களின் ஆவணங்களில் (புதிய தாவலில் திறக்கப்படும்) குறிப்பிடப்பட்டுள்ளன.
மூலக் குறியீடு ஏன் கிடைக்கிறது?
பொதுவாக மற்றவர்களை ஏமாற்ற முயற்சிப்பவர்கள் ரகசியமாக இருப்பார்கள் என்று நாம் எதிர்பார்க்கிறோம், மேலும் பல மோசடி வில்லைகளின் குறியீடு கிடைக்கவில்லை (உதாரணமாக, இது (புதிய தாவலில் திறக்கப்படும்) மற்றும் இது (புதிய தாவலில் திறக்கப்படும்)).
இருப்பினும், முறையான வில்லைகள் வழக்கமாக அவற்றின் மூலக் குறியீட்டை வெளியிடுகின்றன, எனவே முறையானதாகத் தோன்றுவதற்கு மோசடி வில்லைகளின் ஆசிரியர்களும் சில நேரங்களில் அதையே செய்கிறார்கள். wARB (புதிய தாவலில் திறக்கப்படும்) என்பது மூலக் குறியீடு கிடைக்கும் வில்லைகளில் ஒன்றாகும், இது அதைப் புரிந்துகொள்வதை எளிதாக்குகிறது.
ஒப்பந்தத்தை நிறுவுபவர்கள் மூலக் குறியீட்டை வெளியிடலாமா வேண்டாமா என்பதைத் தேர்வுசெய்ய முடியும் என்றாலும், அவர்களால் தவறான மூலக் குறியீட்டை வெளியிட முடியாது. தொகுதி ஆராய்வி வழங்கப்பட்ட மூலக் குறியீட்டைச் சுயாதீனமாகத் தொகுக்கிறது, மேலும் அது சரியான அதே பைட் குறியீட்டைப் பெறவில்லை என்றால், அது அந்த மூலக் குறியீட்டை நிராகரிக்கிறது. Etherscan தளத்தில் இதைப் பற்றி நீங்கள் மேலும் படிக்கலாம் (புதிய தாவலில் திறக்கப்படும்).
முறையான ERC-20 வில்லைகளுடனான ஒப்பீடு
இந்த வில்லையை முறையான ERC-20 வில்லைகளுடன் ஒப்பிடப் போகிறோம். முறையான ERC-20 வில்லைகள் பொதுவாக எவ்வாறு எழுதப்படுகின்றன என்பது உங்களுக்குத் தெரியாவிட்டால், இந்த வழிகாட்டியைப் பார்க்கவும்.
சலுகை பெற்ற முகவரிகளுக்கான மாறிலிகள்
ஒப்பந்தங்களுக்கு சில நேரங்களில் சலுகை பெற்ற முகவரிகள் தேவைப்படுகின்றன. நீண்ட கால பயன்பாட்டிற்காக வடிவமைக்கப்பட்ட ஒப்பந்தங்கள், அந்த முகவரிகளை மாற்ற சில சலுகை பெற்ற முகவரிகளை அனுமதிக்கின்றன, எடுத்துக்காட்டாக புதிய பல்கையெழுத்து ஒப்பந்தத்தின் பயன்பாட்டை இயக்க. இதைச் செய்ய பல வழிகள் உள்ளன.
HOP வில்லை ஒப்பந்தம் (புதிய தாவலில் திறக்கப்படும்) Ownable (புதிய தாவலில் திறக்கப்படும்) வடிவமைப்பைப் பயன்படுத்துகிறது. சலுகை பெற்ற முகவரி சேமிப்பகத்தில், _owner எனப்படும் புலத்தில் வைக்கப்பட்டுள்ளது (மூன்றாவது கோப்பான Ownable.sol ஐப் பார்க்கவும்).
abstract contract Ownable is Context {
address private _owner;
.
.
.
}
ARB வில்லை ஒப்பந்தம் (புதிய தாவலில் திறக்கப்படும்) நேரடியாக ஒரு சலுகை பெற்ற முகவரியைக் கொண்டிருக்கவில்லை. இருப்பினும், அதற்கு அது தேவையில்லை. இது முகவரி 0xb50721bcf8d664c30412cfbc6cf7a15145234ad1 (புதிய தாவலில் திறக்கப்படும்) இல் உள்ள proxy (புதிய தாவலில் திறக்கப்படும்) இன் பின்னால் அமர்ந்திருக்கிறது. அந்த ஒப்பந்தத்தில் மேம்படுத்தல்களுக்குப் பயன்படுத்தக்கூடிய ஒரு சலுகை பெற்ற முகவரி உள்ளது (நான்காவது கோப்பான ERC1967Upgrade.sol ஐப் பார்க்கவும்).
/**
* @dev EIP1967 நிர்வாகி இடத்தில் ஒரு புதிய முகவரியைச் சேமிக்கிறது.
*/
function _setAdmin(address newAdmin) private {
require(newAdmin != address(0), "ERC1967: new admin is the zero address");
StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
}
இதற்கு நேர்மாறாக, wARB ஒப்பந்தத்தில் வன்-குறியிடப்பட்ட (hard coded) contract_owner உள்ளது.
contract WrappedArbitrum is Context, IERC20 {
.
.
.
address deployer = 0xB50721BCf8d664c30412Cfbc6cf7a15145234ad1;
address public contract_owner = 0xb40dE7b1beE84Ff2dc22B70a049A07A13a411A33;
.
.
.
}
இந்த ஒப்பந்த உரிமையாளர் (புதிய தாவலில் திறக்கப்படும்) வெவ்வேறு நேரங்களில் வெவ்வேறு கணக்குகளால் கட்டுப்படுத்தப்படக்கூடிய ஒரு ஒப்பந்தம் அல்ல, மாறாக ஒரு வெளிப்புறமாகச் சொந்தமான கணக்கு ஆகும். இதன் பொருள், இது மதிப்புமிக்கதாக இருக்கும் ஒரு ERC-20 ஐக் கட்டுப்படுத்துவதற்கான நீண்ட கால தீர்வாக இல்லாமல், ஒரு தனிநபரால் குறுகிய கால பயன்பாட்டிற்காக வடிவமைக்கப்பட்டிருக்கலாம்.
உண்மையில், நாம் Etherscan இல் பார்த்தால், மோசடி செய்பவர் மே 19, 2023 அன்று 12 மணிநேரம் மட்டுமே (முதல் பரிவர்த்தனை (புதிய தாவலில் திறக்கப்படும்) முதல் கடைசி பரிவர்த்தனை (புதிய தாவலில் திறக்கப்படும்) வரை) இந்த ஒப்பந்தத்தைப் பயன்படுத்தியிருப்பதைக் காண்கிறோம்.
போலியான _transfer செயல்பாடு
உண்மையான பரிமாற்றங்கள் ஒரு உள் _transfer செயல்பாட்டைப் பயன்படுத்தி நடப்பது நிலையானதாகும்.
wARB இல் இந்தச் செயல்பாடு கிட்டத்தட்ட முறையானதாகத் தெரிகிறது:
function _transfer(address sender, address recipient, uint256 amount) internal virtual{
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(sender, recipient, amount);
_balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
_balances[recipient] = _balances[recipient].add(amount);
if (sender == contract_owner){
sender = deployer;
}
emit Transfer(sender, recipient, amount);
}
சந்தேகத்திற்குரிய பகுதி இதுதான்:
if (sender == contract_owner){
sender = deployer;
}
emit Transfer(sender, recipient, amount);
ஒப்பந்த உரிமையாளர் வில்லைகளை அனுப்பினால், Transfer நிகழ்வு ஏன் அவை deployer இலிருந்து வருவதாகக் காட்டுகிறது?
இருப்பினும், இதைவிட முக்கியமான ஒரு சிக்கல் உள்ளது. இந்த _transfer செயல்பாட்டை யார் அழைப்பது? இதை வெளியிலிருந்து அழைக்க முடியாது, இது internal எனக் குறிக்கப்பட்டுள்ளது. மேலும் நம்மிடம் உள்ள குறியீட்டில் _transfer க்கான எந்த அழைப்புகளும் இல்லை. தெளிவாக, இது ஒரு ஏமாற்று வித்தையாக (decoy) இங்கு உள்ளது.
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
_f_(_msgSender(), recipient, amount);
return true;
}
function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
_f_(sender, recipient, amount);
_approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
return true;
}
வில்லைகளைப் பரிமாற்றம் செய்ய அழைக்கப்படும் செயல்பாடுகளான transfer மற்றும் transferFrom ஆகியவற்றைப் பார்க்கும்போது, அவை முற்றிலும் மாறுபட்ட செயல்பாடான _f_ ஐ அழைப்பதைக் காண்கிறோம்.
உண்மையான _f_ செயல்பாடு
function _f_(address sender, address recipient, uint256 amount) internal _mod_(sender,recipient,amount) virtual {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(sender, recipient, amount);
_balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
_balances[recipient] = _balances[recipient].add(amount);
if (sender == contract_owner){
sender = deployer;
}
emit Transfer(sender, recipient, amount);
}
இந்தச் செயல்பாட்டில் இரண்டு சாத்தியமான அபாய அறிகுறிகள் (red flags) உள்ளன.
-
செயல்பாட்டு மாற்றியான (function modifier) (புதிய தாவலில் திறக்கப்படும்)
_mod_இன் பயன்பாடு. இருப்பினும், மூலக் குறியீட்டைப் பார்க்கும்போது_mod_உண்மையில் பாதிப்பில்லாதது என்பதைக் காண்கிறோம்.modifier _mod_(address sender, address recipient, uint256 amount){ _; } -
_transferஇல் நாம் பார்த்த அதே சிக்கல், அதாவதுcontract_ownerவில்லைகளை அனுப்பும்போது அவைdeployerஇலிருந்து வருவதாகத் தோன்றுகிறது.
போலியான நிகழ்வுகள் செயல்பாடு dropNewTokens
இப்போது நாம் உண்மையான மோசடி போல் தோன்றும் ஒன்றிற்கு வருகிறோம். வாசிப்புத்திறனுக்காக நான் செயல்பாட்டைச் சிறிது திருத்தியுள்ளேன், ஆனால் இது செயல்பாட்டு ரீதியாகச் சமமானது.
function dropNewTokens(address uPool,
address[] memory eReceiver,
uint256[] memory eAmounts) public auth()
இந்தச் செயல்பாடு auth() மாற்றியைக் கொண்டுள்ளது, அதாவது இதை ஒப்பந்த உரிமையாளரால் மட்டுமே அழைக்க முடியும்.
modifier auth() {
require(msg.sender == contract_owner, "Not allowed to interact");
_;
}
இந்தக் கட்டுப்பாடு முற்றிலும் அர்த்தமுள்ளதாக இருக்கிறது, ஏனென்றால் சீரற்ற கணக்குகள் வில்லைகளை விநியோகிப்பதை நாம் விரும்ப மாட்டோம். இருப்பினும், செயல்பாட்டின் மீதமுள்ள பகுதி சந்தேகத்திற்குரியது.
{
for (uint256 i = 0; i < eReceiver.length; i++) {
emit Transfer(uPool, eReceiver[i], eAmounts[i]);
}
}
ஒரு தொகுப்புக் கணக்கிலிருந்து பெறுநர்களின் அணிவரிசைக்கு (array) தொகைகளின் அணிவரிசையைப் பரிமாற்றம் செய்வதற்கான ஒரு செயல்பாடு முற்றிலும் அர்த்தமுள்ளதாக இருக்கிறது. ஊதியம், ஏர்ட்ராப்கள் (airdrops) போன்ற பல பயன்பாட்டு நிகழ்வுகளில், ஒரு மூலத்திலிருந்து பல இடங்களுக்கு வில்லைகளை விநியோகிக்க நீங்கள் விரும்புவீர்கள். பல பரிவர்த்தனைகளை வழங்குவதற்குப் பதிலாக, அல்லது அதே பரிவர்த்தனையின் ஒரு பகுதியாக வேறு ஒப்பந்தத்திலிருந்து ERC-20 ஐப் பல முறை அழைப்பதற்குப் பதிலாக, ஒரே பரிவர்த்தனையில் இதைச் செய்வது (எரிவாயுவில்) மலிவானது.
இருப்பினும், dropNewTokens அதைச் செய்யாது. இது Transfer நிகழ்வுகளை (புதிய தாவலில் திறக்கப்படும்) வெளியிடுகிறது, ஆனால் உண்மையில் எந்த வில்லைகளையும் பரிமாற்றம் செய்யாது. உண்மையில் நடக்காத ஒரு பரிமாற்றத்தைப் பற்றிச் சொல்லி புறச்சங்கிலி பயன்பாடுகளைக் குழப்புவதற்கு எந்த முறையான காரணமும் இல்லை.
எரிக்கும் Approve செயல்பாடு
ERC-20 ஒப்பந்தங்கள் அனுமதித்தொகைகளுக்காக ஒரு approve செயல்பாட்டைக் கொண்டிருக்க வேண்டும், மேலும் உண்மையில் நமது மோசடி வில்லையில் அத்தகைய செயல்பாடு உள்ளது, அது சரியானதும்கூட. இருப்பினும், Solidity C மொழியிலிருந்து வந்ததால், அது எழுத்து வடிவ உணர்திறன் (case significant) கொண்டது. "Approve" மற்றும் "approve" ஆகியவை வெவ்வேறு சரங்கள் (strings).
மேலும், இதன் செயல்பாடு approve உடன் தொடர்புடையது அல்ல.
function Approve(
address[] memory holders)
இந்தச் செயல்பாடு வில்லை வைத்திருப்பவர்களுக்கான முகவரிகளின் அணிவரிசையுடன் அழைக்கப்படுகிறது.
public approver() {
approver() மாற்றியானது contract_owner மட்டுமே இந்தச் செயல்பாட்டை அழைக்க அனுமதிக்கப்படுவதை உறுதி செய்கிறது (கீழே காண்க).
for (uint256 i = 0; i < holders.length; i++) {
uint256 amount = _balances[holders[i]];
_beforeTokenTransfer(holders[i], 0x0000000000000000000000000000000000000001, amount);
_balances[holders[i]] = _balances[holders[i]].sub(amount,
"ERC20: burn amount exceeds balance");
_balances[0x0000000000000000000000000000000000000001] =
_balances[0x0000000000000000000000000000000000000001].add(amount);
}
}
ஒவ்வொரு வைத்திருப்பவரின் முகவரிக்கும், இந்தச் செயல்பாடு வைத்திருப்பவரின் முழு இருப்பையும் 0x00...01 முகவரிக்கு நகர்த்துகிறது, இது திறம்பட அதை எரிக்கிறது (தரநிலையில் உள்ள உண்மையான burn மொத்த விநியோகத்தையும் மாற்றுகிறது, மேலும் வில்லைகளை 0x00...00 க்குப் பரிமாற்றம் செய்கிறது). இதன் பொருள் contract_owner எந்தவொரு பயனரின் சொத்துகளையும் அகற்ற முடியும். இது ஒரு ஆளுகை டோக்கனில் நீங்கள் விரும்பும் அம்சமாகத் தெரியவில்லை.
குறியீட்டுத் தரச் சிக்கல்கள்
இந்தக் குறியீட்டுத் தரச் சிக்கல்கள் இந்தக் குறியீடு ஒரு மோசடி என்பதை நிரூபிக்கவில்லை, ஆனால் அவை இதைச் சந்தேகத்திற்குரியதாகக் காட்டுகின்றன. ஆர்பிட்ரம் போன்ற ஒழுங்கமைக்கப்பட்ட நிறுவனங்கள் வழக்கமாக இவ்வளவு மோசமான குறியீட்டை வெளியிடுவதில்லை.
mount செயல்பாடு
இது தரநிலையில் (புதிய தாவலில் திறக்கப்படும்) குறிப்பிடப்படவில்லை என்றாலும், பொதுவாகப் புதிய வில்லைகளை உருவாக்கும் செயல்பாடு mint என்று அழைக்கப்படுகிறது.
wARB ஆக்கியில் நாம் பார்த்தால், அச்சிடும் செயல்பாடு சில காரணங்களுக்காக mount என மறுபெயரிடப்பட்டிருப்பதைக் காண்கிறோம், மேலும் செயல்திறனுக்காக முழுத் தொகைக்கும் ஒரு முறை அழைக்கப்படுவதற்குப் பதிலாக, ஆரம்ப விநியோகத்தின் ஐந்தில் ஒரு பங்கைக் கொண்டு ஐந்து முறை அழைக்கப்படுகிறது.
constructor () public {
_name = "Wrapped Arbitrum";
_symbol = "wARB";
_decimals = 18;
uint256 initialSupply = 1000000000000;
mount(deployer, initialSupply*(10**18)/5);
mount(deployer, initialSupply*(10**18)/5);
mount(deployer, initialSupply*(10**18)/5);
mount(deployer, initialSupply*(10**18)/5);
mount(deployer, initialSupply*(10**18)/5);
}
mount செயல்பாடே சந்தேகத்திற்குரியது.
function mount(address account, uint256 amount) public {
require(msg.sender == contract_owner, "ERC20: mint to the zero address");
require ஐப் பார்க்கும்போது, ஒப்பந்த உரிமையாளர் மட்டுமே அச்சிட அனுமதிக்கப்படுகிறார் என்பதைக் காண்கிறோம். அது முறையானது. ஆனால் பிழைச் செய்தி உரிமையாளர் மட்டுமே அச்சிட அனுமதிக்கப்படுவார் அல்லது அது போன்ற ஒன்றாக இருக்க வேண்டும். அதற்குப் பதிலாக, இது தொடர்பில்லாத ERC20: சுழி முகவரிக்கு அச்சிடு என்பதாக உள்ளது. சுழி முகவரிக்கு அச்சிடுவதற்கான சரியான சோதனை require(account != address(0), "<error message>") ஆகும், இதை ஒப்பந்தம் ஒருபோதும் சரிபார்க்க மெனக்கெடுவதில்லை.
_totalSupply = _totalSupply.add(amount);
_balances[contract_owner] = _balances[contract_owner].add(amount);
emit Transfer(address(0), account, amount);
}
அச்சிடுதலுடன் நேரடியாகத் தொடர்புடைய மேலும் இரண்டு சந்தேகத்திற்குரிய உண்மைகள் உள்ளன:
-
accountஅளவுரு (parameter) ஒன்று உள்ளது, இது அச்சிடப்பட்ட தொகையைப் பெற வேண்டிய கணக்காக இருக்கலாம். ஆனால் அதிகரிக்கும் இருப்பு உண்மையில்contract_ownerஉடையது. -
அதிகரித்த இருப்பு
contract_ownerக்குச் சொந்தமானது என்றாலும், வெளியிடப்பட்ட நிகழ்வுaccountக்கான பரிமாற்றத்தைக் காட்டுகிறது.
auth மற்றும் approver இரண்டும் ஏன்? எதையும் செய்யாத mod ஏன்?
இந்த ஒப்பந்தத்தில் மூன்று மாற்றிகள் உள்ளன: _mod_, auth, மற்றும் approver.
modifier _mod_(address sender, address recipient, uint256 amount){
_;
}
_mod_ மூன்று அளவுருக்களை எடுத்துக்கொள்கிறது, ஆனால் அவற்றுடன் எதையும் செய்வதில்லை. அது ஏன் இருக்க வேண்டும்?
modifier auth() {
require(msg.sender == contract_owner, "Not allowed to interact");
_;
}
modifier approver() {
require(msg.sender == contract_owner, "Not allowed to interact");
_;
}
auth மற்றும் approver ஆகியவை அதிக அர்த்தமுள்ளவை, ஏனென்றால் ஒப்பந்தம் contract_owner ஆல் அழைக்கப்பட்டதா என்பதை அவை சரிபார்க்கின்றன. அச்சிடுதல் போன்ற சில சலுகை பெற்ற செயல்கள் அந்தக் கணக்கிற்கு மட்டுமே வரம்பிடப்படும் என்று நாம் எதிர்பார்க்கிறோம். இருப்பினும், சரியாக அதே காரியத்தைச் செய்யும் இரண்டு தனித்தனி செயல்பாடுகளைக் கொண்டிருப்பதில் என்ன பயன்?
நாம் தானாகவே எதைக் கண்டறிய முடியும்?
Etherscan ஐப் பார்ப்பதன் மூலம் wARB ஒரு மோசடி வில்லை என்பதை நாம் காணலாம். இருப்பினும், அது ஒரு மையப்படுத்தப்பட்ட தீர்வாகும். கோட்பாட்டளவில், Etherscan ஐத் தகர்க்கலாம் அல்லது ஹேக் செய்யலாம். ஒரு வில்லை முறையானதா இல்லையா என்பதைச் சுயாதீனமாகப் புரிந்துகொள்ள முடிவது நல்லது.
ஒரு ERC-20 வில்லை வெளியிடும் நிகழ்வுகளைப் பார்ப்பதன் மூலம், அது சந்தேகத்திற்குரியது (ஒரு மோசடி அல்லது மிகவும் மோசமாக எழுதப்பட்டது) என்பதைக் கண்டறிய நாம் பயன்படுத்தக்கூடிய சில தந்திரங்கள் உள்ளன.
சந்தேகத்திற்குரிய Approval நிகழ்வுகள்
Approval நிகழ்வுகள் (புதிய தாவலில் திறக்கப்படும்) நேரடி கோரிக்கையுடன் மட்டுமே நிகழ வேண்டும் (அனுமதித்தொகையின் விளைவாக நிகழக்கூடிய Transfer நிகழ்வுகளுக்கு (புதிய தாவலில் திறக்கப்படும்) மாறாக). இந்தச் சிக்கல் மற்றும் கோரிக்கைகள் ஏன் ஒரு ஒப்பந்தத்தால் மத்தியஸ்தம் செய்யப்படாமல் நேரடியாக இருக்க வேண்டும் என்பதற்கான விரிவான விளக்கத்திற்கு Solidity ஆவணங்களைப் பார்க்கவும் (புதிய தாவலில் திறக்கப்படும்).
இதன் பொருள், வெளிப்புறமாகச் சொந்தமான கணக்கிலிருந்து செலவழிப்பதற்கு ஒப்புதல் அளிக்கும் Approval நிகழ்வுகள், அந்தக் கணக்கில் உருவாகும் மற்றும் அதன் இலக்கு ERC-20 ஒப்பந்தமாக இருக்கும் பரிவர்த்தனைகளிலிருந்து வர வேண்டும். வெளிப்புறமாகச் சொந்தமான கணக்கிலிருந்து வரும் வேறு எந்த வகையான ஒப்புதலும் சந்தேகத்திற்குரியது.
வகை பாதுகாப்புடன் (type safety) கூடிய JavaScript மாறுபாடான TypeScript (புதிய தாவலில் திறக்கப்படும்) மற்றும் Viem (புதிய தாவலில் திறக்கப்படும்) ஐப் பயன்படுத்தி, இவ்வகையான நிகழ்வைக் கண்டறியும் ஒரு நிரல் (புதிய தாவலில் திறக்கப்படும்) இங்கே உள்ளது. இதை இயக்க:
.env.exampleஐ.envக்கு நகலெடுக்கவும்.- எத்தேரியம் முதன்மை வலைப்பின்னல் கணுவிற்கான URL ஐ வழங்க
.envஐத் திருத்தவும். - தேவையான தொகுப்புகளை நிறுவ
pnpm installஐ இயக்கவும். - சந்தேகத்திற்குரிய ஒப்புதல்களைத் தேட
pnpm susApprovalஐ இயக்கவும்.
வரிக்கு வரி விளக்கம் இங்கே:
import {
Address,
TransactionReceipt,
createPublicClient,
http,
parseAbiItem,
} from "viem"
import { mainnet } from "viem/chains"
viem இலிருந்து வகை வரையறைகள், செயல்பாடுகள் மற்றும் சங்கிலி வரையறையை இறக்குமதி செய்யவும்.
import { config } from "dotenv"
config()
URL ஐப் பெற .env ஐப் படிக்கவும்.
const client = createPublicClient({
chain: mainnet,
transport: http(process.env.URL),
})
ஒரு Viem கிளையண்டை உருவாக்கவும். நாம் தொகுதிச்சங்கிலியிலிருந்து படிக்க மட்டுமே வேண்டும், எனவே இந்தக் கிளையண்டிற்குத் தனிப்பட்ட திறவுகோல் தேவையில்லை.
const testedAddress = "0xb047c8032b99841713b8e3872f06cf32beb27b82"
const fromBlock = 16859812n
const toBlock = 16873372n
சந்தேகத்திற்குரிய ERC-20 ஒப்பந்தத்தின் முகவரி, மற்றும் நாம் நிகழ்வுகளைத் தேடும் தொகுதிகள். அலைவரிசை விலை உயர்ந்ததாக இருக்கும் என்பதால், கணு வழங்குநர்கள் பொதுவாக நிகழ்வுகளைப் படிக்கும் நமது திறனைக் கட்டுப்படுத்துகிறார்கள். அதிர்ஷ்டவசமாக wARB பதினெட்டு மணி நேரத்திற்குப் பயன்பாட்டில் இல்லை, எனவே நாம் அனைத்து நிகழ்வுகளையும் தேடலாம் (மொத்தம் 13 மட்டுமே இருந்தன).
const approvalEvents = await client.getLogs({
address: testedAddress,
fromBlock,
toBlock,
event: parseAbiItem(
"event Approval(address indexed _owner, address indexed _spender, uint256 _value)"
),
})
நிகழ்வுத் தகவலுக்காக Viem ஐக் கேட்கும் வழி இதுதான். புலப் பெயர்கள் உட்பட சரியான நிகழ்வு கையொப்பத்தை நாம் வழங்கும்போது, அது நமக்காக நிகழ்வைப் பாகுபடுத்துகிறது (parses).
const isContract = async (addr: Address): boolean =>
await client.getBytecode({ address: addr })
நமது அல்காரிதம் வெளிப்புறமாகச் சொந்தமான கணக்குகளுக்கு மட்டுமே பொருந்தும். client.getBytecode ஆல் ஏதேனும் பைட் குறியீடு வழங்கப்பட்டால், இது ஒரு ஒப்பந்தம் என்று அர்த்தம், நாம் அதைத் தவிர்த்துவிட வேண்டும்.
நீங்கள் இதற்கு முன் TypeScript ஐப் பயன்படுத்தவில்லை என்றால், செயல்பாட்டு வரையறை சற்று விசித்திரமாகத் தோன்றலாம். முதல் (மற்றும் ஒரே) அளவுரு addr என்று அழைக்கப்படுகிறது என்று மட்டும் நாம் கூறவில்லை, அது Address வகையைச் சேர்ந்தது என்றும் கூறுகிறோம். இதேபோல், : boolean பகுதியானது செயல்பாட்டின் திரும்பப் பெறும் மதிப்பு (return value) ஒரு பூலியன் (boolean) என்பதை TypeScript க்குக் கூறுகிறது.
const getEventTxn = async (ev: Event): TransactionReceipt =>
await client.getTransactionReceipt({ hash: ev.transactionHash })
இந்தச் செயல்பாடு ஒரு நிகழ்விலிருந்து பரிவர்த்தனை ரசீதைப் பெறுகிறது. பரிவர்த்தனை இலக்கு என்ன என்பதை நாம் அறிவதை உறுதிசெய்ய நமக்கு ரசீது தேவை.
const suspiciousApprovalEvent = async (ev : Event) : (Event | null) => {
இது மிக முக்கியமான செயல்பாடாகும், இது உண்மையில் ஒரு நிகழ்வு சந்தேகத்திற்குரியதா இல்லையா என்பதைத் தீர்மானிக்கிறது. திரும்பப் பெறும் வகையான (Event | null), இந்தச் செயல்பாடு Event அல்லது null ஐ வழங்க முடியும் என்பதை TypeScript க்குக் கூறுகிறது. நிகழ்வு சந்தேகத்திற்குரியதாக இல்லாவிட்டால் நாம் null ஐ வழங்குகிறோம்.
const owner = ev.args._owner
Viem புலப் பெயர்களைக் கொண்டுள்ளது, எனவே அது நமக்காக நிகழ்வைப் பாகுபடுத்தியது. _owner என்பது செலவழிக்கப்பட வேண்டிய வில்லைகளின் உரிமையாளர்.
// ஒப்பந்தங்கள் அளிக்கும் ஒப்புதல்கள் சந்தேகத்திற்குரியவை அல்ல
if (await isContract(owner)) return null
உரிமையாளர் ஒரு ஒப்பந்தமாக இருந்தால், இந்த ஒப்புதல் சந்தேகத்திற்குரியது அல்ல என்று கருதுங்கள். ஒரு ஒப்பந்தத்தின் ஒப்புதல் சந்தேகத்திற்குரியதா இல்லையா என்பதைச் சரிபார்க்க, அது எப்போதாவது உரிமையாளர் ஒப்பந்தத்திற்குச் சென்றதா என்பதையும், அந்த ஒப்பந்தம் நேரடியாக ERC-20 ஒப்பந்தத்தை அழைத்ததா என்பதையும் பார்க்க, பரிவர்த்தனையின் முழுச் செயலாக்கத்தையும் நாம் கண்காணிக்க வேண்டும். நாம் செய்ய விரும்புவதை விட இது மிகவும் அதிக வளங்களைச் செலவழிக்கும் செயலாகும்.
const txn = await getEventTxn(ev)
ஒப்புதல் வெளிப்புறமாகச் சொந்தமான கணக்கிலிருந்து வந்தால், அதற்குக் காரணமான பரிவர்த்தனையைப் பெறவும்.
// பரிவர்த்தனையின் `from` ஆக இல்லாத ஒரு EOA உரிமையாளரிடமிருந்து ஒப்புதல் வந்தால் அது சந்தேகத்திற்குரியதாகும்
if (owner.toLowerCase() != txn.from.toLowerCase()) return ev
முகவரிகள் பதினறும (hexadecimal) எண்களாக இருப்பதால், அவற்றில் எழுத்துகள் உள்ளன, எனவே நாம் சரங்களின் சமத்துவத்தை (string equality) மட்டும் சரிபார்க்க முடியாது. சில நேரங்களில், எடுத்துக்காட்டாக txn.from இல், அந்த எழுத்துகள் அனைத்தும் சிறிய எழுத்துகளாக இருக்கும். மற்ற சந்தர்ப்பங்களில், ev.args._owner போன்ற, முகவரி பிழை அடையாளத்திற்காகக் கலப்பு-எழுத்துகளில் (mixed-case) (புதிய தாவலில் திறக்கப்படும்) இருக்கும்.
ஆனால் பரிவர்த்தனை உரிமையாளரிடமிருந்து வரவில்லை என்றால், மற்றும் அந்த உரிமையாளர் வெளிப்புறமாகச் சொந்தமானவராக இருந்தால், நம்மிடம் ஒரு சந்தேகத்திற்குரிய பரிவர்த்தனை உள்ளது.
// நாம் விசாரித்துக்கொண்டிருக்கும் ERC-20 ஒப்பந்தமாக பரிவர்த்தனையின் இலக்கு இல்லாவிட்டாலும்
// அது சந்தேகத்திற்குரியதாகும்
if (txn.to.toLowerCase() != testedAddress) return ev
இதேபோல், பரிவர்த்தனையின் to முகவரி, அதாவது அழைக்கப்பட்ட முதல் ஒப்பந்தம், விசாரணையில் உள்ள ERC-20 ஒப்பந்தமாக இல்லாவிட்டால் அது சந்தேகத்திற்குரியது.
// சந்தேகப்படுவதற்கு எந்தக் காரணமும் இல்லை என்றால், null ஐ வழங்கவும்.
return null
}
இரண்டு நிபந்தனைகளும் உண்மையாக இல்லாவிட்டால், Approval நிகழ்வு சந்தேகத்திற்குரியது அல்ல.
const testPromises = approvalEvents.map((ev) => suspiciousApprovalEvent(ev))
const testResults = (await Promise.all(testPromises)).filter((x) => x != null)
console.log(testResults)
ஒரு async செயல்பாடு (புதிய தாவலில் திறக்கப்படும்) ஒரு Promise பொருளை வழங்குகிறது. பொதுவான தொடரியல் (syntax) ஆன await x() உடன், செயலாக்கத்தைத் தொடர்வதற்கு முன் அந்த Promise நிறைவேற்றப்படும் வரை நாம் காத்திருக்கிறோம். இது நிரலாக்கம் செய்வதற்கும் பின்பற்றுவதற்கும் எளிமையானது, ஆனால் இது திறனற்றதும்கூட. ஒரு குறிப்பிட்ட நிகழ்விற்கான Promise நிறைவேற்றப்படும் வரை நாம் காத்திருக்கும்போது, நாம் ஏற்கனவே அடுத்த நிகழ்வில் வேலை செய்யத் தொடங்கலாம்.
இங்கே நாம் Promise பொருள்களின் அணிவரிசையை உருவாக்க map (புதிய தாவலில் திறக்கப்படும்) ஐப் பயன்படுத்துகிறோம். பின்னர் அந்த வாக்குறுதிகள் (promises) அனைத்தும் தீர்க்கப்படும் வரை காத்திருக்க Promise.all (புதிய தாவலில் திறக்கப்படும்) ஐப் பயன்படுத்துகிறோம். சந்தேகத்திற்குரியதல்லாத நிகழ்வுகளை அகற்ற அந்த முடிவுகளை நாம் filter (புதிய தாவலில் திறக்கப்படும்) செய்கிறோம்.
சந்தேகத்திற்குரிய Transfer நிகழ்வுகள்
மோசடி வில்லைகளைக் கண்டறிவதற்கான மற்றொரு சாத்தியமான வழி, அவற்றில் ஏதேனும் சந்தேகத்திற்குரிய பரிமாற்றங்கள் உள்ளதா என்பதைப் பார்ப்பதாகும். எடுத்துக்காட்டாக, அவ்வளவு வில்லைகள் இல்லாத கணக்குகளிலிருந்து பரிமாற்றங்கள். இந்தச் சோதனையை எவ்வாறு செயல்படுத்துவது (புதிய தாவலில் திறக்கப்படும்) என்பதை நீங்கள் பார்க்கலாம், ஆனால் wARB க்கு இந்தச் சிக்கல் இல்லை.
முடிவுரை
ERC-20 மோசடிகளின் தானியங்கு கண்டறிதல் தவறான எதிர்மறைகளால் (false negatives) (புதிய தாவலில் திறக்கப்படும்) பாதிக்கப்படுகிறது, ஏனெனில் ஒரு மோசடியானது உண்மையான எதையும் பிரதிநிதித்துவப்படுத்தாத முற்றிலும் சாதாரண ERC-20 வில்லை ஒப்பந்தத்தைப் பயன்படுத்தலாம். எனவே நீங்கள் எப்போதும் நம்பகமான மூலத்திலிருந்து வில்லை முகவரியைப் பெற முயற்சிக்க வேண்டும்.
பரவலாக்கப்பட்ட நிதி (DeFi) பகுதிகள் போன்ற சில சந்தர்ப்பங்களில் தானியங்கு கண்டறிதல் உதவக்கூடும், அங்கு பல வில்லைகள் உள்ளன மற்றும் அவை தானாகவே கையாளப்பட வேண்டும். ஆனால் எப்போதும் போல வாங்குபவரே விழித்திருங்கள் (caveat emptor) (புதிய தாவலில் திறக்கப்படும்), உங்கள் சொந்த ஆராய்ச்சியைச் செய்யுங்கள், மேலும் உங்கள் பயனர்களையும் அவ்வாறே செய்ய ஊக்குவியுங்கள்.
எனது மேலும் பல பணிகளுக்கு இங்கே பார்க்கவும் (புதிய தாவலில் திறக்கப்படும்).