给你的ERC777代币制作一个自己的专属账本

栏目: IT技术 · 发布时间: 4年前

内容简介:如果你持有一个ERC777代币,那么你就可以利用ERC777代币中的钩子函数方法,给自己布署一个账本合约来记录自己的账户每一次接收到的代币数量和对方账户等信息.本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

如果你持有一个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 )
  • 分类:智能合约

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

首席产品官1 从新手到行家

首席产品官1 从新手到行家

车马 / 机械工业出版社 / 2018-9-25 / 79

《首席产品官》共2册,旨在为产品新人成长为产品行家,产品白领成长为产品金领,最后成长为首席产品官(CPO)提供产品认知、能力体系、成长方法三个维度的全方位指导。 作者在互联网领域从业近20年,是中国早期的互联网产品经理,曾是周鸿祎旗下“3721”的产品经理,担任CPO和CEO多年。作者将自己多年来的产品经验体系化,锤炼出了“产品人的能力杠铃模型”(简称“杠铃模型”),简洁、直观、兼容性好、实......一起来看看 《首席产品官1 从新手到行家》 这本书的介绍吧!

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具