కాల్ డేటా ఆప్టిమైజేషన్ కోసం షార్ట్ ABIలు
పరిచయం
ఈ ఆర్టికల్లో, మీరు ఆప్టిమిస్టిక్ రోల్అప్లు, వాటిపై లావాదేవీల ఖర్చు మరియు ఆ విభిన్న వ్యయ నిర్మాణం ఎథీరియం మెయిన్నెట్లో కంటే విభిన్న విషయాల కోసం ఆప్టిమైజ్ చేయడానికి మనల్ని ఎలా కోరుతుందో తెలుసుకుంటారు. ఈ ఆప్టిమైజేషన్ను ఎలా అమలు చేయాలో కూడా మీరు నేర్చుకుంటారు.
పూర్తి బహిర్గతం
నేను పూర్తి సమయం Optimism (opens in a new tab) ఉద్యోగిని, కాబట్టి ఈ ఆర్టికల్లోని ఉదాహరణలు Optimismలో రన్ అవుతాయి. అయినప్పటికీ, ఇక్కడ వివరించిన సాంకేతికత ఇతర రోల్అప్లకు కూడా అంతే బాగా పనిచేస్తుంది.
పదజాలం
రోల్అప్ల గురించి చర్చిస్తున్నప్పుడు, 'లేయర్ 1 (l1)' అనే పదం ప్రొడక్షన్ ఎథీరియం నెట్వర్క్ అయిన మెయిన్నెట్ కోసం ఉపయోగించబడుతుంది. 'లేయర్ 2 (l2)' అనే పదం రోలప్ లేదా భద్రత కోసం L1పై ఆధారపడే కానీ దాని ప్రాసెసింగ్లో ఎక్కువ భాగం ఆఫ్చైన్లో చేసే ఏదైనా ఇతర సిస్టమ్ కోసం ఉపయోగించబడుతుంది.
L2 లావాదేవీల ఖర్చును మనం మరింత ఎలా తగ్గించవచ్చు?
ఆప్టిమిస్టిక్ రోల్అప్లు ప్రతి చారిత్రక లావాదేవీ యొక్క రికార్డును భద్రపరచాలి, తద్వారా ఎవరైనా వాటిని పరిశీలించి ప్రస్తుత స్థితి సరైనదేనని ధృవీకరించగలరు. ఎథీరియం మెయిన్నెట్లోకి డేటాను పొందడానికి చౌకైన మార్గం దానిని కాల్ డేటాగా వ్రాయడం. ఈ పరిష్కారాన్ని Optimism (opens in a new tab) మరియు Arbitrum (opens in a new tab) రెండూ ఎంచుకున్నాయి.
L2 లావాదేవీల ఖర్చు
L2 లావాదేవీల ఖర్చు రెండు భాగాలను కలిగి ఉంటుంది:
- L2 ప్రాసెసింగ్, ఇది సాధారణంగా చాలా చౌకగా ఉంటుంది
- L1 నిల్వ, ఇది మెయిన్నెట్ గ్యాస్ ఖర్చులతో ముడిపడి ఉంటుంది
నేను దీనిని వ్రాస్తున్నప్పుడు, Optimismలో L2 గ్యాస్ ఖర్చు 0.001 Gwei. మరోవైపు, L1 గ్యాస్ ఖర్చు సుమారు 40 Gwei. మీరు ప్రస్తుత ధరలను ఇక్కడ చూడవచ్చు (opens in a new tab).
ఒక బైట్ కాల్ డేటాకు 4 గ్యాస్ (అది సున్నా అయితే) లేదా 16 గ్యాస్ (అది మరేదైనా విలువ అయితే) ఖర్చవుతుంది. EVMలో అత్యంత ఖరీదైన ఆపరేషన్లలో ఒకటి నిల్వకు వ్రాయడం. L2లో నిల్వకు 32-బైట్ పదాన్ని వ్రాయడానికి గరిష్ట ఖర్చు 22100 గ్యాస్. ప్రస్తుతం, ఇది 22.1 Gwei. కాబట్టి మనం కాల్ డేటా యొక్క ఒకే సున్నా బైట్ను ఆదా చేయగలిగితే, మనం నిల్వకు సుమారు 200 బైట్లను వ్రాయగలుగుతాము మరియు ఇప్పటికీ లాభంలో ఉంటాము.
ABI
చాలా వరకు లావాదేవీలు బాహ్యంగా స్వంతమైన ఖాతా నుండి కాంట్రాక్ట్ను యాక్సెస్ చేస్తాయి. చాలా కాంట్రాక్ట్లు Solidityలో వ్రాయబడతాయి మరియు వాటి డేటా ఫీల్డ్ను అప్లికేషన్ బైనరీ ఇంటర్ఫేస్ (ABI) (opens in a new tab) ప్రకారం వివరిస్తాయి.
అయినప్పటికీ, ABI L1 కోసం రూపొందించబడింది, ఇక్కడ ఒక బైట్ కాల్ డేటా ఖర్చు సుమారు నాలుగు అంకగణిత ఆపరేషన్లకు సమానంగా ఉంటుంది, L2 కోసం కాదు, ఇక్కడ ఒక బైట్ కాల్ డేటా ఖర్చు వెయ్యికి పైగా అంకగణిత ఆపరేషన్ల కంటే ఎక్కువగా ఉంటుంది. కాల్ డేటా ఈ విధంగా విభజించబడింది:
| విభాగం | పొడవు | బైట్లు | వృధా అయిన బైట్లు | వృధా అయిన గ్యాస్ | అవసరమైన బైట్లు | అవసరమైన గ్యాస్ |
|---|---|---|---|---|---|---|
| ఫంక్షన్ సెలెక్టర్ | 4 | 0-3 | 3 | 48 | 1 | 16 |
| సున్నాలు | 12 | 4-15 | 12 | 48 | 0 | 0 |
| గమ్యస్థాన చిరునామా | 20 | 16-35 | 0 | 0 | 20 | 320 |
| మొత్తం | 32 | 36-67 | 17 | 64 | 15 | 240 |
| మొత్తం | 68 | 160 | 576 |
వివరణ:
- ఫంక్షన్ సెలెక్టర్: కాంట్రాక్ట్ 256 కంటే తక్కువ ఫంక్షన్లను కలిగి ఉంది, కాబట్టి మనం వాటిని ఒకే బైట్తో వేరు చేయవచ్చు. ఈ బైట్లు సాధారణంగా సున్నా కానివి మరియు అందువల్ల పదహారు గ్యాస్ ఖర్చవుతుంది (opens in a new tab).
- సున్నాలు: ఈ బైట్లు ఎల్లప్పుడూ సున్నాగా ఉంటాయి ఎందుకంటే ఇరవై-బైట్ చిరునామాను ఉంచడానికి ముప్పై-రెండు-బైట్ పదం అవసరం లేదు.
సున్నాను కలిగి ఉన్న బైట్లకు నాలుగు గ్యాస్ ఖర్చవుతుంది (ఎల్లో పేపర్ చూడండి (opens in a new tab), అపెండిక్స్ G,
పేజీ 27,
Gtxdatazeroవిలువ). - మొత్తం: ఈ కాంట్రాక్ట్లో
decimalsపద్దెనిమిది (సాధారణ విలువ) అని మరియు మనం బదిలీ చేసే టోకెన్ల గరిష్ట మొత్తం 1018 అని ఊహిస్తే, మనకు గరిష్టంగా 1036 మొత్తం వస్తుంది. 25615 > 1036, కాబట్టి పదిహేను బైట్లు సరిపోతాయి.
L1లో 160 గ్యాస్ వృధా కావడం సాధారణంగా చాలా తక్కువ. ఒక లావాదేవీకి కనీసం 21,000 గ్యాస్ (opens in a new tab) ఖర్చవుతుంది, కాబట్టి అదనంగా 0.8% పట్టింపు లేదు.
అయినప్పటికీ, L2లో, విషయాలు భిన్నంగా ఉంటాయి. లావాదేవీ యొక్క దాదాపు మొత్తం ఖర్చు దానిని L1కి వ్రాయడమే.
లావాదేవీ కాల్ డేటాతో పాటు, 109 బైట్ల లావాదేవీ హెడర్ (గమ్యస్థాన చిరునామా, సంతకం మొదలైనవి) ఉంటుంది.
అందువల్ల మొత్తం ఖర్చు 109*16+576+160=2480, మరియు మనం అందులో సుమారు 6.5% వృధా చేస్తున్నాము.
మీరు గమ్యస్థానాన్ని నియంత్రించనప్పుడు ఖర్చులను తగ్గించడం
గమ్యస్థాన కాంట్రాక్ట్పై మీకు నియంత్రణ లేదని ఊహిస్తే, మీరు ఇప్పటికీ దీనిలాంటి (opens in a new tab) పరిష్కారాన్ని ఉపయోగించవచ్చు. సంబంధిత ఫైల్లను పరిశీలిద్దాం.
Token.sol
ఇది గమ్యస్థాన కాంట్రాక్ట్ (opens in a new tab).
ఇది ఒక అదనపు ఫీచర్తో కూడిన ప్రామాణిక ERC-20 కాంట్రాక్ట్.
ఈ faucet ఫంక్షన్ ఏ వినియోగదారుకైనా ఉపయోగించడానికి కొంత టోకెన్ను పొందేలా చేస్తుంది.
ఇది ప్రొడక్షన్ ERC-20 కాంట్రాక్ట్ను పనికిరాకుండా చేస్తుంది, కానీ కేవలం టెస్టింగ్ను సులభతరం చేయడానికి మాత్రమే ERC-20 ఉన్నప్పుడు ఇది పనిని సులభతరం చేస్తుంది.
/**
* @dev ఆడుకోవడానికి కాలర్కు 1000 టోకెన్లను ఇస్తుంది
*/
ఫంక్షన్ faucet() external {
_mint(msg.sender, 1000);
} // function faucet
CalldataInterpreter.sol
ఇది లావాదేవీలు చిన్న కాల్ డేటాతో కాల్ చేయాల్సిన కాంట్రాక్ట్ (opens in a new tab). దీన్ని లైన్ బై లైన్ పరిశీలిద్దాం.
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import { OrisUselessToken } from "./Token.sol";
దానిని ఎలా కాల్ చేయాలో తెలుసుకోవడానికి మనకు టోకెన్ ఫంక్షన్ అవసరం.
కాంట్రాక్ట్ CalldataInterpreter {
OrisUselessToken public immutable token;
మనం ప్రతినిధిగా ఉన్న టోకెన్ యొక్క చిరునామా.
/**
* @dev టోకెన్ చిరునామాను పేర్కొనండి
* @param tokenAddr_ ERC-20 కాంట్రాక్ట్ చిరునామా
*/
కన్స్ట్రక్టర్(
address tokenAddr_
) {
token = OrisUselessToken(tokenAddr_);
} // constructor
మనం పేర్కొనవలసిన ఏకైక పరామితి టోకెన్ చిరునామా.
function calldataVal(uint startByte, uint length)
private pure returns (uint) {
కాల్ డేటా నుండి ఒక విలువను చదవండి.
uint _retVal;
require(length < 0x21,
"calldataVal length limit is 32 bytes");
require(length + startByte <= msg.data.length,
"calldataVal trying to read beyond calldatasize");
మనం మెమరీకి ఒకే 32-బైట్ (256-బిట్) పదాన్ని లోడ్ చేయబోతున్నాము మరియు మనకు కావలసిన ఫీల్డ్లో భాగం కాని బైట్లను తీసివేయబోతున్నాము. ఈ అల్గారిథమ్ 32 బైట్ల కంటే పొడవైన విలువల కోసం పనిచేయదు మరియు కాల్ డేటా ముగింపును దాటి మనం చదవలేము. L1లో గ్యాస్ను ఆదా చేయడానికి ఈ పరీక్షలను దాటవేయడం అవసరం కావచ్చు, కానీ L2లో గ్యాస్ చాలా చౌకగా ఉంటుంది, ఇది మనం ఆలోచించగల ఏవైనా తనిఖీలను అనుమతిస్తుంది.
assembly {
_retVal := calldataload(startByte)
}
మనం ఫాల్బ్యాక్() కు కాల్ నుండి డేటాను కాపీ చేసి ఉండవచ్చు (క్రింద చూడండి), కానీ EVM యొక్క అసెంబ్లీ భాష అయిన Yul (opens in a new tab) ను ఉపయోగించడం సులభం.
ఇక్కడ మనం స్టాక్లోకి startByte నుండి startByte+31 బైట్లను చదవడానికి CALLDATALOAD ఆప్కోడ్ (opens in a new tab) ను ఉపయోగిస్తాము.
సాధారణంగా, Yulలో ఆప్కోడ్ యొక్క సింటాక్స్ <opcode name>(<first stack value, if any>,<second stack value, if any>...).
_retVal = _retVal >> (256-length*8);
అత్యంత ముఖ్యమైన length బైట్లు మాత్రమే ఫీల్డ్లో భాగం, కాబట్టి ఇతర విలువలను వదిలించుకోవడానికి మనం కుడివైపుకు షిఫ్ట్ (opens in a new tab) చేస్తాము.
ఇది విలువను ఫీల్డ్ యొక్క కుడివైపుకు తరలించే అదనపు ప్రయోజనాన్ని కలిగి ఉంది, కాబట్టి ఇది విలువ ఇంటు 256ఏదో కాకుండా విలువ మాత్రమే అవుతుంది.
return _retVal;
}
fallback() external {
Solidity కాంట్రాక్ట్కు కాల్ ఏ ఫంక్షన్ సంతకాలతో సరిపోలకపోతే, అది fallback() ఫంక్షన్ను (opens in a new tab) కాల్ చేస్తుంది (ఒకటి ఉందని ఊహిస్తే).
CalldataInterpreter విషయంలో, ఇతర external లేదా public ఫంక్షన్లు లేనందున ఏదైనా కాల్ ఇక్కడికి వస్తుంది.
uint _func;
_func = calldataVal(0, 1);
కాల్ డేటా యొక్క మొదటి బైట్ను చదవండి, ఇది మనకు ఫంక్షన్ను చెబుతుంది. ఇక్కడ ఫంక్షన్ అందుబాటులో ఉండకపోవడానికి రెండు కారణాలు ఉన్నాయి:
pureలేదాviewఅయిన ఫంక్షన్లు స్థితిని మార్చవు మరియు గ్యాస్ ఖర్చు చేయవు (ఆఫ్చైన్లో కాల్ చేసినప్పుడు). వాటి గ్యాస్ ఖర్చును తగ్గించడానికి ప్రయత్నించడంలో అర్థం లేదు.msg.sender(opens in a new tab) పై ఆధారపడే ఫంక్షన్లు.msg.senderయొక్క విలువ కాలర్ కాదు,CalldataInterpreterయొక్క చిరునామా అవుతుంది.
దురదృష్టవశాత్తు, ERC-20 స్పెసిఫికేషన్లను పరిశీలిస్తే (opens in a new tab), ఇది కేవలం ఒక ఫంక్షన్ను మాత్రమే వదిలివేస్తుంది, transfer.
ఇది మనకు కేవలం రెండు ఫంక్షన్లను మాత్రమే మిగులుస్తుంది: transfer (ఎందుకంటే మనం transferFrom ని కాల్ చేయవచ్చు) మరియు faucet (ఎందుకంటే మనం టోకెన్లను మనల్ని కాల్ చేసిన వారికి తిరిగి బదిలీ చేయవచ్చు).
// దీనిని ఉపయోగించి టోకెన్ యొక్క స్థితిని మార్చే పద్ధతులను కాల్ చేయండి
// కాల్ డేటా నుండి సమాచారం
// faucet
if (_func == 1) {
పారామితులు లేని faucet() కు కాల్.
token.faucet();
token.transfer(msg.sender,
token.balanceOf(address(this)));
}
మనం token.faucet() ని కాల్ చేసిన తర్వాత మనకు టోకెన్లు వస్తాయి. అయినప్పటికీ, ప్రతినిధి కాంట్రాక్ట్గా, మనకు టోకెన్లు అవసరం లేదు.
మనల్ని కాల్ చేసిన EOA (బాహ్యంగా స్వంతమైన ఖాతా) లేదా కాంట్రాక్ట్కు అవసరం.
కాబట్టి మనం మన టోకెన్లన్నింటినీ మనల్ని కాల్ చేసిన వారికి బదిలీ చేస్తాము.
// బదిలీ (దాని కోసం మనకు అనుమతి మొత్తం ఉందని అనుకుందాం)
if (_func == 2) {
టోకెన్లను బదిలీ చేయడానికి రెండు పారామితులు అవసరం: గమ్యస్థాన చిరునామా మరియు మొత్తం.
token.transferFrom(
msg.sender,
కాలర్లు తమ స్వంత టోకెన్లను బదిలీ చేయడానికి మాత్రమే మేము అనుమతిస్తాము
address(uint160(calldataVal(1, 20))),
గమ్యస్థాన చిరునామా బైట్ #1 వద్ద ప్రారంభమవుతుంది (బైట్ #0 ఫంక్షన్). చిరునామాగా, ఇది 20-బైట్ల పొడవు ఉంటుంది.
calldataVal(21, 2)
ఈ నిర్దిష్ట కాంట్రాక్ట్ కోసం ఎవరైనా బదిలీ చేయాలనుకునే గరిష్ట టోకెన్ల సంఖ్య రెండు బైట్లలో (65536 కంటే తక్కువ) సరిపోతుందని మేము ఊహిస్తున్నాము.
);
}
మొత్తంమీద, బదిలీకి 35 బైట్ల కాల్ డేటా పడుతుంది:
| విభాగం | పొడవు | బైట్లు |
|---|---|---|
| ఫంక్షన్ సెలెక్టర్ | 1 | 0 |
| గమ్యస్థాన చిరునామా | 32 | 1-32 |
| మొత్తం | 2 | 33-34 |
} // fallback
} // contract CalldataInterpreter
test.js
ఈ JavaScript యూనిట్ టెస్ట్ (opens in a new tab) ఈ యంత్రాంగాన్ని ఎలా ఉపయోగించాలో (మరియు ఇది సరిగ్గా పనిచేస్తుందో లేదో ఎలా ధృవీకరించాలో) మనకు చూపుతుంది. మీకు chai (opens in a new tab) మరియు ethers (opens in a new tab) అర్థమవుతాయని నేను ఊహిస్తున్నాను మరియు కాంట్రాక్ట్కు ప్రత్యేకంగా వర్తించే భాగాలను మాత్రమే వివరిస్తాను.
const { expect } = require("chai");
describe("CalldataInterpreter", function () {
it("Should let us use tokens", async function () {
const Token = await ethers.getContractFactory("OrisUselessToken")
const token = await Token.deploy()
await token.deployed()
console.log("Token addr:", token.address)
const Cdi = await ethers.getContractFactory("CalldataInterpreter")
const cdi = await Cdi.deploy(token.address)
await cdi.deployed()
console.log("CalldataInterpreter addr:", cdi.address)
const signer = await ethers.getSigner()
మేము రెండు కాంట్రాక్ట్లను డిప్లాయ్ చేయడం ద్వారా ప్రారంభిస్తాము.
// ఆడుకోవడానికి టోకెన్లను పొందండి
const faucetTx = {
లావాదేవీలను సృష్టించడానికి మనం సాధారణంగా ఉపయోగించే హై-లెవల్ ఫంక్షన్లను (ఉదాహరణకు token.faucet()) ఉపయోగించలేము, ఎందుకంటే మనం ABIని అనుసరించము.
బదులుగా, మనం లావాదేవీని మనమే నిర్మించి, ఆపై దానిని పంపాలి.
to: cdi.address,
data: "0x01"
లావాదేవీ కోసం మనం అందించాల్సిన రెండు పారామితులు ఉన్నాయి:
to, గమ్యస్థాన చిరునామా. ఇది కాల్ డేటా ఇంటర్ప్రెటర్ కాంట్రాక్ట్.data, పంపాల్సిన కాల్ డేటా. ఫాసెట్ కాల్ విషయంలో, డేటా ఒకే బైట్,0x01.
}
await (await signer.sendTransaction(faucetTx)).wait()
మనం సంతకం చేసేవారి sendTransaction పద్ధతిని (opens in a new tab) కాల్ చేస్తాము ఎందుకంటే మనం ఇప్పటికే గమ్యస్థానాన్ని (faucetTx.to) పేర్కొన్నాము మరియు లావాదేవీకి సంతకం చేయాలి.
// faucet టోకెన్లను సరిగ్గా అందిస్తుందో లేదో తనిఖీ చేయండి
expect(await token.balanceOf(signer.address)).to.equal(1000)
ఇక్కడ మనం బ్యాలెన్స్ని ధృవీకరిస్తాము.
view ఫంక్షన్లపై గ్యాస్ను ఆదా చేయాల్సిన అవసరం లేదు, కాబట్టి మనం వాటిని సాధారణంగా రన్ చేస్తాము.
// CDIకి అనుమతి మొత్తం ఇవ్వండి (ఆమోదాలను ప్రాక్సీ చేయలేము)
const approveTX = await token.approve(cdi.address, 10000)
await approveTX.wait()
expect(await token.allowance(signer.address, cdi.address)).to.equal(10000)
బదిలీలు చేయగలిగేలా కాల్ డేటా ఇంటర్ప్రెటర్కు అనుమతి మొత్తాన్ని ఇవ్వండి.
// టోకెన్లను బదిలీ చేయండి
const destAddr = "0xf5a6ead936fb47f342bb63e676479bddf26ebe1d"
const transferTx = {
to: cdi.address,
data: "0x02" + destAddr.slice(2, 42) + "0100",
}
బదిలీ లావాదేవీని సృష్టించండి. మొదటి బైట్ "0x02", ఆ తర్వాత గమ్యస్థాన చిరునామా మరియు చివరగా మొత్తం (0x0100, ఇది దశాంశంలో 256).
await (await signer.sendTransaction(transferTx)).wait()
// మన వద్ద 256 టోకెన్లు తక్కువగా ఉన్నాయో లేదో తనిఖీ చేయండి
expect (await token.balanceOf(signer.address)).to.equal(1000-256)
// మరియు మన గమ్యస్థానం వాటిని పొందిందో లేదో
expect (await token.balanceOf(destAddr)).to.equal(256)
}) // it
}) // describe
మీరు గమ్యస్థాన కాంట్రాక్ట్ను నియంత్రించినప్పుడు ఖర్చును తగ్గించడం
గమ్యస్థాన కాంట్రాక్ట్పై మీకు నియంత్రణ ఉంటే, మీరు msg.sender తనిఖీలను దాటవేసే ఫంక్షన్లను సృష్టించవచ్చు ఎందుకంటే అవి కాల్ డేటా ఇంటర్ప్రెటర్ను విశ్వసిస్తాయి.
ఇది ఎలా పనిచేస్తుందో దానికి ఉదాహరణను మీరు ఇక్కడ, control-contract బ్రాంచ్లో చూడవచ్చు (opens in a new tab).
కాంట్రాక్ట్ బాహ్య లావాదేవీలకు మాత్రమే ప్రతిస్పందిస్తుంటే, మనం కేవలం ఒక కాంట్రాక్ట్ను కలిగి ఉండటం ద్వారా పని కానివ్వవచ్చు. అయినప్పటికీ, అది కూర్పు సామర్థ్యాన్ని విచ్ఛిన్నం చేస్తుంది. సాధారణ ERC-20 కాల్లకు ప్రతిస్పందించే కాంట్రాక్ట్ మరియు చిన్న కాల్ డేటాతో లావాదేవీలకు ప్రతిస్పందించే మరొక కాంట్రాక్ట్ను కలిగి ఉండటం చాలా మంచిది.
Token.sol
ఈ ఉదాహరణలో మనం Token.sol ని సవరించవచ్చు.
ఇది ప్రతినిధి మాత్రమే కాల్ చేయగల అనేక ఫంక్షన్లను కలిగి ఉండటానికి మనల్ని అనుమతిస్తుంది.
కొత్త భాగాలు ఇక్కడ ఉన్నాయి:
// CalldataInterpreter చిరునామాను పేర్కొనడానికి అనుమతించబడిన ఏకైక చిరునామా
address owner;
// CalldataInterpreter చిరునామా
address proxy = address(0);
ERC-20 కాంట్రాక్ట్కు అధీకృత ప్రతినిధి యొక్క గుర్తింపు తెలుసుకోవాలి. అయినప్పటికీ, మనం ఈ వేరియబుల్ను కన్స్ట్రక్టర్లో సెట్ చేయలేము, ఎందుకంటే మనకు ఇంకా విలువ తెలియదు. ఈ కాంట్రాక్ట్ మొదట ఇన్స్టాన్షియేట్ చేయబడుతుంది ఎందుకంటే ప్రతినిధి దాని కన్స్ట్రక్టర్లో టోకెన్ చిరునామాను ఆశిస్తుంది.
/**
* @dev ERC20 కన్స్ట్రక్టర్ను కాల్ చేస్తుంది.
*/
constructor(
) ERC20("Oris useless token-2", "OUT-2") {
owner = msg.sender;
}
సృష్టికర్త యొక్క చిరునామా (owner అని పిలుస్తారు) ఇక్కడ నిల్వ చేయబడుతుంది ఎందుకంటే ప్రతినిధిని సెట్ చేయడానికి అనుమతించబడిన ఏకైక చిరునామా అదే.
/**
* @dev ప్రాక్సీ (CalldataInterpreter) కోసం చిరునామాను సెట్ చేయండి.
* యజమాని ద్వారా ఒకసారి మాత్రమే కాల్ చేయబడుతుంది
*/
ఫంక్షన్ setProxy(address _proxy) external {
require(msg.sender == owner, "Can only be called by owner");
require(proxy == address(0), "Proxy is already set");
proxy = _proxy;
} // function setProxy
ప్రతినిధికి ప్రత్యేక యాక్సెస్ ఉంది, ఎందుకంటే ఇది భద్రతా తనిఖీలను దాటవేయగలదు.
మనం ప్రతినిధిని విశ్వసించగలమని నిర్ధారించుకోవడానికి మనం owner ని మాత్రమే ఈ ఫంక్షన్ను కాల్ చేయడానికి అనుమతిస్తాము మరియు అది కూడా ఒక్కసారే.
ఒకసారి proxy నిజమైన విలువను (సున్నా కాదు) కలిగి ఉంటే, ఆ విలువ మారదు, కాబట్టి యజమాని మోసపూరితంగా మారాలని నిర్ణయించుకున్నా, లేదా దాని కోసం నిమోనిక్ వెల్లడైనా, మనం ఇప్పటికీ సురక్షితంగా ఉంటాము.
/**
* @dev కొన్ని ఫంక్షన్లు ప్రాక్సీ ద్వారా మాత్రమే కాల్ చేయబడతాయి.
*/
modifier onlyProxy {
ఇది ఒక modifier ఫంక్షన్ (opens in a new tab), ఇది ఇతర ఫంక్షన్లు పనిచేసే విధానాన్ని సవరిస్తుంది.
require(msg.sender == proxy);
ముందుగా, మనం ప్రతినిధి ద్వారా కాల్ చేయబడ్డామని మరియు మరెవరి ద్వారా కాదని ధృవీకరించండి.
లేకపోతే, revert.
_;
}
అలా అయితే, మనం సవరించే ఫంక్షన్ను రన్ చేయండి.
/* ఖాతాల కోసం ప్రాక్సీని వాస్తవానికి ప్రాక్సీ చేయడానికి అనుమతించే ఫంక్షన్లు */
function transferProxy(address from, address to, uint256 amount)
public virtual onlyProxy() returns (bool)
{
_transfer(from, to, amount);
return true;
}
function approveProxy(address from, address spender, uint256 amount)
public virtual onlyProxy() returns (bool)
{
_approve(from, spender, amount);
return true;
}
function transferFromProxy(
address spender,
address from,
address to,
uint256 amount
) public virtual onlyProxy() returns (bool)
{
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}
ఇవి సాధారణంగా టోకెన్లను బదిలీ చేసే లేదా అనుమతి మొత్తాన్ని ఆమోదించే ఎంటిటీ నుండి నేరుగా సందేశం రావాల్సిన మూడు ఆపరేషన్లు. ఇక్కడ మనకు ఈ ఆపరేషన్ల యొక్క ప్రతినిధి వెర్షన్ ఉంది, ఇది:
onlyProxy()ద్వారా సవరించబడింది కాబట్టి వాటిని నియంత్రించడానికి మరెవరికీ అనుమతి లేదు.- సాధారణంగా
msg.senderఅయ్యే చిరునామాను అదనపు పరామితిగా పొందుతుంది.
CalldataInterpreter.sol
కాల్ డేటా ఇంటర్ప్రెటర్ పైన ఉన్నదానితో దాదాపు సమానంగా ఉంటుంది, అయితే ప్రాక్సీ చేయబడిన ఫంక్షన్లు msg.sender పరామితిని పొందుతాయి మరియు transfer కోసం అనుమతి మొత్తం అవసరం లేదు.
// బదిలీ (అనుమతి మొత్తం అవసరం లేదు)
if (_func == 2) {
token.transferProxy(
msg.sender,
address(uint160(calldataVal(1, 20))),
calldataVal(21, 2)
);
}
// approve
if (_func == 3) {
token.approveProxy(
msg.sender,
address(uint160(calldataVal(1, 20))),
calldataVal(21, 2)
);
}
// transferFrom
if (_func == 4) {
token.transferFromProxy(
msg.sender,
address(uint160(calldataVal( 1, 20))),
address(uint160(calldataVal(21, 20))),
calldataVal(41, 2)
);
}
Test.js
మునుపటి టెస్టింగ్ కోడ్ మరియు దీని మధ్య కొన్ని మార్పులు ఉన్నాయి.
const Cdi = await ethers.getContractFactory("CalldataInterpreter")
const cdi = await Cdi.deploy(token.address)
await cdi.deployed()
await token.setProxy(cdi.address)
ఏ ప్రతినిధిని విశ్వసించాలో మనం ERC-20 కాంట్రాక్ట్కు చెప్పాలి
console.log("CalldataInterpreter addr:", cdi.address)
// అనుమతి మొత్తాలను ధృవీకరించడానికి ఇద్దరు సంతకం చేసేవారు అవసరం
const signers = await ethers.getSigners()
const signer = signers[0]
const poorSigner = signers[1]
approve() మరియు transferFrom() ని తనిఖీ చేయడానికి మనకు రెండవ సంతకం చేసేవారు అవసరం.
మనం దానిని poorSigner అని పిలుస్తాము ఎందుకంటే ఇది మన టోకెన్లలో దేనినీ పొందదు (వాస్తవానికి దీనికి ETH ఉండాలి).
// టోకెన్లను బదిలీ చేయండి
const destAddr = "0xf5a6ead936fb47f342bb63e676479bddf26ebe1d"
const transferTx = {
to: cdi.address,
data: "0x02" + destAddr.slice(2, 42) + "0100",
}
await (await signer.sendTransaction(transferTx)).wait()
ERC-20 కాంట్రాక్ట్ ప్రతినిధిని (cdi) విశ్వసిస్తుంది కాబట్టి, బదిలీలను రిలే చేయడానికి మనకు అనుమతి మొత్తం అవసరం లేదు.
// ఆమోదం మరియు transferFrom
const approveTx = {
to: cdi.address,
data: "0x03" + poorSigner.address.slice(2, 42) + "00FF",
}
await (await signer.sendTransaction(approveTx)).wait()
const destAddr2 = "0xE1165C689C0c3e9642cA7606F5287e708d846206"
const transferFromTx = {
to: cdi.address,
data: "0x04" + signer.address.slice(2, 42) + destAddr2.slice(2, 42) + "00FF",
}
await (await poorSigner.sendTransaction(transferFromTx)).wait()
// approve / transferFrom కాంబో సరిగ్గా జరిగిందో లేదో తనిఖీ చేయండి
expect(await token.balanceOf(destAddr2)).to.equal(255)
రెండు కొత్త ఫంక్షన్లను పరీక్షించండి.
transferFromTx కి రెండు చిరునామా పారామితులు అవసరమని గమనించండి: అనుమతి మొత్తాన్ని ఇచ్చేవారు మరియు స్వీకరించేవారు.
ముగింపు
Optimism (opens in a new tab) మరియు Arbitrum (opens in a new tab) రెండూ L1కి వ్రాసిన కాల్ డేటా పరిమాణాన్ని మరియు తద్వారా లావాదేవీల ఖర్చును తగ్గించడానికి మార్గాలను అన్వేషిస్తున్నాయి. అయినప్పటికీ, సాధారణ పరిష్కారాల కోసం చూస్తున్న మౌలిక సదుపాయాల ప్రొవైడర్లుగా, మా సామర్థ్యాలు పరిమితం. dapp డెవలపర్గా, మీకు అప్లికేషన్-నిర్దిష్ట జ్ఞానం ఉంది, ఇది సాధారణ పరిష్కారంలో మేము చేయగలిగిన దానికంటే మీ కాల్ డేటాను మరింత మెరుగ్గా ఆప్టిమైజ్ చేయడానికి మిమ్మల్ని అనుమతిస్తుంది. ఆశాజనక, ఈ ఆర్టికల్ మీ అవసరాలకు అనువైన పరిష్కారాన్ని కనుగొనడంలో మీకు సహాయపడుతుంది.
నా మరిన్ని పనుల కోసం ఇక్కడ చూడండి (opens in a new tab).
పేజీ చివరి నవీకరణ: 3 ఏప్రిల్, 2026