如果你持有一個ERC777代幣,那麼你就可以利用ERC777代幣中的鉤子函數方法,給自己佈署一個賬本合約來記錄自己的賬戶每一次接收到的代幣數量和對方賬戶等信息.

如果你持有一個ERC777代幣,那麼你就可以利用ERC777代幣中的鉤子函數方法,給自己佈署一個賬本合約來記錄自己的賬戶每一次接收到的代幣數量和對方賬戶等信息

鉤子函數

ERC777代幣是ERC20代幣合約的升級版,其中的最重要的升級功能就是每次進行轉賬的時候都會調用鉤子函數,具體的方法我們可以在ERC777的代碼中找到

//ERC777.sol
function _send(address from,address to,uint256 amount,bytes memory userData,bytes memory operatorData,bool requireReceptionAck) internal {
    require(from != address(0), "ERC777: send from the zero address");
    require(to != address(0), "ERC777: send to the zero address");
    address operator = _msgSender();
    //調用發送鉤子
    _callTokensToSend(operator, from, to, amount, userData, operatorData);
    _move(operator, from, to, amount, userData, operatorData);
    //調用接收鉤子
    _callTokensReceived(operator, from, to, amount, userData, operatorData, requireReceptionAck);
}

從上面的代碼中我們看到在執行發送方法時會調用兩次鉤子方法,一次是調用發送鉤子,一次是調用接收鉤子.這兩個鉤子方法的具體實現我們看以下的代碼:

//ERC777.sol
//發送鉤子
function _callTokensToSend(address operator,address from,address to,uint256 amount,bytes memory userData,bytes memory operatorData) private {
    //獲取發送賬戶的接口地址
    address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(from, _TOKENS_SENDER_INTERFACE_HASH);
    if (implementer != address(0)) {
        //執行接口地址的tokensToSend方法
        IERC777Sender(implementer).tokensToSend(operator, from, to, amount, userData, operatorData);
    }
}
//接收鉤子
function _callTokensReceived(address operator,address from,address to,uint256 amount,bytes memory userData,bytes memory operatorData,bool requireReceptionAck) private {
    //獲取接收賬戶的接口地址
    address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(to, _TOKENS_RECIPIENT_INTERFACE_HASH);
    if (implementer != address(0)) {
        //執行接口地址的tokensReceived方法
        IERC777Recipient(implementer).tokensReceived(operator, from, to, amount, userData, operatorData);
    } else if (requireReceptionAck) {
        //如果requireReceptionAck爲true則必須執行接口方法,以防止代幣被鎖死
        require(!to.isContract(), "ERC777: token recipient contract has no implementer for ERC777TokensRecipient");
    }
}

以上就是ERC777合約的鉤子調用方法,我們在兩個鉤子的調用方法中都看到了通過ERC1820註冊表獲取賬戶的接口地址的方法,那麼這個接口地址又是怎麼註冊的呢?我們可以在ERC1820的合約代碼中找到答案:

驗證接口

//ERC1820.sol
/// @notice 如果合約代表某個其他地址實現接口,則返回Magic值。
bytes32 constant internal ERC1820_ACCEPT_MAGIC = keccak256(abi.encodePacked("ERC1820_ACCEPT_MAGIC"));

/// @notice 設置某個地址的接口由哪個合約實現,需要由管理員來設置。(每個地址是他自己的管理員,直到設置了一個新的地址)。
/// @param _addr 待設置的關聯接口的地址(如果'_addr'是零地址,則假定爲'msg.sender')
/// @param _interfaceHash 接口,它是接口名稱字符串的 keccak256 哈希值
/// 例如: 'web3.utils.keccak256("ERC777TokensRecipient")' 表示 'ERC777TokensRecipient' 接口。
/// @param _implementer 爲地址'_addr'實現了 '_interfaceHash'接口的合約地址

function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external {
    address addr = _addr == address(0) ? msg.sender : _addr;
    require(getManager(addr) == msg.sender, "Not the manager");

    require(!isERC165Interface(_interfaceHash), "Must not be an ERC165 hash");
    if (_implementer != address(0) && _implementer != msg.sender) {
        //調用接口合約的canImplementInterfaceForAddress方法,驗證合約是否同意成爲賬戶的接口
        require(
            ERC1820ImplementerInterface(_implementer)
                .canImplementInterfaceForAddress(_interfaceHash, addr) == ERC1820_ACCEPT_MAGIC,
                "Does not implement the interface"
        );
    }
    interfaces[addr][_interfaceHash] = _implementer;
    emit InterfaceImplementerSet(addr, _interfaceHash, _implementer);
}

以上代碼就是ERC1820註冊表合約的註冊接口地址的方法,通過向這個方法傳遞三個參數( _addr , _interfaceHash , _implementer )來爲一個賬戶註冊一個接口合約地址.代碼中的 ERC1820ImplementerInterface(_implementer).canImplementInterfaceForAddress(_interfaceHash, addr) 這句最爲核心,目的是調用參數中的 _implementer 接口合約的 canImplementInterfaceForAddress 方法來驗證接口合約是否同意成爲 _addr 賬戶的 _interfaceHash 這個方法的接口合約,如果 canImplementInterfaceForAddress 方法返回的是 ERC1820_ACCEPT_MAGIC 這個固定值( keccak256(abi.encodePacked("ERC1820_ACCEPT_MAGIC")) )則表示同意.

接口合約

從前面的代碼中我們看到了,接口合約必須實現 canImplementInterfaceForAddress 方法來告訴ERC1820註冊表是否同意成爲賬戶的接口,同時還要實現指定的接口方法,例如 tokensToSendtokensReceived .ERC1820註冊表也不是隻爲這兩個接口服務的,你也可以利用這個原理製作出其他有趣的智能合約.

所以製作一個接口合約我們要做的事情:

tokensReceived
canImplementInterfaceForAddress
setInterfaceImplementer

下面我們來看代碼:

//TokensRecipient.sol
pragma solidity ^0.5.0;

import "@openzeppelin/contracts/ownership/Ownable.sol";
import "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol";
import "@openzeppelin/contracts/introspection/ERC1820Implementer.sol";
import "@openzeppelin/contracts/introspection/IERC1820Registry.sol";
import "@openzeppelin/contracts/token/ERC777/IERC777.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";


contract TokensRecipient is ERC1820Implementer, IERC777Recipient, Ownable {
    bool private allowTokensReceived;
    using SafeMath for uint256;
    // keccak256("ERC777TokensRecipient")
    bytes32 private constant TOKENS_RECIPIENT_INTERFACE_HASH = 0xb281fc8c12954d22544db45de3159a39272895b169a852b314f9cc762e44c53b;

    mapping(address => address) public token;
    mapping(address => address) public operator;
    mapping(address => address) public from;
    mapping(address => address) public to;
    mapping(address => uint256) public amount;
    mapping(address => bytes) public data;
    mapping(address => bytes) public operatorData;
    mapping(address => uint256) public balanceOf;
    //ERC1820註冊表合約地址,全網統一
    IERC1820Registry internal constant ERC1820_REGISTRY = IERC1820Registry(
        0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24
    );

    constructor(bool _setInterface) public {
        if (_setInterface) {
            //爲合約自身也註冊一個接口,如果這個合約可以接收代幣就用得到
            ERC1820_REGISTRY.setInterfaceImplementer(
                address(this),
                TOKENS_RECIPIENT_INTERFACE_HASH,
                address(this)
            );
        }
        _registerInterfaceForAddress(TOKENS_RECIPIENT_INTERFACE_HASH, msg.sender);
        allowTokensReceived = true;
    }

    function tokensReceived(
        address _operator,
        address _from,
        address _to,
        uint256 _amount,
        bytes calldata _data,
        bytes calldata _operatorData
    ) external {
        require(allowTokensReceived, "Receive not allowed");
        token[_from] = msg.sender;
        operator[_from] = _operator;
        from[_from] = _from;
        to[_from] = _to;
        amount[_from] = amount[_from].add(_amount);
        data[_from] = _data;
        operatorData[_from] = _operatorData;
        balanceOf[_from] = IERC777(msg.sender).balanceOf(_from);
        balanceOf[_to] = IERC777(msg.sender).balanceOf(_to);
    }

    function acceptTokens() public onlyOwner {
        allowTokensReceived = true;
    }

    function rejectTokens() public onlyOwner {
        allowTokensReceived = false;
    }
}

以上我們使用了一些Openzeppelin的標準庫, canImplementInterfaceForAddress 方法在 ERC1820Implementer.sol 合約文件中,通過第40行 _registerInterfaceForAddress 方法向 canImplementInterfaceForAddress 方法註冊了同意成爲發送賬戶 msg.senderTOKENS_RECIPIENT_INTERFACE_HASH 接口. 在 tokensReceived 方法中我們將傳入的交易數據一一記錄在合約的變量中,例如通過 amount[_from] = amount[_from].add(_amount); 記錄了發送賬戶累計向你的賬戶發送過多少代幣. acceptTokens()rejectTokens() 兩個方法作爲合約的開關,如果設置 allowTokensReceived 值爲false則你的賬戶將會停止接收代幣,這個方法也是很有用的,在之前的ERC20代幣中很難實現.

佈署合約

佈署合約的方法沒有特別需要講的,如果對佈署合約不熟悉,請參考 崔棉大師的花式發幣法

測試合約

在接口合約佈署之後,合約的功能並不會馬上生效,因爲你還需要調用ERC1820註冊表合約去註冊你的接口合約 我們通過寫一個測試腳本來模擬這個過程:

const assert = require('assert');
const { contract, accounts, web3 } = require('@openzeppelin/test-environment');
const { ether, makeInterfaceId, singletons, expectEvent } = require('@openzeppelin/test-helpers');
const ERC777Contract = contract.fromArtifact("ERC777Contract");
const TokensRecipient = contract.fromArtifact("TokensRecipient");

[owner, sender, receiver] = accounts;
const initialSupply = '1000000000';
const defaultOperators = [sender];
let amount = '100';
const userData = web3.utils.toHex('A gift');
describe("ERC777代幣", function () {
    it('實例化ERC1820註冊表', async function () {
        ERC1820RegistryInstance = await singletons.ERC1820Registry(owner);
    });
    it('佈署代幣合約', async function () {
        ERC777Param = [
            //構造函數的參數
            "My Golden Coin",   //代幣名稱
            "MGC",              //代幣縮寫
            ether(initialSupply),      //發行總量
            defaultOperators    //默認操作員
        ]
        ERC777Instance = await ERC777Contract.new(...ERC777Param, { from: owner });
    });
    it('佈署接受接口合約', async function () {
        TokensRecipientInstance = await TokensRecipient.new(true, { from: receiver });
    });
});

describe("註冊ERC1820接口", function () {
    it('註冊代幣接收接口: setInterfaceImplementer() ERC777TokensRecipient', async function () {
        await ERC1820RegistryInstance.setInterfaceImplementer(
            receiver,
            makeInterfaceId.ERC1820('ERC777TokensRecipient'),
            TokensRecipientInstance.address,
            { from: receiver }
        );
    });
    it('驗證代幣接收接口: setInterfaceImplementer() ERC777TokensRecipient', async function () {
        assert.equal(TokensRecipientInstance.address, await ERC1820RegistryInstance.getInterfaceImplementer(
            receiver,
            makeInterfaceId.ERC1820('ERC777TokensRecipient')
        ))
    });
});

describe("測試ERC777合約的方法", function () {
    //send()
    it('發送方法: send()', async function () {
        let receipt = await ERC777Instance.send(receiver, ether(amount), userData, { from: owner });
        expectEvent(receipt, 'Sent', {
            operator: owner,
            from: owner,
            to: receiver,
            amount: ether(amount),
            data: userData,
            operatorData: null
        });
        expectEvent(receipt, 'Transfer', {
            from: owner,
            to: receiver,
            value: ether(amount),
        });
    });
    it('驗證接收接口: TokensRecipient()', async function () {
        assert.equal(ERC777Instance.address, await TokensRecipientInstance.token(owner));
        assert.equal(owner, await TokensRecipientInstance.operator(owner));
        assert.equal(owner, await TokensRecipientInstance.from(owner));
        assert.equal(receiver, await TokensRecipientInstance.to(owner));
        assert.equal(ether(amount).toString(), (await TokensRecipientInstance.amount(owner)).toString());
        assert.equal(userData, await TokensRecipientInstance.data(owner));
        assert.equal(null, await TokensRecipientInstance.operatorData(owner));
        assert.equal(ether((parseInt(initialSupply) - parseInt(amount)).toString()).toString(), (await TokensRecipientInstance.balanceOf(owner)).toString());
        assert.equal(ether(amount), (await TokensRecipientInstance.balanceOf(receiver)).toString());
    });
});
describe("測試發送和接收接口的拒絕方法", function () {
    it('設置拒絕接收: rejectTokens()', async function () {
        await TokensRecipientInstance.rejectTokens({ from: receiver });
    });
    it('驗證代幣接收者拒絕接收: transfer()', async function () {
        await assert.rejects(ERC777Instance.transfer(receiver, ether(amount), { from: owner }), /Receive not allowed/);
    });
});

在這個測試腳本中,我們首先通過 @openzeppelin/test-helpersawait singletons.ERC1820Registry(owner) 方法模擬出一個ERC1820註冊表.之後佈署了一個ERC777合約,在實際應用中如果你已經有了某個ERC777代幣,則不需要這一步,這一步僅僅是爲了測試而設置的.下一步爲 receiver 賬戶佈署了接收接口的合約.在合約佈署之後,要向ERC1820合約爲 receiver 賬戶註冊接收接口合約的地址,通過 makeInterfaceId.ERC1820('ERC777TokensRecipient') 這個方法將 ERC777TokensRecipient 字符串取哈希值,這樣ERC1820合約就知道了接口合約地址成爲了 receiver 賬戶的 ERC777TokensRecipient 這個方法的接口. 之後我們進行了轉賬的測試,ERC777代幣合約的send方法也要向ERC1820註冊表合約查詢 receiver 賬戶是否註冊了 ERC777TokensRecipient 這個方法的接口合約地址,如果註冊了,就必須要調用接口合約 以上就是實現了一個屬於你自己的ERC777代幣接收賬本.

歡迎關注: 崔棉大師的花式發幣法

如果你持有一個ERC777代幣,那麼你就可以利用ERC777代幣中的鉤子函數方法,給自己佈署一個賬本合約來記錄自己的賬戶每一次接收到的代幣數量和對方賬戶等信息

鉤子函數

ERC777代幣是ERC20代幣合約的升級版,其中的最重要的升級功能就是每次進行轉賬的時候都會調用鉤子函數,具體的方法我們可以在ERC777的代碼中找到

//ERC777.sol
function _send(address from,address to,uint256 amount,bytes memory userData,bytes memory operatorData,bool requireReceptionAck) internal {
    require(from != address(0), "ERC777: send from the zero address");
    require(to != address(0), "ERC777: send to the zero address");
    address operator = _msgSender();
    //調用發送鉤子
    _callTokensToSend(operator, from, to, amount, userData, operatorData);
    _move(operator, from, to, amount, userData, operatorData);
    //調用接收鉤子
    _callTokensReceived(operator, from, to, amount, userData, operatorData, requireReceptionAck);
}

從上面的代碼中我們看到在執行發送方法時會調用兩次鉤子方法,一次是調用發送鉤子,一次是調用接收鉤子.這兩個鉤子方法的具體實現我們看以下的代碼:

//ERC777.sol
//發送鉤子
function _callTokensToSend(address operator,address from,address to,uint256 amount,bytes memory userData,bytes memory operatorData) private {
    //獲取發送賬戶的接口地址
    address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(from, _TOKENS_SENDER_INTERFACE_HASH);
    if (implementer != address(0)) {
        //執行接口地址的tokensToSend方法
        IERC777Sender(implementer).tokensToSend(operator, from, to, amount, userData, operatorData);
    }
}
//接收鉤子
function _callTokensReceived(address operator,address from,address to,uint256 amount,bytes memory userData,bytes memory operatorData,bool requireReceptionAck) private {
    //獲取接收賬戶的接口地址
    address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(to, _TOKENS_RECIPIENT_INTERFACE_HASH);
    if (implementer != address(0)) {
        //執行接口地址的tokensReceived方法
        IERC777Recipient(implementer).tokensReceived(operator, from, to, amount, userData, operatorData);
    } else if (requireReceptionAck) {
        //如果requireReceptionAck爲true則必須執行接口方法,以防止代幣被鎖死
        require(!to.isContract(), "ERC777: token recipient contract has no implementer for ERC777TokensRecipient");
    }
}

以上就是ERC777合約的鉤子調用方法,我們在兩個鉤子的調用方法中都看到了通過ERC1820註冊表獲取賬戶的接口地址的方法,那麼這個接口地址又是怎麼註冊的呢?我們可以在ERC1820的合約代碼中找到答案:

驗證接口

//ERC1820.sol
/// @notice 如果合約代表某個其他地址實現接口,則返回Magic值。
bytes32 constant internal ERC1820_ACCEPT_MAGIC = keccak256(abi.encodePacked("ERC1820_ACCEPT_MAGIC"));

/// @notice 設置某個地址的接口由哪個合約實現,需要由管理員來設置。(每個地址是他自己的管理員,直到設置了一個新的地址)。
/// @param _addr 待設置的關聯接口的地址(如果'_addr'是零地址,則假定爲'msg.sender')
/// @param _interfaceHash 接口,它是接口名稱字符串的 keccak256 哈希值
/// 例如: 'web3.utils.keccak256("ERC777TokensRecipient")' 表示 'ERC777TokensRecipient' 接口。
/// @param _implementer 爲地址'_addr'實現了 '_interfaceHash'接口的合約地址

function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external {
    address addr = _addr == address(0) ? msg.sender : _addr;
    require(getManager(addr) == msg.sender, "Not the manager");

    require(!isERC165Interface(_interfaceHash), "Must not be an ERC165 hash");
    if (_implementer != address(0) && _implementer != msg.sender) {
        //調用接口合約的canImplementInterfaceForAddress方法,驗證合約是否同意成爲賬戶的接口
        require(
            ERC1820ImplementerInterface(_implementer)
                .canImplementInterfaceForAddress(_interfaceHash, addr) == ERC1820_ACCEPT_MAGIC,
                "Does not implement the interface"
        );
    }
    interfaces[addr][_interfaceHash] = _implementer;
    emit InterfaceImplementerSet(addr, _interfaceHash, _implementer);
}

以上代碼就是ERC1820註冊表合約的註冊接口地址的方法,通過向這個方法傳遞三個參數( _addr , _interfaceHash , _implementer )來爲一個賬戶註冊一個接口合約地址.代碼中的 ERC1820ImplementerInterface(_implementer).canImplementInterfaceForAddress(_interfaceHash, addr) 這句最爲核心,目的是調用參數中的 _implementer 接口合約的 canImplementInterfaceForAddress 方法來驗證接口合約是否同意成爲 _addr 賬戶的 _interfaceHash 這個方法的接口合約,如果 canImplementInterfaceForAddress 方法返回的是 ERC1820_ACCEPT_MAGIC 這個固定值( keccak256(abi.encodePacked("ERC1820_ACCEPT_MAGIC")) )則表示同意.

接口合約

從前面的代碼中我們看到了,接口合約必須實現 canImplementInterfaceForAddress 方法來告訴ERC1820註冊表是否同意成爲賬戶的接口,同時還要實現指定的接口方法,例如 tokensToSendtokensReceived .ERC1820註冊表也不是隻爲這兩個接口服務的,你也可以利用這個原理製作出其他有趣的智能合約.

所以製作一個接口合約我們要做的事情:

tokensReceived
canImplementInterfaceForAddress
setInterfaceImplementer

下面我們來看代碼:

//TokensRecipient.sol
pragma solidity ^0.5.0;

import "@openzeppelin/contracts/ownership/Ownable.sol";
import "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol";
import "@openzeppelin/contracts/introspection/ERC1820Implementer.sol";
import "@openzeppelin/contracts/introspection/IERC1820Registry.sol";
import "@openzeppelin/contracts/token/ERC777/IERC777.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";

contract TokensRecipient is ERC1820Implementer, IERC777Recipient, Ownable {
    bool private allowTokensReceived;
    using SafeMath for uint256;
    // keccak256("ERC777TokensRecipient")
    bytes32 private constant TOKENS_RECIPIENT_INTERFACE_HASH = 0xb281fc8c12954d22544db45de3159a39272895b169a852b314f9cc762e44c53b;

    mapping(address => address) public token;
    mapping(address => address) public operator;
    mapping(address => address) public from;
    mapping(address => address) public to;
    mapping(address => uint256) public amount;
    mapping(address => bytes) public data;
    mapping(address => bytes) public operatorData;
    mapping(address => uint256) public balanceOf;
    //ERC1820註冊表合約地址,全網統一
    IERC1820Registry internal constant ERC1820_REGISTRY = IERC1820Registry(
        0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24
    );

    constructor(bool _setInterface) public {
        if (_setInterface) {
            //爲合約自身也註冊一個接口,如果這個合約可以接收代幣就用得到
            ERC1820_REGISTRY.setInterfaceImplementer(
                address(this),
                TOKENS_RECIPIENT_INTERFACE_HASH,
                address(this)
            );
        }
        _registerInterfaceForAddress(TOKENS_RECIPIENT_INTERFACE_HASH, msg.sender);
        allowTokensReceived = true;
    }

    function tokensReceived(
        address _operator,
        address _from,
        address _to,
        uint256 _amount,
        bytes calldata _data,
        bytes calldata _operatorData
    ) external {
        require(allowTokensReceived, "Receive not allowed");
        token[_from] = msg.sender;
        operator[_from] = _operator;
        from[_from] = _from;
        to[_from] = _to;
        amount[_from] = amount[_from].add(_amount);
        data[_from] = _data;
        operatorData[_from] = _operatorData;
        balanceOf[_from] = IERC777(msg.sender).balanceOf(_from);
        balanceOf[_to] = IERC777(msg.sender).balanceOf(_to);
    }

    function acceptTokens() public onlyOwner {
        allowTokensReceived = true;
    }

    function rejectTokens() public onlyOwner {
        allowTokensReceived = false;
    }
}

以上我們使用了一些Openzeppelin的標準庫, canImplementInterfaceForAddress 方法在 ERC1820Implementer.sol 合約文件中,通過第40行 _registerInterfaceForAddress 方法向 canImplementInterfaceForAddress 方法註冊了同意成爲發送賬戶 msg.senderTOKENS_RECIPIENT_INTERFACE_HASH 接口. 在 tokensReceived 方法中我們將傳入的交易數據一一記錄在合約的變量中,例如通過 amount[_from] = amount[_from].add(_amount); 記錄了發送賬戶累計向你的賬戶發送過多少代幣. acceptTokens()rejectTokens() 兩個方法作爲合約的開關,如果設置 allowTokensReceived 值爲false則你的賬戶將會停止接收代幣,這個方法也是很有用的,在之前的ERC20代幣中很難實現.

佈署合約

佈署合約的方法沒有特別需要講的,如果對佈署合約不熟悉,請參考 崔棉大師的花式發幣法

測試合約

在接口合約佈署之後,合約的功能並不會馬上生效,因爲你還需要調用ERC1820註冊表合約去註冊你的接口合約 我們通過寫一個測試腳本來模擬這個過程:

const assert = require('assert');
const { contract, accounts, web3 } = require('@openzeppelin/test-environment');
const { ether, makeInterfaceId, singletons, expectEvent } = require('@openzeppelin/test-helpers');
const ERC777Contract = contract.fromArtifact("ERC777Contract");
const TokensRecipient = contract.fromArtifact("TokensRecipient");

[owner, sender, receiver] = accounts;
const initialSupply = '1000000000';
const defaultOperators = [sender];
let amount = '100';
const userData = web3.utils.toHex('A gift');
describe("ERC777代幣", function () {
    it('實例化ERC1820註冊表', async function () {
        ERC1820RegistryInstance = await singletons.ERC1820Registry(owner);
    });
    it('佈署代幣合約', async function () {
        ERC777Param = [
            //構造函數的參數
            "My Golden Coin",   //代幣名稱
            "MGC",              //代幣縮寫
            ether(initialSupply),      //發行總量
            defaultOperators    //默認操作員
        ]
        ERC777Instance = await ERC777Contract.new(...ERC777Param, { from: owner });
    });
    it('佈署接受接口合約', async function () {
        TokensRecipientInstance = await TokensRecipient.new(true, { from: receiver });
    });
});

describe("註冊ERC1820接口", function () {
    it('註冊代幣接收接口: setInterfaceImplementer() ERC777TokensRecipient', async function () {
        await ERC1820RegistryInstance.setInterfaceImplementer(
            receiver,
            makeInterfaceId.ERC1820('ERC777TokensRecipient'),
            TokensRecipientInstance.address,
            { from: receiver }
        );
    });
    it('驗證代幣接收接口: setInterfaceImplementer() ERC777TokensRecipient', async function () {
        assert.equal(TokensRecipientInstance.address, await ERC1820RegistryInstance.getInterfaceImplementer(
            receiver,
            makeInterfaceId.ERC1820('ERC777TokensRecipient')
        ))
    });
});

describe("測試ERC777合約的方法", function () {
    //send()
    it('發送方法: send()', async function () {
        let receipt = await ERC777Instance.send(receiver, ether(amount), userData, { from: owner });
        expectEvent(receipt, 'Sent', {
            operator: owner,
            from: owner,
            to: receiver,
            amount: ether(amount),
            data: userData,
            operatorData: null
        });
        expectEvent(receipt, 'Transfer', {
            from: owner,
            to: receiver,
            value: ether(amount),
        });
    });
    it('驗證接收接口: TokensRecipient()', async function () {
        assert.equal(ERC777Instance.address, await TokensRecipientInstance.token(owner));
        assert.equal(owner, await TokensRecipientInstance.operator(owner));
        assert.equal(owner, await TokensRecipientInstance.from(owner));
        assert.equal(receiver, await TokensRecipientInstance.to(owner));
        assert.equal(ether(amount).toString(), (await TokensRecipientInstance.amount(owner)).toString());
        assert.equal(userData, await TokensRecipientInstance.data(owner));
        assert.equal(null, await TokensRecipientInstance.operatorData(owner));
        assert.equal(ether((parseInt(initialSupply) - parseInt(amount)).toString()).toString(), (await TokensRecipientInstance.balanceOf(owner)).toString());
        assert.equal(ether(amount), (await TokensRecipientInstance.balanceOf(receiver)).toString());
    });
});
describe("測試發送和接收接口的拒絕方法", function () {
    it('設置拒絕接收: rejectTokens()', async function () {
        await TokensRecipientInstance.rejectTokens({ from: receiver });
    });
    it('驗證代幣接收者拒絕接收: transfer()', async function () {
        await assert.rejects(ERC777Instance.transfer(receiver, ether(amount), { from: owner }), /Receive not allowed/);
    });
});

在這個測試腳本中,我們首先通過 @openzeppelin/test-helpersawait singletons.ERC1820Registry(owner) 方法模擬出一個ERC1820註冊表.之後佈署了一個ERC777合約,在實際應用中如果你已經有了某個ERC777代幣,則不需要這一步,這一步僅僅是爲了測試而設置的.下一步爲 receiver 賬戶佈署了接收接口的合約.在合約佈署之後,要向ERC1820合約爲 receiver 賬戶註冊接收接口合約的地址,通過 makeInterfaceId.ERC1820('ERC777TokensRecipient') 這個方法將 ERC777TokensRecipient 字符串取哈希值,這樣ERC1820合約就知道了接口合約地址成爲了 receiver 賬戶的 ERC777TokensRecipient 這個方法的接口. 之後我們進行了轉賬的測試,ERC777代幣合約的send方法也要向ERC1820註冊表合約查詢 receiver 賬戶是否註冊了 ERC777TokensRecipient 這個方法的接口合約地址,如果註冊了,就必須要調用接口合約 以上就是實現了一個屬於你自己的ERC777代幣接收賬本.

歡迎關注: 崔棉大師的花式發幣法

本文參與登鏈社區寫作激勵計劃 ,好文好收益,歡迎正在閱讀的你也加入。

  • 發表於 24分鐘前
  • 閱讀 ( 20 )
  • 學分 ( 0 )
  • 分類:智能合約
相關文章