1
votes

I'm a beginner and I've been exploring ERC20 tokens. 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. I want to add an extra functionality in the form of an oracle query. I.e., I want to use a service like "Oraclize", to fetch some external data, return the result. 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. It s the contract from 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. The following code is from the Oraclize example contract:

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. And the main contract should inherit all the functions from the oracle token contract.

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.

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. Is it related to Oracle being paid? Because I always put like 1 ether in the top right side of the IDE. 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". 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.

I assume that I should use the getToken() function in the main contract, check if the exchange rate is x, and assign the token? 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?

1

1 Answers

0
votes

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. However, I will note that from the technical standpoint, you can't read events in a Solidity contract. 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. The reason for the failures is oraclizeAPI is dependent on already deployed contracts. 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. It works if you switch environments to Ropsten/Rinkeby and connect through MetaMask.