使用 Oraclize 和 Metamask 转移 ERC20 代币

Posted

技术标签:

【中文标题】使用 Oraclize 和 Metamask 转移 ERC20 代币【英文标题】:Transfer ERC20 token with Oraclize and Metamask 【发布时间】:2018-07-02 05:33:40 【问题描述】:

我是初学者,我一直在探索 ERC20 代币。几天以来,我一直在寻找解决方案,但徒劳无功。

问题如下。我正在创建一个符合 ERC20 协议的合约。我想以 oracle 查询的形式添加额外的功能。 即,我想使用像“Oraclize”这样的服务来获取一些外部数据,返回结果。 根据结果​​,我想转移一些代币或不转移。

1) 我一直在使用的示例代币合约如下。这是来自 CryptoPunks 的合同 (https://github.com/larvalabs/cryptopunks/blob/master/contracts/CryptoPunksMarket.sol):

pragma solidity ^0.4.18;

contract CryptoTokensMarket 

    address owner;

    string public standard = 'CryptoTokens';
    string public name;
    string public symbol;
    uint8 public decimals;
    uint256 public totalSupply;

    uint public nextTokenIndexToAssign = 0;

    bool public allTokensAssigned = false;
    uint public tokensRemainingToAssign = 0;

    //mapping (address => uint) public addressToTokenIndex;
    mapping (uint => address) public tokenIndexToAddress;

    /* This creates an array with all balances */
    mapping (address => uint256) public balanceOf;

    struct Offer 
        bool isForSale;
        uint tokenIndex;
        address seller;
        uint minValue;          // in ether
        address onlySellTo;     // specify to sell only to a specific person
    

    struct Bid 
        bool hasBid;
        uint tokenIndex;
        address bidder;
        uint value;
    

    // A record of tokens that are offered for sale at a specific minimum value, and perhaps to a specific person
    mapping (uint => Offer) public tokensOfferedForSale;

    // A record of the highest token bid
    mapping (uint => Bid) public tokenBids;

    mapping (address => uint) public pendingWithdrawals;

    event Assign(address indexed to, uint256 tokenIndex);
    event Transfer(address indexed from, address indexed to, uint256 value);
    event TokenTransfer(address indexed from, address indexed to, uint256 tokenIndex);
    event TokenOffered(uint indexed tokenIndex, uint minValue, address indexed toAddress);
    event TokenBidEntered(uint indexed tokenIndex, uint value, address indexed fromAddress);
    event TokenBidWithdrawn(uint indexed tokenIndex, uint value, address indexed fromAddress);
    event TokenBought(uint indexed tokenIndex, uint value, address indexed fromAddress, address indexed toAddress);
    event TokenNoLongerForSale(uint indexed tokenIndex);

    /* Initializes contract with initial supply tokens to the creator of the contract */
    function CryptoTokensMarket() payable 
        //        balanceOf[msg.sender] = initialSupply;              // Give the creator all initial tokens
        owner = msg.sender;
        totalSupply = 10000;                        // Update total supply
        tokensRemainingToAssign = totalSupply;
        name = "CRYPTOTokenS";                                   // Set the name for display purposes
        symbol = "Ͼ";                               // Set the symbol for display purposes
        decimals = 0;                                       // Amount of decimals for display purposes
    

    function setInitialOwner(address to, uint tokenIndex) 
        if (msg.sender != owner) revert();
        if (allTokensAssigned) revert();
        if (tokenIndex >= 10000) revert();
        if (tokenIndexToAddress[tokenIndex] != to) 
            if (tokenIndexToAddress[tokenIndex] != 0x0) 
                balanceOf[tokenIndexToAddress[tokenIndex]]--;
             else 
                tokensRemainingToAssign--;
            
            tokenIndexToAddress[tokenIndex] = to;
            balanceOf[to]++;
            Assign(to, tokenIndex);
        
    

    function setInitialOwners(address[] addresses, uint[] indices) 
        if (msg.sender != owner) revert();
        uint n = addresses.length;
        for (uint i = 0; i < n; i++) 
            setInitialOwner(addresses[i], indices[i]);
        
    

    function allInitialOwnersAssigned() 
        if (msg.sender != owner) revert();
        allTokensAssigned = true;
    

    function getToken(uint tokenIndex) 
        if (!allTokensAssigned) revert();
        if (tokensRemainingToAssign == 0) revert();
        if (tokenIndexToAddress[tokenIndex] != 0x0) revert();
        if (tokenIndex >= 10000) revert();
        tokenIndexToAddress[tokenIndex] = msg.sender;
        balanceOf[msg.sender]++;
        tokensRemainingToAssign--;
        Assign(msg.sender, tokenIndex);
    

    // Transfer ownership of a token to another user without requiring payment
    function transferToken(address to, uint tokenIndex) payable 
        if (!allTokensAssigned) revert();
        if (tokenIndexToAddress[tokenIndex] != msg.sender) revert();
        if (tokenIndex >= 10000) revert();
        if (tokensOfferedForSale[tokenIndex].isForSale) 
            tokenNoLongerForSale(tokenIndex);
        
        tokenIndexToAddress[tokenIndex] = to;
        balanceOf[msg.sender]--;
        balanceOf[to]++;
        Transfer(msg.sender, to, 1);
        TokenTransfer(msg.sender, to, tokenIndex);
        // Check for the case where there is a bid from the new owner and refund it.
        // Any other bid can stay in place.
        Bid bid = tokenBids[tokenIndex];
        if (bid.bidder == to) 
            // Kill bid and refund value
            pendingWithdrawals[to] += bid.value;
            tokenBids[tokenIndex] = Bid(false, tokenIndex, 0x0, 0);
        
    

    function tokenNoLongerForSale(uint tokenIndex) 
        if (!allTokensAssigned) revert();
        if (tokenIndexToAddress[tokenIndex] != msg.sender) revert();
        if (tokenIndex >= 10000) revert();
        tokensOfferedForSale[tokenIndex] = Offer(false, tokenIndex, msg.sender, 0, 0x0);
        TokenNoLongerForSale(tokenIndex);
    

    function offerTokenForSale(uint tokenIndex, uint minSalePriceInWei) 
        if (!allTokensAssigned) revert();
        if (tokenIndexToAddress[tokenIndex] != msg.sender) revert();
        if (tokenIndex >= 10000) revert();
        tokensOfferedForSale[tokenIndex] = Offer(true, tokenIndex, msg.sender, minSalePriceInWei, 0x0);
        TokenOffered(tokenIndex, minSalePriceInWei, 0x0);
    

    function offerTokenForSaleToAddress(uint tokenIndex, uint minSalePriceInWei, address toAddress) 
        if (!allTokensAssigned) revert();
        if (tokenIndexToAddress[tokenIndex] != msg.sender) revert();
        if (tokenIndex >= 10000) revert();
        tokensOfferedForSale[tokenIndex] = Offer(true, tokenIndex, msg.sender, minSalePriceInWei, toAddress);
        TokenOffered(tokenIndex, minSalePriceInWei, toAddress);
    

    function buyToken(uint tokenIndex) payable 
        if (!allTokensAssigned) revert();
        Offer offer = tokensOfferedForSale[tokenIndex];
        if (tokenIndex >= 10000) revert();
        if (!offer.isForSale) revert();                // token not actually for sale
        if (offer.onlySellTo != 0x0 && offer.onlySellTo != msg.sender) revert();  // token not supposed to be sold to this user
        if (msg.value < offer.minValue) revert();      // Didn't send enough ETH
        if (offer.seller != tokenIndexToAddress[tokenIndex]) revert(); // Seller no longer owner of token

        address seller = offer.seller;

        tokenIndexToAddress[tokenIndex] = msg.sender;
        balanceOf[seller]--;
        balanceOf[msg.sender]++;
        Transfer(seller, msg.sender, 1);

        tokenNoLongerForSale(tokenIndex);
        pendingWithdrawals[seller] += msg.value;
        TokenBought(tokenIndex, msg.value, seller, msg.sender);

        // Check for the case where there is a bid from the new owner and refund it.
        // Any other bid can stay in place.
        Bid bid = tokenBids[tokenIndex];
        if (bid.bidder == msg.sender) 
            // Kill bid and refund value
            pendingWithdrawals[msg.sender] += bid.value;
            tokenBids[tokenIndex] = Bid(false, tokenIndex, 0x0, 0);
        
    

    function withdraw() payable 
        if (!allTokensAssigned) revert();
        uint amount = pendingWithdrawals[msg.sender];
        // Remember to zero the pending refund before
        // sending to prevent re-entrancy attacks
        pendingWithdrawals[msg.sender] = 0;
        msg.sender.transfer(amount);
    

    function enterBidForToken(uint tokenIndex) payable 
        if (tokenIndex >= 10000) revert();
        if (!allTokensAssigned) revert();                
        if (tokenIndexToAddress[tokenIndex] == 0x0) revert();
        if (tokenIndexToAddress[tokenIndex] == msg.sender) revert();
        if (msg.value == 0) revert();
        Bid existing = tokenBids[tokenIndex];
        if (msg.value <= existing.value) revert();
        if (existing.value > 0) 
            // Refund the failing bid
            pendingWithdrawals[existing.bidder] += existing.value;
        
        tokenBids[tokenIndex] = Bid(true, tokenIndex, msg.sender, msg.value);
        TokenBidEntered(tokenIndex, msg.value, msg.sender);
    

    function acceptBidForToken(uint tokenIndex, uint minPrice) 
        if (tokenIndex >= 10000) revert();
        if (!allTokensAssigned) revert();                
        if (tokenIndexToAddress[tokenIndex] != msg.sender) revert();
        address seller = msg.sender;
        Bid bid = tokenBids[tokenIndex];
        if (bid.value == 0) revert();
        if (bid.value < minPrice) revert();

        tokenIndexToAddress[tokenIndex] = bid.bidder;
        balanceOf[seller]--;
        balanceOf[bid.bidder]++;
        Transfer(seller, bid.bidder, 1);

        tokensOfferedForSale[tokenIndex] = Offer(false, tokenIndex, bid.bidder, 0, 0x0);
        uint amount = bid.value;
        tokenBids[tokenIndex] = Bid(false, tokenIndex, 0x0, 0);
        pendingWithdrawals[seller] += amount;
        TokenBought(tokenIndex, bid.value, seller, bid.bidder);
    

    function withdrawBidForToken(uint tokenIndex) 
        if (tokenIndex >= 10000) revert();
        if (!allTokensAssigned) revert();                
        if (tokenIndexToAddress[tokenIndex] == 0x0) revert();
        if (tokenIndexToAddress[tokenIndex] == msg.sender) revert();
        Bid bid = tokenBids[tokenIndex];
        if (bid.bidder != msg.sender) revert();
        TokenBidWithdrawn(tokenIndex, bid.value, msg.sender);
        uint amount = bid.value;
        tokenBids[tokenIndex] = Bid(false, tokenIndex, 0x0, 0);
        // Refund the bid money
        msg.sender.transfer(amount);
    


2) 创建后,我想从 Oraclize 获取一些数据,并根据外汇美元/英镑汇率转移代币。 以下代码来自 Oraclize 示例合约:

import "github.com/oraclize/ethereum-api/oraclizeAPI.sol";

contract ExampleContract is usingOraclize 

    string public EURGBP;
    string public value = "0.88086";
    event LogPriceUpdated(string price);
    event LogNewOraclizeQuery(string description);

    function ExampleContract() payable public
        updatePrice();

    

    function __callback(bytes32 myid, string result) public 
        if (msg.sender != oraclize_cbAddress()) revert();
        EURGBP = result;
        if (keccak256(result) != keccak256(value)) 
        LogPriceUpdated(value);
        
        else  
           LogPriceUpdated(result);
        
    

    function updatePrice() payable  public
        if (oraclize_getPrice("URL") > this.balance) 
            LogNewOraclizeQuery("Oraclize query was NOT sent, please add some ETH to cover for the query fee");
         else 
            LogNewOraclizeQuery("Oraclize query was sent, standing by for the answer..");
            oraclize_query("URL", "json(http://api.fixer.io/latest?symbols=USD,GBP).rates.GBP");
        
    

根据我的理解,我可以让主代币合约继承自预言机合约。并且主合约应该继承预言机代币合约的所有功能。

Oraclize 是一项付费服务​​,所以我应该让 updatePrice() 始终支付,并在 Remix IDE 的右上方放置 1 ether 之类的东西。

问题是双重的:

a) 在官方 Remix IDE (JS VM) 中,当令牌合约执行时,Oraclize 合约失败并显示“将合约恢复到初始状态”消息。是否与支付甲骨文有关?因为我总是在 IDE 的右上角放 1 个以太币。但我不知道如何准确地解决这个问题。

b) 在 Oraclize (https://dapps.oraclize.it/browser-solidity/) 也使用 JS VM 的 Remix 分支中,它将执行查询,但执行令牌失败,并显示“调用”的“无效操作码”消息。所以我什至无法获得令牌符号。

问题:

1) 此外,除了 IDE 问题,我的疑问还在于,在美元/英镑值为 X 的情况下,我应该如何继续提供代币。

我假设我应该在主合约中使用getToken()函数,检查汇率是否为x,并分配代币?我怎样才能有效地做到这一点?

2) 我应该使用主代币合约中实现的事件之一,还是与它无关?

【问题讨论】:

【参考方案1】:

我不确定我是否可以解决您的设计问题,因为它看起来更像是一个业务问题而不是编码/设计问题(或者我可能不理解这个问题)。如果getToken 是您的销售点,并且您想在汇率过低时拒绝任何请求,那么只需使用require 语句检查该条件即可。但是,我会注意到,从技术角度来看,您无法读取 Solidity 合约中的事件。您只能在成功挖掘交易时接收事件的客户端中监听它们。

不过,我可以解决您的 IDE 问题。失败的原因是 oraclizeAPI 依赖于已经部署的合约。他们有一个修饰符,可以根据运行的环境设置合约的内部网络:

function oraclize_setNetwork(uint8 networkID) internal returns(bool)
        if (getCodeSize(0x1d3B2638a7cC9f2CB3D298A3DA7a90B67E5506ed)>0) //mainnet
            OAR = OraclizeAddrResolverI(0x1d3B2638a7cC9f2CB3D298A3DA7a90B67E5506ed);
            oraclize_setNetworkName("eth_mainnet");
            return true;
        
        if (getCodeSize(0xc03A2615D5efaf5F49F60B7BB6583eaec212fdf1)>0) //ropsten testnet
            OAR = OraclizeAddrResolverI(0xc03A2615D5efaf5F49F60B7BB6583eaec212fdf1);
            oraclize_setNetworkName("eth_ropsten3");
            return true;
        
        if (getCodeSize(0xB7A07BcF2Ba2f2703b24C0691b5278999C59AC7e)>0) //kovan testnet
            OAR = OraclizeAddrResolverI(0xB7A07BcF2Ba2f2703b24C0691b5278999C59AC7e);
            oraclize_setNetworkName("eth_kovan");
            return true;
        
        if (getCodeSize(0x146500cfd35B22E4A392Fe0aDc06De1a1368Ed48)>0) //rinkeby testnet
            OAR = OraclizeAddrResolverI(0x146500cfd35B22E4A392Fe0aDc06De1a1368Ed48);
            oraclize_setNetworkName("eth_rinkeby");
            return true;
        
        if (getCodeSize(0x6f485C8BF6fc43eA212E93BBF8ce046C7f1cb475)>0) //ethereum-bridge
            OAR = OraclizeAddrResolverI(0x6f485C8BF6fc43eA212E93BBF8ce046C7f1cb475);
            return true;
        
        if (getCodeSize(0x20e12A1F859B3FeaE5Fb2A0A32C18F5a65555bBF)>0) //ether.camp ide
            OAR = OraclizeAddrResolverI(0x20e12A1F859B3FeaE5Fb2A0A32C18F5a65555bBF);
            return true;
        
        if (getCodeSize(0x51efaF4c8B3C9AfBD5aB9F4bbC82784Ab6ef8fAA)>0) //browser-solidity
            OAR = OraclizeAddrResolverI(0x51efaF4c8B3C9AfBD5aB9F4bbC82784Ab6ef8fAA);
            return true;
        
        return false;
    

当您在 JS VM(这是它自己的沙箱)中运行示例合约时,它无法访问这些合约并且调用失败。如果您将环境切换到 Ropsten/Rinkeby 并通过 MetaMask 连接,它就可以工作。

【讨论】:

我想我明白了。没有办法使用事件。它们只是只读的。关于需求问题:据我所知,我应该在 getToken() 函数中添加以下内容:require(keccak256(result) != keccak256(value)),Being result 我将从 Oracle 获得的内容并重视我想要检查的值。一个非常基本的问题,为了让我的主角阅读result,我应该让它从oracle合约继承,但是之后我应该如何继续?感谢您的耐心等待。 更新:因为你的回答让我走上了正确的道路,我接受了。

以上是关于使用 Oraclize 和 Metamask 转移 ERC20 代币的主要内容,如果未能解决你的问题,请参考以下文章

区块链预言机架构原理:以 Oraclize 与 Chainlink 为例(上)

Using APIs in Your Ethereum Smart Contract with Oraclize

我可以使用 PHP 从 ERC20 合约中转移代币吗?

MetaMask 钱包使用

安装 metamask 和 coinbase chrome 扩展时如何获取 metamask 以太坊对象?

开发 Solidity 的最佳 IDE / 插件