ERC-1363 एक smart contract को tokens के incoming transfer का पता लगाने और उस पर प्रतिक्रिया देने में सक्षम बनाता है।

ERC-1363 किस समस्या का समाधान करता है?
मान लीजिए कि कोई user किसी contract में ERC-20 token transfer करता है। Smart contract इस transfer के लिए user को credit नहीं दे सकता क्योंकि उसके पास यह देखने का कोई mechanism नहीं है कि transfer किसने किया है।
हालाँकि events इस जानकारी को track करते हैं, लेकिन उनका उपयोग केवल off-chain consumers द्वारा किया जा सकता है। Smart contracts किसी oracle के बिना events को नहीं पढ़ सकते हैं।
पारंपरिक समाधान: receiver को notify करने के बजाय, receiver transferFrom का उपयोग करके tokens को अपने आप में transfer कर लेता है
ऊपर बताई गई समस्या का एक सामान्य उपाय यह है कि tokens का sender, receiving smart contract को sender की ओर से tokens transfer करने के लिए approve कर दे।
contract ReceivingContract {
function deposit(uint256 amount) external {
// will revert if this contract is not approved
// or the user has an insufficient balance
ERC20(token).transferFrom(msg.sender, address.this, amount);
deposits[msg.sender] += amount;
}
}
फिर depositor tokens को sender से contract में transfer करने के लिए receiving smart contract पर एक function (ऊपर दिए गए example code में deposit) को invoke करता है। चूँकि contract जानता है कि उसने user से tokens transfer किए हैं, इसलिए वह उनके account को सही ढंग से credit करने में सक्षम होता है।
हालाँकि, tokens transfer करने के लिए contract को approve करने हेतु एक extra transaction जोड़ने से gas cost बढ़ जाती है।
इसके अतिरिक्त, contract को approve करने के बाद user को contract के लिए approval को शून्य (zero) पर set करना चाहिए, अन्यथा यह खतरा रहता है कि यदि contract को exploit किया जाता है, तो यह user से और अधिक ERC-20 tokens withdraw कर सकता है।
Transfer hooks
Transfer hook receiving smart contract में एक predefined function होता है जिसे tokens प्राप्त होने पर call किया जाएगा। यानी, token contract, transfer instruction प्राप्त करने के बाद, recipient address पर predefined function को call करता है।
यदि function मौजूद नहीं है, revert हो जाता है, या अपेक्षित success value return नहीं करता है, तो transfer revert हो जाता है।
ERC-721 standard में onERC721Received से पहले से परिचित पाठक transfer hook से भी परिचित होंगे।
ERC-1363, ERC-20 standard का विस्तार करता है, और transfer hooks जोड़ता है।
Standard को implement करने के लिए, receiver पर transfer hook को trigger करने हेतु tokens transfer करने के लिए ERC-20 को additional functions (जिन्हें बाद में समझाया गया है) की आवश्यकता होती है, और receiver को standard के अनुसार transfer hook को implement करना चाहिए।
IERC1363Receiver
एक contract जो यह notify होना चाहता है कि उन्हें ERC-1363 tokens प्राप्त हुए हैं, उन्हें IERC1363Receiver को implement करना होगा (यहाँ OpenZeppelin implementation देखें) जिसमें एक सिंगल function onTransferReceived होता है:
pragma solidity ^0.8.20;
interface IERC1363Receiver {
// returns `bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))` on success
function onTransferReceived(
address operator,
address from,
uint256 value,
bytes calldata data
) external returns (bytes4);
}
operatorवह address है जो transfer initiate कर रहा हैfromवह ERC-1363 account है जिससे tokens deduct किए जा रहे हैंvaluetransfer किए जा रहे tokens की मात्रा (amount) हैdata, operator द्वारा receiver को forward करने के लिए specify किया जाता है
इस function को implement करते समय, हमेशा यह check करें कि msg.sender वही ERC-1363 token है जिसे आप प्राप्त करना चाहते हैं, क्योंकि कोई भी arbitrary values के साथ onTransferReceived() को call कर सकता है।
यहाँ एक न्यूनतम (minimum) example contract दिया गया है जो ERC-1363 tokens को accept करता है:
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/interfaces/IERC1363Receiver.sol";
import "@openzeppelin/contracts/interfaces/IERC1363.sol";
contract TokenReceiver is IERC1363Receiver {
address internal erc1363Token;
constructor(address erc1363Token_) {
erc1363Token = erc1363Token_;
}
mapping(address user => uint256 balance) public balances;
function onTransferReceived(
address operator,
address from,
uint256 value,
bytes calldata data
) external returns (bytes4) {
require(msg.sender == erc1363Token, "not the expected token");
balances[from] += value;
return this.onTransferReceived.selector;
}
function withdraw(uint256 value) external {
require(balances[msg.sender] >= value, "balance too low");
balances[msg.sender] -= value;
IERC1363(erc1363Token).transfer(msg.sender, value);
}
}
किसी contract को ERC-20 tokens प्राप्त होने का पता लगाने का पारंपरिक तरीका transferFrom function का उपयोग करना है जिसके लिए पहले approval की आवश्यकता होती है, लेकिन ERC-1363 के साथ, contract यह जान पाता है कि उसे token प्राप्त हुआ है और साथ ही यह approval step को भी समाप्त कर देता है क्योंकि transferAndCall contract में token transfer करता है (बिना approval के) और onTransferReceived function को call करता है।
ERC-20 के साथ Backwards Compatibility को अधिकतम करना
नए token standards के साथ समस्या यह है कि existing protocols उनका उपयोग तब तक नहीं कर पाएंगे जब तक कि वे पूर्व standards के साथ पूरी तरह से compatible न हों।
Backwards compatibility को अधिकतम करने के लिए, ERC-1363 एक ERC-20 token है जो extra functions जोड़ता है जिनका उपयोग करने की आवश्यकता older protocols को नहीं होती है।
सभी existing ERC-20 functions: name, symbol, decimals, totalSupply, balanceOf, transfer, transferFrom, approve, और allowance बिल्कुल वैसे ही व्यवहार करते हैं जैसा कि ERC-20 standard द्वारा specify किया गया है।
ERC-1363 standard, ERC-20 में नए functions जोड़ता है ताकि legacy protocols अभी भी ERC-1363 token के साथ बिल्कुल वैसे ही interact कर सकें जैसे वे ERC-20 tokens के साथ करते हैं। हालाँकि, newer protocols यदि चाहें तो ERC-1363 पर transfer hook का लाभ उठा सकते हैं।
एक compliant ERC-1363 token होने के लिए, code को छह additional functions भी implement करने होंगे:
transferAndCallके दो versionstransferFromAndCallके दो versionsapproveAndCallके दो versions
जैसा कि नाम से पता चलता है, ये functions ERC-20 action करेंगे, फिर recipient के hook function को call करेंगे।
प्रत्येक function के दो versions हैं, एक data parameter के साथ और एक बिना data parameter के। Data parameter इसलिए है ताकि sender receiving contract को data forward कर सके (हम बाद में इसका एक उदाहरण दिखाएंगे)।
उन functions को छोड़कर जो data argument लेते हैं, ये functions अपने ERC-20 समकक्षों (counterparts) के समान क्रम में वही arguments लेते हैं।
// There are two transferAndCall functions,
// one with a data argument and one without
function transferAndCall(
address to,
uint256 value
) external returns (bool);
function transferAndCall(
address to,
uint256 value,
bytes calldata data
) external returns (bool);
// There are two transferFromAndCall functions,
// one with a data argument and one without
function transferFromAndCall(
address from,
address to,
uint256 value
) external returns (bool);
function transferFromAndCall(
address from,
address to,
uint256 value,
bytes calldata data
) external returns (bool);
// There are two approveAndCall functions,// one with a data argument and one without
function approveAndCall(
address spender,
uint256 value
) external returns (bool);
function approveAndCall(
address spender,
uint256 value,
bytes calldata data
) external returns (bool);
ERC-721 प्रेरणा: transferFrom बनाम safeTransferFrom
ERC-721 standard के समान, ERC-1363 में transferFromAndCall और transferFrom के बीच का अंतर वही है जो ERC-721 में transferFrom और safeTransferFrom के बीच है। हालाँकि, “safe” एक आदर्श function name नहीं है, क्योंकि transfer hook एक संभावित re-entrancy vector पेश करता है, इसलिए यह “safe” नहीं है। “call” शब्द का जोड़ जिसका ERC-1363 उपयोग करता है, यह अधिक स्पष्ट (explicit) बनाता है कि function क्या कर रहा है: transfer के बाद receiver को यह notify करने के लिए call करना कि tokens उसे transfer कर दिए गए हैं।
Reference Implementation
एक ERC-1363 implementation यहाँ पाया जा सकता है। हम उस उदाहरण से काफी मात्रा में code का उपयोग करेंगे। Implementation को एक बार में यहाँ paste करने की तुलना में codebase को एक-एक हिस्से (piece-by-piece) करके समझाना आसान है। जो लोग ERC-1363 token implement कर रहे हैं, कृपया ऊपर linked implementation का उपयोग करें। यहाँ दिया गया code केवल illustration उद्देश्यों के लिए है।
ERC-1363 balances और approvals के लिए उन्हीं storage variables का उपयोग करता है जिनका ERC-20 करता है। यह additional information store नहीं करता है।
ERC-1363 का Code overview
ERC-20 को Inherit करना
जैसा कि पहले ज़ोर दिया गया था, ERC-1363 additional functions के साथ एक ERC-20 token है। ERC-1363 बनाने का पहला कदम ERC-20 को inherit करना है:
//SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import "@openzeppelin/[email protected]/token/ERC20/ERC20.sol";
contract ERC1363 is ERC20 {
constructor(
string memory name,
string memory symbol
)ERC20(name, symbol) {}
}
transferFromAndCall(address to, uint256 value) external returns (bool)
transferFromAndCall तभी सफल होता है जब receiving address onTransferReceived() को implement करता है और onTransferReceived() का four byte function selector return करता है।
function transferFromAndCall(
address from,
address to,
uint256 value,
bytes memory data
) public virtual returns (bool) {
// first call the ERC-20 transferFrom function in the parent
if (!transferFrom(from, to, value)) {
revert ERC1363TransferFromFailed(from, to, value);
}
// then call the receiver
_checkOnTransferReceived(from, to, value, data);
return true;
}
// this function has no data parameter and
// forwards empty data
function transferFromAndCall(
address from,
address to,
uint256 value
) public virtual returns (bool) {
// `data` is empty
return transferFromAndCall(from, to, value, "");
}
transferAndCall(address to, uint256 value) external returns (bool)
यह transferFromAndCall के बहुत समान है सिवाय इसके कि from msg.sender है।
function transferAndCall(
address to,
uint256 value,
bytes memory data
) public virtual returns (bool) {
if (!transfer(to, value)) {
revert ERC1363TransferFailed(to, value);
}
_checkOnTransferReceived(msgSender(), to, value, data);
return true;
}
function transferAndCall(
address to,
uint256 value
) public virtual returns (bool) {
return transferAndCall(to, value, "");
}
_checkOnTransferReceived()
यह function check करता है कि क्या receiver एक contract है, और यदि नहीं तो revert हो जाता है। फिर यह onTransferReceived को call करने का प्रयास करता है और यदि इसे 0x88a7ca5c प्राप्त नहीं होता है (जो कि onTransferReceived(address,address,uint256,bytes) का function selector है), तो revert हो जाता है। यदि onTransferReceived revert होता है, तो यह function onTransferReceived से प्राप्त error message के साथ revert हो जाता है।
चूँकि EOA (regular wallet) को भेजे जाने पर यह function revert हो जाता है, इसलिए किसी EOA को ERC-1363 transfer करते समय ERC-20 functions transfer या transferFrom का उपयोग करना चाहिए:
function _checkOnTransferReceived(
address from,
address to,
uint256 value,
bytes memory data
) private {
if (to.code.length == 0) {
revert ERC1363EOAReceiver(to);
}
try IERC1363Receiver(to).onTransferReceived(_msgSender(), from, value, data) returns (bytes4 retval) {
if (retval != IERC1363Receiver.onTransferReceived.selector) {
revert ERC1363InvalidReceiver(to);
}
} catch (bytes memory reason) {
if (reason.length == 0) {
revert ERC1363InvalidReceiver(to);
} else {
// this code causes the ERC-1363 to revert
// with the same revert string as the
// contract it called
assembly {
revert(add(32, reason), mload(reason))
}
}
}
}
approveAndCall
ऊपर दिए गए workflows में, जिस smart contract को call किया जा रहा है वह ERC-1363 tokens का recipient है।
हालाँकि, क्या होगा यदि हम चाहते हैं कि कोई अन्य contract हमारे tokens का sender हो? उदाहरण के लिए, एक router contract, जैसे कि Uniswap V2 Router, tokens की custody नहीं रखता है। यह उन्हें trade करने के लिए Uniswap को forward कर देता है।
पारंपरिक रूप से, ऐसे architectures “approve then transferFrom” workflow का उपयोग करते हैं, लेकिन ERC-1363 के साथ हम इसे एक ही transaction में approveAndCall के जरिए कर सकते हैं। जैसा कि नाम से पता चलता है, जिस contract को अभी-अभी किसी अन्य address के tokens को spend करने का approval मिला है, उसका एक विशेष hook function call किया जाता है।
transferAndCall functions की तरह, transaction में additional data supply करना optional है जो इस बात पर निर्भर करता है कि कौन सा approveAndCall invoke किया गया है:
function approveAndCall(
address spender,
uint256 value
) public virtual returns (bool) {
return approveAndCall(spender, value, "");
}
function approveAndCall(
address spender,
uint256 value,
bytes memory data
) public virtual returns (bool) {
if (!approve(spender, value)) {
revert ERC1363ApproveFailed(spender, value);
}
_checkOnApprovalReceived(spender, value, data);
return true;
}
IERC1363Spender
IERC1363Receiver के समान, जब एक approvalAndCall invoke किया जाता है तो onApprovalReceived नामक एक function trigger होता है।
यहाँ IERC1363Spender के लिए OpenZeppelin द्वारा प्रदान किया गया interface है। नीचे दिए गए code से comments हटा दिए गए हैं:
interface IERC1363Spender {
function onApprovalReceived(
address owner,
uint256 value,
bytes calldata data
) external returns (bytes4);
}
केवल tokens का owner ही किसी अन्य address को approve कर सकता है, इसलिए operator argument की कोई आवश्यकता नहीं है — एक approval के दौरान operator और owner एक ही address होने चाहिए। value approval की amount का size है।
निम्नलिखित contract, onApprovalReceived प्राप्त होने पर tokens को data में specified address पर forward कर देता है।
import "@openzeppelin/contracts/interfaces/IERC1363Spender.sol";
contract Router is IERC1363Spender {
// additional functions are needed for an approved
// wallet to add approved ERC-1363 tokens to this mapping
mapping(address => bool) isApprovedToken;
function onApprovalReceived(
address owner,
uint256 value,
bytes calldata data
) external returns (bytes4) {
require(isApprovedToken[msg.sender], "not an approved token");
// getTarget is not implemented here,
// see the next section for how it works
address target = getTarget(data);
bool success = IERC1363(msg.sender).transferFrom(owner, target, value);
require(success, "transfer failed");
return this.onApprovalReceived.selector;
}
}
इस function को check करना चाहिए कि क्या msg.sender token contract है, क्योंकि यदि किसी को भी इसे call करने की अनुमति है, तो इससे unexpected behavior हो सकता है।
ERC-1363 का उपयोग करने वाले receiver contract का उदाहरण
नीचे दिया गया उदाहरण data argument के लिए एक use case प्रदर्शित करता है।
interface ERC1363Receiver {
function onTransferReceived(
address operator,
address from,
uint256 value,
bytes memory data
) external returns (bytes4);
}
contract ReceiverContract is ERC1363Receiver {
mapping(address => uint256) public deposits;
address immutable token;
constructor(address token_) {
token = token_;
}
event Deposit(
address indexed from,
address indexed beneficiary,
uint256 value
);
function onTransferReceived(
address, // operator
address from,
uint256 value,
bytes memory data
) external returns (bytes4) {
require(msg.sender == token, "Caller not ERC1363 token");
address beneficiary;
if (data.length == 32) {
beneficiary = abi.decode(data, (address));
} else {
beneficiary = from;
}
deposits[from] += value;
emit Deposit(from, beneficiary, value);
return this.onTransferReceived.selector;
}
}
Token hooks को हल करने का प्रयास करने वाले पूर्व standards
ERC-1363, ERC-20 में transfer hooks जोड़ने वाला पहला standard नहीं था। सबसे पहले, ERC-223 को मई 2017 में ERC-20 में transfer और transferFrom में transfer hook जोड़ने के लिए प्रस्तावित किया गया था। लेकिन इसका मतलब यह था कि smart contracts तब तक token प्राप्त नहीं कर सकते थे जब तक कि वे transfer hook को implement न करें। इसने standard को उन protocols के साथ backwards compatible नहीं रहने दिया जो ERC-20 tokens स्वीकार करते थे, लेकिन उनके पास transfer hook नहीं था।
ERC-777 को नवंबर 2017 में पेश किया गया था। इस standard में, receiver का transfer hook तब तक call नहीं किया जाएगा जब तक कि उन्होंने अपना address ERC-1820 registry में register नहीं किया हो।
हालाँकि, protocols को ERC-20 में transfer या transferFrom के लिए अन्य contracts में external call करने हेतु डिज़ाइन नहीं किया गया था। इससे वे contracts reentrancy के प्रति vulnerable हो गए क्योंकि उन्हें यह उम्मीद नहीं थी कि कोई “ERC-20” token अन्य contracts को call करेगा। अधिक जानकारी के लिए Uniswap V1 reentrancy vulnerability write up देखें।
इसके अतिरिक्त, ERC-777 standard गैस के दृष्टिकोण से काफी महंगा था क्योंकि इसे ERC-1820 registry contract में एक अतिरिक्त (additional) call करने की आवश्यकता होती थी।
ERC-1363 इन सभी समस्याओं को ERC-20 standard में transfer और transferFrom को पूरी तरह से अपरिवर्तित (unaltered) छोड़कर हल करता है। सभी transfer hooks उन functions में call किए जाते हैं जिनके नाम में एक explicit call होता है।
ERC-1363 standard का उपयोग कब करें
ERC-1363 standard का उपयोग कहीं भी किया जा सकता है जहाँ ERC-20 standard का उपयोग किया जाएगा। लेखक के दृष्टिकोण से, यह standard ERC-20 के लिए एक वांछनीय (desirable) विकल्प (replacement) है क्योंकि यह ERC-20 के approve step को समाप्त कर सकता है, जिसके कारण फंड्स का काफी नुकसान हुआ है।
RareSkills के साथ और जानें
Smart contract development और token standards के बारे में अधिक जानने के लिए हमारा Solidity bootcamp देखें।
मूल रूप से 4 अप्रैल, 2024 को प्रकाशित