ABI Fupi za Uboreshaji wa Calldata
Utangulizi
Katika makala haya, utajifunza kuhusu optimistic rollups, gharama za miamala juu yao, na jinsi muundo huo tofauti wa gharama unavyotulazimu kuboresha mambo tofauti kuliko kwenye Mtandao Mkuu wa Ethereum. Pia utajifunza jinsi ya kutekeleza uboreshaji huu.
Ufichuzi kamili
Mimi ni mfanyakazi wa muda wote wa Optimism (opens in a new tab), kwa hivyo mifano katika makala haya itaendeshwa kwenye Optimism. Hata hivyo, mbinu iliyoelezwa hapa inapaswa kufanya kazi vizuri vile vile kwa rollups zingine.
Istilahi
Wakati wa kujadili rollups, neno 'safu ya 1' (L1) hutumika kwa Mtandao Mkuu, mtandao wa uzalishaji wa Ethereum. Neno 'safu ya 2' (L2) hutumika kwa rollup au mfumo mwingine wowote unaotegemea L1 kwa usalama lakini hufanya usindikaji wake mwingi nje ya chain.
Je, tunawezaje kupunguza zaidi gharama ya miamala ya L2?
Optimistic rollups zinapaswa kuhifadhi rekodi ya kila muamala wa kihistoria ili mtu yeyote aweze kuzipitia na kuthibitisha kwamba hali ya sasa ni sahihi. Njia ya bei nafuu zaidi ya kuingiza data kwenye Mtandao Mkuu wa Ethereum ni kuiandika kama calldata. Suluhisho hili lilichaguliwa na Optimism (opens in a new tab) na Arbitrum (opens in a new tab).
Gharama ya miamala ya L2
Gharama ya miamala ya L2 inaundwa na vijenzi viwili:
- Usindikaji wa L2, ambao kwa kawaida ni wa bei nafuu sana
- Ghala la L1, ambalo limeunganishwa na gharama za gesi za Mtandao Mkuu
Ninapoandika haya, kwenye Optimism gharama ya gesi ya L2 ni 0.001 Gwei. Gharama ya gesi ya L1, kwa upande mwingine, ni takriban gwei 40. Unaweza kuona bei za sasa hapa (opens in a new tab).
Baiti moja ya calldata hugharimu gesi 4 (ikiwa ni sifuri) au gesi 16 (ikiwa ni thamani nyingine yoyote). Moja ya operesheni za gharama kubwa zaidi kwenye EVM ni kuandika kwenye ghala. Gharama ya juu ya kuandika neno la baiti 32 kwenye ghala kwenye L2 ni gesi 22100. Kwa sasa, hii ni gwei 22.1. Kwa hivyo ikiwa tunaweza kuokoa baiti moja ya sifuri ya calldata, tutaweza kuandika takriban baiti 200 kwenye ghala na bado tuwe na faida.
ABI
Idadi kubwa ya miamala hufikia mkataba kutoka kwa akaunti inayomilikiwa na mtu wa nje. Mikataba mingi imeandikwa katika Solidity na kutafsiri uga wao wa data kulingana na kiolesura cha binary cha programu (ABI) (opens in a new tab).
Hata hivyo, ABI ilitengenezwa kwa ajili ya L1, ambapo baiti ya calldata hugharimu takriban sawa na operesheni nne za hesabu, sio L2 ambapo baiti ya calldata hugharimu zaidi ya operesheni elfu moja za hesabu. Calldata imegawanywa kama ifuatavyo:
| Sehemu | Urefu | Baiti | Baiti zilizopotea | Gesi iliyopotea | Baiti zinazohitajika | Gesi inayohitajika |
|---|---|---|---|---|---|---|
| Kiteuzi cha chaguo za kukokotoa | 4 | 0-3 | 3 | 48 | 1 | 16 |
| Sifuri | 12 | 4-15 | 12 | 48 | 0 | 0 |
| Anwani ya mwisho | 20 | 16-35 | 0 | 0 | 20 | 320 |
| Kiasi | 32 | 36-67 | 17 | 64 | 15 | 240 |
| Jumla | 68 | 160 | 576 |
Maelezo:
- Kiteuzi cha chaguo za kukokotoa: Mkataba una chaguo za kukokotoa chini ya 256, kwa hivyo tunaweza kuzitofautisha kwa baiti moja. Kwa kawaida baiti hizi si sifuri na kwa hivyo hugharimu gesi kumi na sita (opens in a new tab).
- Sifuri: Baiti hizi daima ni sifuri kwa sababu anwani ya baiti ishirini haihitaji neno la baiti thelathini na mbili ili kuihifadhi.
Baiti zinazoshikilia sifuri hugharimu gesi nne (angalia karatasi ya njano (opens in a new tab), Nyongeza G,
k. 27, thamani ya
Gtxdatazero). - Kiasi: Tukichukulia kuwa katika mkataba huu
decimalsni kumi na nane (thamani ya kawaida) na kiasi cha juu cha tokeni tunachohamisha kitakuwa 1018, tunapata kiasi cha juu cha 1036. 25615 > 1036, kwa hivyo baiti kumi na tano zinatosha.
Upotevu wa gesi 160 kwenye L1 kwa kawaida hauzingatiwi. Muamala hugharimu angalau gesi 21,000 (opens in a new tab), kwa hivyo 0.8% ya ziada haijalishi.
Hata hivyo, kwenye L2, mambo ni tofauti. Karibu gharama nzima ya muamala ni kuiandika kwa L1.
Mbali na calldata ya muamala, kuna baiti 109 za kichwa cha muamala (anwani ya mwisho, saini, n.k.).
Gharama ya jumla kwa hivyo ni 109*16+576+160=2480, na tunapoteza takriban 6.5% ya hiyo.
Kupunguza gharama wakati haudhibiti mwishilio
Tukichukulia kuwa huna udhibiti juu ya mkataba wa mwishilio, bado unaweza kutumia suluhisho sawa na hili (opens in a new tab). Wacha tupitie faili zinazohusika.
Token.sol
Huu ni mkataba wa mwishilio (opens in a new tab).
Ni mkataba wa kawaida wa ERC-20, wenye kipengele kimoja cha ziada.
Kitendo hiki cha faucet humruhusu mtumiaji yeyote kupata tokeni fulani ya kutumia.
Ingefanya mkataba wa uzalishaji wa ERC-20 usiwe na maana, lakini hurahisisha maisha wakati ERC-20 ipo tu kuwezesha majaribio.
1 /**2 * @dev Gives the caller 1000 tokens to play with3 */4 function faucet() external {5 _mint(msg.sender, 1000);6 } // function faucetCalldataInterpreter.sol
Huu ni mkataba ambao miamala inapaswa kuita na calldata fupi (opens in a new tab). Wacha tuipitie mstari kwa mstari.
1//SPDX-License-Identifier: Unlicense2pragma solidity ^0.8.0;345import { OrisUselessToken } from "./Token.sol";Tunahitaji chaguo za kukokotoa za tokeni ili kujua jinsi ya kuiita.
1contract CalldataInterpreter {23 OrisUselessToken public immutable token;Anwani ya tokeni ambayo sisi ni proksi.
12 /**3 * @dev Specify the token address4 * @param tokenAddr_ The ERC-20 contract address5 */6 constructor(7 address tokenAddr_8 ) {9 token = OrisUselessToken(tokenAddr_);10 } // constructorOnyesha yoteAnwani ya tokeni ndiyo kigezo pekee tunachohitaji kubainisha.
1 function calldataVal(uint startByte, uint length)2 private pure returns (uint) {Soma thamani kutoka kwa calldata.
1 uint _retVal;23 require(length < 0x21,4 "calldataVal length limit is 32 bytes");56 require(length + startByte <= msg.data.length,7 "calldataVal trying to read beyond calldatasize");Tutaipakia neno moja la baiti 32 (biti 256) kwenye kumbukumbu na kuondoa baiti ambazo si sehemu ya uga tunaotaka. Algorithm hii haifanyi kazi kwa thamani ndefu kuliko baiti 32, na bila shaka hatuwezi kusoma zaidi ya mwisho wa calldata. Kwenye L1 inaweza kuwa muhimu kuruka majaribio haya ili kuokoa gesi, lakini kwenye L2 gesi ni nafuu sana, ambayo huwezesha ukaguzi wowote wa kiakili tunaoweza kufikiria.
1 assembly {2 _retVal := calldataload(startByte)3 }Tungeweza kunakili data kutoka kwa simu hadi fallback() (tazama hapa chini), lakini ni rahisi zaidi kutumia Yul (opens in a new tab), lugha ya mkusanyiko ya EVM.
Hapa tunatumia opcode ya CALLDATALOAD (opens in a new tab) kusoma baiti startByte hadi startByte+31 kwenye rundo.
Kwa ujumla, sintaksia ya opcode katika Yul ni <opcode name>(<first stack value, if any>,<second stack value, if any>...).
12 _retVal = _retVal >> (256-length*8);Ni baiti za urefu muhimu pekee ndizo sehemu ya uga, kwa hivyo tunahamisha kulia (opens in a new tab) ili kuondoa thamani zingine.
Hii ina faida iliyoongezwa ya kuhamisha thamani upande wa kulia wa uga, kwa hivyo ni thamani yenyewe badala ya thamani mara 256kitu.
12 return _retVal;3 }456 fallback() external {Wito kwa mkataba wa Solidity usipofanana na saini zozote za chaguo za kukokotoa, huita chaguo la kukokotoa la fallback() (opens in a new tab) (kwa kudhani kuna moja).
Katika kisa cha CalldataInterpreter, wito yoyote hufika hapa kwa sababu hakuna chaguo zingine za kukokotoa za external au public.
1 uint _func;23 _func = calldataVal(0, 1);Soma baiti ya kwanza ya calldata, ambayo inatuambia chaguo za kukokotoa. Kuna sababu mbili kwa nini chaguo za kukokotoa hazipatikani hapa:
- Chaguo za kukokotoa ambazo ni
pureauviewhazibadilishi hali na hazigharimu gesi (zinapoitwa nje ya chain). Haina maana kujaribu kupunguza gharama yao ya gesi. - Chaguo za kukokotoa zinazotegemea
msg.sender(opens in a new tab). Thamani yamsg.senderitakuwa anwani yaCalldataInterpreter, si ya mpigaji simu.
Kwa bahati mbaya, ukiangalia vipimo vya ERC-20 (opens in a new tab), hii inaacha chaguo moja tu la kukokotoa, transfer.
Hii inatuacha na chaguo mbili tu za kukokotoa: transfer (kwa sababu tunaweza kuita transferFrom) na faucet (kwa sababu tunaweza kuhamisha tokeni kurudi kwa yeyote aliyetuita).
12 // Call the state changing methods of token using3 // information from the calldata45 // faucet6 if (_func == 1) {Wito kwa faucet(), ambayo haina vigezo.
1 token.faucet();2 token.transfer(msg.sender,3 token.balanceOf(address(this)));4 }Baada ya kuita token.faucet() tunapata tokeni. Hata hivyo, kama mkataba wa proksi, hatuhitaji tokeni.
EOA (akaunti inayomilikiwa nje) au mkataba uliotuita unahitaji.
Kwa hivyo tunahamisha tokeni zetu zote kwa yeyote aliyetuita.
1 // transfer (assume we have an allowance for it)2 if (_func == 2) {Kuhamisha tokeni kunahitaji vigezo viwili: anwani ya mwisho na kiasi.
1 token.transferFrom(2 msg.sender,Tunawaruhusu tu wapigaji simu kuhamisha tokeni wanazomiliki
1 address(uint160(calldataVal(1, 20))),Anwani ya mwisho huanza kwenye baiti #1 (baiti #0 ni chaguo za kukokotoa). Kama anwani, ina urefu wa baiti 20.
1 calldataVal(21, 2)Kwa mkataba huu mahususi tunadhani kwamba idadi ya juu ya tokeni ambazo mtu yeyote angetaka kuhamisha inatoshea katika baiti mbili (chini ya 65536).
1 );2 }Kwa ujumla, uhamisho unachukua baiti 35 za calldata:
| Sehemu | Urefu | Baiti |
|---|---|---|
| Kiteuzi cha chaguo za kukokotoa | 1 | 0 |
| Anwani ya mwisho | 32 | 1-32 |
| Kiasi | 2 | 33-34 |
1 } // fallback23} // contract CalldataInterpretertest.js
Jaribio hili la kitengo cha JavaScript (opens in a new tab) linatuonyesha jinsi ya kutumia utaratibu huu (na jinsi ya kuthibitisha kuwa inafanya kazi ipasavyo). Nitachukulia kuwa unaelewa chai (opens in a new tab) na ethers (opens in a new tab) na nitaelezea tu sehemu zinazohusu mkataba haswa.
1const { expect } = require("chai");23describe("CalldataInterpreter", function () {4 it("Inapaswa kuturuhusu kutumia tokeni", async function () {5 const Token = await ethers.getContractFactory("OrisUselessToken")6 const token = await Token.deploy()7 await token.deployed()8 console.log("Anwani ya tokeni:", token.address)910 const Cdi = await ethers.getContractFactory("CalldataInterpreter")11 const cdi = await Cdi.deploy(token.address)12 await cdi.deployed()13 console.log("Anwani ya CalldataInterpreter:", cdi.address)1415 const signer = await ethers.getSigner()Onyesha yoteTunaanza kwa kupeleka mikataba yote miwili.
1 // Get tokens to play with2 const faucetTx = {Hatuwezi kutumia chaguo za kukokotoa za kiwango cha juu ambazo tungetumia kwa kawaida (kama vile token.faucet()) kuunda miamala, kwa sababu hatufuati ABI.
Badala yake, tunapaswa kujenga muamala wenyewe na kisha kuutuma.
1 to: cdi.address,2 data: "0x01"Kuna vigezo viwili tunavyohitaji kutoa kwa muamala:
to, anwani ya mwisho. Huu ni mkataba wa mkalimani wa calldata.data, calldata ya kutuma. Katika kesi ya simu ya bomba, data ni baiti moja,0x01.
12 }3 await (await signer.sendTransaction(faucetTx)).wait()Tunaita mbinu ya sendTransaction ya mtia saini (opens in a new tab) kwa sababu tayari tumebainisha lengo (faucetTx.to) na tunahitaji muamala utiwe saini.
1// Check the faucet provides the tokens correctly2expect(await token.balanceOf(signer.address)).to.equal(1000)Hapa tunathibitisha salio.
Hakuna haja ya kuokoa gesi kwenye chaguo za kukokotoa za view, kwa hivyo tunaziendesha kawaida.
1// Give the CDI an allowance (approvals cannot be proxied)2const approveTX = await token.approve(cdi.address, 10000)3await approveTX.wait()4expect(await token.allowance(signer.address, cdi.address)).to.equal(10000)Mpe mkalimani wa calldata posho ili aweze kufanya uhamisho.
1// Transfer tokens2const destAddr = "0xf5a6ead936fb47f342bb63e676479bddf26ebe1d"3const transferTx = {4 to: cdi.address,5 data: "0x02" + destAddr.slice(2, 42) + "0100",6}Unda muamala wa uhamisho. Baiti ya kwanza ni "0x02", ikifuatiwa na anwani ya mwisho, na hatimaye kiasi (0x0100, ambayo ni 256 katika desimali).
1 await (await signer.sendTransaction(transferTx)).wait()23 // Check that we have 256 tokens less4 expect (await token.balanceOf(signer.address)).to.equal(1000-256)56 // And that our destination got them7 expect (await token.balanceOf(destAddr)).to.equal(256)8 }) // it9}) // describeOnyesha yoteKupunguza gharama unapodhibiti mkataba wa mwisho
Ikiwa una udhibiti juu ya mkataba wa mwisho unaweza kuunda chaguo za kukokotoa zinazokwepa ukaguzi wa msg.sender kwa sababu zinamuamini mkalimani wa calldata.
Unaweza kuona mfano wa jinsi hii inavyofanya kazi hapa, katika tawi la control-contract (opens in a new tab).
Ikiwa mkataba ungekuwa ukijibu tu miamala ya nje, tungeweza kutosheka na kuwa na mkataba mmoja tu. Hata hivyo, hilo lingevunja uwezo wa kutunga. Ni bora zaidi kuwa na mkataba unaojibu simu za kawaida za ERC-20, na mkataba mwingine unaojibu miamala yenye data fupi ya simu.
Token.sol
Katika mfano huu tunaweza kurekebisha Token.sol.
Hii inatuwezesha kuwa na idadi ya chaguo za kukokotoa ambazo proksi pekee inaweza kuita.
Hapa kuna sehemu mpya:
1 // The only address allowed to specify the CalldataInterpreter address2 address owner;34 // The CalldataInterpreter address5 address proxy = address(0);Mkataba wa ERC-20 unahitaji kujua utambulisho wa proksi aliyeidhinishwa. Hata hivyo, hatuwezi kuweka kigezo hiki katika mjenzi, kwa sababu hatujui thamani bado. Mkataba huu unathibitishwa kwanza kwa sababu proksi inatarajia anwani ya tokeni katika mjenzi wake.
1 /**2 * @dev Calls the ERC20 constructor.3 */4 constructor(5 ) ERC20("Oris useless token-2", "OUT-2") {6 owner = msg.sender;7 }Anwani ya muundaji (inayoitwa owner) huhifadhiwa hapa kwa sababu hiyo ndiyo anwani pekee inayoruhusiwa kuweka proksi.
1 /**2 * @dev set the proxy address (CalldataInterpreter).3 * Can only be called once by the owner4 */5 function setProxy(address _proxy) external {6 require(msg.sender == owner, "Can only be called by owner");7 require(proxy == address(0), "Proxy is already set");89 proxy = _proxy;10 } // function setProxyOnyesha yoteProksi ina ufikiaji wa upendeleo, kwa sababu inaweza kukwepa ukaguzi wa usalama.
Ili kuhakikisha tunaweza kuamini proksi tunamruhusu mmiliki pekee kuita chaguo hili la kukokotoa, na mara moja tu.
Mara proksi inapokuwa na thamani halisi (sio sifuri), thamani hiyo haiwezi kubadilika, kwa hivyo hata ikiwa mmiliki ataamua kuwa mhalifu, au mnemonic yake itafichuliwa, bado tuko salama.
1 /**2 * @dev Some functions can only be called by the proxy.3 */4 modifier onlyProxy {Hiki ni kitendakazi cha kirekebishaji (https://www.tutorialspoint.com/solidity/solidity_function_modifiers.htm (opens in a new tab)), kinarekebisha jinsi vitendakazi vingine vinavyofanya kazi.
1 require(msg.sender == proxy);Kwanza, thibitisha tumeitwa na proksi na si mtu mwingine.
Ikiwa sivyo, revert.
1 _;2 }Ikiwa ndivyo, endesha kitendakazi tunachorekebisha.
1 /* Functions that allow the proxy to act on behalf of accounts */23 function transferProxy(address from, address to, uint256 amount)4 public virtual onlyProxy() returns (bool)5 {6 _transfer(from, to, amount);7 return true;8 }910 function approveProxy(address from, address spender, uint256 amount)11 public virtual onlyProxy() returns (bool)12 {13 _approve(from, spender, amount);14 return true;15 }1617 function transferFromProxy(18 address spender,19 address from,20 address to,21 uint256 amount22 ) public virtual onlyProxy() returns (bool)23 {24 _spendAllowance(from, spender, amount);25 _transfer(from, to, amount);26 return true;27 }Onyesha yoteHizi ni shughuli tatu ambazo kwa kawaida huhitaji ujumbe kutoka moja kwa moja kutoka kwa huluki inayohamisha tokeni au kuidhinisha posho. Hapa tuna toleo la proksi la shughuli hizi ambalo:
- Hurekebishwa na
onlyProxy()ili hakuna mtu mwingine anayeruhusiwa kuzidhibiti. - Hupata anwani ambayo kwa kawaida ingekuwa
msg.senderkama kigezo cha ziada.
CalldataInterpreter.sol
Mkalimani wa calldata karibu anafanana na ule ulio juu, isipokuwa kwamba chaguo za kukokotoa zinazowakilishwa hupokea kigezo cha msg.sender na hakuna haja ya posho ya transfer.
1 // transfer (no need for allowance)2 if (_func == 2) {3 token.transferProxy(4 msg.sender,5 address(uint160(calldataVal(1, 20))),6 calldataVal(21, 2)7 );8 }910 // approve11 if (_func == 3) {12 token.approveProxy(13 msg.sender,14 address(uint160(calldataVal(1, 20))),15 calldataVal(21, 2)16 );17 }1819 // transferFrom20 if (_func == 4) {21 token.transferFromProxy(22 msg.sender,23 address(uint160(calldataVal( 1, 20))),24 address(uint160(calldataVal(21, 20))),25 calldataVal(41, 2)26 );27 }Onyesha yoteTest.js
Kuna mabadiliko machache kati ya msimbo wa awali wa majaribio na huu.
1const Cdi = await ethers.getContractFactory("CalldataInterpreter")2const cdi = await Cdi.deploy(token.address)3await cdi.deployed()4await token.setProxy(cdi.address)Tunahitaji kuuambia mkataba wa ERC-20 ni proksi gani ya kuamini
1console.log("CalldataInterpreter address:", cdi.address)23// Need two signers to verify allowances4const signers = await ethers.getSigners()5const signer = signers[0]6const poorSigner = signers[1]Ili kuangalia approve() na transferFrom() tunahitaji mtia saini wa pili.
Tunaiita poorSigner kwa sababu haipati tokeni zetu zozote (inahitaji kuwa na ETH, bila shaka).
1// Transfer tokens2const destAddr = "0xf5a6ead936fb47f342bb63e676479bddf26ebe1d"3const transferTx = {4 to: cdi.address,5 data: "0x02" + destAddr.slice(2, 42) + "0100",6}7await (await signer.sendTransaction(transferTx)).wait()Kwa sababu mkataba wa ERC-20 unaiamini proksi (cdi), hatuhitaji posho ya kuwasilisha uhamisho.
1// approval and transferFrom2const approveTx = {3 to: cdi.address,4 data: "0x03" + poorSigner.address.slice(2, 42) + "00FF",5}6await (await signer.sendTransaction(approveTx)).wait()78const destAddr2 = "0xE1165C689C0c3e9642cA7606F5287e708d846206"910const transferFromTx = {11 to: cdi.address,12 data: "0x04" + signer.address.slice(2, 42) + destAddr2.slice(2, 42) + "00FF",13}14await (await poorSigner.sendTransaction(transferFromTx)).wait()1516// Check the approve / transferFrom combo was done correctly17expect(await token.balanceOf(destAddr2)).to.equal(255)Onyesha yoteJaribu chaguo mbili mpya za kukokotoa.
Kumbuka kwamba transferFromTx inahitaji vigezo viwili vya anwani: mtoaji wa posho na mpokeaji.
Hitimisho
Wote Optimism (opens in a new tab) na Arbitrum (opens in a new tab) wanatafuta njia za kupunguza ukubwa wa calldata iliyoandikwa kwa L1 na kwa hivyo gharama ya miamala. Hata hivyo, kama watoa huduma wa miundombinu wanaotafuta suluhisho za jumla, uwezo wetu una mipaka. Kama msanidi programu wa mfumo mtawanyo wa kimamlaka, una ujuzi maalum wa programu, ambao unakuwezesha kuboresha calldata yako vizuri zaidi kuliko tunavyoweza katika suluhisho la jumla. Tunatumahi, makala haya yanakusaidia kupata suluhisho bora kwa mahitaji yako.
Tazama hapa kwa kazi zangu zaidi (opens in a new tab).
Ukurasa ulihaririwa mwisho: 22 Agosti 2025