简体   繁体   English

使用Oraclize和Metamask传输ERC20令牌

[英]Transfer ERC20 token with Oraclize and Metamask

I'm a beginner and I've been exploring ERC20 tokens. 我是一个初学者,我一直在探索ERC20代币。 Since a couple of days I have been looking for a solution to this, but in vain. 几天以来,我一直在寻找解决方案,但徒劳无功。

The problem is the following. 问题如下。 I am creating a contract, conforming to the ERC20 protocol. 我正在创建符合ERC20协议的合同。 I want to add an extra functionality in the form of an oracle query. 我想以oracle查询的形式添加额外的功能。 Ie, I want to use a service like "Oraclize", to fetch some external data, return the result. 即,我想使用“ Oraclize”之类的服务来获取一些外部数据,并返回结果。 Depending on the result I would like to transfer some tokens or not. 根据结果​​,我想转移一些令牌还是不转移。

1) The example token contract I've been working with is the following. 1)我一直在使用的示例令牌合同如下。 It s the contract from CryptoPunks ( https://github.com/larvalabs/cryptopunks/blob/master/contracts/CryptoPunksMarket.sol ): 它是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) Following the creation, I would like to fetch some data from Oraclize, and depending on the forex USD/GBP rate transfer a token or not. 2)创建完成后,我想从Oraclize中获取一些数据,并根据外汇USD / GBP汇率是否转移令牌来进行。 The following code is from the Oraclize example contract: 以下代码来自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");
        }
    }
}

Based on my understanding, I could make the main token contract inherit from the oracle contract. 根据我的理解,我可以使主令牌合同继承自oracle合同。 And the main contract should inherit all the functions from the oracle token contract. 并且主合同应该继承oracle令牌合同的所有功能。

Oraclize is a paid service, so I should make the updatePrice() always payable, and put something like 1 ether on the upper right side of Remix IDE. Oraclize是一项付费服务​​,因此我应该使updatePrice()始终可支付,并在Remix IDE的右上侧放置1以太之类的东西。

Problems are double: 问题是双重的:

a) In the Official Remix IDE (JS VM), while the token contract executes, the Oraclize contract fails with "reverting the contract to initial state" message. a)在正式Remix IDE(JS VM)中,当执行令牌合同时,Oraclize合同失败,并显示“将合同还原为初始状态”消息。 Is it related to Oracle being paid? 与甲骨文的报酬有关吗? Because I always put like 1 ether in the top right side of the IDE. 因为我总是在IDE的右上方放置1个以太坊。 But I don´t know how to address this exactly. 但是我不知道该如何解决。

b) In the Remix fork that Oraclize has ( https://dapps.oraclize.it/browser-solidity/ ) using JS VM too, it will execute the query but it fails executing the token, with an "Invalid op code" message for the "calls". b)在Oraclize也使用JS VM的Remix分支( https://dapps.oraclize.it/browser-solidity/ )中,它将执行查询,但执行令牌失败,并带有“无效操作码”消息为“通话”。 So I can't even get the token symbol. 因此,我什至无法获得令牌符号。

Questions: 问题:

1) Also, besides the IDE issues, my doubt resides, in how should I proceed in giving a token on the condition that for example the USD/GBP value is X. 1)此外,除了IDE问题外,我的疑问还在于,在USD / GBP值为X的情况下,我应该如何继续提供令牌。

I assume that I should use the getToken() function in the main contract, check if the exchange rate is x, and assign the token? 我假设我应该在主合约中使用getToken()函数,检查汇率是否为x,并分配令牌? How I could do this effectively? 我怎样才能有效地做到这一点?

2) Should I use one of the events implemented in the main token contract, or it has got nothing to do with it? 2)我应该使用主要代币合约中实现的事件之一,还是与它无关?

I'm not sure if I can address you design questions as it seems more like a business problem than a coding/design issue (or I may not be understanding the question). 我不确定我是否可以解决您的设计问题,因为它看起来更像是业务问题,而不是编码/设计问题(或者我可能不理解这个问题)。 If getToken is your point of sale and you want to reject any requests when the exchange rate is too low, then just make that a condition you check with a require statement. 如果getToken是您的销售点,并且您想在汇率太低时拒绝任何请求,则只需使用require语句确定条件即可。 However, I will note that from the technical standpoint, you can't read events in a Solidity contract. 但是,我将注意到,从技术角度来看,您无法读取Solidity合同中的事件。 You can only listen for them in a client which will receive the event(s) when the transaction is successfully mined. 您只能在成功完成交易的客户端中侦听事件。

I can, however, address your IDE issues. 但是,我可以解决您的IDE问题。 The reason for the failures is oraclizeAPI is dependent on already deployed contracts. 失败的原因是oraclizeAPI取决于已部署的合同。 They have a modifier which sets up the internal network of the contract depending on which environment it's running: 他们有一个修饰符,可以根据合同运行的环境来设置合同的内部网络:

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;
    }

When you run your example contract in JS VM (which is it's own sandbox), it doesn't have access to those contracts and the call fails. 在JS VM(这是它自己的沙箱)中运行示例合同时,它无权访问那些合同,并且调用失败。 It works if you switch environments to Ropsten/Rinkeby and connect through MetaMask. 如果您将环境切换到Ropsten / Rinkeby并通过MetaMask进行连接,则它可以工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM