కాంట్రాక్ట్ను రివర్స్ ఇంజనీరింగ్ చేయడం
పరిచయం
బ్లాక్చైన్లో ఎలాంటి రహస్యాలు ఉండవు, జరిగే ప్రతిదీ స్థిరంగా, ధృవీకరించదగినదిగా మరియు పబ్లిక్గా అందుబాటులో ఉంటుంది. ఆదర్శవంతంగా, కాంట్రాక్ట్లు వాటి సోర్స్ కోడ్ను Etherscanలో ప్రచురించి మరియు ధృవీకరించి ఉండాలి (opens in a new tab). అయినప్పటికీ, అది ఎల్లప్పుడూ అలా ఉండదు (opens in a new tab). ఈ కథనంలో మీరు సోర్స్ కోడ్ లేని కాంట్రాక్ట్ను చూడటం ద్వారా కాంట్రాక్ట్లను ఎలా రివర్స్ ఇంజనీరింగ్ చేయాలో నేర్చుకుంటారు, 0x2510c039cc3b061d79e564b38836da87e31b342f (opens in a new tab).
రివర్స్ కంపైలర్లు ఉన్నాయి, కానీ అవి ఎల్లప్పుడూ ఉపయోగించదగిన ఫలితాలను (opens in a new tab) అందించవు. ఈ కథనంలో మీరు ఆప్కోడ్ల (opens in a new tab) నుండి కాంట్రాక్ట్ను మాన్యువల్గా ఎలా రివర్స్ ఇంజనీరింగ్ చేయాలో మరియు అర్థం చేసుకోవాలో, అలాగే డీకంపైలర్ ఫలితాలను ఎలా అన్వయించాలో కూడా నేర్చుకుంటారు.
ఈ కథనాన్ని అర్థం చేసుకోగలగడానికి మీకు ఇప్పటికే EVM యొక్క ప్రాథమిక అంశాలు తెలిసి ఉండాలి మరియు కనీసం కొంతవరకు EVM అసెంబ్లర్తో పరిచయం ఉండాలి. మీరు ఈ అంశాల గురించి ఇక్కడ చదవవచ్చు (opens in a new tab).
ఎక్జిక్యూటబుల్ కోడ్ను సిద్ధం చేయండి
మీరు కాంట్రాక్ట్ కోసం Etherscan కు వెళ్లి, Contract ట్యాబ్ను క్లిక్ చేసి, ఆపై Switch to Opcodes View కు మారడం ద్వారా ఆప్కోడ్లను పొందవచ్చు. మీరు ప్రతి లైన్కు ఒక ఆప్కోడ్ ఉండే వీక్షణను పొందుతారు.
అయితే, జంప్లను అర్థం చేసుకోవడానికి, కోడ్లో ప్రతి ఆప్కోడ్ ఎక్కడ ఉందో మీరు తెలుసుకోవాలి. అలా చేయడానికి, ఒక మార్గం Google Spreadsheet ని తెరిచి, ఆప్కోడ్లను C కాలమ్లో పేస్ట్ చేయడం. ఇప్పటికే సిద్ధం చేసిన ఈ స్ప్రెడ్షీట్ కాపీని చేయడం ద్వారా మీరు కింది దశలను దాటవేయవచ్చు (opens in a new tab).
తదుపరి దశ సరైన కోడ్ స్థానాలను పొందడం, తద్వారా మనం జంప్లను అర్థం చేసుకోగలుగుతాము. మనం ఆప్కోడ్ పరిమాణాన్ని B కాలమ్లో మరియు స్థానాన్ని (హెక్సాడెసిమల్లో) A కాలమ్లో ఉంచుతాము. ఈ ఫంక్షన్ను B1 సెల్లో టైప్ చేసి, ఆపై కోడ్ ముగిసే వరకు మిగిలిన B కాలమ్ కోసం దాన్ని కాపీ చేసి పేస్ట్ చేయండి. మీరు దీన్ని చేసిన తర్వాత B కాలమ్ను దాచవచ్చు.
=1+IF(REGEXMATCH(C1,"PUSH"),REGEXEXTRACT(C1,"PUSH(\d+)"),0)
ముందుగా ఈ ఫంక్షన్ ఆప్కోడ్ కోసం ఒక బైట్ను జోడిస్తుంది, ఆపై PUSH కోసం వెతుకుతుంది. పుష్ ఆప్కోడ్లు ప్రత్యేకమైనవి ఎందుకంటే పుష్ చేయబడుతున్న విలువ కోసం వాటికి అదనపు బైట్లు అవసరం. ఆప్కోడ్ PUSH అయితే, మనం బైట్ల సంఖ్యను సంగ్రహించి దాన్ని జోడిస్తాము.
A1 లో మొదటి ఆఫ్సెట్, సున్నాను ఉంచండి. ఆపై, A2 లో, ఈ ఫంక్షన్ను ఉంచండి మరియు మిగిలిన A కాలమ్ కోసం దాన్ని మళ్లీ కాపీ చేసి పేస్ట్ చేయండి:
=dec2hex(hex2dec(A1)+B1)
ఈ ఫంక్షన్ మనకు హెక్సాడెసిమల్ విలువను ఇవ్వాలి ఎందుకంటే జంప్లకు ముందు పుష్ చేయబడిన విలువలు (JUMP మరియు JUMPI) మనకు హెక్సాడెసిమల్లో ఇవ్వబడతాయి.
ఎంట్రీ పాయింట్ (0x00)
కాంట్రాక్ట్లు ఎల్లప్పుడూ మొదటి బైట్ నుండి అమలు చేయబడతాయి. ఇది కోడ్ యొక్క ప్రారంభ భాగం:
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ (ఆప్కోడ్ తర్వాత) |
|---|---|---|
| 0 | PUSH1 0x80 | 0x80 |
| 2 | PUSH1 0x40 | 0x40, 0x80 |
| 4 | MSTORE | ఖాళీ |
| 5 | PUSH1 0x04 | 0x04 |
| 7 | CALLDATASIZE | CALLDATASIZE 0x04 |
| 8 | LT | CALLDATASIZE<4 |
| 9 | PUSH2 0x005e | 0x5E CALLDATASIZE<4 |
| C | JUMPI | ఖాళీ |
ఈ కోడ్ రెండు పనులు చేస్తుంది:
- 0x80ని 32 బైట్ విలువగా 0x40-0x5F మెమరీ స్థానాలకు వ్రాయడం (0x80 అనేది 0x5Fలో నిల్వ చేయబడుతుంది మరియు 0x40-0x5E అన్నీ సున్నాలుగా ఉంటాయి).
- కాల్ డేటా పరిమాణాన్ని చదవడం. సాధారణంగా Ethereum కాంట్రాక్ట్ కోసం కాల్ డేటా ABI (అప్లికేషన్ బైనరీ ఇంటర్ఫేస్) (opens in a new tab)ని అనుసరిస్తుంది, దీనికి ఫంక్షన్ సెలెక్టర్ కోసం కనీసం నాలుగు బైట్లు అవసరం. కాల్ డేటా పరిమాణం నాలుగు కంటే తక్కువగా ఉంటే, 0x5Eకి జంప్ అవుతుంది.
0x5E వద్ద హ్యాండ్లర్ (నాన్-ABI కాల్ డేటా కోసం)
| ఆఫ్సెట్ | ఆప్కోడ్ |
|---|---|
| 5E | JUMPDEST |
| 5F | CALLDATASIZE |
| 60 | PUSH2 0x007c |
| 63 | JUMPI |
ఈ స్నిప్పెట్ JUMPDESTతో ప్రారంభమవుతుంది. మీరు JUMPDEST కాని ఆప్కోడ్కి జంప్ చేస్తే EVM (Ethereum వర్చువల్ మెషీన్) ప్రోగ్రామ్లు మినహాయింపును (exception) త్రో చేస్తాయి. ఆ తర్వాత ఇది CALLDATASIZEని చూస్తుంది, మరియు అది "నిజం" (అంటే, సున్నా కాదు) అయితే 0x7Cకి జంప్ అవుతుంది. దాని గురించి మనం కింద తెలుసుకుందాం.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ (ఆప్కోడ్ తర్వాత) |
|---|---|---|
| 64 | CALLVALUE | కాల్ ద్వారా అందించబడిన . Solidityలో msg.value అని పిలుస్తారు |
| 65 | PUSH1 0x06 | 6 CALLVALUE |
| 67 | PUSH1 0x00 | 0 6 CALLVALUE |
| 69 | DUP3 | CALLVALUE 0 6 CALLVALUE |
| 6A | DUP3 | 6 CALLVALUE 0 6 CALLVALUE |
| 6B | SLOAD | Storage[6] CALLVALUE 0 6 CALLVALUE |
కాబట్టి కాల్ డేటా లేనప్పుడు మనం Storage[6] విలువను చదువుతాము. ఈ విలువ ఏమిటో మనకు ఇంకా తెలియదు, కానీ కాల్ డేటా లేకుండా కాంట్రాక్ట్ స్వీకరించిన లావాదేవీల కోసం మనం వెతకవచ్చు. ఎలాంటి కాల్ డేటా లేకుండా (అందువల్ల ఎలాంటి పద్ధతి లేకుండా) కేవలం ETHని బదిలీ చేసే లావాదేవీలు Etherscanలో Transfer పద్ధతిని కలిగి ఉంటాయి. వాస్తవానికి, కాంట్రాక్ట్ స్వీకరించిన మొట్టమొదటి లావాదేవీ (opens in a new tab) ఒక బదిలీ.
మనం ఆ లావాదేవీని చూసి, Click to see Moreపై క్లిక్ చేస్తే, ఇన్పుట్ డేటా అని పిలువబడే కాల్ డేటా నిజంగానే ఖాళీగా ఉందని (0x) మనం చూడవచ్చు. విలువ 1.559 ETH అని కూడా గమనించండి, అది తర్వాత ఉపయోగపడుతుంది.
తర్వాత, State ట్యాబ్పై క్లిక్ చేసి, మనం రివర్స్ ఇంజనీరింగ్ చేస్తున్న కాంట్రాక్ట్ను (0x2510...) విస్తరించండి. లావాదేవీ సమయంలో Storage[6] మారిందని మీరు చూడవచ్చు, మరియు మీరు Hexని Numberకి మార్చినట్లయితే, అది 1,559,000,000,000,000,000గా మారినట్లు మీరు చూస్తారు, ఇది weiలో బదిలీ చేయబడిన విలువ (స్పష్టత కోసం నేను కామాలను జోడించాను), ఇది తదుపరి కాంట్రాక్ట్ విలువకు అనుగుణంగా ఉంటుంది.
అదే కాలానికి చెందిన ఇతర Transfer లావాదేవీల (opens in a new tab) వల్ల ఏర్పడిన స్థితి మార్పులను మనం పరిశీలిస్తే, Storage[6] కొంతకాలం పాటు కాంట్రాక్ట్ విలువను ట్రాక్ చేసిందని మనం చూడవచ్చు. ప్రస్తుతానికి మనం దానిని Value* అని పిలుస్తాము. ఆస్టరిస్క్ (*) ఈ వేరియబుల్ ఏమి చేస్తుందో మనకు ఇంకా తెలియదు అని గుర్తుచేస్తుంది, కానీ ఇది కేవలం కాంట్రాక్ట్ విలువను ట్రాక్ చేయడానికి మాత్రమే కాదు, ఎందుకంటే మీరు ADDRESS BALANCEని ఉపయోగించి మీ ఖాతాల బ్యాలెన్స్ను పొందగలిగినప్పుడు, చాలా ఖరీదైన స్టోరేజ్ను ఉపయోగించాల్సిన అవసరం లేదు. మొదటి ఆప్కోడ్ కాంట్రాక్ట్ యొక్క స్వంత చిరునామాను పుష్ చేస్తుంది. రెండవది స్టాక్ పైభాగంలో ఉన్న చిరునామాను చదువుతుంది మరియు దానిని ఆ చిరునామా యొక్క బ్యాలెన్స్తో భర్తీ చేస్తుంది.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 6C | PUSH2 0x0075 | 0x75 Value* CALLVALUE 0 6 CALLVALUE |
| 6F | SWAP2 | CALLVALUE Value* 0x75 0 6 CALLVALUE |
| 70 | SWAP1 | Value* CALLVALUE 0x75 0 6 CALLVALUE |
| 71 | PUSH2 0x01a7 | 0x01A7 Value* CALLVALUE 0x75 0 6 CALLVALUE |
| 74 | JUMP |
మనం జంప్ గమ్యస్థానం వద్ద ఈ కోడ్ను ట్రేస్ చేయడం కొనసాగిస్తాము.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 1A7 | JUMPDEST | Value* CALLVALUE 0x75 0 6 CALLVALUE |
| 1A8 | PUSH1 0x00 | 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE |
| 1AA | DUP3 | CALLVALUE 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE |
| 1AB | NOT | 2^256-CALLVALUE-1 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE |
NOT అనేది బిట్వైస్, కాబట్టి ఇది కాల్ విలువలోని ప్రతి బిట్ విలువను రివర్స్ చేస్తుంది.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 1AC | DUP3 | Value* 2^256-CALLVALUE-1 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE |
| 1AD | GT | Value*>2^256-CALLVALUE-1 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE |
| 1AE | ISZERO | Value*<=2^256-CALLVALUE-1 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE |
| 1AF | PUSH2 0x01df | 0x01DF Value*<=2^256-CALLVALUE-1 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE |
| 1B2 | JUMPI |
Value* అనేది 2^256-CALLVALUE-1 కంటే తక్కువగా లేదా దానికి సమానంగా ఉంటే మనం జంప్ చేస్తాము. ఇది ఓవర్ఫ్లోను నిరోధించే లాజిక్ లాగా కనిపిస్తుంది. మరియు వాస్తవానికి, కొన్ని అర్థరహిత ఆపరేషన్ల తర్వాత (ఉదాహరణకు, మెమరీకి వ్రాయడం తొలగించబడబోతోంది) ఆఫ్సెట్ 0x01DE వద్ద ఓవర్ఫ్లో కనుగొనబడితే కాంట్రాక్ట్ రివర్ట్ అవుతుందని మనం చూస్తాము, ఇది సాధారణ ప్రవర్తన.
అలాంటి ఓవర్ఫ్లో జరగడం చాలా అరుదు అని గమనించండి, ఎందుకంటే దీనికి కాల్ విలువ మరియు Value* కలిపి 2^256 weiకి, అంటే దాదాపు 10^59 ETHకి సమానంగా ఉండాలి. ఇది రాసే సమయానికి, మొత్తం ETH సరఫరా రెండు వందల మిలియన్ల కంటే తక్కువగా ఉంది (opens in a new tab).
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 1DF | JUMPDEST | 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE |
| 1E0 | POP | Value* CALLVALUE 0x75 0 6 CALLVALUE |
| 1E1 | ADD | Value*+CALLVALUE 0x75 0 6 CALLVALUE |
| 1E2 | SWAP1 | 0x75 Value*+CALLVALUE 0 6 CALLVALUE |
| 1E3 | JUMP |
మనం ఇక్కడికి చేరుకుంటే, Value* + CALLVALUEని పొంది, ఆఫ్సెట్ 0x75కి జంప్ చేయండి.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 75 | JUMPDEST | Value*+CALLVALUE 0 6 CALLVALUE |
| 76 | SWAP1 | 0 Value*+CALLVALUE 6 CALLVALUE |
| 77 | SWAP2 | 6 Value*+CALLVALUE 0 CALLVALUE |
| 78 | SSTORE | 0 CALLVALUE |
మనం ఇక్కడికి చేరుకుంటే (దీనికి కాల్ డేటా ఖాళీగా ఉండాలి) మనం Value*కి కాల్ విలువను జోడిస్తాము. ఇది Transfer లావాదేవీలు చేస్తాయని మనం చెప్పే దానికి అనుగుణంగా ఉంటుంది.
| ఆఫ్సెట్ | ఆప్కోడ్ |
|---|---|
| 79 | POP |
| 7A | POP |
| 7B | STOP |
చివరగా, స్టాక్ను క్లియర్ చేయండి (ఇది అవసరం లేదు) మరియు లావాదేవీ విజయవంతంగా ముగిసిందని సిగ్నల్ ఇవ్వండి.
మొత్తంగా చెప్పాలంటే, ప్రారంభ కోడ్ కోసం ఫ్లోచార్ట్ ఇక్కడ ఉంది.
0x7C వద్ద హ్యాండ్లర్
ఈ హ్యాండ్లర్ ఏమి చేస్తుందో నేను ఉద్దేశపూర్వకంగానే శీర్షికలో పెట్టలేదు. ఈ నిర్దిష్ట కాంట్రాక్ట్ ఎలా పనిచేస్తుందో మీకు నేర్పించడం దీని ఉద్దేశ్యం కాదు, కాంట్రాక్ట్లను ఎలా రివర్స్ ఇంజనీరింగ్ చేయాలో నేర్పించడం. కోడ్ను అనుసరించడం ద్వారా నేను ఎలా తెలుసుకున్నానో, మీరు కూడా అదే విధంగా ఇది ఏమి చేస్తుందో నేర్చుకుంటారు.
మనం ఇక్కడికి అనేక ప్రదేశాల నుండి వస్తాము:
- 1, 2, లేదా 3 బైట్ల కాల్ డేటా ఉంటే (ఆఫ్సెట్ 0x63 నుండి)
- పద్ధతి సంతకం తెలియకపోతే (ఆఫ్సెట్లు 0x42 మరియు 0x5D నుండి)
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 7C | JUMPDEST | |
| 7D | PUSH1 0x00 | 0x00 |
| 7F | PUSH2 0x009d | 0x9D 0x00 |
| 82 | PUSH1 0x03 | 0x03 0x9D 0x00 |
| 84 | SLOAD | Storage[3] 0x9D 0x00 |
ఇది మరొక స్టోరేజ్ సెల్, ఏ లావాదేవీలలోనూ నేను దీనిని కనుగొనలేకపోయాను కాబట్టి దీని అర్థం ఏమిటో తెలుసుకోవడం కష్టం. దిగువ ఉన్న కోడ్ దీనిని మరింత స్పష్టం చేస్తుంది.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 85 | PUSH20 0xffffffffffffffffffffffffffffffffffffffff | 0xff....ff Storage[3] 0x9D 0x00 |
| 9A | AND | Storage[3]-as-address 0x9D 0x00 |
ఈ ఆప్కోడ్లు మనం Storage[3] నుండి చదివిన విలువను 160 బిట్లకు కుదిస్తాయి, ఇది Ethereum చిరునామా పొడవు.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 9B | SWAP1 | 0x9D Storage[3]-as-address 0x00 |
| 9C | JUMP | Storage[3]-as-address 0x00 |
మనం తదుపరి ఆప్కోడ్కి వెళుతున్నందున ఈ జంప్ అనవసరం. ఈ కోడ్ ఉండాల్సినంత గ్యాస్-సమర్థవంతంగా లేదు.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 9D | JUMPDEST | Storage[3]-as-address 0x00 |
| 9E | SWAP1 | 0x00 Storage[3]-as-address |
| 9F | POP | Storage[3]-as-address |
| A0 | PUSH1 0x40 | 0x40 Storage[3]-as-address |
| A2 | MLOAD | Mem[0x40] Storage[3]-as-address |
కోడ్ ప్రారంభంలోనే మనం Mem[0x40]ని 0x80కి సెట్ చేసాము. మనం తర్వాత 0x40 కోసం చూస్తే, మనం దానిని మార్చలేదని చూస్తాము - కాబట్టి అది 0x80 అని మనం ఊహించవచ్చు.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| A3 | CALLDATASIZE | CALLDATASIZE 0x80 Storage[3]-as-address |
| A4 | PUSH1 0x00 | 0x00 CALLDATASIZE 0x80 Storage[3]-as-address |
| A6 | DUP3 | 0x80 0x00 CALLDATASIZE 0x80 Storage[3]-as-address |
| A7 | CALLDATACOPY | 0x80 Storage[3]-as-address |
0x80 వద్ద ప్రారంభించి, కాల్ డేటా మొత్తాన్ని మెమరీకి కాపీ చేయండి.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| A8 | PUSH1 0x00 | 0x00 0x80 Storage[3]-as-address |
| AA | DUP1 | 0x00 0x00 0x80 Storage[3]-as-address |
| AB | CALLDATASIZE | CALLDATASIZE 0x00 0x00 0x80 Storage[3]-as-address |
| AC | DUP4 | 0x80 CALLDATASIZE 0x00 0x00 0x80 Storage[3]-as-address |
| AD | DUP6 | Storage[3]-as-address 0x80 CALLDATASIZE 0x00 0x00 0x80 Storage[3]-as-address |
| AE | GAS | GAS Storage[3]-as-address 0x80 CALLDATASIZE 0x00 0x00 0x80 Storage[3]-as-address |
| AF | DELEGATE_CALL |
ఇప్పుడు విషయాలు చాలా స్పష్టంగా ఉన్నాయి. ఈ కాంట్రాక్ట్ ప్రతినిధి (opens in a new tab)గా వ్యవహరించగలదు, అసలు పని చేయడానికి Storage[3]లోని చిరునామాను పిలుస్తుంది. DELEGATE_CALL ఒక ప్రత్యేక కాంట్రాక్ట్ను పిలుస్తుంది, కానీ అదే స్టోరేజ్లో ఉంటుంది. దీని అర్థం డెలిగేట్ చేయబడిన కాంట్రాక్ట్, అంటే మనం దేనికైతే ప్రతినిధిగా ఉన్నామో అది, అదే స్టోరేజ్ స్థలాన్ని యాక్సెస్ చేస్తుంది. కాల్ కోసం పారామితులు ఇవి:
- గ్యాస్: మిగిలిన గ్యాస్ అంతా
- పిలవబడిన చిరునామా: Storage[3]-as-address
- కాల్ డేటా: 0x80 వద్ద ప్రారంభమయ్యే CALLDATASIZE బైట్లు, ఇక్కడే మనం అసలు కాల్ డేటాను ఉంచాము
- రిటర్న్ డేటా: ఏదీ లేదు (0x00 - 0x00) మనం రిటర్న్ డేటాను ఇతర మార్గాల ద్వారా పొందుతాము (క్రింద చూడండి)
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| B0 | RETURNDATASIZE | RETURNDATASIZE (((కాల్ విజయం/వైఫల్యం))) 0x80 Storage[3]-as-address |
| B1 | DUP1 | RETURNDATASIZE RETURNDATASIZE (((కాల్ విజయం/వైఫల్యం))) 0x80 Storage[3]-as-address |
| B2 | PUSH1 0x00 | 0x00 RETURNDATASIZE RETURNDATASIZE (((కాల్ విజయం/వైఫల్యం))) 0x80 Storage[3]-as-address |
| B4 | DUP5 | 0x80 0x00 RETURNDATASIZE RETURNDATASIZE (((కాల్ విజయం/వైఫల్యం))) 0x80 Storage[3]-as-address |
| B5 | RETURNDATACOPY | RETURNDATASIZE (((కాల్ విజయం/వైఫల్యం))) 0x80 Storage[3]-as-address |
ఇక్కడ మనం రిటర్న్ డేటా మొత్తాన్ని 0x80 వద్ద ప్రారంభమయ్యే మెమరీ బఫర్కి కాపీ చేస్తాము.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| B6 | DUP2 | (((కాల్ విజయం/వైఫల్యం))) RETURNDATASIZE (((కాల్ విజయం/వైఫల్యం))) 0x80 Storage[3]-as-address |
| B7 | DUP1 | (((కాల్ విజయం/వైఫల్యం))) (((కాల్ విజయం/వైఫల్యం))) RETURNDATASIZE (((కాల్ విజయం/వైఫల్యం))) 0x80 Storage[3]-as-address |
| B8 | ISZERO | (((కాల్ విఫలమైందా))) (((కాల్ విజయం/వైఫల్యం))) RETURNDATASIZE (((కాల్ విజయం/వైఫల్యం))) 0x80 Storage[3]-as-address |
| B9 | PUSH2 0x00c0 | 0xC0 (((కాల్ విఫలమైందా))) (((కాల్ విజయం/వైఫల్యం))) RETURNDATASIZE (((కాల్ విజయం/వైఫల్యం))) 0x80 Storage[3]-as-address |
| BC | JUMPI | (((కాల్ విజయం/వైఫల్యం))) RETURNDATASIZE (((కాల్ విజయం/వైఫల్యం))) 0x80 Storage[3]-as-address |
| BD | DUP2 | RETURNDATASIZE (((కాల్ విజయం/వైఫల్యం))) RETURNDATASIZE (((కాల్ విజయం/వైఫల్యం))) 0x80 Storage[3]-as-address |
| BE | DUP5 | 0x80 RETURNDATASIZE (((కాల్ విజయం/వైఫల్యం))) RETURNDATASIZE (((కాల్ విజయం/వైఫల్యం))) 0x80 Storage[3]-as-address |
| BF | RETURN |
కాబట్టి కాల్ తర్వాత మనం రిటర్న్ డేటాను 0x80 - 0x80+RETURNDATASIZE బఫర్కి కాపీ చేస్తాము, మరియు కాల్ విజయవంతమైతే మనం ఖచ్చితంగా అదే బఫర్తో RETURN చేస్తాము.
DELEGATECALL విఫలమైంది
మనం ఇక్కడికి, 0xC0కి వస్తే, మనం పిలిచిన కాంట్రాక్ట్ రివర్ట్ అయిందని అర్థం. మనం ఆ కాంట్రాక్ట్కి కేవలం ప్రతినిధి మాత్రమే కాబట్టి, మనం అదే డేటాను తిరిగి ఇవ్వాలనుకుంటున్నాము మరియు రివర్ట్ కూడా చేయాలనుకుంటున్నాము.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| C0 | JUMPDEST | (((కాల్ విజయం/వైఫల్యం))) RETURNDATASIZE (((కాల్ విజయం/వైఫల్యం))) 0x80 Storage[3]-as-address |
| C1 | DUP2 | RETURNDATASIZE (((కాల్ విజయం/వైఫల్యం))) RETURNDATASIZE (((కాల్ విజయం/వైఫల్యం))) 0x80 Storage[3]-as-address |
| C2 | DUP5 | 0x80 RETURNDATASIZE (((కాల్ విజయం/వైఫల్యం))) RETURNDATASIZE (((కాల్ విజయం/వైఫల్యం))) 0x80 Storage[3]-as-address |
| C3 | REVERT |
కాబట్టి మనం ఇంతకు ముందు RETURN కోసం ఉపయోగించిన అదే బఫర్తో REVERT చేస్తాము: 0x80 - 0x80+RETURNDATASIZE
ABI కాల్స్
కాల్ డేటా పరిమాణం నాలుగు బైట్లు లేదా అంతకంటే ఎక్కువ ఉంటే, ఇది చెల్లుబాటు అయ్యే ABI కాల్ కావచ్చు.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| D | PUSH1 0x00 | 0x00 |
| F | CALLDATALOAD | (((కాల్ డేటా యొక్క మొదటి పదం (256 బిట్లు)))) |
| 10 | PUSH1 0xe0 | 0xE0 (((కాల్ డేటా యొక్క మొదటి పదం (256 బిట్లు)))) |
| 12 | SHR | (((కాల్ డేటా యొక్క మొదటి 32 బిట్లు (4 బైట్లు)))) |
1C అనేది తెలియని ఆప్కోడ్ అని Etherscan మనకు చెబుతుంది, ఎందుకంటే Etherscan ఈ ఫీచర్ను రాసిన తర్వాత ఇది జోడించబడింది (opens in a new tab) మరియు వారు దానిని అప్డేట్ చేయలేదు. ఇది కుడివైపుకి షిఫ్ట్ (shift right) అని ఒక తాజా ఆప్కోడ్ పట్టిక (opens in a new tab) మనకు చూపుతుంది.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 13 | DUP1 | (((కాల్ డేటా యొక్క మొదటి 32 బిట్లు (4 బైట్లు)))) (((కాల్ డేటా యొక్క మొదటి 32 బిట్లు (4 బైట్లు)))) |
| 14 | PUSH4 0x3cd8045e | 0x3CD8045E (((కాల్ డేటా యొక్క మొదటి 32 బిట్లు (4 బైట్లు)))) (((కాల్ డేటా యొక్క మొదటి 32 బిట్లు (4 బైట్లు)))) |
| 19 | GT | 0x3CD8045E>కాల్-డేటా-యొక్క-మొదటి-32-బిట్లు (((కాల్ డేటా యొక్క మొదటి 32 బిట్లు (4 బైట్లు)))) |
| 1A | PUSH2 0x0043 | 0x43 0x3CD8045E>కాల్-డేటా-యొక్క-మొదటి-32-బిట్లు (((కాల్ డేటా యొక్క మొదటి 32 బిట్లు (4 బైట్లు)))) |
| 1D | JUMPI | (((కాల్ డేటా యొక్క మొదటి 32 బిట్లు (4 బైట్లు)))) |
పద్ధతి సంతకం (method signature) సరిపోలే పరీక్షలను ఈ విధంగా రెండుగా విభజించడం ద్వారా సగటున సగం పరీక్షలు ఆదా అవుతాయి. దీని తర్వాత వెంటనే వచ్చే కోడ్ మరియు 0x43 లోని కోడ్ అదే నమూనాను అనుసరిస్తాయి: కాల్ డేటా యొక్క మొదటి 32 బిట్లను DUP1 చేస్తుంది, PUSH4 (((method signature>, సమానత్వం కోసం తనిఖీ చేయడానికి EQ రన్ చేస్తుంది, ఆపై పద్ధతి సంతకం సరిపోలితే JUMPI చేస్తుంది. పద్ధతి సంతకాలు, వాటి చిరునామాలు మరియు తెలిస్తే సంబంధిత పద్ధతి నిర్వచనం (opens in a new tab) ఇక్కడ ఉన్నాయి:
| పద్ధతి | పద్ధతి సంతకం | జంప్ చేయాల్సిన ఆఫ్సెట్ |
|---|---|---|
| splitter() (opens in a new tab) | 0x3cd8045e | 0x0103 |
| ??? | 0x81e580d3 | 0x0138 |
| currentWindow() (opens in a new tab) | 0xba0bafb4 | 0x0158 |
| ??? | 0x1f135823 | 0x00C4 |
| merkleRoot() (opens in a new tab) | 0x2eb4a7ab | 0x00ED |
సరిపోలిక కనుగొనబడకపోతే, మనం ప్రతినిధిగా ఉన్న కాంట్రాక్ట్లో సరిపోలిక ఉంటుందనే ఆశతో, కోడ్ 0x7C వద్ద ఉన్న ప్రతినిధి హ్యాండ్లర్కు జంప్ చేస్తుంది.
splitter()
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 103 | JUMPDEST | |
| 104 | CALLVALUE | CALLVALUE |
| 105 | DUP1 | CALLVALUE CALLVALUE |
| 106 | ISZERO | CALLVALUE==0 CALLVALUE |
| 107 | PUSH2 0x010f | 0x010F CALLVALUE==0 CALLVALUE |
| 10A | JUMPI | CALLVALUE |
| 10B | PUSH1 0x00 | 0x00 CALLVALUE |
| 10D | DUP1 | 0x00 0x00 CALLVALUE |
| 10E | REVERT |
ఈ ఫంక్షన్ చేసే మొదటి పని ఏమిటంటే, కాల్ ద్వారా ఎలాంటి ETH పంపబడలేదని తనిఖీ చేయడం. ఈ ఫంక్షన్ payable (opens in a new tab) కాదు. ఎవరైనా మనకు ETH పంపితే అది పొరపాటు అయి ఉండాలి, మరియు వారు ఆ ETHని తిరిగి పొందలేని చోట ఉంచడాన్ని నివారించడానికి మనం REVERT చేయాలనుకుంటున్నాము.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 10F | JUMPDEST | |
| 110 | POP | |
| 111 | PUSH1 0x03 | 0x03 |
| 113 | SLOAD | (((Storage[3] అనగా మనం ప్రతినిధిగా ఉన్న కాంట్రాక్ట్))) |
| 114 | PUSH1 0x40 | 0x40 (((Storage[3] అనగా మనం ప్రతినిధిగా ఉన్న కాంట్రాక్ట్))) |
| 116 | MLOAD | 0x80 (((Storage[3] అనగా మనం ప్రతినిధిగా ఉన్న కాంట్రాక్ట్))) |
| 117 | PUSH20 0xffffffffffffffffffffffffffffffffffffffff | 0xFF...FF 0x80 (((Storage[3] అనగా మనం ప్రతినిధిగా ఉన్న కాంట్రాక్ట్))) |
| 12C | SWAP1 | 0x80 0xFF...FF (((Storage[3] అనగా మనం ప్రతినిధిగా ఉన్న కాంట్రాక్ట్))) |
| 12D | SWAP2 | (((Storage[3] అనగా మనం ప్రతినిధిగా ఉన్న కాంట్రాక్ట్))) 0xFF...FF 0x80 |
| 12E | AND | ProxyAddr 0x80 |
| 12F | DUP2 | 0x80 ProxyAddr 0x80 |
| 130 | MSTORE | 0x80 |
మరియు 0x80 ఇప్పుడు ప్రతినిధి చిరునామాను కలిగి ఉంది
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 131 | PUSH1 0x20 | 0x20 0x80 |
| 133 | ADD | 0xA0 |
| 134 | PUSH2 0x00e4 | 0xE4 0xA0 |
| 137 | JUMP | 0xA0 |
E4 కోడ్
మనం ఈ పంక్తులను చూడటం ఇదే మొదటిసారి, కానీ ఇవి ఇతర పద్ధతులతో భాగస్వామ్యం చేయబడ్డాయి (క్రింద చూడండి). కాబట్టి మనం స్టాక్లోని విలువను X అని పిలుస్తాము, మరియు splitter() లో ఈ X విలువ 0xA0 అని గుర్తుంచుకోండి.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| E4 | JUMPDEST | X |
| E5 | PUSH1 0x40 | 0x40 X |
| E7 | MLOAD | 0x80 X |
| E8 | DUP1 | 0x80 0x80 X |
| E9 | SWAP2 | X 0x80 0x80 |
| EA | SUB | X-0x80 0x80 |
| EB | SWAP1 | 0x80 X-0x80 |
| EC | RETURN |
కాబట్టి ఈ కోడ్ స్టాక్ (X) లో మెమరీ పాయింటర్ను స్వీకరిస్తుంది, మరియు కాంట్రాక్ట్ 0x80 - X బఫర్తో RETURN అయ్యేలా చేస్తుంది.
splitter() విషయంలో, ఇది మనం ప్రతినిధిగా ఉన్న చిరునామాను తిరిగి ఇస్తుంది. RETURN 0x80-0x9F లో బఫర్ను తిరిగి ఇస్తుంది, ఇక్కడే మనం ఈ డేటాను వ్రాసాము (పైన ఆఫ్సెట్ 0x130).
currentWindow()
ఆఫ్సెట్లు 0x158-0x163 లోని కోడ్ (JUMPI గమ్యం మినహా) splitter() లోని 0x103-0x10E లో మనం చూసిన దానికి సమానంగా ఉంటుంది, కాబట్టి currentWindow() కూడా payable కాదని మనకు తెలుసు.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 164 | JUMPDEST | |
| 165 | POP | |
| 166 | PUSH2 0x00da | 0xDA |
| 169 | PUSH1 0x01 | 0x01 0xDA |
| 16B | SLOAD | Storage[1] 0xDA |
| 16C | DUP2 | 0xDA Storage[1] 0xDA |
| 16D | JUMP | Storage[1] 0xDA |
DA కోడ్
ఈ కోడ్ ఇతర పద్ధతులతో కూడా భాగస్వామ్యం చేయబడింది. కాబట్టి మనం స్టాక్లోని విలువను Y అని పిలుస్తాము, మరియు currentWindow() లో ఈ Y విలువ Storage[1] అని గుర్తుంచుకోండి.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| DA | JUMPDEST | Y 0xDA |
| DB | PUSH1 0x40 | 0x40 Y 0xDA |
| DD | MLOAD | 0x80 Y 0xDA |
| DE | SWAP1 | Y 0x80 0xDA |
| DF | DUP2 | 0x80 Y 0x80 0xDA |
| E0 | MSTORE | 0x80 0xDA |
Y ని 0x80-0x9F కు వ్రాయండి.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| E1 | PUSH1 0x20 | 0x20 0x80 0xDA |
| E3 | ADD | 0xA0 0xDA |
మరియు మిగిలినవి ఇప్పటికే పైన వివరించబడ్డాయి. కాబట్టి 0xDA కు జంప్లు స్టాక్ టాప్ (Y) ని 0x80-0x9F కు వ్రాస్తాయి మరియు ఆ విలువను తిరిగి ఇస్తాయి. currentWindow() విషయంలో, ఇది Storage[1] ని తిరిగి ఇస్తుంది.
merkleRoot()
ఆఫ్సెట్లు 0xED-0xF8 లోని కోడ్ (JUMPI గమ్యస్థానం మినహా) splitter() లోని 0x103-0x10E లో మనం చూసిన దానికి సమానంగా ఉంటుంది, కాబట్టి merkleRoot() కూడా payable కాదని మనకు తెలుసు.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| F9 | JUMPDEST | |
| FA | POP | |
| FB | PUSH2 0x00da | 0xDA |
| FE | PUSH1 0x00 | 0x00 0xDA |
| 100 | SLOAD | Storage[0] 0xDA |
| 101 | DUP2 | 0xDA Storage[0] 0xDA |
| 102 | JUMP | Storage[0] 0xDA |
జంప్ తర్వాత ఏమి జరుగుతుందో మనం ఇప్పటికే కనుక్కున్నాము. కాబట్టి merkleRoot() Storage[0] ని తిరిగి ఇస్తుంది.
0x81e580d3
ఆఫ్సెట్లు 0x138-0x143 లోని కోడ్, splitter() లోని 0x103-0x10E లో మనం చూసిన దానికి సమానంగా ఉంటుంది (JUMPI గమ్యస్థానం మినహా), కాబట్టి ఈ ఫంక్షన్ కూడా payable కాదని మనకు తెలుసు.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 144 | JUMPDEST | |
| 145 | POP | |
| 146 | PUSH2 0x00da | 0xDA |
| 149 | PUSH2 0x0153 | 0x0153 0xDA |
| 14C | CALLDATASIZE | CALLDATASIZE 0x0153 0xDA |
| 14D | PUSH1 0x04 | 0x04 CALLDATASIZE 0x0153 0xDA |
| 14F | PUSH2 0x018f | 0x018F 0x04 CALLDATASIZE 0x0153 0xDA |
| 152 | JUMP | 0x04 CALLDATASIZE 0x0153 0xDA |
| 18F | JUMPDEST | 0x04 CALLDATASIZE 0x0153 0xDA |
| 190 | PUSH1 0x00 | 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 192 | PUSH1 0x20 | 0x20 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 194 | DUP3 | 0x04 0x20 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 195 | DUP5 | CALLDATASIZE 0x04 0x20 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 196 | SUB | CALLDATASIZE-4 0x20 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 197 | SLT | CALLDATASIZE-4<32 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 198 | ISZERO | CALLDATASIZE-4>=32 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 199 | PUSH2 0x01a0 | 0x01A0 CALLDATASIZE-4>=32 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 19C | JUMPI | 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
ఈ ఫంక్షన్ కనీసం 32 బైట్ల (ఒక పదం) కాల్ డేటాను తీసుకుంటుందని తెలుస్తోంది.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 19D | DUP1 | 0x00 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 19E | DUP2 | 0x00 0x00 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 19F | REVERT |
దీనికి కాల్ డేటా అందకపోతే, ఎలాంటి రిటర్న్ డేటా లేకుండా లావాదేవీ రివర్ట్ చేయబడుతుంది.
ఫంక్షన్కు అవసరమైన కాల్ డేటా లభిస్తే ఏం జరుగుతుందో చూద్దాం.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 1A0 | JUMPDEST | 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 1A1 | POP | 0x04 CALLDATASIZE 0x0153 0xDA |
| 1A2 | CALLDATALOAD | calldataload(4) CALLDATASIZE 0x0153 0xDA |
calldataload(4) అనేది మెథడ్ సంతకం తర్వాత కాల్ డేటా యొక్క మొదటి పదం
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 1A3 | SWAP2 | 0x0153 CALLDATASIZE calldataload(4) 0xDA |
| 1A4 | SWAP1 | CALLDATASIZE 0x0153 calldataload(4) 0xDA |
| 1A5 | POP | 0x0153 calldataload(4) 0xDA |
| 1A6 | JUMP | calldataload(4) 0xDA |
| 153 | JUMPDEST | calldataload(4) 0xDA |
| 154 | PUSH2 0x016e | 0x016E calldataload(4) 0xDA |
| 157 | JUMP | calldataload(4) 0xDA |
| 16E | JUMPDEST | calldataload(4) 0xDA |
| 16F | PUSH1 0x04 | 0x04 calldataload(4) 0xDA |
| 171 | DUP2 | calldataload(4) 0x04 calldataload(4) 0xDA |
| 172 | DUP2 | 0x04 calldataload(4) 0x04 calldataload(4) 0xDA |
| 173 | SLOAD | Storage[4] calldataload(4) 0x04 calldataload(4) 0xDA |
| 174 | DUP2 | calldataload(4) Storage[4] calldataload(4) 0x04 calldataload(4) 0xDA |
| 175 | LT | calldataload(4)<Storage[4] calldataload(4) 0x04 calldataload(4) 0xDA |
| 176 | PUSH2 0x017e | 0x017EC calldataload(4)<Storage[4] calldataload(4) 0x04 calldataload(4) 0xDA |
| 179 | JUMPI | calldataload(4) 0x04 calldataload(4) 0xDA |
మొదటి పదం Storage[4] కంటే తక్కువ కాకపోతే, ఫంక్షన్ విఫలమవుతుంది. ఇది ఎలాంటి రిటర్న్ విలువ లేకుండా రివర్ట్ చేయబడుతుంది:
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 17A | PUSH1 0x00 | 0x00 ... |
| 17C | DUP1 | 0x00 0x00 ... |
| 17D | REVERT |
calldataload(4) అనేది Storage[4] కంటే తక్కువగా ఉంటే, మనకు ఈ కోడ్ వస్తుంది:
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 17E | JUMPDEST | calldataload(4) 0x04 calldataload(4) 0xDA |
| 17F | PUSH1 0x00 | 0x00 calldataload(4) 0x04 calldataload(4) 0xDA |
| 181 | SWAP2 | 0x04 calldataload(4) 0x00 calldataload(4) 0xDA |
| 182 | DUP3 | 0x00 0x04 calldataload(4) 0x00 calldataload(4) 0xDA |
| 183 | MSTORE | calldataload(4) 0x00 calldataload(4) 0xDA |
మరియు మెమరీ స్థానాలు 0x00-0x1F ఇప్పుడు 0x04 డేటాను కలిగి ఉంటాయి (0x00-0x1E అన్నీ సున్నాలు, 0x1F అనేది నాలుగు)
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 184 | PUSH1 0x20 | 0x20 calldataload(4) 0x00 calldataload(4) 0xDA |
| 186 | SWAP1 | calldataload(4) 0x20 0x00 calldataload(4) 0xDA |
| 187 | SWAP2 | 0x00 0x20 calldataload(4) calldataload(4) 0xDA |
| 188 | SHA3 | (((SHA3 of 0x00-0x1F))) calldataload(4) calldataload(4) 0xDA |
| 189 | ADD | (((SHA3 of 0x00-0x1F)))+calldataload(4) calldataload(4) 0xDA |
| 18A | SLOAD | Storage[(((SHA3 of 0x00-0x1F))) + calldataload(4)] calldataload(4) 0xDA |
కాబట్టి స్టోరేజ్లో ఒక లుకప్ టేబుల్ ఉంది, ఇది 0x000...0004 యొక్క SHA3 వద్ద ప్రారంభమవుతుంది మరియు ప్రతి చట్టబద్ధమైన కాల్ డేటా విలువకు (Storage[4] కంటే తక్కువ విలువ) ఒక ఎంట్రీని కలిగి ఉంటుంది.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 18B | SWAP1 | calldataload(4) Storage[(((SHA3 of 0x00-0x1F))) + calldataload(4)] 0xDA |
| 18C | POP | Storage[(((SHA3 of 0x00-0x1F))) + calldataload(4)] 0xDA |
| 18D | DUP2 | 0xDA Storage[(((SHA3 of 0x00-0x1F))) + calldataload(4)] 0xDA |
| 18E | JUMP | Storage[(((SHA3 of 0x00-0x1F))) + calldataload(4)] 0xDA |
ఆఫ్సెట్ 0xDA వద్ద ఉన్న కోడ్ ఏం చేస్తుందో మనకు ఇప్పటికే తెలుసు, ఇది స్టాక్ పైభాగంలో ఉన్న విలువను కాలర్కు తిరిగి ఇస్తుంది. కాబట్టి ఈ ఫంక్షన్ లుకప్ టేబుల్ నుండి విలువను కాలర్కు తిరిగి ఇస్తుంది.
0x1f135823
ఆఫ్సెట్లు 0xC4-0xCF లోని కోడ్ splitter() లో 0x103-0x10E వద్ద మనం చూసిన దానికి సమానంగా ఉంటుంది (JUMPI గమ్యం మినహా), కాబట్టి ఈ ఫంక్షన్ కూడా payable కాదని మనకు తెలుసు.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| D0 | JUMPDEST | |
| D1 | POP | |
| D2 | PUSH2 0x00da | 0xDA |
| D5 | PUSH1 0x06 | 0x06 0xDA |
| D7 | SLOAD | Value* 0xDA |
| D8 | DUP2 | 0xDA Value* 0xDA |
| D9 | JUMP | Value* 0xDA |
ఆఫ్సెట్ 0xDA వద్ద ఉన్న కోడ్ ఏమి చేస్తుందో మనకు ఇప్పటికే తెలుసు, ఇది స్టాక్ పైభాగంలో ఉన్న విలువను కాలర్కు తిరిగి ఇస్తుంది. కాబట్టి ఈ ఫంక్షన్ Value* ని తిరిగి ఇస్తుంది.
మెథడ్ సారాంశం
ఈ దశలో మీకు కాంట్రాక్ట్ అర్థమైందని భావిస్తున్నారా? నాకైతే లేదు. ఇప్పటివరకు మనకు ఈ మెథడ్లు ఉన్నాయి:
| మెథడ్ | అర్థం |
|---|---|
| బదిలీ | కాల్ ద్వారా అందించబడిన విలువను అంగీకరించి, ఆ మొత్తంతో Value* ని పెంచుతుంది |
| splitter() | Storage[3], ప్రతినిధి చిరునామాను తిరిగి ఇస్తుంది |
| currentWindow() | Storage[1] ని తిరిగి ఇస్తుంది |
| merkleRoot() | Storage[0] ని తిరిగి ఇస్తుంది |
| 0x81e580d3 | పరామితి Storage[4] కంటే తక్కువగా ఉంటే, లుకప్ టేబుల్ నుండి విలువను తిరిగి ఇస్తుంది |
| 0x1f135823 | Storage[6], అనగా Value* ని తిరిగి ఇస్తుంది |
కానీ ఏదైనా ఇతర కార్యాచరణ Storage[3] లోని కాంట్రాక్ట్ ద్వారా అందించబడుతుందని మనకు తెలుసు. బహుశా ఆ కాంట్రాక్ట్ ఏమిటో మనకు తెలిస్తే అది మనకు ఒక క్లూ ఇస్తుంది. కృతజ్ఞతగా, ఇది బ్లాక్చైన్ మరియు కనీసం సిద్ధాంతపరంగానైనా ప్రతిదీ తెలుసు. Storage[3] ని సెట్ చేసే మెథడ్లు ఏవీ మనం చూడలేదు, కాబట్టి ఇది కన్స్ట్రక్టర్ ద్వారా సెట్ చేయబడి ఉండాలి.
కన్స్ట్రక్టర్
మనం ఒక కాంట్రాక్ట్ను చూసినప్పుడు (opens in a new tab), దానిని సృష్టించిన లావాదేవీని కూడా చూడవచ్చు.
మనం ఆ లావాదేవీని, ఆపై స్థితి ట్యాబ్ను క్లిక్ చేస్తే, పారామితుల ప్రారంభ విలువలను మనం చూడవచ్చు. నిర్దిష్టంగా, Storage[3] లో 0x2f81e57ff4f4d83b40a9f719fd892d8e806e0761 (opens in a new tab) ఉన్నట్లు మనం చూడవచ్చు. ఆ కాంట్రాక్ట్ తప్పనిసరిగా లోపించిన కార్యాచరణను కలిగి ఉండాలి. మనం పరిశోధిస్తున్న కాంట్రాక్ట్ కోసం ఉపయోగించిన సాధనాలనే ఉపయోగించి మనం దానిని అర్థం చేసుకోవచ్చు.
ప్రతినిధి కాంట్రాక్ట్
పైన ఉన్న అసలు కాంట్రాక్ట్ కోసం మనం ఉపయోగించిన పద్ధతులనే ఉపయోగించి, ఈ క్రింది సందర్భాలలో కాంట్రాక్ట్ రివర్ట్ అవుతుందని మనం చూడవచ్చు:
- కాల్కు ఏదైనా ETH జోడించబడి ఉంటే (0x05-0x0F)
- కాల్ డేటా పరిమాణం నాలుగు కంటే తక్కువగా ఉంటే (0x10-0x19 మరియు 0xBE-0xC2)
మరియు ఇది మద్దతు ఇచ్చే పద్ధతులు (methods):
| పద్ధతి | పద్ధతి సంతకం | జంప్ చేయడానికి ఆఫ్సెట్ |
|---|---|---|
| scaleAmountByPercentage(uint256,uint256) (opens in a new tab) | 0x8ffb5c97 | 0x0135 |
| isClaimed(uint256,address) (opens in a new tab) | 0xd2ef0795 | 0x0151 |
| claim(uint256,address,uint256,bytes32[]) (opens in a new tab) | 0x2e7ba6ef | 0x00F4 |
| incrementWindow() (opens in a new tab) | 0x338b1d31 | 0x0110 |
| ??? | 0x3f26479e | 0x0118 |
| ??? | 0x1e7df9d3 | 0x00C3 |
| currentWindow() (opens in a new tab) | 0xba0bafb4 | 0x0148 |
| merkleRoot() (opens in a new tab) | 0x2eb4a7ab | 0x0107 |
| ??? | 0x81e580d3 | 0x0122 |
| ??? | 0x1f135823 | 0x00D8 |
మనం ఎప్పటికీ వాటిని చేరుకోలేము కాబట్టి దిగువన ఉన్న నాలుగు పద్ధతులను విస్మరించవచ్చు. వాటి సంతకాలు ఎలా ఉన్నాయంటే, మన అసలు కాంట్రాక్ట్ వాటిని స్వయంగా చూసుకుంటుంది (వివరాలను పైన చూడటానికి మీరు సంతకాలపై క్లిక్ చేయవచ్చు), కాబట్టి అవి ఖచ్చితంగా ఓవర్రైడ్ చేయబడిన పద్ధతులు (opens in a new tab) అయి ఉంటాయి.
మిగిలిన పద్ధతులలో ఒకటి claim(<params>), మరియు మరొకటి isClaimed(<params>), కాబట్టి ఇది ఎయిర్డ్రాప్ కాంట్రాక్ట్ లాగా కనిపిస్తోంది. మిగిలిన వాటిని ఆప్కోడ్ తర్వాత ఆప్కోడ్గా పరిశీలించే బదులు, మనం డీకంపైలర్ని ప్రయత్నించవచ్చు (opens in a new tab), ఇది ఈ కాంట్రాక్ట్ నుండి మూడు ఫంక్షన్ల కోసం ఉపయోగించదగిన ఫలితాలను అందిస్తుంది. మిగిలిన వాటిని రివర్స్ ఇంజనీరింగ్ చేయడం పాఠకులకు ఒక అభ్యాసంగా వదిలివేయబడింది.
scaleAmountByPercentage
ఈ ఫంక్షన్ కోసం డీకంపైలర్ మనకు ఇచ్చేది ఇదే:
def unknown8ffb5c97(uint256 _param1, uint256 _param2) payable:
require calldata.size - 4 >=′ 64
if _param1 and _param2 > -1 / _param1:
revert with 0, 17
return (_param1 * _param2 / 100 * 10^6)
మొదటి require కాల్ డేటాలో, ఫంక్షన్ సంతకం యొక్క నాలుగు బైట్లతో పాటు, కనీసం 64 బైట్లు ఉన్నాయో లేదో పరీక్షిస్తుంది, ఇది రెండు పారామితులకు సరిపోతుంది. అలా లేకపోతే స్పష్టంగా ఏదో తప్పు ఉన్నట్లు.
if స్టేట్మెంట్ _param1 సున్నా కాదని మరియు _param1 * _param2 ప్రతికూలమైనది (negative) కాదని తనిఖీ చేస్తున్నట్లు కనిపిస్తోంది. ఇది బహుశా ర్యాప్ అరౌండ్ (wrap around) కేసులను నిరోధించడానికి కావచ్చు.
చివరగా, ఫంక్షన్ స్కేల్ చేయబడిన విలువను అందిస్తుంది.
claim
డీకంపైలర్ సృష్టించే కోడ్ సంక్లిష్టమైనది, మరియు అదంతా మనకు సంబంధించినది కాదు. ఉపయోగకరమైన సమాచారాన్ని అందిస్తాయని నేను విశ్వసించే పంక్తులపై దృష్టి పెట్టడానికి నేను దానిలో కొంత భాగాన్ని దాటవేయబోతున్నాను
def unknown2e7ba6ef(uint256 _param1, uint256 _param2, uint256 _param3, array _param4) payable:
...
require _param2 == addr(_param2)
...
if currentWindow <= _param1:
revert with 0, 'cannot claim for a future window'
మనం ఇక్కడ రెండు ముఖ్యమైన విషయాలను చూస్తాము:
_param2, ఇదిuint256గా ప్రకటించబడినప్పటికీ, వాస్తవానికి ఇది ఒక చిరునామా_param1అనేది క్లెయిమ్ చేయబడుతున్న విండో, ఇదిcurrentWindowలేదా అంతకంటే ముందుది అయి ఉండాలి.
...
if stor5[_claimWindow][addr(_claimFor)]:
revert with 0, 'Account already claimed the given window'
కాబట్టి ఇప్పుడు మనకు Storage[5] అనేది విండోలు మరియు చిరునామాల శ్రేణి (array) అని, మరియు ఆ విండో కోసం చిరునామా ప్రతిఫలాన్ని క్లెయిమ్ చేసిందా లేదా అని తెలుసు.
...
idx = 0
s = 0
while idx < _param4.length:
...
if s + sha3(mem[(32 * _param4.length) + 328 len mem[(32 * _param4.length) + 296]]) > mem[(32 * idx) + 296]:
mem[mem[64] + 32] = mem[(32 * idx) + 296]
...
s = sha3(mem[_62 + 32 len mem[_62]])
continue
...
s = sha3(mem[_66 + 32 len mem[_66]])
continue
if unknown2eb4a7ab != s:
revert with 0, 'Invalid proof'
unknown2eb4a7ab వాస్తవానికి merkleRoot() ఫంక్షన్ అని మనకు తెలుసు, కాబట్టి ఈ కోడ్ మెర్కల్ రుజువు (opens in a new tab)ని ధృవీకరిస్తున్నట్లు కనిపిస్తోంది. దీని అర్థం _param4 అనేది ఒక మెర్కల్ రుజువు.
call addr(_param2) with:
value unknown81e580d3[_param1] * _param3 / 100 * 10^6 wei
gas 30000 wei
ఒక కాంట్రాక్ట్ తన స్వంత ETHని మరొక చిరునామాకు (కాంట్రాక్ట్ లేదా బాహ్యంగా స్వంతం చేసుకున్నది) ఈ విధంగా బదిలీ చేస్తుంది. బదిలీ చేయవలసిన మొత్తంగా ఉన్న విలువతో ఇది దానిని కాల్ చేస్తుంది. కాబట్టి ఇది ETH యొక్క ఎయిర్డ్రాప్ లాగా కనిపిస్తోంది.
if not return_data.size:
if not ext_call.success:
require ext_code.size(stor2)
call stor2.deposit() with:
value unknown81e580d3[_param1] * _param3 / 100 * 10^6 wei
దిగువన ఉన్న రెండు పంక్తులు Storage[2] కూడా మనం కాల్ చేసే కాంట్రాక్ట్ అని చెబుతున్నాయి. మనం కన్స్ట్రక్టర్ లావాదేవీని పరిశీలిస్తే (opens in a new tab) ఈ కాంట్రాక్ట్ 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 (opens in a new tab) అని మనం చూస్తాము, ఇది ఒక ర్యాప్డ్ ఈథర్ (weth) కాంట్రాక్ట్, దీని సోర్స్ కోడ్ Etherscanకి అప్లోడ్ చేయబడింది (opens in a new tab).
కాబట్టి కాంట్రాక్ట్లు _param2 కి ETHని పంపడానికి ప్రయత్నిస్తున్నట్లు కనిపిస్తోంది. అది చేయగలిగితే, చాలా మంచిది. లేకపోతే, అది WETH (opens in a new tab) ని పంపడానికి ప్రయత్నిస్తుంది. _param2 అనేది బాహ్యంగా స్వంతం చేసుకున్న ఖాతా (EOA) అయితే అది ఎల్లప్పుడూ ETHని స్వీకరించగలదు, కానీ కాంట్రాక్ట్లు ETHని స్వీకరించడానికి నిరాకరించవచ్చు. అయితే, WETH అనేది ERC-20 మరియు కాంట్రాక్ట్లు దానిని అంగీకరించడానికి నిరాకరించలేవు.
...
log 0xdbd5389f: addr(_param2), unknown81e580d3[_param1] * _param3 / 100 * 10^6, bool(ext_call.success)
ఫంక్షన్ చివరలో ఒక లాగ్ ఎంట్రీ రూపొందించబడటాన్ని మనం చూస్తాము. రూపొందించబడిన లాగ్ ఎంట్రీలను చూడండి (opens in a new tab) మరియు 0xdbd5... తో ప్రారంభమయ్యే అంశంపై ఫిల్టర్ చేయండి. అటువంటి ఎంట్రీని రూపొందించిన లావాదేవీలలో ఒకదానిపై మనం క్లిక్ చేస్తే (opens in a new tab), అది నిజంగా ఒక క్లెయిమ్ లాగా కనిపిస్తుందని మనం చూస్తాము - ఖాతా మనం రివర్స్ ఇంజనీరింగ్ చేస్తున్న కాంట్రాక్ట్కు ఒక సందేశాన్ని పంపింది మరియు దానికి ప్రతిఫలంగా ETHని పొందింది.
1e7df9d3
ఈ ఫంక్షన్ పైన ఉన్న claim కి చాలా పోలి ఉంటుంది. ఇది కూడా ఒక మెర్కల్ రుజువుని తనిఖీ చేస్తుంది, మొదటి దానికి ETHని బదిలీ చేయడానికి ప్రయత్నిస్తుంది మరియు అదే రకమైన లాగ్ ఎంట్రీని ఉత్పత్తి చేస్తుంది.
def unknown1e7df9d3(uint256 _param1, uint256 _param2, array _param3) payable:
...
idx = 0
s = 0
while idx < _param3.length:
if idx >= mem[96]:
revert with 0, 50
_55 = mem[(32 * idx) + 128]
if s + sha3(mem[(32 * _param3.length) + 160 len mem[(32 * _param3.length) + 128]]) > mem[(32 * idx) + 128]:
...
s = sha3(mem[_58 + 32 len mem[_58]])
continue
mem[mem[64] + 32] = s + sha3(mem[(32 * _param3.length) + 160 len mem[(32 * _param3.length) + 128]])
...
if unknown2eb4a7ab != s:
revert with 0, 'Invalid proof'
...
call addr(_param1) with:
value s wei
gas 30000 wei
if not return_data.size:
if not ext_call.success:
require ext_code.size(stor2)
call stor2.deposit() with:
value s wei
gas gas_remaining wei
...
log 0xdbd5389f: addr(_param1), s, bool(ext_call.success)
ప్రధాన వ్యత్యాసం ఏమిటంటే, మొదటి పరామితి (parameter), ఉపసంహరించుకోవాల్సిన విండో, అక్కడ లేదు. దానికి బదులుగా, క్లెయిమ్ చేయగల అన్ని విండోలపై ఒక లూప్ (loop) ఉంటుంది.
idx = 0
s = 0
while idx < currentWindow:
...
if stor5[mem[0]]:
if idx == -1:
revert with 0, 17
idx = idx + 1
s = s
continue
...
stor5[idx][addr(_param1)] = 1
if idx >= unknown81e580d3.length:
revert with 0, 50
mem[0] = 4
if unknown81e580d3[idx] and _param2 > -1 / unknown81e580d3[idx]:
revert with 0, 17
if s > !(unknown81e580d3[idx] * _param2 / 100 * 10^6):
revert with 0, 17
if idx == -1:
revert with 0, 17
idx = idx + 1
s = s + (unknown81e580d3[idx] * _param2 / 100 * 10^6)
continue
కాబట్టి ఇది అన్ని విండోలను క్లెయిమ్ చేసే claim వేరియంట్ లాగా కనిపిస్తోంది.
ముగింపు
సోర్స్ కోడ్ అందుబాటులో లేని కాంట్రాక్ట్లను, ఆప్కోడ్లను లేదా (అది పనిచేసినప్పుడు) డీకంపెలర్ను ఉపయోగించి ఎలా అర్థం చేసుకోవాలో ఇప్పటికి మీకు తెలిసి ఉండాలి. ఈ వ్యాసం పొడవును బట్టి చూస్తే, ఒక కాంట్రాక్ట్ను రివర్స్ ఇంజనీరింగ్ చేయడం అంత సులభం కాదని స్పష్టమవుతుంది, కానీ భద్రత అత్యంత అవసరమైన సిస్టమ్లో, కాంట్రాక్ట్లు వాగ్దానం చేసినట్లుగా పనిచేస్తున్నాయో లేదో ధృవీకరించగలగడం ఒక ముఖ్యమైన నైపుణ్యం.



![Storage[6]లో మార్పు](/_next/image/?url=%2Fcontent%2Fdevelopers%2Ftutorials%2Freverse-engineering-a-contract%2Fstorage6.png&w=1920&q=75)



